Browse Source

初始化工作流dto

master
Yen 3 years ago
parent
commit
49df4a2f93
  1. 22
      pom.xml
  2. 16
      src/main/java/com/qs/serve/Application.java
  3. 78
      src/main/java/com/qs/serve/modules/bpm/common/consts/ProcessConstants.java
  4. 43
      src/main/java/com/qs/serve/modules/bpm/common/enums/FlowComment.java
  5. 219
      src/main/java/com/qs/serve/modules/bpm/common/util/FindNextNodeUtil.java
  6. 590
      src/main/java/com/qs/serve/modules/bpm/common/util/FlowableUtils.java
  7. 43
      src/main/java/com/qs/serve/modules/bpm/conf/FlowServiceFactory.java
  8. 19
      src/main/java/com/qs/serve/modules/bpm/conf/FlowableConfig.java
  9. 18
      src/main/java/com/qs/serve/modules/bpm/conf/listener/UserTaskListener.java
  10. 26
      src/main/java/com/qs/serve/modules/bpm/domian/dto/BpmnDto.java
  11. 24
      src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowCommentDto.java
  12. 12
      src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowFromFieldDTO.java
  13. 24
      src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowNextDto.java
  14. 143
      src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowTaskDto.java
  15. 24
      src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowViewerDto.java
  16. 60
      src/main/java/com/qs/serve/modules/bpm/domian/vo/FlowTaskVo.java
  17. 7
      src/main/resources/application-dev.yml

22
pom.xml

@ -31,6 +31,28 @@
<dependencies>
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.activiti</groupId>-->
<!-- <artifactId>activiti-spring-boot-starter</artifactId>-->
<!-- <version>7.1.0.M4</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.activiti.dependencies</groupId>-->
<!-- <artifactId>activiti-dependencies</artifactId>-->
<!-- <version>7.1.0.M4</version>-->
<!-- <type>pom</type>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>

16
src/main/java/com/qs/serve/Application.java

@ -1,11 +1,16 @@
package com.qs.serve;
import com.qs.serve.common.config.DevEnvironmentConfig;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@ -46,4 +51,15 @@ public class Application extends SpringBootServletInitializer {
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
}
@Bean
public CommandLineRunner init(final RepositoryService repositoryService,
final RuntimeService runtimeService,
final TaskService taskService) {
return strings -> {
System.out.println("Number of process definitions : " + repositoryService.createProcessDefinitionQuery().count());
System.out.println("Number of tasks : " + taskService.createTaskQuery().count());
//runtimeService.startProcessInstanceByKey("oneTaskProcess");
//System.out.println("Number of tasks after process start: " + taskService.createTaskQuery().count());
};
}
}

78
src/main/java/com/qs/serve/modules/bpm/common/consts/ProcessConstants.java

@ -0,0 +1,78 @@
package com.qs.serve.modules.bpm.common.consts;
/**
* 流程常量信息
* @author YenHex
* @since 2022/8/18
*/
public class ProcessConstants {
/**
* 动态数据
*/
public static final String DATA_TYPE = "dynamic";
/**
* 固定任务接收
*/
public static final String FIXED = "fixed";
/**
* 单个审批人
*/
public static final String USER_TYPE_ASSIGNEE = "assignee";
/**
* 候选人
*/
public static final String USER_TYPE_USERS = "candidateUsers";
/**
* 审批组
*/
public static final String USER_TYPE_ROUPS = "candidateGroups";
/**
* 单个审批人
*/
public static final String PROCESS_APPROVAL = "approval";
/**
* 会签人员
*/
public static final String PROCESS_MULTI_INSTANCE_USER = "userList";
/**
* nameapace
*/
public static final String NAMASPASE = "http://flowable.org/bpmn";
/**
* 会签节点
*/
public static final String PROCESS_MULTI_INSTANCE = "multiInstance";
/**
* 自定义属性 dataType
*/
public static final String PROCESS_CUSTOM_DATA_TYPE = "dataType";
/**
* 自定义属性 userType
*/
public static final String PROCESS_CUSTOM_USER_TYPE = "userType";
/**
* 初始化人员
*/
public static final String PROCESS_INITIATOR = "INITIATOR";
/**
* 流程跳过
*/
public static final String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
}

43
src/main/java/com/qs/serve/modules/bpm/common/enums/FlowComment.java

@ -0,0 +1,43 @@
package com.qs.serve.modules.bpm.common.enums;
/**
* 流程意见类型
* @author YenHex
* @since 2022/8/18
*/
public enum FlowComment {
/**
* 说明
*/
NORMAL("1", "正常意见"),
REBACK("2", "退回意见"),
REJECT("3", "驳回意见"),
DELEGATE("4", "委派意见"),
ASSIGN("5", "转办意见"),
STOP("6", "终止流程");
/**
* 类型
*/
private final String type;
/**
* 说明
*/
private final String remark;
FlowComment(String type, String remark) {
this.type = type;
this.remark = remark;
}
public String getType() {
return type;
}
public String getRemark() {
return remark;
}
}

219
src/main/java/com/qs/serve/modules/bpm/common/util/FindNextNodeUtil.java

@ -0,0 +1,219 @@
package com.qs.serve.modules.bpm.common.util;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.ProcessDefinition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* @author YenHex
* @since 2022/8/18
*/
public class FindNextNodeUtil {
/**
* 获取下一步骤的用户任务
* @param repositoryService
* @param map
* @return
*/
public static List<UserTask> getNextUserTasks(RepositoryService repositoryService, org.flowable.task.api.Task task, Map<String, Object> map) {
List<UserTask> data = new ArrayList<>();
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
Process mainProcess = bpmnModel.getMainProcess();
Collection<FlowElement> flowElements = mainProcess.getFlowElements();
String key = task.getTaskDefinitionKey();
FlowElement flowElement = bpmnModel.getFlowElement(key);
next(flowElements, flowElement, map, data);
return data;
}
public static void next(Collection<FlowElement> flowElements, FlowElement flowElement, Map<String, Object> map, List<UserTask> nextUser) {
//如果是结束节点
if (flowElement instanceof EndEvent) {
//如果是子任务的结束节点
if (getSubProcess(flowElements, flowElement) != null) {
flowElement = getSubProcess(flowElements, flowElement);
}
}
//获取Task的出线信息--可以拥有多个
List<SequenceFlow> outGoingFlows = null;
if (flowElement instanceof Task) {
outGoingFlows = ((Task) flowElement).getOutgoingFlows();
} else if (flowElement instanceof Gateway) {
outGoingFlows = ((Gateway) flowElement).getOutgoingFlows();
} else if (flowElement instanceof StartEvent) {
outGoingFlows = ((StartEvent) flowElement).getOutgoingFlows();
} else if (flowElement instanceof SubProcess) {
outGoingFlows = ((SubProcess) flowElement).getOutgoingFlows();
} else if (flowElement instanceof CallActivity) {
outGoingFlows = ((CallActivity) flowElement).getOutgoingFlows();
}
if (outGoingFlows != null && outGoingFlows.size() > 0) {
//遍历所有的出线--找到可以正确执行的那一条
for (SequenceFlow sequenceFlow : outGoingFlows) {
//1.有表达式,且为true
//2.无表达式
String expression = sequenceFlow.getConditionExpression();
if (expression == null ||
expressionResult(map, expression.substring(expression.lastIndexOf("{") + 1, expression.lastIndexOf("}")))) {
//出线的下一节点
String nextFlowElementID = sequenceFlow.getTargetRef();
if (checkSubProcess(nextFlowElementID, flowElements, nextUser)) {
continue;
}
//查询下一节点的信息
FlowElement nextFlowElement = getFlowElementById(nextFlowElementID, flowElements);
//调用流程
if (nextFlowElement instanceof CallActivity) {
CallActivity ca = (CallActivity) nextFlowElement;
if (ca.getLoopCharacteristics() != null) {
UserTask userTask = new UserTask();
userTask.setId(ca.getId());
userTask.setId(ca.getId());
userTask.setLoopCharacteristics(ca.getLoopCharacteristics());
userTask.setName(ca.getName());
nextUser.add(userTask);
}
next(flowElements, nextFlowElement, map, nextUser);
}
//用户任务
if (nextFlowElement instanceof UserTask) {
nextUser.add((UserTask) nextFlowElement);
}
//排他网关
else if (nextFlowElement instanceof ExclusiveGateway) {
next(flowElements, nextFlowElement, map, nextUser);
}
//并行网关
else if (nextFlowElement instanceof ParallelGateway) {
next(flowElements, nextFlowElement, map, nextUser);
}
//接收任务
else if (nextFlowElement instanceof ReceiveTask) {
next(flowElements, nextFlowElement, map, nextUser);
}
//服务任务
else if (nextFlowElement instanceof ServiceTask) {
next(flowElements, nextFlowElement, map, nextUser);
}
//子任务的起点
else if (nextFlowElement instanceof StartEvent) {
next(flowElements, nextFlowElement, map, nextUser);
}
//结束节点
else if (nextFlowElement instanceof EndEvent) {
next(flowElements, nextFlowElement, map, nextUser);
}
}
}
}
}
/**
* 判断是否是多实例子流程并且需要设置集合类型变量
*/
public static boolean checkSubProcess(String Id, Collection<FlowElement> flowElements, List<UserTask> nextUser) {
for (FlowElement flowElement1 : flowElements) {
if (flowElement1 instanceof SubProcess && flowElement1.getId().equals(Id)) {
SubProcess sp = (SubProcess) flowElement1;
if (sp.getLoopCharacteristics() != null) {
String inputDataItem = sp.getLoopCharacteristics().getInputDataItem();
UserTask userTask = new UserTask();
userTask.setId(sp.getId());
userTask.setLoopCharacteristics(sp.getLoopCharacteristics());
userTask.setName(sp.getName());
nextUser.add(userTask);
return true;
}
}
}
return false;
}
/**
* 查询一个节点的是否子任务中的节点如果是返回子任务
* @param flowElements 全流程的节点集合
* @param flowElement 当前节点
* @return
*/
public static FlowElement getSubProcess(Collection<FlowElement> flowElements, FlowElement flowElement) {
for (FlowElement flowElement1 : flowElements) {
if (flowElement1 instanceof SubProcess) {
for (FlowElement flowElement2 : ((SubProcess) flowElement1).getFlowElements()) {
if (flowElement.equals(flowElement2)) {
return flowElement1;
}
}
}
}
return null;
}
/**
* 根据ID查询流程节点对象, 如果是子任务则返回子任务的开始节点
* @param Id 节点ID
* @param flowElements 流程节点集合
* @return
*/
public static FlowElement getFlowElementById(String Id, Collection<FlowElement> flowElements) {
for (FlowElement flowElement : flowElements) {
if (flowElement.getId().equals(Id)) {
//如果是子任务,则查询出子任务的开始节点
if (flowElement instanceof SubProcess) {
return getStartFlowElement(((SubProcess) flowElement).getFlowElements());
}
return flowElement;
}
if (flowElement instanceof SubProcess) {
FlowElement flowElement1 = getFlowElementById(Id, ((SubProcess) flowElement).getFlowElements());
if (flowElement1 != null) {
return flowElement1;
}
}
}
return null;
}
/**
* 返回流程的开始节点
* @param flowElements 节点集合
* @description:
*/
public static FlowElement getStartFlowElement(Collection<FlowElement> flowElements) {
for (FlowElement flowElement : flowElements) {
if (flowElement instanceof StartEvent) {
return flowElement;
}
}
return null;
}
/**
* 校验el表达式
* @param map
* @param expression
* @return
*/
public static boolean expressionResult(Map<String, Object> map, String expression) {
Expression exp = AviatorEvaluator.compile(expression);
final Object execute = exp.execute(map);
return Boolean.parseBoolean(String.valueOf(execute));
}
}

590
src/main/java/com/qs/serve/modules/bpm/common/util/FlowableUtils.java

@ -0,0 +1,590 @@
package com.qs.serve.modules.bpm.common.util;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.*;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.task.api.history.HistoricTaskInstance;
import java.util.*;
/**
* @author YenHex
* @since 2022/8/18
*/
@Slf4j
public class FlowableUtils {
/**
* 根据节点获取入口连线
* @param source
* @return
*/
public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
List<SequenceFlow> sequenceFlows = null;
if (source instanceof FlowNode) {
sequenceFlows = ((FlowNode) 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 FlowNode) {
sequenceFlows = ((FlowNode) 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 已经经过的连线的 ID用于判断线路是否重复
* @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;
}
}
// 继续迭代
userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
}
}
return userTaskList;
}
/**
* 根据正在运行的任务节点迭代获取子级任务节点列表向后找
* @param source 起始节点(退回节点)
* @param runTaskKeyList 正在运行的任务 Key用于校验任务节点是否是正在运行的节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return
*/
public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
if (source instanceof EndEvent && source.getSubProcess() != null) {
userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, 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 && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (childUserTaskList != null && childUserTaskList.size() > 0) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
}
return userTaskList;
}
/**
* 迭代获取子流程用户任务节点
* @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;
}
}
// 继续迭代
userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), 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;
}
}
// 继续迭代
dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), passRoads, 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);
}
// 继续迭代
dirtyRoads = findChildProcessAllDirtyRoad(sequenceFlow.getTargetFlowElement(), 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 && 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);
}
// 继续迭代
inChildProcess = dirtyTargetInChildProcess(sequenceFlow.getTargetFlowElement(), 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 || 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) {
break;
}
// 这条线路存在目标节点,这条线路完成,进入下个线路
if (targetKsy.equals(sequenceFlow.getSourceFlowElement().getId())) {
continue;
}
if (sequenceFlow.getSourceFlowElement() instanceof StartEvent) {
isSequential = false;
break;
}
// 否则就继续迭代
isSequential = iteratorCheckSequentialReferTarget(sequenceFlow.getSourceFlowElement(), targetKsy, 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());
}
// 继续迭代
roads = findRoad(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, roads);
}
} else {
// 添加路线
roads.add(passRoads);
}
return roads;
}
/**
* 历史节点数据清洗清洗掉又回滚导致的脏数据
* @param allElements 全部节点信息
* @param historicTaskInstanceList 历史任务实例信息数据采用开始时间升序
* @return
*/
public static List<String> historicTaskInstanceClean(Collection<FlowElement> allElements, List<HistoricTaskInstance> historicTaskInstanceList) {
// 会签节点收集
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<HistoricTaskInstance> stack = new Stack<>();
historicTaskInstanceList.forEach(stack::push);
// 清洗后的历史任务实例
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().getTaskDefinitionKey())) {
isDirtyData[0] = true;
}
}
// 删除原因不为空,说明从这条数据开始回跳或者回退的
// MI_END:会签完成后,其他未签到节点的删除原因,不在处理范围内
if (stack.peek().getDeleteReason() != null && !"MI_END".equals(stack.peek().getDeleteReason())) {
// 可以理解为脏线路起点
String dirtyPoint = "";
if (stack.peek().getDeleteReason().contains("Change activity to ")) {
dirtyPoint = stack.peek().getDeleteReason().replace("Change activity to ", "");
}
// 会签回退删除原因有点不同
if (stack.peek().getDeleteReason().contains("Change parent activity to ")) {
dirtyPoint = stack.peek().getDeleteReason().replace("Change parent activity to ", "");
}
FlowElement dirtyTask = null;
// 获取变更节点的对应的入口处连线
// 如果是网关并行回退情况,会变成两条脏数据路线,效果一样
for (FlowElement flowElement : allElements) {
if (flowElement.getId().equals(stack.peek().getTaskDefinitionKey())) {
dirtyTask = flowElement;
}
}
// 获取脏数据线路
Set<String> dirtyDataLine = FlowableUtils.iteratorFindDirtyRoads(dirtyTask, null, null, Arrays.asList(dirtyPoint.split(",")), null);
// 自己本身也是脏线路上的点,加进去
dirtyDataLine.add(stack.peek().getTaskDefinitionKey());
log.info(stack.peek().getTaskDefinitionKey() + "点脏路线集合:" + 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().getTaskDefinitionKey());
}
// 校验脏线路是否结束
for (int i = 0; i < deleteKeyList.size(); i ++) {
// 如果发现脏数据属于会签,记录下下标与对应 Key,以备后续比对,会签脏数据范畴开始
if (multiKey == null && multiTask.contains(stack.peek().getTaskDefinitionKey())
&& deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) {
multiIndex = i;
multiKey = new StringBuilder(stack.peek().getTaskDefinitionKey());
}
// 会签脏数据处理,节点退回会签清空
// 如果在会签脏数据范畴中发现 Key改变,说明会签脏数据在上个节点就结束了,可以把会签脏数据删掉
if (multiKey != null && !multiKey.toString().equals(stack.peek().getTaskDefinitionKey())) {
deleteKeyList.set(multiIndex , deleteKeyList.get(multiIndex).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
multiKey = null;
// 结束进行下校验删除
multiOpera = true;
}
// 其他脏数据处理
// 发现该路线最后一条脏数据,说明这条脏数据线路处理完了,删除脏数据信息
// 脏数据产生的新实例中是否包含这条数据
if (multiKey == null && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) {
// 删除匹配到的部分
deleteKeyList.set(i , deleteKeyList.get(i).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
}
// 如果每组中的元素都以匹配过,说明脏数据结束
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().getTaskDefinitionKey());
}
log.info("清洗后的历史节点数据:" + lastHistoricTaskInstanceList);
return lastHistoricTaskInstanceList;
}
}

43
src/main/java/com/qs/serve/modules/bpm/conf/FlowServiceFactory.java

@ -0,0 +1,43 @@
package com.qs.serve.modules.bpm.conf;
import lombok.Getter;
import org.flowable.engine.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author YenHex
* @since 2022/8/18
*/
@Component
@Getter
public class FlowServiceFactory {
@Resource
protected RepositoryService repositoryService;
@Resource
protected RuntimeService runtimeService;
@Resource
protected IdentityService identityService;
@Resource
protected TaskService taskService;
@Resource
protected FormService formService;
@Resource
protected HistoryService historyService;
@Resource
protected ManagementService managementService;
@Qualifier("processEngine")
@Resource
protected ProcessEngine processEngine;
}

19
src/main/java/com/qs/serve/modules/bpm/conf/FlowableConfig.java

@ -0,0 +1,19 @@
package com.qs.serve.modules.bpm.conf;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
/**
* @author YenHex
* @since 2022/8/18
*/
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
@Override
public void configure(SpringProcessEngineConfiguration engineConfiguration) {
engineConfiguration.setActivityFontName("宋体");
engineConfiguration.setLabelFontName("宋体");
engineConfiguration.setAnnotationFontName("宋体");
}
}

18
src/main/java/com/qs/serve/modules/bpm/conf/listener/UserTaskListener.java

@ -0,0 +1,18 @@
package com.qs.serve.modules.bpm.conf.listener;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
/**
* @author YenHex
* @since 2022/8/18
*/
@Slf4j
public class UserTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
log.debug("UserTaskListener--id:{}",delegateTask.getId());
}
}

26
src/main/java/com/qs/serve/modules/bpm/domian/dto/BpmnDto.java

@ -0,0 +1,26 @@
package com.qs.serve.modules.bpm.domian.dto;
import lombok.Data;
/**
* @author YenHex
* @since 2022/8/15
*/
@Data
public class BpmnDto {
private static class StartEvent{
private String id;
private String name;
}
private static class EndEvent{
private String id;
private String name;
}
private static class ServiceTask{
private String id;
private String name;
private String activitiClass;
}
}

24
src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowCommentDto.java

@ -0,0 +1,24 @@
package com.qs.serve.modules.bpm.domian.dto;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @author YenHex
* @since 2022/8/18
*/
@Data
@Builder
public class FlowCommentDto implements Serializable {
/**
* 意见类别 0 正常意见 1 退回意见 2 驳回意见
*/
private String type;
/**
* 意见内容
*/
private String comment;
}

12
src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowFromFieldDTO.java

@ -0,0 +1,12 @@
package com.qs.serve.modules.bpm.domian.dto;
import lombok.Data;
/**
* @author YenHex
* @since 2022/8/18
*/
@Data
public class FlowFromFieldDTO {
private Object fields;
}

24
src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowNextDto.java

@ -0,0 +1,24 @@
package com.qs.serve.modules.bpm.domian.dto;
import com.qs.serve.modules.sys.entity.SysRole;
import com.qs.serve.modules.sys.entity.SysUser;
import java.io.Serializable;
import java.util.List;
/**
* 动态人员
* @author YenHex
* @since 2022/8/18
*/
public class FlowNextDto implements Serializable {
private String type;
private String vars;
private List<SysUser> userList;
private List<SysRole> roleList;
}

143
src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowTaskDto.java

@ -0,0 +1,143 @@
package com.qs.serve.modules.bpm.domian.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* 工作流任务返回参数
* @author YenHex
* @since 2022/8/18
*/
@Data
public class FlowTaskDto {
/**
* 任务编号
*/
private String taskId;
/**
* 任务执行编号
*/
private String executionId;
/**
* 任务名称
*/
private String taskName;
/**
* 任务Key
*/
private String taskDefKey;
/**
* 任务执行人Id
*/
private Long assigneeId;
/**
* 部门名称
*/
private String deptName;
/**
* 流程发起人部门名称
*/
private String startDeptName;
/**
* 任务执行人名称
*/
private String assigneeName;
/**
* 流程发起人Id
*/
private String startUserId;
/**
* 流程发起人名称
*/
private String startUserName;
/**
* 流程类型
*/
private String category;
/**
* 流程变量信息
*/
private Object procVars;
/**
* 局部变量信息
*/
private Object taskLocalVars;
/**
* 流程部署编号
*/
private String deployId;
/**
* 流程ID
*/
private String procDefId;
/**
* 流程key
*/
private String procDefKey;
/**
* 流程定义名称
*/
private String procDefName;
/**
* 流程定义内置使用版本
*/
private int procDefVersion;
/**
* 流程实例ID
*/
private String procInsId;
/**
* 历史流程实例ID
*/
private String hisProcInsId;
/**
* 任务耗时
*/
private String duration;
/**
* 任务意见
*/
private FlowCommentDto comment;
/**
* 候选执行人
*/
private String candidate;
/**
* 任务创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 任务完成时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date finishTime;
}

24
src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowViewerDto.java

@ -0,0 +1,24 @@
package com.qs.serve.modules.bpm.domian.dto;
import lombok.Data;
import java.io.Serializable;
/**
* @author YenHex
* @since 2022/8/18
*/
@Data
public class FlowViewerDto implements Serializable {
/**
* 流程key
*/
private String key;
/**
* 是否完成(已经审批)
*/
private boolean completed;
}

60
src/main/java/com/qs/serve/modules/bpm/domian/vo/FlowTaskVo.java

@ -0,0 +1,60 @@
package com.qs.serve.modules.bpm.domian.vo;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* 工作流任务 请求参数
* @author YenHex
* @since 2022/8/18
*/
@Data
public class FlowTaskVo {
/**
* 任务Id
*/
private String taskId;
/**
* 用户Id
*/
private String userId;
/**
* 任务意见
*/
private String comment;
/**
* 流程实例Id
*/
private String instanceId;
/**
* 节点
*/
private String targetKey;
/**
* 流程变量信息
*/
private Map<String, Object> values;
/**
* 审批人
*/
private String assignee;
/**
* 候选人
*/
private List<String> candidateUsers;
/**
* 审批组
*/
private List<String> candidateGroups;
}

7
src/main/resources/application-dev.yml

@ -8,7 +8,8 @@ spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
#url: jdbc:mysql://192.168.0.9:3306/gy_oa_qs?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
url: jdbc:mysql://192.168.0.9:3333/qs-base?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
# &nullCatalogMeansCurrent=true
url: jdbc:mysql://192.168.0.9:3333/qs-base-bpm?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
redis:
@ -16,7 +17,9 @@ spring:
host: 127.0.0.1
port: 6379
password:
#项目配置
#工作流配置
flowable:
database-schema-update: true
project:
upload:
#上传映射地址

Loading…
Cancel
Save