com.github.javen205
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/pom.xml b/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/pom.xml
new file mode 100644
index 000000000..799c55f89
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 4.0.0
+
+ com.pig4cloud
+ pigx-flow-engine
+ 5.2.0-snapshot
+
+
+ pigx-flow-engine-api
+
+
+
+
+ com.pig4cloud
+ pigx-common-core
+
+
+
+ com.baomidou
+ mybatis-plus-extension
+
+
+
+ com.pig4cloud
+ pigx-common-feign
+
+
+
+ com.pig4cloud
+ pigx-common-excel
+
+
+
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/api/feign/packge-info.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/api/feign/packge-info.java
new file mode 100644
index 000000000..b3dce91f5
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/api/feign/packge-info.java
@@ -0,0 +1,6 @@
+/*
+ @author pigx archetype
+ *
+ * feign client 存放目录,注意 @EnablePigxFeignClients 的扫描范围
+ */
+package com.pig4cloud.pigx.flow.engine.api.feign;
\ No newline at end of file
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/constant/packge-info.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/constant/packge-info.java
new file mode 100644
index 000000000..99b19b1da
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/constant/packge-info.java
@@ -0,0 +1,6 @@
+/*
+ * @author pigx archetype
+ *
+ * 常量和枚举定义
+ */
+package com.pig4cloud.pigx.flow.engine.constant;
\ No newline at end of file
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/dto/packge-info.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/dto/packge-info.java
new file mode 100644
index 000000000..6d96d5cdd
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/dto/packge-info.java
@@ -0,0 +1,6 @@
+/*
+ * @author pigx archetype
+ *
+ * DTO 存放目录
+ */
+package com.pig4cloud.pigx.flow.engine.dto;
\ No newline at end of file
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/entity/packge-info.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/entity/packge-info.java
new file mode 100644
index 000000000..f841cdff5
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-api/src/main/java/com/pig4cloud/pigx/flow/engine/entity/packge-info.java
@@ -0,0 +1,6 @@
+/*
+ * @author pigx archetype
+ *
+ * 实体 存放目录
+ */
+package com.pig4cloud.pigx.flow.engine.entity;
\ No newline at end of file
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/Dockerfile b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/Dockerfile
new file mode 100644
index 000000000..2d03ba560
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/Dockerfile
@@ -0,0 +1,18 @@
+FROM pig4cloud/java:8-jre
+
+MAINTAINER wangiegie@gmail.com
+
+ENV TZ=Asia/Shanghai
+ENV JAVA_OPTS="-Xms512m -Xmx1024m -Djava.security.egd=file:/dev/./urandom"
+
+RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+RUN mkdir -p /pigx-flow-engine
+
+WORKDIR /pigx-flow-engine
+
+EXPOSE 4000
+
+ADD ./target/pigx-flow-engine-biz.jar ./
+
+CMD sleep 60;java $JAVA_OPTS -jar pigx-flow-engine-biz.jar
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/pom.xml b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/pom.xml
new file mode 100644
index 000000000..d7bcfcf09
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/pom.xml
@@ -0,0 +1,134 @@
+
+
+ 4.0.0
+
+ com.pig4cloud
+ pigx-flow-engine
+ 5.2.0-snapshot
+
+
+ pigx-flow-engine-biz
+
+
+
+
+ org.flowable
+ flowable-bpmn-layout
+ ${flowable.version}
+
+
+ org.flowable
+ flowable-spring-boot-starter-process
+ ${flowable.version}
+
+
+
+ com.pig4cloud
+ pigx-flow-task-api
+
+
+
+ com.googlecode.aviator
+ aviator
+
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+ com.pig4cloud
+ pigx-common-data
+
+
+
+ com.pig4cloud
+ pigx-common-security
+
+
+
+ com.pig4cloud
+ pigx-common-xss
+
+
+
+ com.pig4cloud
+ pigx-common-sentinel
+
+
+
+ com.pig4cloud
+ pigx-common-feign
+
+
+
+ com.pig4cloud
+ pigx-flow-engine-api
+
+
+
+ com.pig4cloud
+ pigx-common-log
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+
+ com.alibaba
+ druid-spring-boot-starter
+
+
+
+ com.mysql
+ mysql-connector-j
+
+
+
+ com.pig4cloud
+ pigx-common-swagger
+
+
+ cn.hutool
+ hutool-cache
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ io.fabric8
+ docker-maven-plugin
+
+
+
+
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/PigxFlowEngineApplication.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/PigxFlowEngineApplication.java
new file mode 100644
index 000000000..fef1590f2
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/PigxFlowEngineApplication.java
@@ -0,0 +1,26 @@
+package com.pig4cloud.pigx.flow.engine;
+
+import com.pig4cloud.pigx.common.feign.annotation.EnablePigxFeignClients;
+import com.pig4cloud.pigx.common.security.annotation.EnablePigxResourceServer;
+import com.pig4cloud.pigx.common.swagger.annotation.EnableOpenApi;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * @author pigx archetype
+ *
+ * 项目启动类
+ */
+@EnableOpenApi("engine")
+@EnablePigxFeignClients
+@EnableDiscoveryClient
+@EnablePigxResourceServer
+@SpringBootApplication
+public class PigxFlowEngineApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(PigxFlowEngineApplication.class, args);
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/controller/EngineFlowController.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/controller/EngineFlowController.java
new file mode 100644
index 000000000..0d98c83fe
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/controller/EngineFlowController.java
@@ -0,0 +1,298 @@
+package com.pig4cloud.pigx.flow.engine.controller;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pigx.common.core.util.R;
+import com.pig4cloud.pigx.flow.engine.utils.ModelUtil;
+import com.pig4cloud.pigx.flow.task.dto.*;
+import com.pig4cloud.pigx.flow.task.entity.Process;
+import com.pig4cloud.pigx.flow.task.utils.NodeUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.bpmn.BpmnAutoLayout;
+import org.flowable.bpmn.converter.BpmnXMLConverter;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.common.engine.impl.identity.Authentication;
+import org.flowable.engine.HistoryService;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.history.HistoricActivityInstanceQuery;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.runtime.Execution;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.image.impl.DefaultProcessDiagramGenerator;
+import org.flowable.task.api.TaskQuery;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.InputStream;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 工作流控制器 负责流程模型的创建、启动、审批等功能
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/flow")
+public class EngineFlowController {
+
+ private final TaskService taskService;
+
+ private final HistoryService historyService;
+
+ private final RepositoryService repositoryService;
+
+ private final RuntimeService runtimeService;
+
+ private final ObjectMapper objectMapper;
+
+ /**
+ * 创建流程定义
+ * @param map 创建参数
+ * @return 流程定义ID
+ */
+ @PostMapping("create")
+ @SneakyThrows
+ public R create(@RequestBody Map map) {
+
+ Long userId = MapUtil.getLong(map, "userId");
+
+ Process process = MapUtil.get(map, "process", Process.class);
+
+ String flowId = "P" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN)
+ + RandomUtil.randomString(5).toUpperCase();
+ log.info("flowId={}", flowId);
+ BpmnModel bpmnModel = ModelUtil.buildBpmnModel(objectMapper.readValue(process.getProcess(), Node.class),
+ process.getName(), flowId);
+ {
+ byte[] bpmnBytess = new BpmnXMLConverter().convertToXML(bpmnModel);
+ String filename = "/tmp/flowable-deployment/" + flowId + ".bpmn20.xml";
+ log.debug("部署时的模型文件:{}", filename);
+ FileUtil.writeBytes(bpmnBytess, filename);
+ }
+ repositoryService.createDeployment().addBpmnModel(StrUtil.format("{}.bpmn20.xml", "pig"), bpmnModel).deploy();
+
+ return R.ok(flowId);
+ }
+
+ /**
+ * 启动流程实例
+ * @param processInstanceParamDto 启动参数
+ * @return 流程实例ID
+ */
+ @PostMapping("/start")
+ public R start(@RequestBody ProcessInstanceParamDto processInstanceParamDto) {
+ Authentication.setAuthenticatedUserId(processInstanceParamDto.getStartUserId());
+ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processInstanceParamDto.getFlowId(),
+ processInstanceParamDto.getParamMap());
+
+ String processInstanceId = processInstance.getProcessInstanceId();
+ return R.ok(processInstanceId);
+
+ }
+
+ /**
+ * 审批任务
+ * @param taskId 任务ID
+ * @param approved 是否通过
+ */
+ @PostMapping("/approve")
+ public void approve(String taskId, boolean approved) {
+ Map variables = new HashMap<>();
+ variables.put("approved", approved);
+ variables.put("ko", 10);
+ variables.put("assigneeListSub", CollUtil.newArrayList("aa", "bb"));
+ taskService.complete(taskId, variables);
+
+ }
+
+ /**
+ * 停止流程实例
+ * @param taskParamDto 参数
+ * @return 操作结果
+ */
+ @PostMapping("stopProcessInstance")
+ public R stopProcessInstance(@RequestBody TaskParamDto taskParamDto) {
+ List processInstanceIdList = taskParamDto.getProcessInstanceIdList();
+ processInstanceIdList.forEach(processInstanceId -> {
+ // 查询流程实例
+ ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .singleResult();
+ if (Optional.ofNullable(processInstance).isPresent()) {
+ // 查询执行实例
+ List executionIds = runtimeService.createExecutionQuery()
+ .parentId(processInstanceId)
+ .list()
+ .stream()
+ .map(Execution::getId)
+ .collect(Collectors.toList());
+ // 更改活动状态为结束
+ runtimeService.createChangeActivityStateBuilder()
+ .moveExecutionsToSingleActivityId(executionIds, "end")
+ .changeState();
+ }
+ });
+ return R.ok();
+ }
+
+ /**
+ * 查询用户已办任务
+ * @param taskQueryParamDto 查询参数
+ * @return 分页任务信息
+ */
+ @PostMapping("/queryCompletedTask")
+ public R queryCompletedTask(@RequestBody TaskQueryParamDto taskQueryParamDto) {
+ HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService
+ .createHistoricActivityInstanceQuery();
+ HistoricActivityInstanceQuery activityInstanceQuery = historicActivityInstanceQuery
+ .taskAssignee(taskQueryParamDto.getAssign())
+ .finished()
+ .orderByHistoricActivityInstanceEndTime()
+ .desc();
+
+ if (ArrayUtil.isNotEmpty(taskQueryParamDto.getTaskTime())) {
+ ZoneId zoneId = ZoneId.systemDefault();
+ ZonedDateTime zonedBeforeDateTime = taskQueryParamDto.getTaskTime()[0].atZone(zoneId);
+ ZonedDateTime zonedAfterDateTime = taskQueryParamDto.getTaskTime()[1].atZone(zoneId);
+ Date beforeDate = Date.from(zonedBeforeDateTime.toInstant());
+ Date afterDate = Date.from(zonedAfterDateTime.toInstant());
+ activityInstanceQuery.finishedBefore(afterDate).finishedAfter(beforeDate);
+ }
+
+ List list = activityInstanceQuery.listPage(
+ (taskQueryParamDto.getPageNum() - 1) * taskQueryParamDto.getPageSize(),
+ taskQueryParamDto.getPageSize());
+
+ long count = activityInstanceQuery.count();
+ List taskDtoList = new ArrayList<>();
+
+ for (HistoricActivityInstance historicActivityInstance : list) {
+ String activityId = historicActivityInstance.getActivityId();
+ String activityName = historicActivityInstance.getActivityName();
+ String executionId = historicActivityInstance.getExecutionId();
+ String taskId = historicActivityInstance.getTaskId();
+ Date startTime = historicActivityInstance.getStartTime();
+ Date endTime = historicActivityInstance.getEndTime();
+ Long durationInMillis = historicActivityInstance.getDurationInMillis();
+ String processInstanceId = historicActivityInstance.getProcessInstanceId();
+
+ String processDefinitionId = historicActivityInstance.getProcessDefinitionId();
+ // 流程id
+ String flowId = NodeUtil.getFlowId(processDefinitionId);
+
+ TaskDto taskDto = new TaskDto();
+ taskDto.setFlowId(flowId);
+ taskDto.setTaskCreateTime(startTime);
+ taskDto.setTaskEndTime(endTime);
+ taskDto.setNodeId(activityId);
+ taskDto.setExecutionId(executionId);
+ taskDto.setProcessInstanceId(processInstanceId);
+ taskDto.setDurationInMillis(durationInMillis);
+ taskDto.setTaskId(taskId);
+ taskDto.setAssign(historicActivityInstance.getAssignee());
+ taskDto.setTaskName(activityName);
+
+ taskDtoList.add(taskDto);
+ }
+
+ Page pageResultDto = new Page<>();
+ pageResultDto.setTotal(count);
+ pageResultDto.setRecords(taskDtoList);
+ return R.ok(pageResultDto);
+ }
+
+ /**
+ * 查询用户待办任务
+ * @param taskQueryParamDto 查询参数
+ * @return 分页任务信息
+ */
+ @PostMapping("/queryAssignTask")
+ public R queryAssignTask(@RequestBody TaskQueryParamDto taskQueryParamDto) {
+
+ String assign = taskQueryParamDto.getAssign();
+
+ List taskDtoList = new ArrayList<>();
+
+ int pageIndex = taskQueryParamDto.getPageNum() - 1;
+ int pageSize = taskQueryParamDto.getPageSize();
+
+ Page pageResultDto = new Page<>();
+ TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(assign).orderByTaskCreateTime().desc();
+ if (StrUtil.isNotBlank(taskQueryParamDto.getProcessName())) {
+ List processInstanceList = runtimeService.createProcessInstanceQuery()
+ .processInstanceNameLikeIgnoreCase(taskQueryParamDto.getProcessName())
+ .list();
+ if (CollUtil.isNotEmpty(processInstanceList)) {
+ taskQuery.processInstanceIdIn(processInstanceList.stream()
+ .map(ProcessInstance::getProcessDefinitionId)
+ .collect(Collectors.toList()));
+ }
+ }
+
+ if (ArrayUtil.isNotEmpty(taskQueryParamDto.getTaskTime())) {
+ ZoneId zoneId = ZoneId.systemDefault();
+ ZonedDateTime zonedBeforeDateTime = taskQueryParamDto.getTaskTime()[0].atZone(zoneId);
+ ZonedDateTime zonedAfterDateTime = taskQueryParamDto.getTaskTime()[1].atZone(zoneId);
+ Date beforeDate = Date.from(zonedBeforeDateTime.toInstant());
+ Date afterDate = Date.from(zonedAfterDateTime.toInstant());
+ taskQuery.taskCreatedBefore(afterDate).taskCreatedAfter(beforeDate);
+ }
+
+ taskQuery.listPage(pageIndex * pageSize, pageSize).forEach(task -> {
+ String taskId = task.getId();
+ String processInstanceId = task.getProcessInstanceId();
+ log.debug("(taskId) " + task.getName() + " processInstanceId={} executrionId={}", processInstanceId,
+ task.getExecutionId());
+
+ Map taskServiceVariables = taskService.getVariables(taskId);
+ Map runtimeServiceVariables = runtimeService.getVariables(processInstanceId);
+ Map variables = runtimeService.getVariables(task.getExecutionId());
+ log.debug("任务变量:{}", JSONUtil.toJsonStr(taskServiceVariables));
+ log.debug("流程节点:{}", JSONUtil.toJsonStr(runtimeServiceVariables));
+ log.debug("执行节点变量:{}", JSONUtil.toJsonStr(variables));
+
+ String taskDefinitionKey = task.getTaskDefinitionKey();
+ String processDefinitionId = task.getProcessDefinitionId();
+ String flowId = NodeUtil.getFlowId(processDefinitionId);
+
+ TaskDto taskDto = new TaskDto();
+ taskDto.setFlowId(flowId);
+ taskDto.setTaskCreateTime(task.getCreateTime());
+ taskDto.setNodeId(taskDefinitionKey);
+ taskDto.setParamMap(taskServiceVariables);
+ taskDto.setProcessInstanceId(processInstanceId);
+ taskDto.setTaskId(taskId);
+ taskDto.setAssign(task.getAssignee());
+ taskDto.setTaskName(task.getName());
+
+ taskDtoList.add(taskDto);
+ });
+
+ long count = taskQuery.count();
+
+ log.debug("当前有" + count + " 个任务:");
+
+ pageResultDto.setTotal(count);
+ pageResultDto.setRecords(taskDtoList);
+
+ return R.ok(pageResultDto);
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/controller/EngineProcessInstanceController.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/controller/EngineProcessInstanceController.java
new file mode 100644
index 000000000..be2d2b0e5
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/controller/EngineProcessInstanceController.java
@@ -0,0 +1,71 @@
+package com.pig4cloud.pigx.flow.engine.controller;
+
+import com.pig4cloud.pigx.common.core.util.R;
+import com.pig4cloud.pigx.flow.task.dto.IndexPageStatistics;
+import com.pig4cloud.pigx.flow.task.dto.VariableQueryParamDto;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.HistoryService;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.history.HistoricActivityInstanceQuery;
+import org.flowable.task.api.TaskQuery;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * 任务控制器
+ */
+@RestController
+@Slf4j
+@RequiredArgsConstructor
+@RequestMapping("/process-instance")
+public class EngineProcessInstanceController {
+
+ private final TaskService taskService;
+
+ private final HistoryService historyService;
+
+ private final RuntimeService runtimeService;
+
+ /**
+ * 查询首页统计数量
+ * @param userId 用户ID
+ * @return 统计结果
+ */
+ @GetMapping("querySimpleData")
+ public R querySimpleData(long userId) {
+ TaskQuery taskQuery = taskService.createTaskQuery();
+
+ // 待办数量
+ long pendingNum = taskQuery.taskAssignee(String.valueOf(userId)).count();
+ // 已完成任务
+ HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService
+ .createHistoricActivityInstanceQuery();
+
+ long completedNum = historicActivityInstanceQuery.taskAssignee(String.valueOf(userId)).finished().count();
+
+ IndexPageStatistics indexPageStatistics = IndexPageStatistics.builder()
+ .pendingNum(pendingNum)
+ .completedNum(completedNum)
+ .build();
+
+ return R.ok(indexPageStatistics);
+ }
+
+ /**
+ * 查询流程变量
+ * @param paramDto 查询参数
+ * @return 流程变量
+ */
+ @PostMapping("queryVariables")
+ public R queryVariables(@RequestBody VariableQueryParamDto paramDto) {
+
+ Map variables = runtimeService.getVariables(paramDto.getExecutionId());
+
+ return R.ok(variables);
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/controller/EngineTaskController.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/controller/EngineTaskController.java
new file mode 100644
index 000000000..4171408d1
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/controller/EngineTaskController.java
@@ -0,0 +1,149 @@
+package com.pig4cloud.pigx.flow.engine.controller;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import com.pig4cloud.pigx.common.core.util.R;
+import com.pig4cloud.pigx.flow.task.dto.TaskParamDto;
+import com.pig4cloud.pigx.flow.task.dto.TaskResultDto;
+import com.pig4cloud.pigx.flow.task.dto.VariableQueryParamDto;
+import com.pig4cloud.pigx.flow.task.utils.NodeUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.HistoryService;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.TaskService;
+import org.flowable.task.api.DelegationState;
+import org.flowable.task.api.Task;
+import org.flowable.task.api.TaskQuery;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 任务控制器
+ */
+@RestController
+@Slf4j
+@RequiredArgsConstructor
+@RequestMapping("/task")
+public class EngineTaskController {
+
+ private final TaskService taskService;
+
+ private final HistoryService historyService;
+
+ private final RuntimeService runtimeService;
+
+ /**
+ * 查询任务变量
+ * @param paramDto 参数DTO
+ * @return 变量Map
+ */
+ @PostMapping("queryTaskVariables")
+ public R queryTaskVariables(@RequestBody VariableQueryParamDto paramDto) {
+
+ List keyList = paramDto.getKeyList();
+ if (CollUtil.isEmpty(keyList)) {
+ TaskQuery taskQuery = taskService.createTaskQuery();
+
+ Task task = taskQuery.taskId(paramDto.getTaskId()).singleResult();
+ if (task == null) {
+ return R.failed("任务不存在");
+ }
+
+ Map variables = runtimeService.getVariables(task.getExecutionId());
+
+ return R.ok(variables);
+ }
+
+ Map variables = taskService.getVariables(paramDto.getTaskId(), keyList);
+ return R.ok(variables);
+
+ }
+
+ /**
+ * 查询任务详情
+ * @param taskId 任务ID
+ * @param userId 用户ID
+ * @return 任务结果DTO
+ */
+ @GetMapping("/engine/queryTask")
+ public R queryTask(String taskId, String userId) {
+ Optional task = Optional
+ .ofNullable(taskService.createTaskQuery().taskId(taskId).taskAssignee(userId).singleResult());
+
+ if (task.isPresent()) {
+ String processDefinitionId = task.get().getProcessDefinitionId();
+ String taskDefinitionKey = task.get().getTaskDefinitionKey();
+ DelegationState delegationState = task.get().getDelegationState();
+ String processInstanceId = task.get().getProcessInstanceId();
+ Object delegateVariable = taskService.getVariableLocal(taskId, "delegate");
+
+ String flowId = NodeUtil.getFlowId(processDefinitionId);
+ Map variables = taskService.getVariables(taskId);
+ Map variableAll = new HashMap<>(variables);
+
+ TaskResultDto taskResultDto = new TaskResultDto();
+ taskResultDto.setFlowId(flowId);
+ taskResultDto.setProcessInstanceId(processDefinitionId);
+ taskResultDto.setNodeId(taskDefinitionKey);
+ taskResultDto.setCurrentTask(true);
+ taskResultDto.setDelegate(Convert.toBool(delegateVariable, false));
+ taskResultDto.setVariableAll(variableAll);
+ taskResultDto.setProcessInstanceId(processInstanceId);
+ taskResultDto.setDelegationState(delegationState == null ? null : delegationState.toString());
+
+ return R.ok(taskResultDto);
+ }
+ else {
+ Optional historicTaskInstance = Optional
+ .ofNullable(historyService.createHistoricTaskInstanceQuery()
+ .taskId(taskId)
+ .taskAssignee(userId)
+ .singleResult());
+
+ if (historicTaskInstance.isPresent()) {
+ String processDefinitionId = historicTaskInstance.get().getProcessDefinitionId();
+ String taskDefinitionKey = historicTaskInstance.get().getTaskDefinitionKey();
+ String processInstanceId = historicTaskInstance.get().getProcessInstanceId();
+
+ String flowId = NodeUtil.getFlowId(processDefinitionId);
+ Map variableAll = new HashMap<>();
+
+ TaskResultDto taskResultDto = new TaskResultDto();
+ taskResultDto.setFlowId(flowId);
+ taskResultDto.setNodeId(taskDefinitionKey);
+ taskResultDto.setCurrentTask(false);
+ taskResultDto.setVariableAll(variableAll);
+ taskResultDto.setProcessInstanceId(processInstanceId);
+
+ return R.ok(taskResultDto);
+ }
+ else {
+ return R.failed("任务不存在");
+ }
+ }
+ }
+
+ /**
+ * 完成任务
+ * @param taskParamDto 任务参数DTO
+ * @return 操作结果
+ */
+ @PostMapping("/complete")
+ public R complete(@RequestBody TaskParamDto taskParamDto) {
+ Map taskLocalParamMap = taskParamDto.getTaskLocalParamMap();
+ if (CollUtil.isNotEmpty(taskLocalParamMap)) {
+ taskService.setVariablesLocal(taskParamDto.getTaskId(), taskLocalParamMap);
+ }
+
+ taskService.complete(taskParamDto.getTaskId(), taskParamDto.getParamMap());
+
+ return R.ok();
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/ExpressionHandler.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/ExpressionHandler.java
new file mode 100644
index 000000000..a0c80faa6
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/ExpressionHandler.java
@@ -0,0 +1,311 @@
+package com.pig4cloud.pigx.flow.engine.expression;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.EscapeUtil;
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.googlecode.aviator.AviatorEvaluator;
+import com.pig4cloud.pigx.admin.api.entity.SysUser;
+import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
+import com.pig4cloud.pigx.common.core.util.R;
+import com.pig4cloud.pigx.flow.task.constant.NodeUserTypeEnum;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+import com.pig4cloud.pigx.flow.task.dto.SelectValue;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 表达式解析
+ */
+@Slf4j
+@Component("expressionHandler")
+@RequiredArgsConstructor
+public class ExpressionHandler {
+
+ private final RemoteUserService remoteUserService;
+
+ private final ObjectMapper objectMapper;
+
+ @SneakyThrows
+ public Long getUserId(String key, DelegateExecution execution) {
+ Object variable = execution.getVariable(key);
+ NodeUser nodeUserDto = objectMapper
+ .readValue(objectMapper.writeValueAsString(variable), new TypeReference>() {
+ })
+ .get(0);
+ return nodeUserDto.getId();
+ }
+
+ /**
+ * 日期时间比较
+ * @param key
+ * @param symbol
+ * @param param
+ * @param execution
+ * @param format 时间格式化模式
+ * @return
+ */
+ public boolean dateTimeCompare(String key, String symbol, Object param, DelegateExecution execution,
+ String format) {
+
+ Object value = execution.getVariable(key);
+
+ // 表单值为空
+ if (value == null) {
+ return false;
+ }
+
+ long valueTime = DateUtil.parse(value.toString(), format).getTime();
+ long paramTime = DateUtil.parse(param.toString(), format).getTime();
+
+ return compare(StrUtil.format("key{}{}", symbol, paramTime), Dict.create().set("key", valueTime));
+ }
+
+ /**
+ * 数字类型比较
+ * @param key 表单key
+ * @param symbol 比较符号
+ * @param param 表单参数
+ * @param execution
+ * @return
+ */
+ public boolean numberCompare(String key, String symbol, Object param, DelegateExecution execution) {
+
+ Object value = execution.getVariable(key);
+
+ // 表单值为空
+ if (value == null) {
+ return false;
+ }
+
+ return compare(StrUtil.format("key{}{}", symbol, param), Dict.create().set("key", Convert.toNumber(value)));
+
+ }
+
+ private Boolean compare(String symbol, Dict value) {
+ Object result = AviatorEvaluator.getInstance().execute(symbol, value);
+ // 渲染结果
+ log.debug("验证结果:{}", result);
+ return Convert.toBool(result, false);
+ }
+
+ /**
+ * 日期类型对比
+ * @param key 表单key
+ * @param symbol 比较符号
+ * @param param 比较参数值
+ * @param execution 上下午执行前
+ * @param format 日期格式化字符串
+ * @return
+ */
+ public boolean dateCompare(String key, String symbol, Object param, DelegateExecution execution, String format) {
+
+ Object value = execution.getVariable(key);
+
+ // 表单值为空
+ if (value == null) {
+ return false;
+ }
+
+ // 处理表单值
+ DateTime valueDateTime = DateUtil.parse(value.toString(), format);
+ log.debug("表单值:{} 格式化显示:{}", valueDateTime.getTime(), DateUtil.formatDateTime(valueDateTime));
+ // 处理参数值
+ DateTime paramDateTime = DateUtil.parse(param.toString(), format);
+ log.debug("参数值:{} 格式化显示:{}", paramDateTime.getTime(), DateUtil.formatDateTime(paramDateTime));
+
+ // 获取模板
+ return compare(StrUtil.format("key{}{}", symbol, paramDateTime.getTime()),
+ Dict.create().set("key", valueDateTime.getTime()));
+
+ }
+
+ /**
+ * 判断数字数组包含
+ * @param key 表单key
+ * @param array 条件值
+ * @return
+ */
+ public boolean numberContain(String key, DelegateExecution execution, Object... array) {
+
+ Object value = execution.getVariable(key);
+
+ if (value == null) {
+ return false;
+ }
+
+ return numberContain(value, array);
+ }
+
+ private static boolean numberContain(Object value, Object[] array) {
+ BigDecimal valueBigDecimal = Convert.toBigDecimal(value);
+
+ for (Object aLong : array) {
+ if (valueBigDecimal.compareTo(Convert.toBigDecimal(aLong)) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 单选处理
+ * @param key 表单key
+ * @param array 条件值
+ * @return
+ */
+ public boolean singleSelectHandler(String key, DelegateExecution execution, String... array) {
+ Object value = execution.getVariable(key);
+
+ if (value == null) {
+ return false;
+ }
+ List list = Convert.toList(SelectValue.class, value);
+ if (CollUtil.isEmpty(list)) {
+ return false;
+ }
+ return ArrayUtil.contains(array, list.get(0).getKey());
+ }
+
+ /**
+ * 字符串判断包含
+ * @param key 表单key
+ * @param param 参数
+ * @return
+ */
+ public boolean stringContain(String key, String param, DelegateExecution execution) {
+ Object value = execution.getVariable(key);
+
+ if (value == null) {
+ return false;
+ }
+ return StrUtil.contains(value.toString(), param);
+ }
+
+ /**
+ * 字符串判断相等
+ * @param key 表单key
+ * @param param 参数
+ * @return
+ */
+ public boolean stringEqual(String key, String param, DelegateExecution execution) {
+ Object value = execution.getVariable(key);
+
+ if (value == null) {
+ return false;
+ }
+ return StrUtil.equals(value.toString(), param);
+ }
+
+ @SneakyThrows
+ public boolean deptCompare(String key, String param, String symbol, DelegateExecution execution) {
+ param = EscapeUtil.unescape(param);
+
+ Object value = execution.getVariable(key);
+
+ String jsonString = objectMapper.writeValueAsString(value);
+ log.debug("表单值:key={} value={} symbol={}", key, jsonString, symbol);
+ log.debug("条件 参数:{}", param);
+ if (value == null) {
+ return false;
+ }
+
+ // 表单值
+ List nodeUserDtoList = objectMapper.readValue(jsonString, new TypeReference>() {
+ });
+ if (CollUtil.isEmpty(nodeUserDtoList) || nodeUserDtoList.size() != 1) {
+ return false;
+ }
+ NodeUser nodeUserDto = nodeUserDtoList.get(0);
+
+ // 参数
+
+ List paramDeptList = objectMapper.readValue(param, new TypeReference>() {
+ });
+ Long deptId = nodeUserDto.getId();
+ List deptIdList = paramDeptList.stream().map(NodeUser::getId).collect(Collectors.toList());
+
+ return inCompare(symbol, deptId, deptIdList);
+
+ }
+
+ private static boolean inCompare(String symbol, Long deptId, List deptIdList) {
+ if (StrUtil.equalsAny(symbol, "in", "==")) {
+ // 属于
+ return deptIdList.contains(deptId);
+ }
+ if (StrUtil.equalsAny(symbol, "notin", "!=")) {
+ // 属于
+ return !deptIdList.contains(deptId);
+ }
+
+ return false;
+ }
+
+ /**
+ * user判断
+ * @param key 表单key
+ * @param param 参数
+ * @return
+ */
+ @SneakyThrows
+ public boolean userCompare(String key, String param, String symbol, DelegateExecution execution) {
+ param = EscapeUtil.unescape(param);
+
+ Object value = execution.getVariable(key);
+
+ String jsonString = objectMapper.writeValueAsString(value);
+ log.debug("表单值:key={} value={} symbol={} ", key, jsonString, symbol);
+ log.debug("条件 参数:{}", param);
+
+ if (value == null) {
+ return false;
+ }
+
+ // 表单值
+ List nodeUserDtoList = objectMapper.readValue(jsonString, new TypeReference>() {
+ });
+
+ if (CollUtil.isEmpty(nodeUserDtoList) || nodeUserDtoList.size() != 1) {
+ return false;
+ }
+
+ NodeUser nodeUserDto = nodeUserDtoList.get(0);
+
+ // 参数
+ List paramDeptList = objectMapper.readValue(param, new TypeReference>() {
+ });
+
+ List deptIdList = paramDeptList.stream()
+ .filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.DEPT.getKey()))
+ .map(NodeUser::getId)
+ .collect(Collectors.toList());
+
+ List userIdList = paramDeptList.stream()
+ .filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.USER.getKey()))
+ .map(NodeUser::getId)
+ .collect(Collectors.toList());
+
+ if (CollUtil.isNotEmpty(deptIdList)) {
+ R> r = remoteUserService.getUserIdListByDeptIdList(deptIdList);
+ List data = r.getData().stream().map(SysUser::getUserId).collect(Collectors.toList());
+ userIdList.addAll(data);
+ }
+
+ return inCompare(symbol, Convert.toLong(nodeUserDto.getId()), userIdList);
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/NodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/NodeConditionStrategy.java
new file mode 100644
index 000000000..6eca2fd28
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/NodeConditionStrategy.java
@@ -0,0 +1,15 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition;
+
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+
+/**
+ * 节点单个条件处理器
+ */
+public interface NodeConditionStrategy {
+
+ /**
+ * 抽象方法 处理表达式
+ */
+ String handle(Condition condition);
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/NodeExpressionStrategyFactory.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/NodeExpressionStrategyFactory.java
new file mode 100644
index 000000000..150766156
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/NodeExpressionStrategyFactory.java
@@ -0,0 +1,99 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import com.pig4cloud.pigx.flow.task.dto.GroupCondition;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class NodeExpressionStrategyFactory {
+
+ public static String handleSingleCondition(Condition nodeConditionDto) {
+ Map nodeConditionStrategyMap = SpringUtil
+ .getBeansOfType(NodeConditionStrategy.class);
+ NodeConditionStrategy nodeConditionHandler = nodeConditionStrategyMap
+ .get(nodeConditionDto.getKeyType() + "NodeConditionStrategy");
+ if (nodeConditionHandler == null) {
+ return "(1==1)";
+ }
+ return nodeConditionHandler.handle(nodeConditionDto);
+ }
+
+ /**
+ * 组内处理表达式
+ * @param groupDto
+ * @return
+ */
+ public static String handleGroupCondition(GroupCondition groupDto) {
+
+ List exps = new ArrayList<>();
+
+ for (Condition condition : groupDto.getConditionList()) {
+ String singleExpression = handleSingleCondition(condition);
+ exps.add(singleExpression);
+ }
+ Boolean mode = groupDto.getMode();
+
+ if (!mode) {
+ String join = CollUtil.join(exps, "||");
+
+ return "(" + join + ")";
+ }
+
+ String join = CollUtil.join(exps, "&&");
+ return "(" + join + ")";
+ }
+
+ /**
+ * 处理单个分支表达式
+ * @return
+ */
+ public static String handle(Node node) {
+
+ List exps = new ArrayList<>();
+
+ List groups = node.getConditionList();
+ if (CollUtil.isEmpty(groups)) {
+ return "${1==1}";
+ }
+ for (GroupCondition group : groups) {
+ String s = handleGroupCondition(group);
+ exps.add(s);
+ }
+
+ if (!node.getGroupMode()) {
+ String join = CollUtil.join(exps, "||");
+ return "${(" + join + ")}";
+ }
+
+ String join = CollUtil.join(exps, "&&");
+ return "${(" + join + ")}";
+ }
+
+ public static String handleDefaultBranch(List branchs, int currentIndex) {
+
+ List expList = new ArrayList<>();
+
+ int index = 1;
+ for (Node branch : branchs) {
+
+ if (index == currentIndex + 1) {
+ continue;
+ }
+
+ String exp = handle(branch);
+ String s = StrUtil.subBetween(exp, "${", "}");
+ expList.add(StrUtil.format("({})", s));
+
+ index++;
+ }
+ String join = StrUtil.format("!({})", CollUtil.join(expList, "||"));
+ return "${" + join + "}";
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/DateNodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/DateNodeConditionStrategy.java
new file mode 100644
index 000000000..85a17089f
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/DateNodeConditionStrategy.java
@@ -0,0 +1,29 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeConditionStrategy;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字符类型处理器
+ */
+@Component("DateNodeConditionStrategy")
+public class DateNodeConditionStrategy implements NodeConditionStrategy {
+
+ /**
+ * 抽象方法 处理表达式
+ */
+ @Override
+ public String handle(Condition condition) {
+
+ String compare = condition.getExpression();
+ String id = condition.getKey();
+ Object value = condition.getValue();
+
+ return StrUtil.format("(expressionHandler.dateTimeCompare(\"{}\",\"{}\",\"{}\",execution,\"yyyy-MM-dd\"))", id,
+ compare, value);
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/DateTimeNodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/DateTimeNodeConditionStrategy.java
new file mode 100644
index 000000000..c6e891525
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/DateTimeNodeConditionStrategy.java
@@ -0,0 +1,30 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeConditionStrategy;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字符类型处理器
+ */
+@Component("DateTimeNodeConditionStrategy")
+public class DateTimeNodeConditionStrategy implements NodeConditionStrategy {
+
+ /**
+ * 抽象方法 处理表达式
+ */
+ @Override
+ public String handle(Condition condition) {
+
+ String compare = condition.getExpression();
+ String id = condition.getKey();
+ Object value = condition.getValue();
+
+ return StrUtil.format(
+ "(expressionHandler.dateTimeCompare(\"{}\",\"{}\",\"{}\",execution,\"yyyy-MM-dd " + "HH:mm:ss\"))", id,
+ compare, value);
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/InputNodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/InputNodeConditionStrategy.java
new file mode 100644
index 000000000..6176f80b6
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/InputNodeConditionStrategy.java
@@ -0,0 +1,39 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeConditionStrategy;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字符类型处理器
+ */
+@Component("InputNodeConditionStrategy")
+public class InputNodeConditionStrategy implements NodeConditionStrategy {
+
+ /**
+ * 抽象方法 处理表达式
+ *
+ */
+ @Override
+ public String handle(Condition condition) {
+
+ String compare = condition.getExpression();
+ String id = condition.getKey();
+ Object value = condition.getValue();
+ if (StrUtil.equals(compare, "==")) {
+ return StrUtil.format("(expressionHandler.stringEqual(\"{}\",\"{}\",execution))", id, value);
+ }
+ if (StrUtil.equals(compare, "contain")) {
+ return StrUtil.format("(expressionHandler.stringContain(\"{}\",\"{}\",execution))", id, value);
+ }
+ if (StrUtil.equals(compare, "notcontain")) {
+ return StrUtil.format("(!expressionHandler.stringContain(\"{}\",\"{}\",execution))", id, value);
+ }
+ if (StrUtil.equals(compare, "!=")) {
+ return StrUtil.format("(!expressionHandler.stringEqual(\"{}\",\"{}\",execution))", id, value);
+ }
+ return "(2==2)";
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/MoneyNodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/MoneyNodeConditionStrategy.java
new file mode 100644
index 000000000..90e720521
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/MoneyNodeConditionStrategy.java
@@ -0,0 +1,28 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeConditionStrategy;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字符类型处理器
+ */
+@Component("MoneyNodeConditionStrategy")
+public class MoneyNodeConditionStrategy implements NodeConditionStrategy {
+
+ /**
+ * 抽象方法 处理表达式
+ */
+ @Override
+ public String handle(Condition condition) {
+
+ String compare = condition.getExpression();
+ String id = condition.getKey();
+ Object value = condition.getValue();
+
+ return StrUtil.format("(expressionHandler.numberCompare(\"{}\",\"{}\",{},execution))", id, compare, value);
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/NumberNodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/NumberNodeConditionStrategy.java
new file mode 100644
index 000000000..a55086c55
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/NumberNodeConditionStrategy.java
@@ -0,0 +1,28 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeConditionStrategy;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字符类型处理器
+ */
+@Component("NumberNodeConditionStrategy")
+public class NumberNodeConditionStrategy implements NodeConditionStrategy {
+
+ /**
+ * 抽象方法 处理表达式
+ */
+ @Override
+ public String handle(Condition condition) {
+
+ String compare = condition.getExpression();
+ String id = condition.getKey();
+ Object value = condition.getValue();
+
+ return StrUtil.format("(expressionHandler.numberCompare(\"{}\",\"{}\",{},execution))", id, compare, value);
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/SelectDeptNodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/SelectDeptNodeConditionStrategy.java
new file mode 100644
index 000000000..6694dfc13
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/SelectDeptNodeConditionStrategy.java
@@ -0,0 +1,37 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition.impl;
+
+import cn.hutool.core.util.EscapeUtil;
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeConditionStrategy;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字符类型处理器
+ */
+@RequiredArgsConstructor
+@Component("SelectDeptNodeConditionStrategy")
+public class SelectDeptNodeConditionStrategy implements NodeConditionStrategy {
+
+ private final ObjectMapper objectMapper;
+
+ /**
+ * 抽象方法 处理表达式
+ */
+ @Override
+ @SneakyThrows
+ public String handle(Condition condition) {
+
+ String compare = condition.getExpression();
+ String id = condition.getKey();
+ Object value = condition.getValue();
+
+ return StrUtil.format("(expressionHandler.deptCompare(\"{}\",\"{}\",\"{}\", execution))", id,
+ EscapeUtil.escape(objectMapper.writeValueAsString(value)), compare);
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/SelectUserNodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/SelectUserNodeConditionStrategy.java
new file mode 100644
index 000000000..3af9c9749
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/SelectUserNodeConditionStrategy.java
@@ -0,0 +1,37 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition.impl;
+
+import cn.hutool.core.util.EscapeUtil;
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeConditionStrategy;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字符类型处理器
+ */
+@RequiredArgsConstructor
+@Component("SelectUserNodeConditionStrategy")
+public class SelectUserNodeConditionStrategy implements NodeConditionStrategy {
+
+ private final ObjectMapper objectMapper;
+
+ /**
+ * 抽象方法 处理表达式
+ */
+ @Override
+ @SneakyThrows
+ public String handle(Condition condition) {
+
+ String compare = condition.getExpression();
+ String id = condition.getKey();
+ Object value = condition.getValue();
+
+ return StrUtil.format("(expressionHandler.userCompare(\"{}\",\"{}\",\"{}\", execution))", id,
+ EscapeUtil.escape(objectMapper.writeValueAsString(value)), compare);
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/SingleSelectNodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/SingleSelectNodeConditionStrategy.java
new file mode 100644
index 000000000..7b4496b0b
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/SingleSelectNodeConditionStrategy.java
@@ -0,0 +1,48 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeConditionStrategy;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import com.pig4cloud.pigx.flow.task.dto.SelectValue;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 字符类型处理器
+ */
+@Component("SingleSelectNodeConditionStrategy")
+public class SingleSelectNodeConditionStrategy implements NodeConditionStrategy {
+
+ /**
+ * 抽象方法 处理表达式
+ */
+ @Override
+ public String handle(Condition condition) {
+
+ String compare = condition.getExpression();
+ String id = condition.getKey();
+ Object value = condition.getValue();
+
+ List list = Convert.toList(SelectValue.class, value);
+
+ StringBuilder sb = new StringBuilder();
+
+ for (SelectValue o : list) {
+ sb.append(",\"").append(o.getKey()).append("\"");
+ }
+ String string = sb.toString();
+ if (CollUtil.isNotEmpty(list)) {
+ string = string.substring(1);
+ }
+ if (compare.equals("in")) {
+ return StrUtil.format("(expressionHandler.singleSelectHandler(\"{}\", execution,{}))", id, string);
+ }
+
+ return StrUtil.format("(!expressionHandler.singleSelectHandler(\"{}\", execution,{}))", id, string);
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/TextareaNodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/TextareaNodeConditionStrategy.java
new file mode 100644
index 000000000..d4c8b219d
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/TextareaNodeConditionStrategy.java
@@ -0,0 +1,33 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeConditionStrategy;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字符类型处理器
+ */
+@Component("TextareaNodeConditionStrategy")
+public class TextareaNodeConditionStrategy implements NodeConditionStrategy {
+
+ /**
+ * 抽象方法 处理表达式
+ *
+ */
+ @Override
+ public String handle(Condition condition) {
+
+ String compare = condition.getExpression();
+ String id = condition.getKey();
+ Object value = condition.getValue();
+ if (StrUtil.equals(compare, "==")) {
+ return StrUtil.format("(expressionHandler.stringEqual(\"{}\",\"{}\",execution))", id, value);
+ }
+ if (StrUtil.equals(compare, "!=")) {
+ return StrUtil.format("(!expressionHandler.stringEqual(\"{}\",\"{}\",execution))", id, value);
+ }
+ return "(2==2)";
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/TimeNodeConditionStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/TimeNodeConditionStrategy.java
new file mode 100644
index 000000000..44db0c2f0
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/expression/condition/impl/TimeNodeConditionStrategy.java
@@ -0,0 +1,29 @@
+package com.pig4cloud.pigx.flow.engine.expression.condition.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeConditionStrategy;
+import com.pig4cloud.pigx.flow.task.dto.Condition;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字符类型处理器
+ */
+@Component("TimeNodeConditionStrategy")
+public class TimeNodeConditionStrategy implements NodeConditionStrategy {
+
+ /**
+ * 抽象方法 处理表达式
+ */
+ @Override
+ public String handle(Condition condition) {
+
+ String compare = condition.getExpression();
+ String id = condition.getKey();
+ Object value = condition.getValue();
+
+ return StrUtil.format("(expressionHandler.dateTimeCompare(\"{}\",\"{}\",\"{}\",execution,\"HH:mm:ss\"))", id,
+ compare, value);
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/ApprovalCreateListener.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/ApprovalCreateListener.java
new file mode 100644
index 000000000..5fbdc3989
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/ApprovalCreateListener.java
@@ -0,0 +1,91 @@
+package com.pig4cloud.pigx.flow.engine.listeners;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.pig4cloud.pigx.common.core.util.R;
+import com.pig4cloud.pigx.flow.task.api.feign.RemoteFlowTaskService;
+import com.pig4cloud.pigx.flow.task.constant.ProcessInstanceConstant;
+import com.pig4cloud.pigx.flow.task.dto.Nobody;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+import com.pig4cloud.pigx.flow.task.utils.NodeUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.TaskService;
+import org.flowable.task.service.delegate.DelegateTask;
+import org.flowable.task.service.delegate.TaskListener;
+import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
+
+/**
+ * 审批创建监听器
+ */
+@Slf4j
+public class ApprovalCreateListener implements TaskListener {
+
+ /**
+ * 当任务被触发时,执行该方法
+ * @param delegateTask 委派的任务对象
+ */
+ @Override
+ public void notify(DelegateTask delegateTask) {
+ log.debug(delegateTask.getClass().getCanonicalName());
+ TaskService taskService = SpringUtil.getBean(TaskService.class);
+
+ String assignee = delegateTask.getAssignee();
+ String name = delegateTask.getName();
+ log.debug("任务{}-执行人:{}", name, assignee);
+ TaskEntityImpl taskEntity = (TaskEntityImpl) delegateTask;
+ String nodeId = taskEntity.getTaskDefinitionKey();
+ String processDefinitionId = taskEntity.getProcessDefinitionId();
+
+ // 获取流程id
+ String flowId = NodeUtil.getFlowId(processDefinitionId);
+
+ if (StrUtil.isBlank(assignee)
+ || StrUtil.equals(ProcessInstanceConstant.DEFAULT_EMPTY_ASSIGN.toString(), assignee)) {
+
+ RemoteFlowTaskService remoteFlowTaskService = SpringUtil.getBean(RemoteFlowTaskService.class);
+
+ // 查询节点原始数据
+ Node node = remoteFlowTaskService.queryNodeOriData(flowId, nodeId).getData();
+
+ Nobody nobody = node.getNobody();
+
+ String handler = nobody.getHandler();
+
+ if (StrUtil.equals(handler, ProcessInstanceConstant.USER_TASK_NOBODY_HANDLER_TO_PASS)) {
+ // 直接通过
+ Dict param = Dict.create().set(StrUtil.format("{}_approve_condition", nodeId), true);
+ taskService.complete(taskEntity.getId(), param);
+ }
+
+ if (StrUtil.equals(handler, ProcessInstanceConstant.USER_TASK_NOBODY_HANDLER_TO_ADMIN)) {
+ // 指派给管理员
+
+ R longR = remoteFlowTaskService.queryProcessAdmin(flowId);
+
+ Long adminId = longR.getData();
+
+ taskService.setAssignee(taskEntity.getId(), String.valueOf(adminId));
+ }
+
+ if (StrUtil.equals(handler, ProcessInstanceConstant.USER_TASK_NOBODY_HANDLER_TO_USER)) {
+ // 指定用户
+
+ NodeUser nodeUser = nobody.getAssignedUser().get(0);
+
+ taskService.setAssignee(taskEntity.getId(), nodeUser.getId().toString());
+ }
+
+ if (StrUtil.equals(handler, ProcessInstanceConstant.USER_TASK_NOBODY_HANDLER_TO_REFUSE)) {
+ // 结束
+ Dict param = Dict.create().set(StrUtil.format("{}_approve_condition", nodeId), false);
+ taskService.complete(taskEntity.getId(), param);
+
+ }
+
+ }
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/FlowProcessEventListener.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/FlowProcessEventListener.java
new file mode 100644
index 000000000..4519627f6
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/FlowProcessEventListener.java
@@ -0,0 +1,296 @@
+package com.pig4cloud.pigx.flow.engine.listeners;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pigx.flow.task.api.feign.RemoteFlowTaskService;
+import com.pig4cloud.pigx.flow.task.dto.*;
+import com.pig4cloud.pigx.flow.task.utils.NodeUtil;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
+import org.flowable.common.engine.api.delegate.event.FlowableEntityEvent;
+import org.flowable.common.engine.api.delegate.event.FlowableEvent;
+import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.delegate.event.impl.FlowableActivityEventImpl;
+import org.flowable.engine.delegate.event.impl.FlowableMultiInstanceActivityCompletedEventImpl;
+import org.flowable.engine.delegate.event.impl.FlowableProcessStartedEventImpl;
+import org.flowable.engine.delegate.event.impl.FlowableProcessTerminatedEventImpl;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
+import org.flowable.task.api.DelegationState;
+import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
+import org.flowable.variable.api.event.FlowableVariableEvent;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 流程监听器
+ */
+@Slf4j
+public class FlowProcessEventListener implements FlowableEventListener {
+
+ /**
+ * 当事件被触发时调用
+ * @param event 事件对象
+ */
+ @SneakyThrows
+ @Override
+ public void onEvent(FlowableEvent event) {
+ RemoteFlowTaskService remoteFlowTaskService = SpringUtil.getBean(RemoteFlowTaskService.class);
+
+ log.info("分支监听器 类型={} class={}", event.getType(), event.getClass().getCanonicalName());
+ if (event.getType().toString().equals(FlowableEngineEventType.ACTIVITY_STARTED.toString())) {
+ // 节点开始执行
+ FlowableActivityEventImpl flowableActivityEvent = (FlowableActivityEventImpl) event;
+ String activityId = flowableActivityEvent.getActivityId();
+ String activityName = flowableActivityEvent.getActivityName();
+ log.info("节点id:{} 名字:{}", activityId, activityName);
+
+ String processInstanceId = flowableActivityEvent.getProcessInstanceId();
+
+ String processDefinitionId = flowableActivityEvent.getProcessDefinitionId();
+ String flowId = NodeUtil.getFlowId(processDefinitionId);
+
+ Node node = remoteFlowTaskService.queryNodeOriData(flowId, activityId).getData();
+
+ ProcessNodeRecordParamDto processNodeRecordParamDto = new ProcessNodeRecordParamDto();
+ processNodeRecordParamDto.setFlowId(flowId);
+ processNodeRecordParamDto.setProcessInstanceId(processInstanceId);
+ processNodeRecordParamDto.setNodeId(activityId);
+ if (node != null) {
+ processNodeRecordParamDto.setNodeType(String.valueOf(node.getType()));
+ }
+ processNodeRecordParamDto.setNodeName(activityName);
+ processNodeRecordParamDto.setExecutionId(flowableActivityEvent.getExecutionId());
+ remoteFlowTaskService.startNodeEvent(processNodeRecordParamDto);
+ }
+
+ if (event.getType()
+ .toString()
+ .equals(FlowableEngineEventType.MULTI_INSTANCE_ACTIVITY_COMPLETED_WITH_CONDITION.toString())
+ || event.getType()
+ .toString()
+ .equals(FlowableEngineEventType.MULTI_INSTANCE_ACTIVITY_COMPLETED.toString())) {
+ // 多实例任务
+ FlowableMultiInstanceActivityCompletedEventImpl flowableActivityEvent = (FlowableMultiInstanceActivityCompletedEventImpl) event;
+ String activityId = flowableActivityEvent.getActivityId();
+ String activityName = flowableActivityEvent.getActivityName();
+ log.info("节点id:{} 名字:{}", activityId, activityName);
+
+ String processInstanceId = flowableActivityEvent.getProcessInstanceId();
+
+ String processDefinitionId = flowableActivityEvent.getProcessDefinitionId();
+ String flowId = NodeUtil.getFlowId(processDefinitionId);
+
+ ProcessNodeRecordParamDto processNodeRecordParamDto = new ProcessNodeRecordParamDto();
+ processNodeRecordParamDto.setFlowId(flowId);
+ processNodeRecordParamDto.setExecutionId(flowableActivityEvent.getExecutionId());
+ processNodeRecordParamDto.setProcessInstanceId(processInstanceId);
+ // processNodeRecordParamDto.setData(JSON.toJSONString(processVariables));
+ processNodeRecordParamDto.setNodeId(activityId);
+ // processNodeRecordParamDto.setNodeType(nodeDto.getType());
+ processNodeRecordParamDto.setNodeName(activityName);
+
+ remoteFlowTaskService.endNodeEvent(processNodeRecordParamDto);
+ }
+ if (event.getType().toString().equals(FlowableEngineEventType.ACTIVITY_COMPLETED.toString())) {
+ // 节点完成执行
+
+ FlowableActivityEventImpl flowableActivityEvent = (FlowableActivityEventImpl) event;
+ String activityId = flowableActivityEvent.getActivityId();
+ String activityName = flowableActivityEvent.getActivityName();
+ log.info("节点id:{} 名字:{}", activityId, activityName);
+
+ String processInstanceId = flowableActivityEvent.getProcessInstanceId();
+
+ String processDefinitionId = flowableActivityEvent.getProcessDefinitionId();
+ String flowId = NodeUtil.getFlowId(processDefinitionId);
+
+ ProcessNodeRecordParamDto processNodeRecordParamDto = new ProcessNodeRecordParamDto();
+ processNodeRecordParamDto.setFlowId(flowId);
+ processNodeRecordParamDto.setExecutionId(flowableActivityEvent.getExecutionId());
+ processNodeRecordParamDto.setProcessInstanceId(processInstanceId);
+ processNodeRecordParamDto.setNodeId(activityId);
+ processNodeRecordParamDto.setNodeName(activityName);
+
+ remoteFlowTaskService.endNodeEvent(processNodeRecordParamDto);
+
+ }
+
+ if (event.getType().toString().equals(FlowableEngineEventType.VARIABLE_UPDATED.toString())) {
+ // 变量变化了
+ FlowableVariableEvent flowableVariableEvent = (FlowableVariableEvent) event;
+ log.debug("变量[{}]变化了:{} ", flowableVariableEvent.getVariableName(),
+ flowableVariableEvent.getVariableValue());
+ }
+
+ if (event.getType().toString().equals(FlowableEngineEventType.VARIABLE_CREATED.toString())) {
+ // 变量创建了
+ FlowableVariableEvent flowableVariableEvent = (FlowableVariableEvent) event;
+ log.debug("变量[{}]创建了:{} ", flowableVariableEvent.getVariableName(),
+ flowableVariableEvent.getVariableValue());
+ }
+ if (event.getType().toString().equals(FlowableEngineEventType.VARIABLE_DELETED.toString())) {
+ // 变量删除了
+ FlowableVariableEvent flowableVariableEvent = (FlowableVariableEvent) event;
+ log.debug("变量[{}]删除了:{} ", flowableVariableEvent.getVariableName(),
+ flowableVariableEvent.getVariableValue());
+ }
+ if (event.getType()
+ .toString()
+ .equals(FlowableEngineEventType.PROCESS_COMPLETED_WITH_TERMINATE_END_EVENT.toString())) {
+ // 流程开完成
+ FlowableProcessTerminatedEventImpl e = (FlowableProcessTerminatedEventImpl) event;
+ DelegateExecution execution = e.getExecution();
+ String processInstanceId = e.getProcessInstanceId();
+ ExecutionEntityImpl entity = (ExecutionEntityImpl) e.getEntity();
+
+ ProcessInstanceParamDto processInstanceParamDto = new ProcessInstanceParamDto();
+ processInstanceParamDto.setProcessInstanceId(processInstanceId);
+ remoteFlowTaskService.endProcessEvent(processInstanceParamDto);
+
+ }
+
+ ObjectMapper objectMapper = SpringUtil.getBean(ObjectMapper.class);
+ if (event.getType().toString().equals(FlowableEngineEventType.TASK_COMPLETED.toString())) {
+
+ TaskService taskService = SpringUtil.getBean(TaskService.class);
+
+ // 任务完成
+ FlowableEntityEvent flowableEntityEvent = (FlowableEntityEvent) event;
+ TaskEntityImpl task = (TaskEntityImpl) flowableEntityEvent.getEntity();
+ // 执行人id
+ String assignee = task.getAssignee();
+
+ // nodeid
+ String taskDefinitionKey = task.getTaskDefinitionKey();
+
+ // 实例id
+ String processInstanceId = task.getProcessInstanceId();
+
+ String processDefinitionId = task.getProcessDefinitionId();
+ // 流程id
+ String flowId = NodeUtil.getFlowId(processDefinitionId);
+ ProcessNodeRecordAssignUserParamDto processNodeRecordAssignUserParamDto = new ProcessNodeRecordAssignUserParamDto();
+ processNodeRecordAssignUserParamDto.setFlowId(flowId);
+ processNodeRecordAssignUserParamDto.setProcessInstanceId(processInstanceId);
+ processNodeRecordAssignUserParamDto
+ .setData(objectMapper.writeValueAsString(taskService.getVariables(task.getId())));
+ processNodeRecordAssignUserParamDto
+ .setLocalData(objectMapper.writeValueAsString(taskService.getVariablesLocal(task.getId())));
+ processNodeRecordAssignUserParamDto.setNodeId(taskDefinitionKey);
+ processNodeRecordAssignUserParamDto.setUserId(Long.parseLong(assignee));
+ processNodeRecordAssignUserParamDto.setTaskId(task.getId());
+ processNodeRecordAssignUserParamDto.setNodeName(task.getName());
+ processNodeRecordAssignUserParamDto.setTaskType("COMPLETE");
+ processNodeRecordAssignUserParamDto.setApproveDesc(Convert.toStr(task.getVariableLocal("approveDesc")));
+ processNodeRecordAssignUserParamDto.setExecutionId(task.getExecutionId());
+
+ remoteFlowTaskService.taskEndEvent(processNodeRecordAssignUserParamDto);
+
+ }
+ if (event.getType().toString().equals(FlowableEngineEventType.TASK_ASSIGNED.toString())) {
+ // 任务被指派了人员
+ FlowableEntityEvent flowableEntityEvent = (FlowableEntityEvent) event;
+ TaskEntityImpl task = (TaskEntityImpl) flowableEntityEvent.getEntity();
+ // 执行人id
+ String assignee = task.getAssignee();
+ // 任务拥有者
+ String owner = task.getOwner();
+ //
+ String delegationStateString = task.getDelegationStateString();
+
+ // nodeid
+ String taskDefinitionKey = task.getTaskDefinitionKey();
+
+ // 实例id
+ String processInstanceId = task.getProcessInstanceId();
+
+ String processDefinitionId = task.getProcessDefinitionId();
+ // 流程id
+ String flowId = NodeUtil.getFlowId(processDefinitionId);
+ ProcessNodeRecordAssignUserParamDto processNodeRecordAssignUserParamDto = new ProcessNodeRecordAssignUserParamDto();
+ processNodeRecordAssignUserParamDto.setFlowId(flowId);
+ processNodeRecordAssignUserParamDto.setProcessInstanceId(processInstanceId);
+ // processNodeRecordAssignUserParamDto.setData();
+ processNodeRecordAssignUserParamDto.setNodeId(taskDefinitionKey);
+ processNodeRecordAssignUserParamDto.setUserId(Long.parseLong(assignee));
+ processNodeRecordAssignUserParamDto.setTaskId(task.getId());
+ processNodeRecordAssignUserParamDto.setNodeName(task.getName());
+ processNodeRecordAssignUserParamDto
+ .setTaskType(StrUtil.equals(DelegationState.PENDING.toString(), delegationStateString) ? "DELEGATION"
+ : (StrUtil.equals(DelegationState.RESOLVED.toString(), delegationStateString) ? "RESOLVED"
+ : ""));
+ processNodeRecordAssignUserParamDto.setApproveDesc(Convert.toStr(task.getVariableLocal("approveDesc")));
+ processNodeRecordAssignUserParamDto.setExecutionId(task.getExecutionId());
+
+ remoteFlowTaskService.startAssignUser(processNodeRecordAssignUserParamDto);
+
+ }
+
+ if (event.getType().toString().equals(FlowableEngineEventType.PROCESS_STARTED.toString())) {
+ // 流程开始了
+ FlowableProcessStartedEventImpl flowableProcessStartedEvent = (FlowableProcessStartedEventImpl) event;
+
+ ExecutionEntityImpl entity = (ExecutionEntityImpl) flowableProcessStartedEvent.getEntity();
+ DelegateExecution execution = flowableProcessStartedEvent.getExecution();
+ String processInstanceId = flowableProcessStartedEvent.getProcessInstanceId();
+ {
+ // 上级实例id
+ String nestedProcessInstanceId = flowableProcessStartedEvent.getNestedProcessInstanceId();
+
+ String flowId = entity.getProcessDefinitionKey();
+
+ Object variable = execution.getVariable("root");
+
+ List nodeUsers = objectMapper.readValue(objectMapper.writeValueAsString(variable),
+ new TypeReference>() {
+ });
+ Long startUserId = nodeUsers.get(0).getId();
+ Map variables = execution.getVariables();
+
+ ProcessInstanceRecordParamDto processInstanceRecordParamDto = new ProcessInstanceRecordParamDto();
+ processInstanceRecordParamDto.setUserId(startUserId);
+ processInstanceRecordParamDto.setParentProcessInstanceId(nestedProcessInstanceId);
+ processInstanceRecordParamDto.setFlowId(flowId);
+ processInstanceRecordParamDto.setProcessInstanceId(processInstanceId);
+ processInstanceRecordParamDto.setFormData(objectMapper.writeValueAsString(variables));
+ remoteFlowTaskService.createProcessEvent(processInstanceRecordParamDto);
+ }
+
+ }
+ }
+
+ /**
+ * 如果监听器抛出异常是否终止当前操作
+ * @return 是否终止当前操作
+ */
+ @Override
+ public boolean isFailOnException() {
+ return false;
+ }
+
+ /**
+ * 返回监听器是否在事务生命周期事件发生时立即触发
+ * @return 是否在事务生命周期事件上触发
+ */
+ @Override
+ public boolean isFireOnTransactionLifecycleEvent() {
+ return false;
+ }
+
+ /**
+ * 如果非空,表示在当前事务的生命周期中的触发点
+ * @return 事务生命周期中的触发点
+ */
+ @Override
+ public String getOnTransaction() {
+ return null;
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/AssignUserStrategy.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/AssignUserStrategy.java
new file mode 100644
index 000000000..2850070be
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/AssignUserStrategy.java
@@ -0,0 +1,22 @@
+package com.pig4cloud.pigx.flow.engine.node;
+
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 指定用户策略处理器
+ */
+public interface AssignUserStrategy {
+
+ /**
+ * 抽象方法 处理表达式
+ * @param node
+ * @param rootUser
+ * @param variables
+ */
+ List handle(Node node, NodeUser rootUser, Map variables);
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/MultiInstanceHandler.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/MultiInstanceHandler.java
new file mode 100644
index 000000000..bc45fe858
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/MultiInstanceHandler.java
@@ -0,0 +1,137 @@
+package com.pig4cloud.pigx.flow.engine.node;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pigx.flow.task.api.feign.RemoteFlowTaskService;
+import com.pig4cloud.pigx.flow.task.constant.ProcessInstanceConstant;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RequiredArgsConstructor
+@Component("multiInstanceHandler")
+public class MultiInstanceHandler {
+
+ private final RemoteFlowTaskService remoteFlowTaskService;
+
+ private final Map assignUserStrategyMap;
+
+ private final ObjectMapper objectMapper;
+
+ /**
+ * 解析执行人
+ * @param execution 流程执行对象
+ * @return 执行人集合
+ */
+ @SneakyThrows
+ public List resolveAssignee(DelegateExecution execution) {
+ // 执行人集合
+ List assignList = new ArrayList<>();
+
+ ExecutionEntityImpl entity = (ExecutionEntityImpl) execution;
+
+ String flowId = entity.getProcessDefinitionKey();
+ String nodeId = entity.getActivityId();
+
+ log.debug("nodeId={} nodeName={}", nodeId, entity.getActivityName());
+
+ // 发起人
+ Object rootUserObj = execution.getVariable("root");
+ String rootUserJson = objectMapper.writeValueAsString(rootUserObj);
+ NodeUser rootUser = objectMapper.readValue(rootUserJson, new TypeReference>() {
+ }).get(0);
+
+ // 节点数据
+ Node node = remoteFlowTaskService.queryNodeOriData(flowId, nodeId).getData();
+ if (node != null) {
+ Map variables = execution.getVariables();
+ Integer assignedType = node.getAssignedType();
+ List userIdList = assignUserStrategyMap.get(assignedType + "AssignUserStrategy")
+ .handle(node, rootUser, variables);
+ assignList.addAll(userIdList);
+ }
+ else {
+ // 默认值
+ String format = StrUtil.format("{}_assignee_default_list", nodeId);
+ Object variable = execution.getVariable(format);
+ String variableJson = objectMapper.writeValueAsString(variable);
+
+ List nodeUserDtos = objectMapper.readValue(variableJson, new TypeReference>() {
+ });
+ if (CollUtil.isNotEmpty(nodeUserDtos)) {
+ List collect = nodeUserDtos.stream().map(NodeUser::getId).collect(Collectors.toList());
+ assignList.addAll(collect);
+ }
+ }
+
+ Optional.of(assignList).orElseGet(() -> {
+ List defaultList = new ArrayList<>();
+ defaultList.add(ProcessInstanceConstant.DEFAULT_EMPTY_ASSIGN);
+ return defaultList;
+ });
+
+ return assignList;
+ }
+
+ /**
+ * 会签或者或签完成条件检查 检查节点是否满足会签或者或签的完成条件
+ * @param execution 执行实例对象
+ * @return boolean 如果节点满足条件则返回true,否则返回false
+ */
+ public boolean completionCondition(DelegateExecution execution) {
+ ExecutionEntityImpl entity = (ExecutionEntityImpl) execution;
+ String processDefinitionKey = entity.getProcessDefinitionKey();
+ String nodeId = execution.getCurrentActivityId();
+
+ Node node = remoteFlowTaskService.queryNodeOriData(processDefinitionKey, nodeId).getData();
+ Integer multipleMode = node.getMultipleMode();
+ BigDecimal modePercentage = BigDecimal.valueOf(100);
+
+ Object variable = execution.getVariable(StrUtil.format("{}_approve_condition", nodeId));
+ log.debug("当前节点审批结果:{}", variable);
+ Boolean approve = Convert.toBool(variable);
+
+ if (multipleMode == ProcessInstanceConstant.MULTIPLE_MODE_AL_SAME
+ || multipleMode == ProcessInstanceConstant.MULTIPLE_MODE_ALL_SORT) {
+ // 如果是会签或者顺序签署
+ if (!approve) {
+ return true;
+ }
+ }
+
+ if (multipleMode == ProcessInstanceConstant.MULTIPLE_MODE_ONE) {
+ // 如果是或签
+ if (approve) {
+ return true;
+ }
+ }
+
+ int nrOfInstances = (int) execution.getVariable("nrOfInstances");
+ int nrOfCompletedInstances = (int) execution.getVariable("nrOfCompletedInstances");
+ log.debug("当前节点完成实例数:{} 总实例数:{} 需要完成比例:{}", nrOfCompletedInstances, nrOfInstances, modePercentage);
+
+ if (multipleMode == ProcessInstanceConstant.MULTIPLE_MODE_AL_SAME) {
+ return BigDecimal.valueOf(nrOfCompletedInstances * 100L)
+ .compareTo(BigDecimal.valueOf(nrOfCompletedInstances).multiply(modePercentage)) > 0;
+ }
+
+ return nrOfCompletedInstances == nrOfInstances;
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserFixedStrategyImpl.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserFixedStrategyImpl.java
new file mode 100644
index 000000000..63e1f893a
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserFixedStrategyImpl.java
@@ -0,0 +1,70 @@
+package com.pig4cloud.pigx.flow.engine.node.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pigx.admin.api.entity.SysUser;
+import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
+import com.pig4cloud.pigx.common.core.util.R;
+import com.pig4cloud.pigx.flow.engine.node.AssignUserStrategy;
+import com.pig4cloud.pigx.flow.task.constant.NodeUserTypeEnum;
+import com.pig4cloud.pigx.flow.task.constant.ProcessInstanceConstant;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 指定具体用户
+ *
+ * @author Huijun Zhao
+ * @description
+ * @date 2023-07-07 13:42
+ */
+@RequiredArgsConstructor
+@Component(ProcessInstanceConstant.AssignedTypeClass.USER + "AssignUserStrategy")
+public class AssignUserFixedStrategyImpl implements AssignUserStrategy {
+
+ private final RemoteUserService remoteUserService;
+
+ /**
+ * 处理节点并返回用户ID列表。
+ * @param node 节点
+ * @param rootUser 根用户
+ * @param variables 变量
+ * @return 用户ID列表
+ */
+ @Override
+ public List handle(Node node, NodeUser rootUser, Map variables) {
+
+ // 指定人员
+ List userDtoList = node.getNodeUserList();
+ // 用户ID列表
+ List userIdList = userDtoList.stream()
+ .filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.USER.getKey()))
+ .map(NodeUser::getId)
+ .collect(Collectors.toList());
+ // 部门ID列表
+ List deptIdList = userDtoList.stream()
+ .filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.DEPT.getKey()))
+ .map(NodeUser::getId)
+ .collect(Collectors.toList());
+
+ if (CollUtil.isNotEmpty(deptIdList)) {
+ R> r = remoteUserService.getUserIdListByDeptIdList(deptIdList);
+ if (CollUtil.isNotEmpty(r.getData())) {
+ for (SysUser user : r.getData()) {
+ if (!userIdList.contains(user.getUserId())) {
+ userIdList.add(user.getUserId());
+ }
+ }
+ }
+ }
+
+ return userIdList;
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserFormStrategyImpl.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserFormStrategyImpl.java
new file mode 100644
index 000000000..2facc3138
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserFormStrategyImpl.java
@@ -0,0 +1,47 @@
+package com.pig4cloud.pigx.flow.engine.node.impl;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pigx.flow.engine.node.AssignUserStrategy;
+import com.pig4cloud.pigx.flow.task.constant.ProcessInstanceConstant;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 来自表单
+ *
+ * @author Huijun Zhao
+ * @description
+ * @date 2023-07-07 13:42
+ */
+@RequiredArgsConstructor
+@Component(ProcessInstanceConstant.AssignedTypeClass.FORM_USER + "AssignUserStrategy")
+public class AssignUserFormStrategyImpl implements AssignUserStrategy {
+
+ private final ObjectMapper objectMapper;
+
+ @SneakyThrows
+ @Override
+ public List handle(Node node, NodeUser rootUser, Map variables) {
+ List assignList = new ArrayList<>();
+
+ Object variable = variables.get(node.getFormUserId());
+ if (variable != null && !StrUtil.isBlankIfStr(variable)) {
+ String jsonString = objectMapper.writeValueAsString(variable);
+ List nodeUserDtoList = JSONUtil.toList(jsonString, NodeUser.class);
+ assignList.addAll(nodeUserDtoList.stream().map(NodeUser::getId).collect(Collectors.toList()));
+ }
+
+ return assignList;
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserLeaderStrategyImpl.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserLeaderStrategyImpl.java
new file mode 100644
index 000000000..8c6b7e822
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserLeaderStrategyImpl.java
@@ -0,0 +1,42 @@
+package com.pig4cloud.pigx.flow.engine.node.impl;
+
+import com.pig4cloud.pigx.admin.api.feign.RemoteDeptService;
+import com.pig4cloud.pigx.common.core.util.R;
+import com.pig4cloud.pigx.flow.engine.node.AssignUserStrategy;
+import com.pig4cloud.pigx.flow.task.constant.ProcessInstanceConstant;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 指定主管
+ *
+ * @author Huijun Zhao
+ * @description
+ * @date 2023-07-07 13:42
+ */
+@RequiredArgsConstructor
+@Component(ProcessInstanceConstant.AssignedTypeClass.LEADER + "AssignUserStrategy")
+public class AssignUserLeaderStrategyImpl implements AssignUserStrategy {
+
+ private final RemoteDeptService deptService;
+
+ @Override
+ public List handle(Node node, NodeUser rootUser, Map variables) {
+ // 获取部门ID
+ return node.getNodeUserList()
+ .stream()
+ .map(nodeUser -> deptService.getAllDeptLeader(nodeUser.getId()))
+ .flatMap((Function>, Stream>) listR -> listR.getData() == null ? null
+ : listR.getData().stream())
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserRoleStrategyImpl.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserRoleStrategyImpl.java
new file mode 100644
index 000000000..18e3a3100
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserRoleStrategyImpl.java
@@ -0,0 +1,51 @@
+package com.pig4cloud.pigx.flow.engine.node.impl;
+
+import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
+import com.pig4cloud.pigx.common.core.util.RetOps;
+import com.pig4cloud.pigx.flow.engine.node.AssignUserStrategy;
+import com.pig4cloud.pigx.flow.task.constant.ProcessInstanceConstant;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 来自角色
+ *
+ * @author Huijun Zhao
+ * @description
+ * @date 2023-07-07 13:42
+ */
+@RequiredArgsConstructor
+@Component(ProcessInstanceConstant.AssignedTypeClass.ROLE + "AssignUserStrategy")
+public class AssignUserRoleStrategyImpl implements AssignUserStrategy {
+
+ private final RemoteUserService remoteUserService;
+
+ /**
+ * 处理节点并返回用户ID列表。
+ * @param node 节点
+ * @param rootUser 根用户
+ * @param variables 变量
+ * @return 用户ID列表
+ */
+ @Override
+ public List handle(Node node, NodeUser rootUser, Map variables) {
+ // 使用 lambda 表达式和方法引用从 NodeUser 列表中提取角色 ID
+ List roleIds = node.getNodeUserList().stream().map(NodeUser::getId).collect(Collectors.toList());
+ // 提取 Optional 结果中的数据
+ List data = RetOps.of(remoteUserService.getUserIdListByRoleIdList(roleIds))
+ .getData()
+ .orElseGet(Collections::emptyList);
+
+ // 返回用户 ID 列表
+ return new ArrayList<>(data);
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserSelfSelectStrategyImpl.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserSelfSelectStrategyImpl.java
new file mode 100644
index 000000000..fe2641c1f
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserSelfSelectStrategyImpl.java
@@ -0,0 +1,56 @@
+package com.pig4cloud.pigx.flow.engine.node.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pigx.flow.engine.node.AssignUserStrategy;
+import com.pig4cloud.pigx.flow.task.constant.ProcessInstanceConstant;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 发起人自选
+ *
+ * @author Huijun Zhao
+ * @description
+ * @date 2023-07-07 13:42
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Component(ProcessInstanceConstant.AssignedTypeClass.SELF_SELECT + "AssignUserStrategy")
+public class AssignUserSelfSelectStrategyImpl implements AssignUserStrategy {
+
+ private final ObjectMapper objectMapper;
+
+ @SneakyThrows
+ @Override
+ public List handle(Node node, NodeUser rootUser, Map variables) {
+
+ List assignList = new ArrayList<>();
+
+ Object variable = variables.get(StrUtil.format("{}_assignee_select", node.getId()));
+ log.info("{}-发起人自选参数:{}", node.getName(), variable);
+ if (variable == null) {
+ return assignList;
+ }
+
+ List nodeUserDtos = objectMapper.readValue(objectMapper.writeValueAsString(variable),
+ new TypeReference>() {
+ });
+
+ List collect = nodeUserDtos.stream().map(NodeUser::getId).collect(Collectors.toList());
+
+ assignList.addAll(collect);
+ return assignList;
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserSelfStrategyImpl.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserSelfStrategyImpl.java
new file mode 100644
index 000000000..2c6227683
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/node/impl/AssignUserSelfStrategyImpl.java
@@ -0,0 +1,28 @@
+package com.pig4cloud.pigx.flow.engine.node.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.pig4cloud.pigx.flow.engine.node.AssignUserStrategy;
+import com.pig4cloud.pigx.flow.task.constant.ProcessInstanceConstant;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 发起人自己
+ *
+ * @author Huijun Zhao
+ * @description
+ * @date 2023-07-07 13:42
+ */
+@Component(ProcessInstanceConstant.AssignedTypeClass.SELF + "AssignUserStrategy")
+public class AssignUserSelfStrategyImpl implements AssignUserStrategy {
+
+ @Override
+ public List handle(Node node, NodeUser rootUser, Map variables) {
+ return CollUtil.newArrayList(rootUser.getId());
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/service/ApproveServiceTask.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/service/ApproveServiceTask.java
new file mode 100644
index 000000000..eb62a2436
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/service/ApproveServiceTask.java
@@ -0,0 +1,63 @@
+package com.pig4cloud.pigx.flow.engine.service;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.pig4cloud.pigx.flow.task.api.feign.RemoteFlowTaskService;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.Refuse;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.delegate.JavaDelegate;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
+
+/**
+ * 审批任务处理器--java服务任务
+ */
+@Slf4j
+public class ApproveServiceTask implements JavaDelegate {
+
+ @Override
+ public void execute(DelegateExecution execution) {
+
+ ExecutionEntityImpl entity = (ExecutionEntityImpl) execution;
+ String nodeIdO = entity.getActivityId();
+ String flowId = entity.getProcessDefinitionKey();
+ String processInstanceId = entity.getProcessInstanceId();
+
+ String nodeId = StrUtil.subAfter(nodeIdO, "approve_service_task_", true);
+
+ Boolean approve = execution.getVariable(StrUtil.format("{}_approve_condition", nodeId), Boolean.class);
+
+ if (approve != null) {
+
+ RuntimeService runtimeService = SpringUtil.getBean(RuntimeService.class);
+
+ if (!approve) {
+ // 跳转
+ RemoteFlowTaskService remoteFlowTaskService = SpringUtil.getBean(RemoteFlowTaskService.class);
+ Node node = remoteFlowTaskService.queryNodeOriData(flowId, nodeId).getData();
+ Refuse refuse = node.getRefuse();
+ if (refuse != null) {
+ String handler = refuse.getHandler();
+ if (StrUtil.equals(handler, "TO_NODE")) {
+ runtimeService.createChangeActivityStateBuilder()
+ .processInstanceId(processInstanceId)
+ .moveActivityIdTo(nodeIdO, refuse.getNodeId())
+ .changeState();
+ }
+ else {
+ runtimeService.createChangeActivityStateBuilder()
+ .processInstanceId(processInstanceId)
+ .moveActivityIdTo(nodeIdO, "end")
+ .changeState();
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/service/CopyServiceTask.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/service/CopyServiceTask.java
new file mode 100644
index 000000000..72f9e5c38
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/service/CopyServiceTask.java
@@ -0,0 +1,98 @@
+package com.pig4cloud.pigx.flow.engine.service;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pigx.common.core.util.R;
+import com.pig4cloud.pigx.flow.task.api.feign.RemoteFlowTaskService;
+import com.pig4cloud.pigx.flow.task.constant.NodeUserTypeEnum;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.NodeUser;
+import com.pig4cloud.pigx.flow.task.dto.ProcessCopyDto;
+import lombok.SneakyThrows;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.delegate.JavaDelegate;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 抄送任务处理器--java服务任务
+ */
+public class CopyServiceTask implements JavaDelegate {
+
+ /**
+ * 执行给定执行的任务。
+ * @param execution 要处理的执行
+ */
+ @SneakyThrows
+ @Override
+ public void execute(DelegateExecution execution) {
+
+ ExecutionEntityImpl entity = (ExecutionEntityImpl) execution;
+ String nodeId = entity.getActivityId();
+ String flowId = entity.getProcessDefinitionKey();
+
+ RemoteFlowTaskService remoteFlowTaskService = SpringUtil.getBean(RemoteFlowTaskService.class);
+ Node node = remoteFlowTaskService.queryNodeOriData(flowId, nodeId).getData();
+
+ // 获取指定人员
+ List userDtoList = node.getNodeUserList();
+ // 用户ID列表
+ List userIdList = userDtoList.stream()
+ .filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.USER.getKey()))
+ .map(w -> Convert.toStr(w.getId()))
+ .collect(Collectors.toList());
+ // 部门ID列表
+ List deptIdList = userDtoList.stream()
+ .filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.DEPT.getKey()))
+ .map(w -> Convert.toStr(w.getId()))
+ .collect(Collectors.toList());
+
+ if (CollUtil.isNotEmpty(deptIdList)) {
+
+ R> r = remoteFlowTaskService.queryUserIdListByDepIdList(deptIdList);
+
+ List data = r.getData();
+ if (CollUtil.isNotEmpty(data)) {
+ for (String datum : data) {
+ if (!userIdList.contains(datum)) {
+ userIdList.add(datum);
+ }
+ }
+ }
+ }
+
+ ObjectMapper objectMapper = SpringUtil.getBean(ObjectMapper.class);
+ // 获取发起人
+ Object rootUserObj = execution.getVariable("root");
+ NodeUser rootUser = objectMapper
+ .readValue(objectMapper.writeValueAsString(rootUserObj), new TypeReference>() {
+ })
+ .get(0);
+
+ Map variables = execution.getVariables();
+
+ for (String userIds : userIdList) {
+ // 发送抄送任务
+ ProcessCopyDto processCopyDto = new ProcessCopyDto();
+ processCopyDto.setNodeTime(LocalDateTime.now());
+ processCopyDto.setStartUserId(rootUser.getId());
+ processCopyDto.setFlowId(flowId);
+ processCopyDto.setProcessInstanceId(execution.getProcessInstanceId());
+ processCopyDto.setNodeId(nodeId);
+ processCopyDto.setNodeName(node.getName());
+ processCopyDto.setFormData(objectMapper.writeValueAsString(variables));
+ processCopyDto.setUserId(Long.parseLong(userIds));
+ remoteFlowTaskService.saveCC(processCopyDto);
+ }
+
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/service/packge-info.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/service/packge-info.java
new file mode 100644
index 000000000..d749389d8
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/service/packge-info.java
@@ -0,0 +1,4 @@
+/*
+ * @author pigx archetype
+ */
+package com.pig4cloud.pigx.flow.engine.service;
\ No newline at end of file
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/utils/FlowableUtils.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/utils/FlowableUtils.java
new file mode 100644
index 000000000..66bd02153
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/utils/FlowableUtils.java
@@ -0,0 +1,664 @@
+package com.pig4cloud.pigx.flow.engine.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.flowable.bpmn.model.*;
+import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
+import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
+
+import java.util.*;
+
+/**
+ * 流程引擎工具类封装
+ *
+ * @author: linjinp
+ * @create: 2019-12-24 13:51
+ **/
+public class FlowableUtils {
+
+ public static final Logger logger = LogManager.getLogger(FlowableUtils.class);
+
+ /**
+ * 根据节点获取入口连线集合
+ * @param source 流程元素节点
+ * @return 入口连线集合
+ */
+ public static List getElementIncomingFlows(FlowElement source) {
+ List sequenceFlows = null;
+ if (source instanceof Task) {
+ sequenceFlows = ((Task) source).getIncomingFlows();
+ }
+ else if (source instanceof Gateway) {
+ sequenceFlows = ((Gateway) source).getIncomingFlows();
+ }
+ else if (source instanceof SubProcess) {
+ sequenceFlows = ((SubProcess) source).getIncomingFlows();
+ }
+ else if (source instanceof StartEvent) {
+ sequenceFlows = ((StartEvent) source).getIncomingFlows();
+ }
+ else if (source instanceof EndEvent) {
+ sequenceFlows = ((EndEvent) source).getIncomingFlows();
+ }
+ return sequenceFlows;
+ }
+
+ /**
+ * 根据节点获取出口连线集合
+ * @param source 流程元素节点
+ * @return 出口连线集合
+ */
+ public static List getElementOutgoingFlows(FlowElement source) {
+ List sequenceFlows = null;
+ if (source instanceof Task) {
+ sequenceFlows = ((Task) source).getOutgoingFlows();
+ }
+ else if (source instanceof Gateway) {
+ sequenceFlows = ((Gateway) source).getOutgoingFlows();
+ }
+ else if (source instanceof SubProcess) {
+ sequenceFlows = ((SubProcess) source).getOutgoingFlows();
+ }
+ else if (source instanceof StartEvent) {
+ sequenceFlows = ((StartEvent) source).getOutgoingFlows();
+ }
+ else if (source instanceof EndEvent) {
+ sequenceFlows = ((EndEvent) source).getOutgoingFlows();
+ }
+ return sequenceFlows;
+ }
+
+ /**
+ * 递归获取流程定义所有的流程元素
+ * @param flowElements 当前级别流程元素集合
+ * @param allElements 用于存储的全部流程元素集合
+ * @return 全部流程元素集合
+ */
+ public static Collection getAllElements(Collection flowElements,
+ Collection allElements) {
+ allElements = allElements == null ? new ArrayList<>() : allElements;
+
+ for (FlowElement flowElement : flowElements) {
+ allElements.add(flowElement);
+ if (flowElement instanceof SubProcess) {
+ // 继续深入子流程,进一步获取子流程
+ allElements = FlowableUtils.getAllElements(((SubProcess) flowElement).getFlowElements(), allElements);
+ }
+ }
+ return allElements;
+ }
+
+ /**
+ * 从后向前递归获取父级用户任务节点
+ * @param source 当前节点
+ * @param hasSequenceFlow 已处理过的顺序流集合,避免循环
+ * @param userTaskList 递归获取的用户任务节点集合
+ * @return 用户任务节点集合
+ */
+ public static List iteratorFindParentUserTasks(FlowElement source, Set hasSequenceFlow,
+ List userTaskList) {
+ userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
+ hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+
+ // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
+ if (source instanceof StartEvent && source.getSubProcess() != null) {
+ userTaskList = iteratorFindParentUserTasks(source.getSubProcess(), hasSequenceFlow, userTaskList);
+ }
+
+ // 根据类型,获取入口连线
+ List sequenceFlows = getElementIncomingFlows(source);
+
+ if (sequenceFlows != null) {
+ // 循环找到目标元素
+ for (SequenceFlow sequenceFlow : sequenceFlows) {
+ // 如果发现连线重复,说明循环了,跳过这个循环
+ if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+ continue;
+ }
+ // 添加已经走过的连线
+ hasSequenceFlow.add(sequenceFlow.getId());
+ // 类型为用户节点,则新增父级节点
+ if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
+ userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
+ continue;
+ }
+ // 类型为子流程,则添加子流程开始节点出口处相连的节点
+ if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
+ // 获取子流程用户任务节点
+ List childUserTaskList = findChildProcessUserTasks(
+ (StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements()
+ .toArray()[0],
+ null, null);
+ // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
+ if (childUserTaskList != null && childUserTaskList.size() > 0) {
+ userTaskList.addAll(childUserTaskList);
+ continue;
+ }
+ }
+ // 继续迭代
+ // 注意:已经经过的节点与连线都应该用浅拷贝出来的对象
+ // 比如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
+ userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(),
+ new HashSet<>(hasSequenceFlow), userTaskList);
+ }
+ }
+ return userTaskList;
+ }
+
+ /**
+ * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找
+ * @param source 起始节点
+ * @param runActiveIdList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点
+ * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
+ * @param flowElementList 需要撤回的用户任务列表
+ * @return
+ */
+ public static List iteratorFindChildUserTasks(FlowElement source, List runActiveIdList,
+ Set hasSequenceFlow, List flowElementList) {
+ hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+ flowElementList = flowElementList == null ? new ArrayList<>() : flowElementList;
+
+ // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
+ if (source instanceof EndEvent && source.getSubProcess() != null) {
+ flowElementList = iteratorFindChildUserTasks(source.getSubProcess(), runActiveIdList, hasSequenceFlow,
+ flowElementList);
+ }
+
+ // 根据类型,获取出口连线
+ List sequenceFlows = getElementOutgoingFlows(source);
+
+ if (sequenceFlows != null) {
+ // 循环找到目标元素
+ for (SequenceFlow sequenceFlow : sequenceFlows) {
+ // 如果发现连线重复,说明循环了,跳过这个循环
+ if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+ continue;
+ }
+ // 添加已经走过的连线
+ hasSequenceFlow.add(sequenceFlow.getId());
+ // 如果为用户任务类型,或者为网关
+ // 活动节点ID 在运行的任务中存在,添加
+ if ((sequenceFlow.getTargetFlowElement() instanceof UserTask
+ || sequenceFlow.getTargetFlowElement() instanceof Gateway)
+ && runActiveIdList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
+ flowElementList.add(sequenceFlow.getTargetFlowElement());
+ continue;
+ }
+ // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
+ if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
+ List childUserTaskList = iteratorFindChildUserTasks(
+ (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements()
+ .toArray()[0]),
+ runActiveIdList, hasSequenceFlow, null);
+ // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
+ if (childUserTaskList != null && childUserTaskList.size() > 0) {
+ flowElementList.addAll(childUserTaskList);
+ continue;
+ }
+ }
+ // 继续迭代
+ // 注意:已经经过的节点与连线都应该用浅拷贝出来的对象
+ // 比如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
+ flowElementList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runActiveIdList,
+ new HashSet<>(hasSequenceFlow), flowElementList);
+ }
+ }
+ return flowElementList;
+ }
+
+ /**
+ * 迭代获取子流程用户任务节点
+ * @param source 起始节点
+ * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
+ * @param userTaskList 需要撤回的用户任务列表
+ * @return
+ */
+ public static List findChildProcessUserTasks(FlowElement source, Set hasSequenceFlow,
+ List userTaskList) {
+ hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+ userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
+
+ // 根据类型,获取出口连线
+ List sequenceFlows = getElementOutgoingFlows(source);
+
+ if (sequenceFlows != null) {
+ // 循环找到目标元素
+ for (SequenceFlow sequenceFlow : sequenceFlows) {
+ // 如果发现连线重复,说明循环了,跳过这个循环
+ if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+ continue;
+ }
+ // 添加已经走过的连线
+ hasSequenceFlow.add(sequenceFlow.getId());
+ // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
+ if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
+ userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
+ continue;
+ }
+ // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
+ if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
+ List childUserTaskList = findChildProcessUserTasks(
+ (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements()
+ .toArray()[0]),
+ hasSequenceFlow, null);
+ // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
+ if (childUserTaskList != null && childUserTaskList.size() > 0) {
+ userTaskList.addAll(childUserTaskList);
+ continue;
+ }
+ }
+ // 继续迭代
+ // 注意:已经经过的节点与连线都应该用浅拷贝出来的对象
+ // 比如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
+ userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(),
+ new HashSet<>(hasSequenceFlow), userTaskList);
+ }
+ }
+ return userTaskList;
+ }
+
+ /**
+ * 从后向前寻路,获取所有脏线路上的点
+ * @param source 起始节点
+ * @param passRoads 已经经过的点集合
+ * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
+ * @param targets 目标脏线路终点
+ * @param dirtyRoads 确定为脏数据的点,因为不需要重复,因此使用 set 存储
+ * @return
+ */
+ public static Set iteratorFindDirtyRoads(FlowElement source, List passRoads,
+ Set hasSequenceFlow, List targets, Set dirtyRoads) {
+ passRoads = passRoads == null ? new ArrayList<>() : passRoads;
+ dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
+ hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+
+ // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
+ if (source instanceof StartEvent && source.getSubProcess() != null) {
+ dirtyRoads = iteratorFindDirtyRoads(source.getSubProcess(), passRoads, hasSequenceFlow, targets,
+ dirtyRoads);
+ }
+
+ // 根据类型,获取入口连线
+ List sequenceFlows = getElementIncomingFlows(source);
+
+ if (sequenceFlows != null) {
+ // 循环找到目标元素
+ for (SequenceFlow sequenceFlow : sequenceFlows) {
+ // 如果发现连线重复,说明循环了,跳过这个循环
+ if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+ continue;
+ }
+ // 添加已经走过的连线
+ hasSequenceFlow.add(sequenceFlow.getId());
+ // 新增经过的路线
+ passRoads.add(sequenceFlow.getSourceFlowElement().getId());
+ // 如果此点为目标点,确定经过的路线为脏线路,添加点到脏线路中,然后找下个连线
+ if (targets.contains(sequenceFlow.getSourceFlowElement().getId())) {
+ dirtyRoads.addAll(passRoads);
+ continue;
+ }
+ // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
+ if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
+ dirtyRoads = findChildProcessAllDirtyRoad(
+ (StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements()
+ .toArray()[0],
+ null, dirtyRoads);
+ // 是否存在子流程上,true 是,false 否
+ Boolean isInChildProcess = dirtyTargetInChildProcess(
+ (StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements()
+ .toArray()[0],
+ null, targets, null);
+ if (isInChildProcess) {
+ // 已在子流程上找到,该路线结束
+ continue;
+ }
+ }
+ // 继续迭代
+ // 注意:已经经过的节点与连线都应该用浅拷贝出来的对象
+ // 比如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
+ dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), new ArrayList<>(passRoads),
+ new HashSet<>(hasSequenceFlow), targets, dirtyRoads);
+ }
+ }
+ return dirtyRoads;
+ }
+
+ /**
+ * 迭代获取子流程脏路线 说明,假如回退的点就是子流程,那么也肯定会回退到子流程最初的用户任务节点,因此子流程中的节点全是脏路线
+ * @param source 起始节点
+ * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
+ * @param dirtyRoads 确定为脏数据的点,因为不需要重复,因此使用 set 存储
+ * @return
+ */
+ public static Set findChildProcessAllDirtyRoad(FlowElement source, Set hasSequenceFlow,
+ Set dirtyRoads) {
+ hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+ dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
+
+ // 根据类型,获取出口连线
+ List sequenceFlows = getElementOutgoingFlows(source);
+
+ if (sequenceFlows != null) {
+ // 循环找到目标元素
+ for (SequenceFlow sequenceFlow : sequenceFlows) {
+ // 如果发现连线重复,说明循环了,跳过这个循环
+ if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+ continue;
+ }
+ // 添加已经走过的连线
+ hasSequenceFlow.add(sequenceFlow.getId());
+ // 添加脏路线
+ dirtyRoads.add(sequenceFlow.getTargetFlowElement().getId());
+ // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
+ if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
+ dirtyRoads = findChildProcessAllDirtyRoad(
+ (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements()
+ .toArray()[0]),
+ hasSequenceFlow, dirtyRoads);
+ }
+ // 继续迭代
+ // 注意:已经经过的节点与连线都应该用浅拷贝出来的对象
+ // 比如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
+ dirtyRoads = findChildProcessAllDirtyRoad(sequenceFlow.getTargetFlowElement(),
+ new HashSet<>(hasSequenceFlow), dirtyRoads);
+ }
+ }
+ return dirtyRoads;
+ }
+
+ /**
+ * 判断脏路线结束节点是否在子流程上
+ * @param source 起始节点
+ * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
+ * @param targets 判断脏路线节点是否存在子流程上,只要存在一个,说明脏路线只到子流程为止
+ * @param inChildProcess 是否存在子流程上,true 是,false 否
+ * @return
+ */
+ public static Boolean dirtyTargetInChildProcess(FlowElement source, Set hasSequenceFlow,
+ List targets, Boolean inChildProcess) {
+ hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+ inChildProcess = inChildProcess == null ? false : inChildProcess;
+
+ // 根据类型,获取出口连线
+ List sequenceFlows = getElementOutgoingFlows(source);
+
+ if (sequenceFlows != null && !inChildProcess) {
+ // 循环找到目标元素
+ for (SequenceFlow sequenceFlow : sequenceFlows) {
+ // 如果发现连线重复,说明循环了,跳过这个循环
+ if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+ continue;
+ }
+ // 添加已经走过的连线
+ hasSequenceFlow.add(sequenceFlow.getId());
+ // 如果发现目标点在子流程上存在,说明只到子流程为止
+ if (targets.contains(sequenceFlow.getTargetFlowElement().getId())) {
+ inChildProcess = true;
+ break;
+ }
+ // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
+ if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
+ inChildProcess = dirtyTargetInChildProcess(
+ (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements()
+ .toArray()[0]),
+ hasSequenceFlow, targets, inChildProcess);
+ }
+ // 继续迭代
+ // 注意:已经经过的节点与连线都应该用浅拷贝出来的对象
+ // 比如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
+ inChildProcess = dirtyTargetInChildProcess(sequenceFlow.getTargetFlowElement(),
+ new HashSet<>(hasSequenceFlow), targets, inChildProcess);
+ }
+ }
+ return inChildProcess;
+ }
+
+ /**
+ * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况
+ * @param source 起始节点
+ * @param isSequential 是否串行
+ * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
+ * @param targetKsy 目标节点
+ * @return
+ */
+ public static Boolean iteratorCheckSequentialReferTarget(FlowElement source, String targetKsy,
+ Set hasSequenceFlow, Boolean isSequential) {
+ isSequential = isSequential == null ? true : isSequential;
+ hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+
+ // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
+ if (source instanceof StartEvent && source.getSubProcess() != null) {
+ isSequential = iteratorCheckSequentialReferTarget(source.getSubProcess(), targetKsy, hasSequenceFlow,
+ isSequential);
+ }
+
+ // 根据类型,获取入口连线
+ List sequenceFlows = getElementIncomingFlows(source);
+
+ if (sequenceFlows != null) {
+ // 循环找到目标元素
+ for (SequenceFlow sequenceFlow : sequenceFlows) {
+ // 如果发现连线重复,说明循环了,跳过这个循环
+ if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+ continue;
+ }
+ // 添加已经走过的连线
+ hasSequenceFlow.add(sequenceFlow.getId());
+ // 如果目标节点已被判断为并行,后面都不需要执行,直接返回
+ if (isSequential == false) {
+ break;
+ }
+ // 这条线路存在目标节点,这条线路完成,进入下个线路
+ if (targetKsy.equals(sequenceFlow.getSourceFlowElement().getId())) {
+ continue;
+ }
+ if (sequenceFlow.getSourceFlowElement() instanceof StartEvent) {
+ isSequential = false;
+ break;
+ }
+ // 否则就继续迭代
+ // 注意:已经经过的节点与连线都应该用浅拷贝出来的对象
+ // 比如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
+ isSequential = iteratorCheckSequentialReferTarget(sequenceFlow.getSourceFlowElement(), targetKsy,
+ new HashSet<>(hasSequenceFlow), isSequential);
+ }
+ }
+ return isSequential;
+ }
+
+ /**
+ * 从后向前寻路,获取到达节点的所有路线 不存在直接回退到子流程,但是存在回退到父级流程的情况
+ * @param source 起始节点
+ * @param passRoads 已经经过的点集合
+ * @param roads 路线
+ * @return
+ */
+ public static List> findRoad(FlowElement source, List passRoads,
+ Set hasSequenceFlow, List> roads) {
+ passRoads = passRoads == null ? new ArrayList<>() : passRoads;
+ roads = roads == null ? new ArrayList<>() : roads;
+ hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
+
+ // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
+ if (source instanceof StartEvent && source.getSubProcess() != null) {
+ roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads);
+ }
+
+ // 根据类型,获取入口连线
+ List sequenceFlows = getElementIncomingFlows(source);
+
+ if (sequenceFlows != null && sequenceFlows.size() != 0) {
+ for (SequenceFlow sequenceFlow : sequenceFlows) {
+ // 如果发现连线重复,说明循环了,跳过这个循环
+ if (hasSequenceFlow.contains(sequenceFlow.getId())) {
+ continue;
+ }
+ // 添加已经走过的连线
+ hasSequenceFlow.add(sequenceFlow.getId());
+ // 添加经过路线
+ if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
+ passRoads.add((UserTask) sequenceFlow.getSourceFlowElement());
+ }
+ // 继续迭代
+ // 注意:已经经过的节点与连线都应该用浅拷贝出来的对象
+ // 比如分支:a->b->c与a->d->c,走完a->b->c后走另一个路线是,已经经过的节点应该不包含a->b->c路线的数据
+ roads = findRoad(sequenceFlow.getSourceFlowElement(), new ArrayList<>(passRoads),
+ new HashSet<>(hasSequenceFlow), roads);
+ }
+ }
+ else {
+ // 添加路线
+ roads.add(passRoads);
+ }
+ return roads;
+ }
+
+ /**
+ * 历史节点数据清洗,清洗掉又回滚导致的脏数据
+ * @param allElements 全部节点信息
+ * @param historicActivityIdList 历史任务实例信息,数据采用开始时间升序
+ * @return
+ */
+ public static List historicTaskInstanceClean(Collection allElements,
+ List historicActivityIdList) {
+ // 会签节点收集
+ List multiTask = new ArrayList<>();
+ allElements.forEach(flowElement -> {
+ if (flowElement instanceof UserTask) {
+ // 如果该节点的行为为会签行为,说明该节点为会签节点
+ if (((UserTask) flowElement).getBehavior() instanceof ParallelMultiInstanceBehavior
+ || ((UserTask) flowElement).getBehavior() instanceof SequentialMultiInstanceBehavior) {
+ multiTask.add(flowElement.getId());
+ }
+ }
+ });
+ // 循环放入栈,栈 LIFO:后进先出
+ Stack stack = new Stack<>();
+ historicActivityIdList.forEach(item -> stack.push(item));
+ // 清洗后的历史任务实例
+ List lastHistoricTaskInstanceList = new ArrayList<>();
+ // 网关存在可能只走了部分分支情况,且还存在跳转废弃数据以及其他分支数据的干扰,因此需要对历史节点数据进行清洗
+ // 临时用户任务 key
+ StringBuilder userTaskKey = null;
+ // 临时被删掉的任务 key,存在并行情况
+ List deleteKeyList = new ArrayList<>();
+ // 临时脏数据线路
+ List> dirtyDataLineList = new ArrayList<>();
+ // 由某个点跳到会签点,此时出现多个会签实例对应 1 个跳转情况,需要把这些连续脏数据都找到
+ // 会签特殊处理下标
+ int multiIndex = -1;
+ // 会签特殊处理 key
+ StringBuilder multiKey = null;
+ // 会签特殊处理操作标识
+ boolean multiOpera = false;
+ while (!stack.empty()) {
+ // 从这里开始 userTaskKey 都还是上个栈的 key
+ // 是否是脏数据线路上的点
+ final boolean[] isDirtyData = { false };
+ for (Set oldDirtyDataLine : dirtyDataLineList) {
+ if (oldDirtyDataLine.contains(stack.peek().getActivityId())) {
+ isDirtyData[0] = true;
+ }
+ }
+ // 删除原因不为空,说明从这条数据开始回跳或者回退的
+ // MI_END:会签完成后,其他未签到节点的删除原因,不在处理范围内
+ if (stack.peek().getDeleteReason() != null && !stack.peek().getDeleteReason().equals("MI_END")) {
+ // 可以理解为脏线路起点
+ String dirtyPoint = "";
+ if (stack.peek().getDeleteReason().indexOf("Change activity to ") >= 0) {
+ dirtyPoint = stack.peek().getDeleteReason().replace("Change activity to ", "");
+ }
+ // 会签回退删除原因有点不同
+ if (stack.peek().getDeleteReason().indexOf("Change parent activity to ") >= 0) {
+ dirtyPoint = stack.peek().getDeleteReason().replace("Change parent activity to ", "");
+ }
+ FlowElement dirtyTask = null;
+ // 获取变更节点的对应的入口处连线
+ // 如果是网关并行回退情况,会变成两条脏数据路线,效果一样
+ for (FlowElement flowElement : allElements) {
+ if (flowElement.getId().equals(stack.peek().getActivityId())) {
+ dirtyTask = flowElement;
+ }
+ }
+ // 获取脏数据线路
+ Set dirtyDataLine = FlowableUtils.iteratorFindDirtyRoads(dirtyTask, null, null,
+ Arrays.asList(dirtyPoint.split(",")), null);
+ // 自己本身也是脏线路上的点,加进去
+ dirtyDataLine.add(stack.peek().getActivityId());
+ logger.info(stack.peek().getActivityId() + "点脏路线集合:" + dirtyDataLine);
+ // 是全新的需要添加的脏线路
+ boolean isNewDirtyData = true;
+ for (int i = 0; i < dirtyDataLineList.size(); i++) {
+ // 如果发现他的上个节点在脏线路内,说明这个点可能是并行的节点,或者连续驳回
+ // 这时,都以之前的脏线路节点为标准,只需合并脏线路即可,也就是路线补全
+ if (dirtyDataLineList.get(i).contains(userTaskKey.toString())) {
+ isNewDirtyData = false;
+ dirtyDataLineList.get(i).addAll(dirtyDataLine);
+ }
+ }
+ // 已确定时全新的脏线路
+ if (isNewDirtyData) {
+ // deleteKey 单一路线驳回到并行,这种同时生成多个新实例记录情况,这时 deleteKey 其实是由多个值组成
+ // 按照逻辑,回退后立刻生成的实例记录就是回退的记录
+ // 至于驳回所生成的 Key,直接从删除原因中获取,因为存在驳回到并行的情况
+ deleteKeyList.add(dirtyPoint + ",");
+ dirtyDataLineList.add(dirtyDataLine);
+ }
+ // 添加后,现在这个点变成脏线路上的点了
+ isDirtyData[0] = true;
+ }
+ // 如果不是脏线路上的点,说明是有效数据,添加历史实例 Key
+ if (!isDirtyData[0]) {
+ lastHistoricTaskInstanceList.add(stack.peek().getActivityId());
+ }
+ // 校验脏线路是否结束
+ for (int i = 0; i < deleteKeyList.size(); i++) {
+ // 如果发现脏数据属于会签,记录下下标与对应 Key,以备后续比对,会签脏数据范畴开始
+ if (multiKey == null && multiTask.contains(stack.peek().getActivityId())
+ && deleteKeyList.get(i).contains(stack.peek().getActivityId())) {
+ multiIndex = i;
+ multiKey = new StringBuilder(stack.peek().getActivityId());
+ }
+ // 会签脏数据处理,节点退回会签清空
+ // 如果在会签脏数据范畴中发现 Key改变,说明会签脏数据在上个节点就结束了,可以把会签脏数据删掉
+ if (multiKey != null && !multiKey.toString().equals(stack.peek().getActivityId())) {
+ deleteKeyList.set(multiIndex,
+ deleteKeyList.get(multiIndex).replace(stack.peek().getActivityId() + ",", ""));
+ multiKey = null;
+ // 结束进行下校验删除
+ multiOpera = true;
+ }
+ // 其他脏数据处理
+ // 发现该路线最后一条脏数据,说明这条脏数据线路处理完了,删除脏数据信息
+ // 脏数据产生的新实例中是否包含这条数据
+ if (multiKey == null && deleteKeyList.get(i).contains(stack.peek().getActivityId())) {
+ // 删除匹配到的部分
+ deleteKeyList.set(i, deleteKeyList.get(i).replace(stack.peek().getActivityId() + ",", ""));
+ }
+ // 如果每组中的元素都以匹配过,说明脏数据结束
+ if ("".equals(deleteKeyList.get(i))) {
+ // 同时删除脏数据
+ deleteKeyList.remove(i);
+ dirtyDataLineList.remove(i);
+ break;
+ }
+ }
+ // 会签数据处理需要在循环外处理,否则可能导致溢出
+ // 会签的数据肯定是之前放进去的所以理论上不会溢出,但还是校验下
+ if (multiOpera && deleteKeyList.size() > multiIndex && "".equals(deleteKeyList.get(multiIndex))) {
+ // 同时删除脏数据
+ deleteKeyList.remove(multiIndex);
+ dirtyDataLineList.remove(multiIndex);
+ multiIndex = -1;
+ multiOpera = false;
+ }
+ // pop() 方法与 peek() 方法不同,在返回值的同时,会把值从栈中移除
+ // 保存新的 userTaskKey 在下个循环中使用
+ userTaskKey = new StringBuilder(stack.pop().getActivityId());
+ }
+ logger.info("清洗后的历史节点数据:" + lastHistoricTaskInstanceList);
+ return lastHistoricTaskInstanceList;
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/utils/ModelUtil.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/utils/ModelUtil.java
new file mode 100644
index 000000000..476461487
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/utils/ModelUtil.java
@@ -0,0 +1,697 @@
+package com.pig4cloud.pigx.flow.engine.utils;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.json.JSONUtil;
+import com.pig4cloud.pigx.flow.engine.expression.condition.NodeExpressionStrategyFactory;
+import com.pig4cloud.pigx.flow.engine.listeners.ApprovalCreateListener;
+import com.pig4cloud.pigx.flow.engine.listeners.FlowProcessEventListener;
+import com.pig4cloud.pigx.flow.engine.service.ApproveServiceTask;
+import com.pig4cloud.pigx.flow.engine.service.CopyServiceTask;
+import com.pig4cloud.pigx.flow.task.api.feign.RemoteFlowTaskService;
+import com.pig4cloud.pigx.flow.task.constant.NodeTypeEnum;
+import com.pig4cloud.pigx.flow.task.constant.ProcessInstanceConstant;
+import com.pig4cloud.pigx.flow.task.dto.Node;
+import com.pig4cloud.pigx.flow.task.dto.ProcessNodeDataDto;
+import com.pig4cloud.pigx.flow.task.utils.NodeUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.bpmn.model.Process;
+import org.flowable.bpmn.model.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 模型工具类 处理模型构建相关的
+ */
+@Slf4j
+public class ModelUtil {
+ /**
+ * 构建模型
+ *
+ * @param nodeDto 前端传输节点
+ * @return
+ */
+ public static BpmnModel buildBpmnModel(Node nodeDto, String processName, String flowId) {
+ BpmnModel bpmnModel = new BpmnModel();
+ bpmnModel.setTargetNamespace("pig");
+
+ Process process = new Process();
+ process.setId(flowId);
+ process.setName(processName);
+
+ //流程监听器
+ ArrayList eventListeners = new ArrayList<>();
+
+ {
+ //流程实例监听器
+ EventListener eventListener = new EventListener();
+
+ eventListener.setImplementationType("class");
+ eventListener.setImplementation(FlowProcessEventListener.class.getCanonicalName());
+
+
+ eventListeners.add(eventListener);
+
+ }
+ process.setEventListeners(eventListeners);
+
+ NodeUtil.addEndNode(nodeDto);
+
+
+ //创建所有的节点
+ buildAllNode(process, nodeDto, flowId);
+ //创建所有的内部节点连接线
+ buildAllNodeInnerSequence(process, nodeDto, flowId);
+ //创建节点间连线
+ buildAllNodeOuterSequence(process, nodeDto, null);
+ //处理分支和下级连线
+
+
+ bpmnModel.addProcess(process);
+ return bpmnModel;
+ }
+
+
+ /**
+ * 先创建所有的节点
+ *
+ * @param process
+ * @param nodeDto
+ * @param flowId
+ */
+ public static void buildAllNode(Process process, Node nodeDto, String flowId) {
+ if (!NodeUtil.isNode(nodeDto)) {
+ return;
+ }
+
+
+ List flowElementList = buildNode(nodeDto, flowId);
+ for (FlowElement flowElement : flowElementList) {
+ if (process.getFlowElement(flowElement.getId()) == null) {
+ process.addFlowElement(flowElement);
+ }
+ }
+
+ //子节点
+ Node children = nodeDto.getChildren();
+
+ if (NodeTypeEnum.getByValue(nodeDto.getType()).getBranch()) {
+
+ //条件分支
+ List branchs = nodeDto.getConditionNodes();
+ for (Node branch : branchs) {
+ buildAllNode(process, branch.getChildren(),
+ flowId);
+
+
+ }
+ if (NodeUtil.isNode(children)) {
+ buildAllNode(process, children, flowId);
+ }
+
+ } else {
+
+ if (NodeUtil.isNode(children)) {
+ buildAllNode(process, children, flowId);
+ }
+ }
+
+
+ }
+
+ /**
+ * 先创建所有的内部节点连接线
+ *
+ * @param process
+ * @param nodeDto
+ * @param flowId
+ */
+ public static void buildAllNodeInnerSequence(Process process, Node nodeDto, String flowId) {
+ if (!NodeUtil.isNode(nodeDto)) {
+ return;
+ }
+
+ //画内部线
+ List flowList = buildInnerSequenceFlow(nodeDto, flowId);
+ for (SequenceFlow sequenceFlow : flowList) {
+ process.addFlowElement(sequenceFlow);
+ }
+
+ //子节点
+ Node children = nodeDto.getChildren();
+ if (NodeTypeEnum.getByValue(nodeDto.getType()).getBranch() ) {
+ //条件分支
+ List branchs = nodeDto.getConditionNodes();
+ for (Node branch : branchs) {
+ buildAllNodeInnerSequence(process, branch.getChildren(),
+ flowId);
+
+
+ }
+ if (NodeUtil.isNode(children)) {
+ buildAllNodeInnerSequence(process, children, flowId);
+ }
+
+ } else {
+
+ if (NodeUtil.isNode(children)) {
+ buildAllNodeInnerSequence(process, children, flowId);
+ }
+ }
+
+
+ }
+
+
+ /**
+ * 递归创建节点间连线
+ *
+ * @param process 流程
+ * @param nodeDto 节点对象
+ * @param nextId
+ */
+ public static void buildAllNodeOuterSequence(Process process, Node nodeDto, String nextId) {
+
+ if (!NodeUtil.isNode(nodeDto)) {
+ return;
+ }
+
+ //子节点
+ Node children = nodeDto.getChildren();
+ if (NodeTypeEnum.getByValue(nodeDto.getType()).getBranch() ) {
+// children = children.getChildren();
+ //条件分支
+ List branchs = nodeDto.getConditionNodes();
+ int ord = 1;
+ int size = branchs.size();
+ for (Node branch : branchs) {
+
+
+ buildAllNodeOuterSequence(process, branch.getChildren(), nodeDto.getTailId());
+
+ String expression = null;
+
+ if (nodeDto.getType() == NodeTypeEnum.EXCLUSIVE_GATEWAY.getValue().intValue() ) {
+ if (ord == size) {
+ expression = NodeExpressionStrategyFactory.handleDefaultBranch(branchs, ord - 1);
+ } else if (nodeDto.getType() == NodeTypeEnum.EXCLUSIVE_GATEWAY.getValue().intValue() && ord > 1) {
+ expression = NodeExpressionStrategyFactory.handleDefaultBranch(branchs, ord - 1);
+ } else {
+ expression = NodeExpressionStrategyFactory.handle(branch);
+ }
+
+ }
+
+
+ //添加连线
+ if (!NodeUtil.isNode(branch.getChildren())) {
+ //当前分支 没有其他节点了 所有就是网关和网关后面节点直接连线
+
+ SequenceFlow sequenceFlow = buildSingleSequenceFlow(nodeDto.getId(), nodeDto.getTailId(),
+ expression,
+ StrUtil.format("{}->{}", nodeDto.getName(), nodeDto.getName())
+ );
+ process.addFlowElement(sequenceFlow);
+ } else {
+
+ SequenceFlow sequenceFlow = buildSingleSequenceFlow(nodeDto.getId(), branch.getChildren().getHeadId(),
+ expression,
+ StrUtil.format("{}->{}", nodeDto.getName(), branch.getChildren().getName())
+ );
+ process.addFlowElement(sequenceFlow);
+ }
+ ord++;
+
+ }
+ //分支结尾的合并分支节点-》下一个节点
+ if (children != null && StrUtil.isNotBlank(children.getHeadId()) && StrUtil.isNotBlank(nodeDto.getTailId())) {
+
+ SequenceFlow sequenceFlow = buildSingleSequenceFlow(nodeDto.getTailId(), children.getHeadId(),
+ "",
+ StrUtil.format("{}->{}", nodeDto.getName(), children.getName())
+ );
+ process.addFlowElement(sequenceFlow);
+
+ } else if (StrUtil.isAllNotBlank(nodeDto.getTailId(), nextId)) {
+ SequenceFlow sequenceFlow = buildSingleSequenceFlow(nodeDto.getTailId(), nextId,
+ "",
+ StrUtil.format("{}->{}", nodeDto.getName(), nextId)
+ );
+ process.addFlowElement(sequenceFlow);
+ }
+
+
+ buildAllNodeOuterSequence(process, children, nextId);
+
+
+ } else {
+ //添加连线
+ if (NodeUtil.isNode(children)) {
+ List sequenceFlowList = buildSequenceFlow(children, nodeDto, "");
+ for (SequenceFlow sequenceFlow : sequenceFlowList) {
+ process.addFlowElement(sequenceFlow);
+ }
+ buildAllNodeOuterSequence(process, children, nextId);
+ } else if (nodeDto.getType() != NodeTypeEnum.END.getValue().intValue()) {
+ SequenceFlow seq = buildSingleSequenceFlow(nodeDto.getTailId(), nextId, "", StrUtil.format("{}->{}",
+ nodeDto.getName(), nextId));
+
+ process.addFlowElement(seq);
+
+ }
+ }
+
+ }
+
+
+ /**
+ * 构建节点
+ *
+ * @param node 前端传输节点
+ * @param flowId
+ * @return
+ */
+ private static List buildNode(Node node, String flowId) {
+ List flowElementList = new ArrayList<>();
+ if (!NodeUtil.isNode(node)) {
+ return flowElementList;
+ }
+
+ //设置节点的连线头节点
+ node.setHeadId(node.getId());
+ //设置节点的连线尾节点
+ node.setTailId(node.getId());
+ node.setName(StrUtil.format("{}[{}]", node.getName(), RandomUtil.randomNumbers(5)));
+
+ //存储节点数据
+ RemoteFlowTaskService remoteFlowTaskService = SpringUtil.getBean(RemoteFlowTaskService.class);
+ ProcessNodeDataDto processNodeDataDto = new ProcessNodeDataDto();
+ processNodeDataDto.setFlowId(flowId);
+ processNodeDataDto.setNodeId(node.getId());
+ processNodeDataDto.setData(JSONUtil.toJsonStr(node));
+ remoteFlowTaskService.saveNodeOriData(processNodeDataDto);
+
+ //开始
+ if (node.getType() == NodeTypeEnum.ROOT.getValue().intValue()) {
+ flowElementList.addAll(buildStartNode(node));
+ }
+
+ //结束
+ if (node.getType() == NodeTypeEnum.END.getValue().intValue()) {
+ flowElementList.add(buildEndNode(node, false));
+ }
+
+ //审批
+ if (node.getType() == NodeTypeEnum.APPROVAL.getValue().intValue()) {
+
+
+ flowElementList.addAll(buildApproveNode(node));
+ }
+
+ //抄送
+ if (node.getType() == NodeTypeEnum.CC.getValue().intValue()) {
+ flowElementList.add(buildCCNode(node));
+ }
+ //条件分支
+ if (node.getType() == NodeTypeEnum.EXCLUSIVE_GATEWAY.getValue().intValue()) {
+ flowElementList.addAll(buildInclusiveGatewayNode(node));
+ }
+ //并行分支
+ if (node.getType() == NodeTypeEnum.PARALLEL_GATEWAY.getValue().intValue()) {
+ flowElementList.addAll(buildParallelGatewayNode(node));
+ }
+
+ return flowElementList;
+ }
+
+ /**
+ * 构建开始节点
+ * 添加一个自动完成任务的用户任务节点
+ *
+ * @param node 前端传输节点
+ * @return
+ */
+ private static List buildStartNode(Node node) {
+
+ List flowElementList = new ArrayList<>();
+
+
+ StartEvent startEvent = new StartEvent();
+ startEvent.setId(node.getId());
+ startEvent.setName(node.getName());
+
+ flowElementList.add(startEvent);
+
+
+
+
+ return flowElementList;
+ }
+
+ /**
+ * 构建审批节点
+ *
+ * @param node
+ * @return
+ */
+ private static List buildApproveNode(Node node) {
+ List flowElementList = new ArrayList<>();
+ node.setTailId(StrUtil.format("approve_service_task_{}", node.getId()));
+
+
+ //创建了任务执行监听器
+ //先执行指派人 后创建
+ //https://tkjohn.github.io/flowable-userguide/#eventDispatcher
+ FlowableListener createListener = new FlowableListener();
+ createListener.setImplementation(ApprovalCreateListener.class.getCanonicalName());
+ createListener.setImplementationType("class");
+ createListener.setEvent("create");
+
+
+ UserTask userTask = buildUserTask(node, createListener);
+ flowElementList.add(userTask);
+
+
+
+ ServiceTask serviceTask = new ServiceTask();
+ serviceTask.setId(StrUtil.format("approve_service_task_{}", node.getId()));
+ serviceTask.setName(StrUtil.format("{}_服务任务", node.getName()));
+ serviceTask.setImplementationType("class");
+ serviceTask.setImplementation(ApproveServiceTask.class.getCanonicalName());
+ serviceTask.setAsynchronous(false);
+
+
+
+ flowElementList.add(serviceTask);
+
+
+ {
+
+
+ //执行人处理
+
+ String inputDataItem = "${multiInstanceHandler.resolveAssignee(execution)}";
+
+
+ //串行
+
+ boolean isSequential = true;
+
+ Integer multipleMode = node.getMultipleMode();
+ //多人
+ if ((multipleMode == ProcessInstanceConstant.MULTIPLE_MODE_AL_SAME)) {
+ //并行会签
+ isSequential = false;
+ }
+ if ((multipleMode == ProcessInstanceConstant.MULTIPLE_MODE_ALL_SORT)) {
+
+ //串行会签
+ }
+ if ((multipleMode == ProcessInstanceConstant.MULTIPLE_MODE_ONE)) {
+
+ //或签
+ isSequential = false;
+ }
+
+ MultiInstanceLoopCharacteristics loopCharacteristics = new MultiInstanceLoopCharacteristics();
+ loopCharacteristics.setSequential(isSequential);
+ loopCharacteristics.setInputDataItem(inputDataItem);
+ loopCharacteristics.setElementVariable(StrUtil.format("{}_assignee_temp", node.getId()));
+
+ loopCharacteristics.setCompletionCondition("${multiInstanceHandler.completionCondition(execution)}");
+
+ userTask.setLoopCharacteristics(loopCharacteristics);
+ String format = StrUtil.format("${{}_assignee_temp}", node.getId());
+ userTask.setAssignee(format);
+
+ }
+ return flowElementList;
+ }
+
+
+
+ /**
+ * 创建用户任务
+ *
+ * @param node 前端传输节点
+ * @return
+ */
+ private static UserTask buildUserTask(Node node, FlowableListener... flowableListeners) {
+ UserTask userTask = new UserTask();
+ userTask.setId(node.getId());
+ userTask.setName(node.getName());
+
+ if (flowableListeners != null) {
+ List taskListeners = new ArrayList<>();
+
+ for (FlowableListener flowableListener : flowableListeners) {
+ taskListeners.add(flowableListener);
+
+ }
+ userTask.setTaskListeners(taskListeners);
+ }
+
+ return userTask;
+ }
+
+ /**
+ * 构建简单的包容网关
+ *
+ * @param node
+ * @return
+ */
+ private static FlowElement buildSimpleExclusiveGatewayNode(Node node) {
+
+
+ ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
+ exclusiveGateway.setId(node.getId());
+ exclusiveGateway.setName(node.getName());
+
+ return exclusiveGateway;
+
+ }
+
+
+ /**
+ * 构建并行网关
+ *
+ * @param node
+ * @return
+ */
+ private static List buildParallelGatewayNode(Node node) {
+ node.setTailId(StrUtil.format("{}_merge_gateway", node.getId()));
+ List flowElementList = new ArrayList<>();
+
+ ParallelGateway inclusiveGateway = new ParallelGateway();
+ inclusiveGateway.setId(node.getId());
+ inclusiveGateway.setName(node.getName());
+ flowElementList.add(inclusiveGateway);
+
+ //合并网关
+ ParallelGateway parallelGateway = new ParallelGateway();
+ parallelGateway.setId(StrUtil.format("{}_merge_gateway", node.getId()));
+ parallelGateway.setName(StrUtil.format("{}_合并网关", node.getName()));
+ flowElementList.add(parallelGateway);
+
+ return flowElementList;
+ }
+
+ /**
+ * 构建包容网关
+ *
+ * @param node
+ * @return
+ */
+ private static List buildInclusiveGatewayNode(Node node) {
+
+ node.setTailId(StrUtil.format("{}_merge_gateway", node.getId()));
+
+ List flowElementList = new ArrayList<>();
+
+ InclusiveGateway inclusiveGateway = new InclusiveGateway();
+ inclusiveGateway.setId(node.getId());
+ inclusiveGateway.setName(node.getName());
+ flowElementList.add(inclusiveGateway);
+
+ //合并网关
+ InclusiveGateway gateway = new InclusiveGateway();
+ gateway.setId(StrUtil.format("{}_merge_gateway", node.getId()));
+ gateway.setName(StrUtil.format("{}_合并网关", node.getName()));
+ flowElementList.add(gateway);
+
+ return flowElementList;
+ }
+
+ /**
+ * 构建结束节点
+ *
+ * @param node 前端传输节点
+ * @param terminateAll
+ * @return
+ */
+ private static EndEvent buildEndNode(Node node, boolean terminateAll) {
+ EndEvent endEvent = new EndEvent();
+ endEvent.setId(node.getId());
+ endEvent.setName(node.getName());
+
+
+ List definitionList = new ArrayList<>();
+ TerminateEventDefinition definition = new TerminateEventDefinition();
+ definition.setTerminateAll(terminateAll);
+ definitionList.add(definition);
+ endEvent.setEventDefinitions(definitionList);
+
+ return endEvent;
+ }
+
+ /**
+ * 创建连接线
+ *
+ * @param node 子级节点
+ * @param parentNode 父级节点
+ * @param expression
+ * @return 所有连接线
+ */
+ private static List buildSequenceFlow(Node node, Node parentNode, String expression
+ ) {
+ List sequenceFlowList = new ArrayList<>();
+ //没有子级了
+ if (!NodeUtil.isNode(node)) {
+ return sequenceFlowList;
+ }
+
+ String pid = parentNode.getId();
+
+ if (StrUtil.hasBlank(pid, node.getId())) {
+ return sequenceFlowList;
+ }
+
+
+ SequenceFlow sequenceFlow = buildSingleSequenceFlow(parentNode.getTailId(), node.getHeadId(), expression,
+ StrUtil.format("{}->{}", parentNode.getName(), node.getName())
+ );
+ sequenceFlowList.add(sequenceFlow);
+
+
+ return sequenceFlowList;
+ }
+
+ /**
+ * 生成扩展数据
+ *
+ * @param key
+ * @param val
+ * @return
+ */
+ public static ExtensionAttribute generateExtensionAttribute(String key, String val) {
+ ExtensionAttribute ea = new ExtensionAttribute();
+
+ ea.setName(key);
+ ea.setValue(val);
+ return ea;
+ }
+
+ /**
+ * 创建抄送节点
+ *
+ * @param node
+ * @return
+ */
+ private static FlowElement buildCCNode(Node node) {
+
+ ServiceTask serviceTask = new ServiceTask();
+ serviceTask.setId(node.getId());
+ serviceTask.setName(node.getName());
+ serviceTask.setAsynchronous(false);
+ serviceTask.setImplementationType("class");
+ serviceTask.setImplementation(CopyServiceTask.class.getCanonicalName());
+
+ ExtensionElement e = new ExtensionElement();
+ {
+
+
+ e.setName("flowable:failedJobRetryTimeCycle");
+ //上面的例子会让作业执行器重试5次,并在每次重试前等待1分钟。
+ e.setElementText("R5/PT1M");
+
+ }
+
+ serviceTask.addExtensionElement(e);
+ return serviceTask;
+ }
+
+
+
+ /**
+ * 创建连接线
+ *
+ * @param node 父级节点
+ * @param flowId
+ * @return 所有连接线
+ */
+ private static List buildInnerSequenceFlow(Node node, String flowId
+ ) {
+
+
+ List sequenceFlowList = new ArrayList<>();
+ if (!NodeUtil.isNode(node)) {
+ return sequenceFlowList;
+ }
+
+
+ String nodeId = node.getId();
+ if (StrUtil.hasBlank(nodeId)) {
+ return sequenceFlowList;
+ }
+
+
+ if (node.getType() == NodeTypeEnum.APPROVAL.getValue().intValue()) {
+
+
+ String gatewayId = StrUtil.format("approve_service_task_{}", nodeId);
+
+
+ {
+ SequenceFlow sequenceFlow = buildSingleSequenceFlow(nodeId, gatewayId, "${12==12}", null);
+ sequenceFlowList.add(sequenceFlow);
+ }
+
+
+ }
+
+
+
+
+
+ return sequenceFlowList;
+ }
+
+ /**
+ * 创建单个连接线
+ *
+ * @param pId 父级id
+ * @param childId 子级id
+ * @param expression 表达式
+ * @param name
+ * @return
+ */
+ private static SequenceFlow buildSingleSequenceFlow(String pId, String childId, String expression, String name) {
+ if (StrUtil.hasBlank(pId, childId)) {
+ return null;
+ }
+ SequenceFlow sequenceFlow = new SequenceFlow(pId, childId);
+ sequenceFlow.setConditionExpression(expression);
+ sequenceFlow.setName(StrUtil.format("{}|{}", pId, childId));
+ sequenceFlow.setName(StrUtil.format("连线[{}]", RandomUtil.randomString(5)));
+ if (StrUtil.isNotBlank(name)) {
+ sequenceFlow.setName(name);
+ }
+ sequenceFlow.setId(StrUtil.format("sq-id-{}-{}", IdUtil.fastSimpleUUID(), RandomUtil.randomInt(1, 10000000)));
+ return sequenceFlow;
+ }
+
+
+}
diff --git a/pigx-visual/pigx-oa-platform/src/main/resources/application.yml b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/resources/application.yml
similarity index 97%
rename from pigx-visual/pigx-oa-platform/src/main/resources/application.yml
rename to pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/resources/application.yml
index ef069930c..c8907744f 100644
--- a/pigx-visual/pigx-oa-platform/src/main/resources/application.yml
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/resources/application.yml
@@ -1,5 +1,5 @@
server:
- port: 5005
+ port: 9020
spring:
application:
diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/resources/logback-spring.xml b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/resources/logback-spring.xml
new file mode 100644
index 000000000..0cd6c2983
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/resources/logback-spring.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${CONSOLE_LOG_PATTERN}
+
+
+
+
+
+ ${log.path}/debug.log
+
+ ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz
+ 50MB
+ 30
+
+
+ %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n
+
+
+
+
+
+ ${log.path}/error.log
+
+ ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz
+ 50MB
+ 30
+
+
+ %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n
+
+
+ ERROR
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pigx-flow/pigx-flow-engine/pom.xml b/pigx-flow/pigx-flow-engine/pom.xml
new file mode 100644
index 000000000..9b9127c06
--- /dev/null
+++ b/pigx-flow/pigx-flow-engine/pom.xml
@@ -0,0 +1,22 @@
+
+
+
+ 4.0.0
+
+
+ com.pig4cloud
+ pigx-flow
+ 5.2.0-snapshot
+
+
+ pigx-flow-engine
+ pom
+ flowable 工作流核心引擎
+
+
+
+ pigx-flow-engine-api
+ pigx-flow-engine-biz
+
+
diff --git a/pigx-flow/pigx-flow-task/pigx-flow-task-api/pom.xml b/pigx-flow/pigx-flow-task/pigx-flow-task-api/pom.xml
new file mode 100644
index 000000000..619bd9d9b
--- /dev/null
+++ b/pigx-flow/pigx-flow-task/pigx-flow-task-api/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 4.0.0
+
+ com.pig4cloud
+ pigx-flow-task
+ 5.2.0-snapshot
+
+
+ pigx-flow-task-api
+
+
+
+
+ com.pig4cloud
+ pigx-common-core
+
+
+
+ com.baomidou
+ mybatis-plus-extension
+
+
+
+ com.pig4cloud
+ pigx-common-feign
+
+
+
+ com.pig4cloud
+ pigx-common-excel
+
+
+
diff --git a/pigx-flow/pigx-flow-task/pigx-flow-task-api/src/main/java/com/pig4cloud/pigx/flow/task/api/feign/RemoteFlowEngineService.java b/pigx-flow/pigx-flow-task/pigx-flow-task-api/src/main/java/com/pig4cloud/pigx/flow/task/api/feign/RemoteFlowEngineService.java
new file mode 100644
index 000000000..6bb830147
--- /dev/null
+++ b/pigx-flow/pigx-flow-task/pigx-flow-task-api/src/main/java/com/pig4cloud/pigx/flow/task/api/feign/RemoteFlowEngineService.java
@@ -0,0 +1,128 @@
+package com.pig4cloud.pigx.flow.task.api.feign;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pigx.common.core.constant.ServiceNameConstants;
+import com.pig4cloud.pigx.common.core.util.R;
+import com.pig4cloud.pigx.flow.task.dto.*;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.Map;
+
+/**
+ * @author lengleng
+ * @date 2023/7/14
+ */
+@FeignClient(contextId = "remoteFlowEngineService", value = ServiceNameConstants.FLOW_ENGINE_SERVER)
+public interface RemoteFlowEngineService {
+
+ /**
+ * 查询任务变量
+ * @param variableQueryParamDto 变量查询参数
+ * @return 任务变量结果
+ */
+ @PostMapping("/task/queryTaskVariables")
+ R