feat: initial iShare project code

This commit is contained in:
purovps
2026-02-16 23:20:59 +08:00
parent 8c83a6fd46
commit 6f270a972e
1910 changed files with 218015 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-flow-engine</artifactId>
<version>5.2.0</version>
</parent>
<artifactId>pigx-flow-engine-api</artifactId>
<dependencies>
<!--core 工具类-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-core</artifactId>
</dependency>
<!--mybatis plus extension,包含了mybatis plus core-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<!--feign 工具类-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-feign</artifactId>
</dependency>
<!-- excel 导入导出 -->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-excel</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,6 @@
/*
@author pigx archetype
* <p>
* feign client 存放目录,注意 @EnablePigxFeignClients 的扫描范围
*/
package com.pig4cloud.pigx.flow.engine.api.feign;

View File

@@ -0,0 +1,6 @@
/*
* @author pigx archetype
* <p>
* 常量和枚举定义
*/
package com.pig4cloud.pigx.flow.engine.constant;

View File

@@ -0,0 +1,6 @@
/*
* @author pigx archetype
* <p>
* DTO 存放目录
*/
package com.pig4cloud.pigx.flow.engine.dto;

View File

@@ -0,0 +1,6 @@
/*
* @author pigx archetype
* <p>
* 实体 存放目录
*/
package com.pig4cloud.pigx.flow.engine.entity;

View File

@@ -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 9020
ADD ./target/pigx-flow-engine-biz.jar ./
CMD sleep 60;java $JAVA_OPTS -jar pigx-flow-engine-biz.jar

View File

@@ -0,0 +1,138 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-flow-engine</artifactId>
<version>5.2.0</version>
</parent>
<artifactId>pigx-flow-engine-biz</artifactId>
<dependencies>
<!-- flowable 核心依赖-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-bpmn-layout</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
<version>${flowable.version}</version>
</dependency>
<!--task 模块接口-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-flow-task-api</artifactId>
</dependency>
<!--计算引擎-->
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
</dependency>
<!--必备: undertow容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!--必备: spring boot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--必备: 注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--必备: 配置中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--必备: 操作数据源相关-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-data</artifactId>
</dependency>
<!--必备pigx安全模块-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-security</artifactId>
</dependency>
<!--必备xss 过滤模块-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-xss</artifactId>
</dependency>
<!--必备: sentinel 依赖-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-sentinel</artifactId>
</dependency>
<!--必备: feign 依赖-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-feign</artifactId>
</dependency>
<!--必备: 依赖api模块-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-flow-engine-api</artifactId>
</dependency>
<!--必备: log 依赖-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-log</artifactId>
</dependency>
<!--选配: mybatis 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--选配: druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--选配: mysql 数据库驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!--选配: swagger文档-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-swagger</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-cache</artifactId>
</dependency>
<!--测试: spring boot test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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
* <p>
* 项目启动类
*/
@EnableOpenApi("engine")
@EnablePigxFeignClients
@EnableDiscoveryClient
@EnablePigxResourceServer
@SpringBootApplication
public class PigxFlowEngineApplication {
public static void main(String[] args) {
SpringApplication.run(PigxFlowEngineApplication.class, args);
}
}

View File

@@ -0,0 +1,285 @@
package com.pig4cloud.pigx.flow.engine.controller;
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.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.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.TaskQuery;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
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<String, Object> 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<String, Object> 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<String> processInstanceIdList = taskParamDto.getProcessInstanceIdList();
processInstanceIdList.forEach(processInstanceId -> {
// 查询流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId).singleResult();
if (Optional.ofNullable(processInstance).isPresent()) {
// 查询执行实例
List<String> 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<HistoricActivityInstance> list = activityInstanceQuery.listPage(
(taskQueryParamDto.getPageNum() - 1) * taskQueryParamDto.getPageSize(),
taskQueryParamDto.getPageSize());
long count = activityInstanceQuery.count();
List<TaskDto> 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<TaskDto> 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<TaskDto> taskDtoList = new ArrayList<>();
int pageIndex = taskQueryParamDto.getPageNum() - 1;
int pageSize = taskQueryParamDto.getPageSize();
Page<TaskDto> pageResultDto = new Page<>();
TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(assign).orderByTaskCreateTime().desc();
if (StrUtil.isNotBlank(taskQueryParamDto.getProcessName())) {
List<ProcessInstance> 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<String, Object> taskServiceVariables = taskService.getVariables(taskId);
Map<String, Object> runtimeServiceVariables = runtimeService.getVariables(processInstanceId);
Map<String, Object> 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);
}
}

View File

@@ -0,0 +1,69 @@
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<IndexPageStatistics> 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<String, Object> variables = runtimeService.getVariables(paramDto.getExecutionId());
return R.ok(variables);
}
}

View File

@@ -0,0 +1,146 @@
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<String> 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<String, Object> variables = runtimeService.getVariables(task.getExecutionId());
return R.ok(variables);
}
Map<String, Object> 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> 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<String, Object> variables = taskService.getVariables(taskId);
Map<String, Object> 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> 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<String, Object> 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<String, Object> taskLocalParamMap = taskParamDto.getTaskLocalParamMap();
if (CollUtil.isNotEmpty(taskLocalParamMap)) {
taskService.setVariablesLocal(taskParamDto.getTaskId(), taskLocalParamMap);
}
taskService.complete(taskParamDto.getTaskId(), taskParamDto.getParamMap());
return R.ok();
}
}

View File

@@ -0,0 +1,308 @@
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<List<NodeUser>>() {
}).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<SelectValue> 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<NodeUser> nodeUserDtoList = objectMapper.readValue(jsonString, new TypeReference<List<NodeUser>>() {
});
if (CollUtil.isEmpty(nodeUserDtoList) || nodeUserDtoList.size() != 1) {
return false;
}
NodeUser nodeUserDto = nodeUserDtoList.get(0);
// 参数
List<NodeUser> paramDeptList = objectMapper.readValue(param, new TypeReference<List<NodeUser>>() {
});
Long deptId = nodeUserDto.getId();
List<Long> deptIdList = paramDeptList.stream().map(NodeUser::getId).collect(Collectors.toList());
return inCompare(symbol, deptId, deptIdList);
}
private static boolean inCompare(String symbol, Long deptId, List<Long> 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<NodeUser> nodeUserDtoList = objectMapper.readValue(jsonString, new TypeReference<List<NodeUser>>() {
});
if (CollUtil.isEmpty(nodeUserDtoList) || nodeUserDtoList.size() != 1) {
return false;
}
NodeUser nodeUserDto = nodeUserDtoList.get(0);
// 参数
List<NodeUser> paramDeptList = objectMapper.readValue(param, new TypeReference<List<NodeUser>>() {
});
List<Long> deptIdList = paramDeptList.stream()
.filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.DEPT.getKey())).map(NodeUser::getId)
.collect(Collectors.toList());
List<Long> userIdList = paramDeptList.stream()
.filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.USER.getKey())).map(NodeUser::getId)
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(deptIdList)) {
R<List<SysUser>> r = remoteUserService.getUserIdListByDeptIdList(deptIdList);
List<Long> data = r.getData().stream().map(SysUser::getUserId).collect(Collectors.toList());
userIdList.addAll(data);
}
return inCompare(symbol, Convert.toLong(nodeUserDto.getId()), userIdList);
}
}

View File

@@ -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);
}

View File

@@ -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<String, NodeConditionStrategy> 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<String> 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<String> exps = new ArrayList<>();
List<GroupCondition> 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<Node> branchs, int currentIndex) {
List<String> 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 + "}";
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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)";
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<SelectValue> 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);
}
}

View File

@@ -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)";
}
}

View File

@@ -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);
}
}

View File

@@ -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<Long> 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);
}
}
}
}

View File

@@ -0,0 +1,293 @@
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<NodeUser> nodeUsers = objectMapper.readValue(objectMapper.writeValueAsString(variable),
new TypeReference<List<NodeUser>>() {
});
Long startUserId = nodeUsers.get(0).getId();
Map<String, Object> 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;
}
}

View File

@@ -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<Long> handle(Node node, NodeUser rootUser, Map<String, Object> variables);
}

View File

@@ -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<String, AssignUserStrategy> assignUserStrategyMap;
private final ObjectMapper objectMapper;
/**
* 解析执行人
* @param execution 流程执行对象
* @return 执行人集合
*/
@SneakyThrows
public List<Long> resolveAssignee(DelegateExecution execution) {
// 执行人集合
List<Long> 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<List<NodeUser>>() {
}).get(0);
// 节点数据
Node node = remoteFlowTaskService.queryNodeOriData(flowId, nodeId).getData();
if (node != null) {
Map<String, Object> variables = execution.getVariables();
Integer assignedType = node.getAssignedType();
List<Long> 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<NodeUser> nodeUserDtos = objectMapper.readValue(variableJson, new TypeReference<List<NodeUser>>() {
});
if (CollUtil.isNotEmpty(nodeUserDtos)) {
List<Long> collect = nodeUserDtos.stream().map(NodeUser::getId).collect(Collectors.toList());
assignList.addAll(collect);
}
}
Optional.of(assignList).orElseGet(() -> {
List<Long> 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;
}
}

View File

@@ -0,0 +1,68 @@
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<Long> handle(Node node, NodeUser rootUser, Map<String, Object> variables) {
// 指定人员
List<NodeUser> userDtoList = node.getNodeUserList();
// 用户ID列表
List<Long> userIdList = userDtoList.stream()
.filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.USER.getKey())).map(NodeUser::getId)
.collect(Collectors.toList());
// 部门ID列表
List<Long> deptIdList = userDtoList.stream()
.filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.DEPT.getKey())).map(NodeUser::getId)
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(deptIdList)) {
R<List<SysUser>> 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;
}
}

View File

@@ -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<Long> handle(Node node, NodeUser rootUser, Map<String, Object> variables) {
List<Long> assignList = new ArrayList<>();
Object variable = variables.get(node.getFormUserId());
if (variable != null && !StrUtil.isBlankIfStr(variable)) {
String jsonString = objectMapper.writeValueAsString(variable);
List<NodeUser> nodeUserDtoList = JSONUtil.toList(jsonString, NodeUser.class);
assignList.addAll(nodeUserDtoList.stream().map(NodeUser::getId).collect(Collectors.toList()));
}
return assignList;
}
}

View File

@@ -0,0 +1,40 @@
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<Long> handle(Node node, NodeUser rootUser, Map<String, Object> variables) {
// 获取部门ID
return node.getNodeUserList().stream().map(nodeUser -> deptService.getAllDeptLeader(nodeUser.getId()))
.flatMap((Function<R<List<Long>>, Stream<Long>>) listR -> listR.getData() == null ? null
: listR.getData().stream())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,50 @@
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<Long> handle(Node node, NodeUser rootUser, Map<String, Object> variables) {
// 使用 lambda 表达式和方法引用从 NodeUser 列表中提取角色 ID
List<Long> roleIds = node.getNodeUserList().stream().map(NodeUser::getId).collect(Collectors.toList());
// 提取 Optional 结果中的数据
List<Long> data = RetOps.of(remoteUserService.getUserIdListByRoleIdList(roleIds)).getData()
.orElseGet(Collections::emptyList);
// 返回用户 ID 列表
return new ArrayList<>(data);
}
}

View File

@@ -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<Long> handle(Node node, NodeUser rootUser, Map<String, Object> variables) {
List<Long> assignList = new ArrayList<>();
Object variable = variables.get(StrUtil.format("{}_assignee_select", node.getId()));
log.info("{}-发起人自选参数:{}", node.getName(), variable);
if (variable == null) {
return assignList;
}
List<NodeUser> nodeUserDtos = objectMapper.readValue(objectMapper.writeValueAsString(variable),
new TypeReference<List<NodeUser>>() {
});
List<Long> collect = nodeUserDtos.stream().map(NodeUser::getId).collect(Collectors.toList());
assignList.addAll(collect);
return assignList;
}
}

View File

@@ -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<Long> handle(Node node, NodeUser rootUser, Map<String, Object> variables) {
return CollUtil.newArrayList(rootUser.getId());
}
}

View File

@@ -0,0 +1,59 @@
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();
}
}
}
}
}
}

View File

@@ -0,0 +1,95 @@
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<NodeUser> userDtoList = node.getNodeUserList();
// 用户ID列表
List<String> userIdList = userDtoList.stream()
.filter(w -> StrUtil.equals(w.getType(), NodeUserTypeEnum.USER.getKey()))
.map(w -> Convert.toStr(w.getId())).collect(Collectors.toList());
// 部门ID列表
List<String> 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<List<String>> r = remoteFlowTaskService.queryUserIdListByDepIdList(deptIdList);
List<String> 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<List<NodeUser>>() {
}).get(0);
Map<String, Object> 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);
}
}
}

View File

@@ -0,0 +1,4 @@
/*
* @author pigx archetype
*/
package com.pig4cloud.pigx.flow.engine.service;

View File

@@ -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<SequenceFlow> getElementIncomingFlows(FlowElement source) {
List<SequenceFlow> 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<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
List<SequenceFlow> 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<FlowElement> getAllElements(Collection<FlowElement> flowElements,
Collection<FlowElement> 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<UserTask> iteratorFindParentUserTasks(FlowElement source, Set<String> hasSequenceFlow,
List<UserTask> 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<SequenceFlow> 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<UserTask> 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<FlowElement> iteratorFindChildUserTasks(FlowElement source, List<String> runActiveIdList,
Set<String> hasSequenceFlow, List<FlowElement> 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<SequenceFlow> 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<FlowElement> 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<UserTask> findChildProcessUserTasks(FlowElement source, Set<String> hasSequenceFlow,
List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 根据类型,获取出口连线
List<SequenceFlow> 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<UserTask> 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<String> iteratorFindDirtyRoads(FlowElement source, List<String> passRoads,
Set<String> hasSequenceFlow, List<String> targets, Set<String> 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<SequenceFlow> 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<String> findChildProcessAllDirtyRoad(FlowElement source, Set<String> hasSequenceFlow,
Set<String> dirtyRoads) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
// 根据类型,获取出口连线
List<SequenceFlow> 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<String> hasSequenceFlow,
List<String> targets, Boolean inChildProcess) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
inChildProcess = inChildProcess == null ? false : inChildProcess;
// 根据类型,获取出口连线
List<SequenceFlow> 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<String> 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<SequenceFlow> 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<List<UserTask>> findRoad(FlowElement source, List<UserTask> passRoads,
Set<String> hasSequenceFlow, List<List<UserTask>> 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<SequenceFlow> 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<String> historicTaskInstanceClean(Collection<FlowElement> allElements,
List<HistoricActivityInstance> historicActivityIdList) {
// 会签节点收集
List<String> 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<HistoricActivityInstance> stack = new Stack<>();
historicActivityIdList.forEach(item -> stack.push(item));
// 清洗后的历史任务实例
List<String> lastHistoricTaskInstanceList = new ArrayList<>();
// 网关存在可能只走了部分分支情况,且还存在跳转废弃数据以及其他分支数据的干扰,因此需要对历史节点数据进行清洗
// 临时用户任务 key
StringBuilder userTaskKey = null;
// 临时被删掉的任务 key存在并行情况
List<String> deleteKeyList = new ArrayList<>();
// 临时脏数据线路
List<Set<String>> 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<String> 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<String> 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;
}
}

View File

@@ -0,0 +1,627 @@
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<EventListener> 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<FlowElement> 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<Node> 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<SequenceFlow> flowList = buildInnerSequenceFlow(nodeDto, flowId);
for (SequenceFlow sequenceFlow : flowList) {
process.addFlowElement(sequenceFlow);
}
// 子节点
Node children = nodeDto.getChildren();
if (NodeTypeEnum.getByValue(nodeDto.getType()).getBranch()) {
// 条件分支
List<Node> 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<Node> 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<SequenceFlow> 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<FlowElement> buildNode(Node node, String flowId) {
List<FlowElement> 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<FlowElement> buildStartNode(Node node) {
List<FlowElement> 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<FlowElement> buildApproveNode(Node node) {
List<FlowElement> 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<FlowableListener> 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<FlowElement> buildParallelGatewayNode(Node node) {
node.setTailId(StrUtil.format("{}_merge_gateway", node.getId()));
List<FlowElement> 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<FlowElement> buildInclusiveGatewayNode(Node node) {
node.setTailId(StrUtil.format("{}_merge_gateway", node.getId()));
List<FlowElement> 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<EventDefinition> 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<SequenceFlow> buildSequenceFlow(Node node, Node parentNode, String expression) {
List<SequenceFlow> 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<SequenceFlow> buildInnerSequenceFlow(Node node, String flowId) {
List<SequenceFlow> 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;
}
}

View File

@@ -0,0 +1,18 @@
server:
port: 9020
spring:
application:
name: @artifactId@
cloud:
nacos:
username: @nacos.username@
password: @nacos.password@
discovery:
server-addr: ${NACOS_HOST:pigx-register}:${NACOS_PORT:8848}
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
config:
import:
- optional:nacos:application-@profiles.active@.yml
- optional:nacos:${spring.application.name}-@profiles.active@.yml

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
小技巧: 在根pom里面设置统一存放路径统一管理方便维护
<properties>
<log-path>/Users/lengleng</log-path>
</properties>
1. 其他模块加日志输出直接copy本文件放在resources 目录即可
2. 注意修改 <property name="${log-path}/log.path" value=""/> 的value模块
-->
<configuration debug="false" scan="false">
<property name="log.path" value="logs/${project.artifactId}"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- Console log output -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Log file debug output -->
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
</appender>
<!-- Log file error output -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<logger name="org.activiti.engine.impl.db" level="DEBUG">
<appender-ref ref="debug"/>
</logger>
<!--nacos 心跳 INFO 屏蔽-->
<logger name="com.alibaba.nacos" level="OFF">
<appender-ref ref="error"/>
</logger>
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
</root>
</configuration>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-flow</artifactId>
<version>5.2.0</version>
</parent>
<artifactId>pigx-flow-engine</artifactId>
<packaging>pom</packaging>
<description>flowable 工作流核心引擎</description>
<!--项目子模块-->
<modules>
<module>pigx-flow-engine-api</module>
<module>pigx-flow-engine-biz</module>
</modules>
</project>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-flow-task</artifactId>
<version>5.2.0</version>
</parent>
<artifactId>pigx-flow-task-api</artifactId>
<dependencies>
<!--core 工具类-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-core</artifactId>
</dependency>
<!--mybatis plus extension,包含了mybatis plus core-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<!--feign 工具类-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-feign</artifactId>
</dependency>
<!-- excel 导入导出 -->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-excel</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,127 @@
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<Map<String, Object>> queryTaskVariables(@RequestBody VariableQueryParamDto variableQueryParamDto);
/**
* 查询简单数据
* @param userId 用户ID
* @return 索引页统计数据
*/
@GetMapping("/process-instance/querySimpleData")
R<IndexPageStatistics> querySimpleData(@RequestParam("userId") Long userId);
/**
* 创建流程
* @param map 流程JSON对象
* @return 创建流程结果
*/
@PostMapping("/flow/create")
R<String> createFlow(@RequestBody Map<String, Object> map);
/**
* 启动流程实例
* @param processInstanceParamDto 流程实例参数
* @return 启动流程结果
*/
@PostMapping("/flow/start")
R<String> startProcess(@RequestBody ProcessInstanceParamDto processInstanceParamDto);
/**
* 查询待办任务
* @param paramDto 任务查询参数
* @return 待办任务列表
*/
@PostMapping("/flow/queryAssignTask")
R<Page<TaskDto>> queryAssignTask(@RequestBody TaskQueryParamDto paramDto);
/**
* 查询已完成任务
* @param paramDto 任务查询参数
* @return 已完成任务列表
*/
@PostMapping("/flow/queryCompletedTask")
R<Page<TaskDto>> queryCompletedTask(@RequestBody TaskQueryParamDto paramDto);
/**
* 完成任务
* @param taskParamDto 任务参数
* @return 完成任务结果
*/
@PostMapping("/task/complete")
R<String> completeTask(@RequestBody TaskParamDto taskParamDto);
/**
* 设置任务负责人
* @param taskParamDto 任务参数
* @return 设置负责人结果
*/
@PostMapping("/task/setAssignee")
R<String> setAssignee(@RequestBody TaskParamDto taskParamDto);
/**
* 停止流程实例
* @param taskParamDto 任务参数
* @return 停止流程实例结果
*/
@PostMapping("/flow/stopProcessInstance")
R<String> stopProcessInstance(@RequestBody TaskParamDto taskParamDto);
/**
* 解决任务
* @param taskParamDto 任务参数
* @return 解决任务结果
*/
@PostMapping("/task/resolveTask")
R<String> resolveTask(@RequestBody TaskParamDto taskParamDto);
/**
* 退回任务
* @param taskParamDto 任务参数
* @return 退回任务结果
*/
@PostMapping("/task/back")
R<String> back(@RequestBody TaskParamDto taskParamDto);
/**
* 委派任务
* @param taskParamDto 任务参数
* @return 委派任务结果
*/
@PostMapping("/task/delegateTask")
R<String> delegateTask(@RequestBody TaskParamDto taskParamDto);
/**
* 查询任务
* @param taskId 任务ID
* @param userId 用户ID
* @return 任务查询结果
*/
@GetMapping("/task/engine/queryTask")
R<TaskResultDto> queryTask(@RequestParam("taskId") String taskId, @RequestParam("userId") Long userId);
}

View File

@@ -0,0 +1,107 @@
package com.pig4cloud.pigx.flow.task.api.feign;
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.List;
/**
* @author lengleng
* @date 2023/7/14
*/
@FeignClient(contextId = "remoteFlowTaskService", value = ServiceNameConstants.FLOW_TASK_SERVER)
public interface RemoteFlowTaskService {
/**
* 节点开始事件
* @param nodeRecordParamDto
*/
@PostMapping("/remote/startNodeEvent")
void startNodeEvent(@RequestBody ProcessNodeRecordParamDto nodeRecordParamDto);
/**
* 节点结束事件
* @param nodeRecordParamDto
*/
@PostMapping("/remote/endNodeEvent")
void endNodeEvent(@RequestBody ProcessNodeRecordParamDto nodeRecordParamDto);
/**
* 流程结束事件
* @param processInstanceParamDto
*/
@PostMapping("/remote/endProcess")
void endProcessEvent(@RequestBody ProcessInstanceParamDto processInstanceParamDto);
/**
* 创建流程事件
* @param processInstanceRecordParamDto
*/
@PostMapping("/remote/createProcessEvent")
void createProcessEvent(@RequestBody ProcessInstanceRecordParamDto processInstanceRecordParamDto);
/**
* 根据角色id集合查询用户id集合
* @param roleIdList
*/
@PostMapping("/remote/queryUserIdListByRoleIdList")
R<List<String>> queryUserIdListByRoleIdList(@RequestBody List<String> roleIdList);
/**
* 根据部门id集合查询所有的用户id集合
* @param deptIdList
*/
@PostMapping("/remote/queryUserIdListByDepIdList")
R<List<String>> queryUserIdListByDepIdList(@RequestBody List<String> deptIdList);
/**
* 查询流程管理员
* @param flowId 流程id
*/
@GetMapping("/remote/queryProcessAdmin")
R<Long> queryProcessAdmin(@RequestParam("flowId") String flowId);
/**
* 查询节点数据
* @param flowId 流程id
* @param nodeId 节点id
* @return
*/
@GetMapping("/processNodeData/getNodeData")
R<Node> queryNodeOriData(@RequestParam("flowId") String flowId, @RequestParam("nodeId") String nodeId);
/**
* 节点开始指派用户了
* @param processNodeRecordAssignUserParamDto
*/
@PostMapping("/remote/startAssignUser")
R startAssignUser(@RequestBody ProcessNodeRecordAssignUserParamDto processNodeRecordAssignUserParamDto);
/**
* 任务结束事件
* @param processNodeRecordAssignUserParamDto
*/
@PostMapping("/remote/taskEndEvent")
R taskEndEvent(@RequestBody ProcessNodeRecordAssignUserParamDto processNodeRecordAssignUserParamDto);
/**
* 保存抄送数据
* @param processCopyDto
*/
@PostMapping("/remote/savecc")
R saveCC(@RequestBody ProcessCopyDto processCopyDto);
/**
* 保存节点原始数据
* @param processNodeDataDto
*/
@PostMapping("/processNodeData/saveNodeData")
R saveNodeOriData(@RequestBody ProcessNodeDataDto processNodeDataDto);
}

View File

@@ -0,0 +1,6 @@
/*
@author pigx archetype
* <p>
* feign client 存放目录,注意 @EnablePigxFeignClients 的扫描范围
*/
package com.pig4cloud.pigx.flow.task.api.feign;

View File

@@ -0,0 +1,28 @@
package com.pig4cloud.pigx.flow.task.constant;
/**
* EasyCaptcha 验证码类型枚举
*
* @author haoxr
* @since 2023/03/24
*/
public enum CaptchaTypeEnum {
/**
* 算数
*/
ARITHMETIC,
/**
* 中文
*/
CHINESE,
/**
* 中文闪图
*/
CHINESE_GIF,
/**
* 闪图
*/
GIF, SPEC
}

View File

@@ -0,0 +1,30 @@
package com.pig4cloud.pigx.flow.task.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.ArrayList;
/**
* 表单类型枚举
*/
@Getter
@AllArgsConstructor
public enum FormTypeEnum {
INPUT("Input", "单行文本", ""), TEXTAREA("Textarea", "多行文本", ""), NUMBER("Number", "数字", null),
DATE("Date", "日期", null), DATE_TIME("DateTime", "日期时间", null),
SEQUENCE("Sequence", "发号器", null), LAYOUT("Layout", "明细", null), TIME("Time", "时间", null),
MONEY("Money", "金额", null), SINGLE_SELECT("SingleSelect", "单选", new ArrayList<>()),
SELECT_DEPT("SelectDept", "部门", new ArrayList<>()), SELECT_USER("SelectUser", "用户", new ArrayList<>()),
;
private String type;
private String name;
private Object defaultValue;
}

View File

@@ -0,0 +1,74 @@
package com.pig4cloud.pigx.flow.task.constant;
import cn.hutool.core.util.ObjectUtil;
import java.util.EnumSet;
import java.util.Objects;
/**
* 枚举通用接口
*
* @author haoxr
* @since 2022/3/27 12:06
*/
public interface IBaseEnum<T> {
T getValue();
String getLabel();
/**
* 根据值获取枚举
* @param value
* @param clazz
* @param <E> 枚举
* @return
*/
static <E extends Enum<E> & IBaseEnum> E getEnumByValue(Object value, Class<E> clazz) {
Objects.requireNonNull(value);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
E matchEnum = allEnums.stream().filter(e -> ObjectUtil.equal(e.getValue(), value)).findFirst().orElse(null);
return matchEnum;
}
/**
* 根据文本标签获取值
* @param value
* @param clazz
* @param <E>
* @return
*/
static <E extends Enum<E> & IBaseEnum> String getLabelByValue(Object value, Class<E> clazz) {
Objects.requireNonNull(value);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
E matchEnum = allEnums.stream().filter(e -> ObjectUtil.equal(e.getValue(), value)).findFirst().orElse(null);
String label = null;
if (matchEnum != null) {
label = matchEnum.getLabel();
}
return label;
}
/**
* 根据文本标签获取值
* @param label
* @param clazz
* @param <E>
* @return
*/
static <E extends Enum<E> & IBaseEnum> Object getValueByLabel(String label, Class<E> clazz) {
Objects.requireNonNull(label);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
String finalLabel = label;
E matchEnum = allEnums.stream().filter(e -> ObjectUtil.equal(e.getLabel(), finalLabel)).findFirst()
.orElse(null);
Object value = null;
if (matchEnum != null) {
value = matchEnum.getValue();
}
return value;
}
}

View File

@@ -0,0 +1,30 @@
package com.pig4cloud.pigx.flow.task.constant;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
/**
* 菜单类型枚举
*
* @author haoxr
* @since 2022/4/23 9:36
*/
public enum MenuTypeEnum implements IBaseEnum<Integer> {
NULL(0, null), MENU(1, "菜单"), CATALOG(2, "目录"), EXTLINK(3, "外链"), BUTTON(4, "按钮");
@Getter
@EnumValue // Mybatis-Plus 提供注解表示插入数据库时插入该值
private Integer value;
@Getter
// @JsonValue // 表示对枚举序列化时返回此字段
private String label;
MenuTypeEnum(Integer value, String label) {
this.value = value;
this.label = label;
}
}

View File

@@ -0,0 +1,16 @@
package com.pig4cloud.pigx.flow.task.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum NodeStatusEnum {
WKS(0, "未开始"), JXZ(1, "进行中"), YJS(2, "已结束"),;
private int code;
private String name;
}

View File

@@ -0,0 +1,37 @@
package com.pig4cloud.pigx.flow.task.constant;
import lombok.Getter;
import java.util.Arrays;
/**
* 节点枚举
*/
@Getter
public enum NodeTypeEnum {
ROOT("根节点", 0, false), END("结束节点", -1, false),
APPROVAL("审批节点", 1, false), CC("抄送节点", 2, false), EXCLUSIVE_GATEWAY("条件分支", 4, true),
PARALLEL_GATEWAY("并行分支", 5, true), EMPTY("", 3, false),
;
public static NodeTypeEnum getByValue(int value) {
return Arrays.stream(NodeTypeEnum.values()).filter(w -> w.getValue() == value).findAny().orElse(null);
}
NodeTypeEnum(String name, Integer value, Boolean branch) {
this.name = name;
this.value = value;
this.branch = branch;
}
private String name;
private Integer value;
private Boolean branch;
}

View File

@@ -0,0 +1,19 @@
package com.pig4cloud.pigx.flow.task.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 用户节点类型枚举
*/
@Getter
@AllArgsConstructor
public enum NodeUserTypeEnum {
USER("user", "用户"), DEPT("dept", "部门"), ROLE("role", "角色"),;
private String key;
private String name;
}

View File

@@ -0,0 +1,95 @@
package com.pig4cloud.pigx.flow.task.constant;
public class ProcessInstanceConstant {
/**
* 空执行人
*/
public static final Long DEFAULT_EMPTY_ASSIGN = -99999999L;
/**
* 用户任务没有执行人的情况下如何处理 自动通过
*/
public static final String USER_TASK_NOBODY_HANDLER_TO_PASS = "TO_PASS";
/**
* 转交给管理员
*/
public static final String USER_TASK_NOBODY_HANDLER_TO_ADMIN = "TO_ADMIN";
/**
* 指定人员
*/
public static final String USER_TASK_NOBODY_HANDLER_TO_USER = "TO_USER";
/**
* 结束流程
*/
public static final String USER_TASK_NOBODY_HANDLER_TO_END = "TO_END";
public static final String USER_TASK_NOBODY_HANDLER_TO_REFUSE = "TO_REFUSE";
/**
* 拒绝之后 结束流程
*/
public static final String USER_TASK_REFUSE_TYPE_TO_END = "TO_END";
/**
* 拒绝之后 到某个节点
*/
public static final String USER_TASK_REFUSE_TYPE_TO_NODE = "TO_NODE";
/**
* 会签
*/
public static final int MULTIPLE_MODE_AL_SAME = 1;
/**
* 或签
*/
public static final int MULTIPLE_MODE_ONE = 2;
/**
* 顺签
*/
public static final int MULTIPLE_MODE_ALL_SORT = 3;
public static class AssignedTypeClass {
// 指定用户
public static final int USER = 1;
// 指定主管
public static final int LEADER = 2;
// 发起人自己
public static final int SELF = 5;
// 表单人员
public static final int FORM_USER = 8;
// 发起人自选
public static final int SELF_SELECT = 4;
// 角色
public static final int ROLE = 3;
}
/**
* 表单权限
*/
public static class FormPermClass {
// 隐藏
public static final String HIDE = "H";
// 只读
public static final String READ = "R";
// 编辑
public static final String EDIT = "E";
}
}

View File

@@ -0,0 +1,26 @@
package com.pig4cloud.pigx.flow.task.constant;
import lombok.Getter;
/**
* 状态枚举
*
* @author haoxr
* @since 2022/10/14
*/
public enum StatusEnum implements IBaseEnum<Integer> {
ENABLE(1, "启用"), DISABLE(0, "禁用");
@Getter
private Integer value;
@Getter
private String label;
StatusEnum(Integer value, String label) {
this.value = value;
this.label = label;
}
}

View File

@@ -0,0 +1,26 @@
package com.pig4cloud.pigx.flow.task.constant;
/**
* 系统常量
*
* @author haoxr
* @since 2022/10/22
*/
public interface SystemConstants {
/**
* 根节点ID
*/
Long ROOT_NODE_ID = 0L;
/**
* 系统默认密码
*/
String DEFAULT_PASSWORD = "123456";
/**
* 超级管理员角色编码
*/
String ROOT_ROLE_CODE = "ROOT";
}

View File

@@ -0,0 +1,6 @@
/*
* @author pigx archetype
* <p>
* 常量和枚举定义
*/
package com.pig4cloud.pigx.flow.task.constant;

View File

@@ -0,0 +1,23 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.util.List;
/**
* 检查子部门DTO
*/
@Data
public class CheckChildDto {
/**
* 子部门ID
*/
private Long childId;
/**
* 父部门ID列表
*/
private List<Long> deptIdList;
}

View File

@@ -0,0 +1,18 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.util.List;
/**
* 检查是否是给定的父级
*
*/
@Data
public class CheckParentDto {
private Long parentId;
private List<Long> deptIdList;
}

View File

@@ -0,0 +1,31 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
/**
* 条件类
*/
@Data
public class Condition {
/**
* 条件键
*/
private String key;
/**
* 表达式
*/
private String expression;
/**
* 值
*/
private Object value;
/**
* 键类型
*/
private String keyType;
}

View File

@@ -0,0 +1,23 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.util.List;
/**
* 分组条件类
*/
@Data
public class GroupCondition {
/**
* 是否并行
*/
private Boolean mode;
/**
* 条件列表
*/
private List<Condition> conditionList;
}

View File

@@ -0,0 +1,37 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 首页统计数据
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class IndexPageStatistics {
/**
* 待办数量
*/
private Long pendingNum;
/**
* 发起数量
*/
private Long startedNum;
/**
* 抄送任务
*/
private Long copyNum;
/**
* 完成数量
*/
private Long completedNum;
}

View File

@@ -0,0 +1,14 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.util.List;
@Data
public class Nobody {
private String handler;
private List<NodeUser> assignedUser;
}

View File

@@ -0,0 +1,58 @@
package com.pig4cloud.pigx.flow.task.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class Node {
private String id;
private String parentId;
private String headId;
private String tailId;
private String placeHolder;
private Integer type;
@JsonProperty(value = "nodeName")
private String name;
private Boolean error;
@JsonProperty("childNode")
private Node children;
private Integer assignedType;
private Boolean multiple;
private Integer multipleMode;
private Integer deptLeaderLevel;
private String formUserId;
private String formUserName;
private List<NodeUser> nodeUserList;
private List<Node> conditionNodes;
private Map<String, String> formPerms;
private Nobody nobody;
private Boolean groupMode;
private List<GroupCondition> conditionList;
private Refuse refuse;
}

View File

@@ -0,0 +1,17 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.util.Map;
/**
* 节点处理http请求结果对象
*/
@Data
public class NodeHttpResultVO {
private Boolean ok;
private Map<String, Object> data;
}

View File

@@ -0,0 +1,39 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 节点用户对象
*/
@NoArgsConstructor
@Data
@Builder
@AllArgsConstructor
public class NodeUser {
/**
* 用户od
*/
private Long id;
/**
* 用户名称
*/
private String name;
/**
* 类型
*/
private String type;
/**
* 选择
*/
private Boolean selected;
private String avatar;
}

View File

@@ -0,0 +1,50 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ProcessCopyDto {
/**
* 当前节点时间
*/
private LocalDateTime nodeTime;
/**
* 发起人
*/
private Long startUserId;
/**
* 流程id
*/
private String flowId;
/**
* 实例id
*/
private String processInstanceId;
/**
* 节点id
*/
private String nodeId;
/**
* 节点 名称
*/
private String nodeName;
/**
* 表单数据
*/
private String formData;
/**
* 抄送人id
*/
private Long userId;
}

View File

@@ -0,0 +1,34 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* 流程实例参数对象
*/
@Data
public class ProcessInstanceParamDto {
/**
* 流程id
*/
private String flowId;
/**
* 参数集合
*/
private Map<String, Object> paramMap = new HashMap<>();
/**
* 发起人
*/
private String startUserId;
/**
* 实例id
*/
private String processInstanceId;
}

View File

@@ -0,0 +1,40 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 流程记录
* </p>
*
* @author Vincent
* @since 2023-05-07
*/
@Getter
@Setter
public class ProcessInstanceRecordParamDto {
/**
* 用户id
*/
private Long userId;
/**
* 流程id
*/
private String flowId;
/**
* 流程实例id
*/
private String processInstanceId;
private String parentProcessInstanceId;
/**
* 表单数据
*/
private String formData;
}

View File

@@ -0,0 +1,14 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
@Data
public class ProcessNodeDataDto {
private String flowId;
private String nodeId;
private String data;
}

View File

@@ -0,0 +1,66 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
/**
* 流程节点记录-执行人
*/
@Data
public class ProcessNodeRecordAssignUserParamDto {
/**
* 流程id (process id)
*/
private String flowId;
/**
* 流程实例id (process instance id)
*/
private String processInstanceId;
/**
* 表单数据 (form data)
*/
private String data;
/**
* 本地数据 (local data)
*/
private String localData;
/**
* 节点id (node id)
*/
private String nodeId;
/**
* 用户id (user id)
*/
private Long userId;
/**
* 执行id (execution id)
*/
private String executionId;
/**
* 任务id (task id)
*/
private String taskId;
/**
* 审批描述 (approval description)
*/
private String approveDesc;
/**
* 节点名称 (node name)
*/
private String nodeName;
/**
* 任务类型 (task type)
*/
private String taskType;
}

View File

@@ -0,0 +1,61 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
/**
* 流程节点记录
*/
@Data
public class ProcessNodeRecordParamDto {
/**
* 流程id (process id)
*/
private String flowId;
/**
* 流程实例id (process instance id)
*/
private String processInstanceId;
/**
* 表单数据 (form data)
*/
private String data;
/**
* 节点id (node id)
*/
private String nodeId;
/**
* 节点类型 (node type)
*/
private String nodeType;
/**
* 节点名字 (node name)
*/
private String nodeName;
/**
* 执行id (execution id)
*/
private String executionId;
/**
* 任务id (task id)
*/
private String taskId;
/**
* 审批描述 (approval description)
*/
private String approveDesc;
/**
* 任务类型 (task type)
*/
private String taskType;
}

View File

@@ -0,0 +1,12 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
@Data
public class Refuse {
private String handler;
private String nodeId;
}

View File

@@ -0,0 +1,17 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
/**
* @author Huijun Zhao
* @description
* @date 2023-07-28 10:36
*/
@Data
public class SelectValue {
private String key;
private String value;
}

View File

@@ -0,0 +1,97 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;
/**
* 任务对象
*/
@Data
public class TaskDto {
/**
* 流程id
*/
private String flowId;
/**
* 参数集合
*/
private Map<String, Object> paramMap;
/**
* 实例id
*/
private String processInstanceId;
/**
* 执行id
*/
private String executionId;
/**
* 耗时
*/
private Long durationInMillis;
/**
* 任务id
*/
private String taskId;
/**
* 执行人
*/
private String assign;
/**
* 任务名称
*/
private String taskName;
/**
* 节点id
*/
private String nodeId;
/**
* 任务创建时间
*/
private Date taskCreateTime;
private Date taskEndTime;
/**
* 流程组名字
*/
private String groupName;
/**
* 发起人id
*/
private Long rootUserId;
/**
* 发起人名字
*/
private String rootUserName;
/**
* 发起人头像
*/
private String rootUserAvatarUrl;
/**
* 发起时间
*/
private LocalDateTime startTime;
/**
* 流程名称
*/
private String processName;
}

View File

@@ -0,0 +1,59 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* 任务完成参数对象
*
*/
@Data
public class TaskParamDto {
private String processInstanceId;
private List<String> processInstanceIdList;
/**
* 节点id
*/
private String nodeId;
/**
* 添加子流程发起人
*/
private Boolean appendChildProcessRootId;
/**
* 任务id
*/
private String taskId;
/**
* 用户id
*/
private String userId;
/**
* 模板用户id
*/
private String targetUserId;
/**
* 参数
*/
private Map<String, Object> paramMap;
/**
* 任务本地变量
*/
private Map<String, Object> taskLocalParamMap;
/**
* 模板节点
*/
private String targetNodeId;
}

View File

@@ -0,0 +1,43 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户任务查询参数
*/
@Data
public class TaskQueryParamDto {
/**
* 任务执行人
*/
private String assign;
/**
* 页码
*/
private Integer pageNum;
/**
* 每页的数量
*/
private Integer pageSize;
/**
* 流程名称
*/
private String processName;
/**
* 任务时间
*/
private LocalDateTime[] taskTime;
/**
* 状态
*/
private Integer status;
}

View File

@@ -0,0 +1,45 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.util.Map;
/**
* 任务结果对象
*
*/
@Data
public class TaskResultDto {
private Boolean currentTask;
/**
* 流程id
*/
private String flowId;
private Node taskNode;
private String nodeId;
/**
* 实例id
*/
private String processInstanceId;
/**
* 委派状态
*/
private String delegationState;
/**
* 是否允许继续委派
*/
private Boolean delegate;
/**
* 所有变量
*/
private Map<String, Object> variableAll;
}

View File

@@ -0,0 +1,16 @@
package com.pig4cloud.pigx.flow.task.dto;
import lombok.Data;
import java.util.List;
@Data
public class VariableQueryParamDto {
private String taskId;
private List<String> keyList;
private String executionId;
}

View File

@@ -0,0 +1,6 @@
/*
* @author pigx archetype
* <p>
* DTO 存放目录
*/
package com.pig4cloud.pigx.flow.task.dto;

View File

@@ -0,0 +1,136 @@
package com.pig4cloud.pigx.flow.task.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* <p>
*
* </p>
*
* @author Vincent
* @since 2023-07-06
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("process")
public class Process {
/**
* 表单ID
*/
@TableField("flow_id")
private String flowId;
/**
* 表单名称
*/
@TableField("name")
private String name;
/**
* 图标配置
*/
@TableField("logo")
private String logo;
/**
* 设置项
*/
@TableField("settings")
private String settings;
/**
* 分组ID
*/
@TableField("group_id")
private Long groupId;
/**
* 表单设置内容
*/
@TableField("form_items")
private String formItems;
/**
* 流程设置内容
*/
@TableField("process")
private String process;
/**
* 备注
*/
@TableField("remark")
private String remark;
@TableField("sort")
private Integer sort;
/**
* 0 正常 1=隐藏
*/
@TableField("is_hidden")
private Boolean hidden;
/**
* 0 正常 1=停用
*/
@TableField("is_stop")
private Boolean stop;
/**
* 流程管理员
*/
@TableField("admin_id")
private Long adminId;
/**
* 唯一性id
*/
@TableField("unique_id")
private String uniqueId;
/**
* 管理员
*/
@TableField("admin_list")
private String adminList;
/**
* 范围描述显示
*/
@TableField("range_show")
private String rangeShow;
/**
* 用户id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 逻辑删除字段
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private String delFlag;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,121 @@
package com.pig4cloud.pigx.flow.task.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* <p>
* 流程抄送数据
* </p>
*
* @author Vincent
* @since 2023-07-06
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("process_copy")
public class ProcessCopy {
/**
* 用户id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 流程发起时间
*/
@TableField("start_time")
private LocalDateTime startTime;
/**
* 当前节点时间
*/
@TableField("node_time")
private LocalDateTime nodeTime;
/**
* 发起人
*/
@TableField("start_user_id")
private Long startUserId;
/**
* 流程id
*/
@TableField("flow_id")
private String flowId;
/**
* 实例id
*/
@TableField("process_instance_id")
private String processInstanceId;
/**
* 节点id
*/
@TableField("node_id")
private String nodeId;
/**
* 分组id
*/
@TableField("group_id")
private Long groupId;
/**
* 分组名称
*/
@TableField("group_name")
private String groupName;
/**
* 流程名称
*/
@TableField("process_name")
private String processName;
/**
* 节点 名称
*/
@TableField("node_name")
private String nodeName;
/**
* 表单数据
*/
@TableField("form_data")
private String formData;
/**
* 抄送人id
*/
@TableField("user_id")
private Long userId;
/**
* 逻辑删除字段
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private String delFlag;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,61 @@
package com.pig4cloud.pigx.flow.task.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* <p>
*
* </p>
*
* @author Vincent
* @since 2023-07-06
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("process_group")
public class ProcessGroup {
/**
* 分组名
*/
@TableField("group_name")
private String groupName;
/**
* 排序
*/
@TableField("sort")
private Integer sort;
/**
* 用户id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 逻辑删除字段
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private String delFlag;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,116 @@
package com.pig4cloud.pigx.flow.task.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.Date;
/**
* <p>
* 流程记录
* </p>
*
* @author Vincent
* @since 2023-07-06
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("process_instance_record")
public class ProcessInstanceRecord {
/**
* 流程名字
*/
@TableField("name")
private String name;
/**
* 头像
*/
@TableField("logo")
private String logo;
/**
* 用户id
*/
@TableField("user_id")
private Long userId;
/**
* 流程id
*/
@TableField("flow_id")
private String flowId;
/**
* 流程实例id
*/
@TableField("process_instance_id")
private String processInstanceId;
/**
* 表单数据
*/
@TableField("form_data")
private String formData;
/**
* 组id
*/
@TableField("group_id")
private Long groupId;
/**
* 组名称
*/
@TableField("group_name")
private String groupName;
/**
* 状态
*/
@TableField("status")
private Integer status;
/**
* 结束时间
*/
@TableField("end_time")
private Date endTime;
/**
* 上级流程实例id
*/
@TableField("parent_process_instance_id")
private String parentProcessInstanceId;
/**
* 用户id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 逻辑删除字段
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private String delFlag;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,64 @@
package com.pig4cloud.pigx.flow.task.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* <p>
* 流程节点数据
* </p>
*
* @author Vincent
* @since 2023-07-06
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("process_node_data")
public class ProcessNodeData {
/**
* 流程id
*/
@TableField("flow_id")
private String flowId;
/**
* 表单数据
*/
@TableField("data")
private String data;
@TableField("node_id")
private String nodeId;
/**
* 用户id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 逻辑删除字段
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private String delFlag;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,107 @@
package com.pig4cloud.pigx.flow.task.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.Date;
/**
* <p>
* 流程节点记录
* </p>
*
* @author Vincent
* @since 2023-07-06
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("process_node_record")
public class ProcessNodeRecord {
/**
* 流程id
*/
@TableField("flow_id")
private String flowId;
/**
* 流程实例id
*/
@TableField("process_instance_id")
private String processInstanceId;
/**
* 表单数据
*/
@TableField("data")
private String data;
@TableField("node_id")
private String nodeId;
/**
* 节点类型
*/
@TableField("node_type")
private String nodeType;
/**
* 节点名字
*/
@TableField("node_name")
private String nodeName;
/**
* 节点状态
*/
@TableField("status")
private Integer status;
/**
* 开始时间
*/
@TableField("start_time")
private Date startTime;
/**
* 结束时间
*/
@TableField("end_time")
private Date endTime;
/**
* 执行id
*/
@TableField("execution_id")
private String executionId;
/**
* 用户id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 逻辑删除字段
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private String delFlag;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,130 @@
package com.pig4cloud.pigx.flow.task.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* <p>
* 流程节点记录-执行人
* </p>
*
* @author Vincent
* @since 2023-07-06
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("process_node_record_assign_user")
public class ProcessNodeRecordAssignUser {
/**
* 流程id
*/
@TableField("flow_id")
private String flowId;
/**
* 流程实例id
*/
@TableField("process_instance_id")
private String processInstanceId;
/**
* 表单数据
*/
@TableField("data")
private String data;
@TableField("node_id")
private String nodeId;
/**
* 用户id
*/
@TableField("user_id")
private String userId;
/**
* 节点状态
*/
@TableField("status")
private Integer status;
/**
* 开始时间
*/
@TableField("start_time")
private LocalDateTime startTime;
/**
* 结束时间
*/
@TableField("end_time")
private LocalDateTime endTime;
/**
* 执行id
*/
@TableField("execution_id")
private String executionId;
/**
* 任务id
*/
@TableField("task_id")
private String taskId;
/**
* 审批意见
*/
@TableField("approve_desc")
private String approveDesc;
/**
* 节点名称
*/
@TableField("node_name")
private String nodeName;
/**
* 任务类型
*/
@TableField("task_type")
private String taskType;
/**
* 表单本地数据
*/
@TableField("local_data")
private String localData;
/**
* 用户id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 逻辑删除字段
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private String delFlag;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,67 @@
package com.pig4cloud.pigx.flow.task.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* <p>
* 流程发起人
* </p>
*
* @author Vincent
* @since 2023-07-06
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("process_starter")
public class ProcessStarter {
/**
* 用户id或者部门id
*/
@TableField("type_id")
private Long typeId;
/**
* 类型 user dept
*/
@TableField("type")
private String type;
/**
* 流程id
*/
@TableField("process_id")
private Long processId;
/**
* 用户id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 逻辑删除字段
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private String delFlag;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,6 @@
/*
* @author pigx archetype
* <p>
* 实体 存放目录
*/
package com.pig4cloud.pigx.flow.task.entity;

View File

@@ -0,0 +1,82 @@
package com.pig4cloud.pigx.flow.task.utils;
import cn.hutool.core.util.StrUtil;
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 java.util.ArrayList;
import java.util.List;
public class NodeUtil {
public static String getFlowId(String processDefinitionId) {
return StrUtil.subBefore(processDefinitionId, ":", false);
}
public static boolean isNode(Node childNode) {
return childNode != null && StrUtil.isNotBlank(childNode.getId());
}
/**
* 添加结束节点
* @param node
*/
public static void addEndNode(Node node) {
Node children = node.getChildren();
if (isNode(children)) {
addEndNode(children);
}
else {
Node end = new Node();
end.setId("end");
end.setType(NodeTypeEnum.END.getValue());
end.setName("结束节点");
end.setParentId(node.getId());
node.setChildren(end);
}
}
/**
* 需要发起人选择用户的节点
* @param node
*/
public static List<String> selectUserNodeId(Node node) {
List<String> list = new ArrayList<>();
if (!isNode(node)) {
return list;
}
Integer type = node.getType();
if (type == NodeTypeEnum.APPROVAL.getValue().intValue()) {
Integer assignedType = node.getAssignedType();
boolean selfSelect = assignedType == ProcessInstanceConstant.AssignedTypeClass.SELF_SELECT;
if (selfSelect) {
list.add(node.getId());
}
}
if (type == NodeTypeEnum.EXCLUSIVE_GATEWAY.getValue().intValue()
|| type == NodeTypeEnum.PARALLEL_GATEWAY.getValue().intValue()) {
// 条件分支
List<Node> branchs = node.getConditionNodes();
for (Node branch : branchs) {
Node children = branch.getChildren();
List<String> strings = selectUserNodeId(children);
list.addAll(strings);
}
}
List<String> next = selectUserNodeId(node.getChildren());
list.addAll(next);
return list;
}
}

View File

@@ -0,0 +1,20 @@
package com.pig4cloud.pigx.flow.task.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author : willian fu
* @date : 2022/7/4
*/
@Data
@AllArgsConstructor
public class ErrorRspVo {
private Integer code;
private String msg;
private String desp;
}

View File

@@ -0,0 +1,57 @@
package com.pig4cloud.pigx.flow.task.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
/**
* @author : willian fu
* @date : 2020/9/21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FormGroupVo {
private Long id;
/**
* 流程名字
*/
private String name;
/**
* 流程
*/
private List<FlowVo> items;
@Data
@Builder
@AllArgsConstructor
public static class FlowVo {
private String flowId;
/**
* 发起范围
*/
private String rangeShow;
private String name;
private String logo;
private Boolean stop;
private String remark;
private LocalDateTime updated;
}
}

View File

@@ -0,0 +1,59 @@
package com.pig4cloud.pigx.flow.task.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 表单
*/
@Data
@NoArgsConstructor
public class FormItemVO {
private String id;
private String perm;
private String icon;
private String name;
private String type;
private Boolean required;
private String typeName;
private String placeholder;
private Props props;
@Data
@NoArgsConstructor
public static class Props {
private Object value;
private Object dictValue;
private Object options;
private Boolean self;
private Boolean multi;
private Object oriForm;
private Object maxLength;
private Object minLength;
private Object regex;
private Object regexDesc;
private String prefix;
}
}

View File

@@ -0,0 +1,18 @@
package com.pig4cloud.pigx.flow.task.vo;
import lombok.Data;
import java.util.Map;
@Data
public class NodeFormatParamVo {
private String flowId;
private String processInstanceId;
private String taskId;
private Map<String, Object> paramMap;
}

View File

@@ -0,0 +1,63 @@
package com.pig4cloud.pigx.flow.task.vo;
import lombok.Data;
import java.util.List;
/**
* 流程节点显示对象
*/
@Data
public class NodeVo {
/**
* nodeId
*/
private String id;
/**
* 用户列表
*/
private List<UserVo> userVoList;
/**
* 显示
*/
private String placeholder;
/**
* 状态 1进行中2已完成
*/
private Integer status;
/**
* 节点名称
*/
private String name;
/**
* 节点类型
*/
private Object type;
/**
* 发起人选择用户
*/
private Boolean selectUser;
/**
* 是否多选
*/
private Boolean multiple;
/**
* 子级列表
*/
private List<NodeVo> children;
/**
* 分支列表
*/
private List<NodeVo> branch;
}

View File

@@ -0,0 +1,42 @@
package com.pig4cloud.pigx.flow.task.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : willian fu
* @version : 1.0
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrgTreeVo {
/**
* 用户od
*/
private Long id;
/**
* 用户名称
*/
private String name;
/**
* 类型
*/
private String type;
/**
* 选择
*/
private Boolean selected;
private String avatar;
private Integer status = 1;
}

View File

@@ -0,0 +1,11 @@
package com.pig4cloud.pigx.flow.task.vo;
import com.pig4cloud.pigx.flow.task.entity.ProcessCopy;
import lombok.Data;
@Data
public class ProcessCopyVo extends ProcessCopy {
private String startUserName;
}

View File

@@ -0,0 +1,19 @@
package com.pig4cloud.pigx.flow.task.vo;
import com.pig4cloud.pigx.flow.task.entity.Process;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class ProcessVO extends Process {
/**
* 需要发起人选择的节点id
*/
private List<String> selectUserNodeId;
private Map<String, Object> variableMap;
}

View File

@@ -0,0 +1,42 @@
package com.pig4cloud.pigx.flow.task.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserVo {
/**
* 用户od
*/
private Long id;
/**
* 用户名称
*/
private String name;
private LocalDateTime showTime;
private String avatar;
/**
* 意见
*/
private String approveDesc;
private String operType;
/**
* 状态 1进行中2已完成
*/
private Integer status = 0;
}

Some files were not shown because too many files have changed in this diff Show More