From 49df4a2f93fc80296d2277f98654c584b85eb017 Mon Sep 17 00:00:00 2001 From: Yen Date: Thu, 18 Aug 2022 10:03:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=B5=81dto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 22 + src/main/java/com/qs/serve/Application.java | 16 + .../bpm/common/consts/ProcessConstants.java | 78 +++ .../modules/bpm/common/enums/FlowComment.java | 43 ++ .../bpm/common/util/FindNextNodeUtil.java | 219 +++++++ .../bpm/common/util/FlowableUtils.java | 590 ++++++++++++++++++ .../modules/bpm/conf/FlowServiceFactory.java | 43 ++ .../modules/bpm/conf/FlowableConfig.java | 19 + .../bpm/conf/listener/UserTaskListener.java | 18 + .../serve/modules/bpm/domian/dto/BpmnDto.java | 26 + .../bpm/domian/dto/FlowCommentDto.java | 24 + .../bpm/domian/dto/FlowFromFieldDTO.java | 12 + .../modules/bpm/domian/dto/FlowNextDto.java | 24 + .../modules/bpm/domian/dto/FlowTaskDto.java | 143 +++++ .../modules/bpm/domian/dto/FlowViewerDto.java | 24 + .../modules/bpm/domian/vo/FlowTaskVo.java | 60 ++ src/main/resources/application-dev.yml | 7 +- 17 files changed, 1366 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/qs/serve/modules/bpm/common/consts/ProcessConstants.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/common/enums/FlowComment.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/common/util/FindNextNodeUtil.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/common/util/FlowableUtils.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/conf/FlowServiceFactory.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/conf/FlowableConfig.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/conf/listener/UserTaskListener.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/domian/dto/BpmnDto.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowCommentDto.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowFromFieldDTO.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowNextDto.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowTaskDto.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowViewerDto.java create mode 100644 src/main/java/com/qs/serve/modules/bpm/domian/vo/FlowTaskVo.java diff --git a/pom.xml b/pom.xml index 68a0dc4..a2e6973 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,28 @@ + + com.googlecode.aviator + aviator + 5.3.1 + + + org.flowable + flowable-spring-boot-starter + 6.7.0 + + + + + + + + + + + + + cn.hutool hutool-all diff --git a/src/main/java/com/qs/serve/Application.java b/src/main/java/com/qs/serve/Application.java index 81aefc8..707b5b8 100644 --- a/src/main/java/com/qs/serve/Application.java +++ b/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()); + }; + } } diff --git a/src/main/java/com/qs/serve/modules/bpm/common/consts/ProcessConstants.java b/src/main/java/com/qs/serve/modules/bpm/common/consts/ProcessConstants.java new file mode 100644 index 0000000..8164984 --- /dev/null +++ b/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"; + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/common/enums/FlowComment.java b/src/main/java/com/qs/serve/modules/bpm/common/enums/FlowComment.java new file mode 100644 index 0000000..83e4753 --- /dev/null +++ b/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; + } + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/common/util/FindNextNodeUtil.java b/src/main/java/com/qs/serve/modules/bpm/common/util/FindNextNodeUtil.java new file mode 100644 index 0000000..8182bbf --- /dev/null +++ b/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 getNextUserTasks(RepositoryService repositoryService, org.flowable.task.api.Task task, Map map) { + List data = new ArrayList<>(); + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + Process mainProcess = bpmnModel.getMainProcess(); + Collection flowElements = mainProcess.getFlowElements(); + String key = task.getTaskDefinitionKey(); + FlowElement flowElement = bpmnModel.getFlowElement(key); + next(flowElements, flowElement, map, data); + return data; + } + + public static void next(Collection flowElements, FlowElement flowElement, Map map, List nextUser) { + //如果是结束节点 + if (flowElement instanceof EndEvent) { + //如果是子任务的结束节点 + if (getSubProcess(flowElements, flowElement) != null) { + flowElement = getSubProcess(flowElements, flowElement); + } + } + //获取Task的出线信息--可以拥有多个 + List 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 flowElements, List 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 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 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 flowElements) { + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof StartEvent) { + return flowElement; + } + } + return null; + } + + /** + * 校验el表达式 + * @param map + * @param expression + * @return + */ + public static boolean expressionResult(Map map, String expression) { + Expression exp = AviatorEvaluator.compile(expression); + final Object execute = exp.execute(map); + return Boolean.parseBoolean(String.valueOf(execute)); + } + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/common/util/FlowableUtils.java b/src/main/java/com/qs/serve/modules/bpm/common/util/FlowableUtils.java new file mode 100644 index 0000000..515d92e --- /dev/null +++ b/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 getElementIncomingFlows(FlowElement source) { + List 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 getElementOutgoingFlows(FlowElement source) { + List 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 getAllElements(Collection flowElements, Collection allElements) { + allElements = allElements == null ? new ArrayList<>() : allElements; + + for (FlowElement flowElement : flowElements) { + allElements.add(flowElement); + if (flowElement instanceof SubProcess) { + // 继续深入子流程,进一步获取子流程 + allElements = FlowableUtils.getAllElements(((SubProcess) flowElement).getFlowElements(), allElements); + } + } + return allElements; + } + + /** + * 迭代获取父级任务节点列表,向前找 + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 已找到的用户任务节点 + * @return + */ + public static List iteratorFindParentUserTasks(FlowElement source, Set hasSequenceFlow, List userTaskList) { + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + userTaskList = iteratorFindParentUserTasks(source.getSubProcess(), hasSequenceFlow, userTaskList); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 类型为用户节点,则新增父级节点 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement()); + continue; + } + // 类型为子流程,则添加子流程开始节点出口处相连的节点 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { + // 获取子流程用户任务节点 + List childUserTaskList = findChildProcessUserTasks((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList); + } + } + return userTaskList; + } + + /** + * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找 + * @param source 起始节点(退回节点) + * @param runTaskKeyList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return + */ + public static List iteratorFindChildUserTasks(FlowElement source, List runTaskKeyList, Set hasSequenceFlow, List 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 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 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 findChildProcessUserTasks(FlowElement source, Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = findChildProcessUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList); + } + } + return userTaskList; + } + + /** + * 从后向前寻路,获取所有脏线路上的点 + * @param source 起始节点 + * @param passRoads 已经经过的点集合 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param targets 目标脏线路终点 + * @param dirtyRoads 确定为脏数据的点,因为不需要重复,因此使用 set 存储 + * @return + */ + public static Set iteratorFindDirtyRoads(FlowElement source, List passRoads, Set hasSequenceFlow, List targets, Set dirtyRoads) { + passRoads = passRoads == null ? new ArrayList<>() : passRoads; + dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + dirtyRoads = iteratorFindDirtyRoads(source.getSubProcess(), passRoads, hasSequenceFlow, targets, dirtyRoads); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 新增经过的路线 + passRoads.add(sequenceFlow.getSourceFlowElement().getId()); + // 如果此点为目标点,确定经过的路线为脏线路,添加点到脏线路中,然后找下个连线 + if (targets.contains(sequenceFlow.getSourceFlowElement().getId())) { + dirtyRoads.addAll(passRoads); + continue; + } + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { + dirtyRoads = findChildProcessAllDirtyRoad((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, dirtyRoads); + // 是否存在子流程上,true 是,false 否 + Boolean isInChildProcess = dirtyTargetInChildProcess((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, targets, null); + if (isInChildProcess) { + // 已在子流程上找到,该路线结束 + continue; + } + } + // 继续迭代 + dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, targets, dirtyRoads); + } + } + return dirtyRoads; + } + + /** + * 迭代获取子流程脏路线 + * 说明,假如回退的点就是子流程,那么也肯定会回退到子流程最初的用户任务节点,因此子流程中的节点全是脏路线 + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param dirtyRoads 确定为脏数据的点,因为不需要重复,因此使用 set 存储 + * @return + */ + public static Set findChildProcessAllDirtyRoad(FlowElement source, Set hasSequenceFlow, Set dirtyRoads) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 添加脏路线 + dirtyRoads.add(sequenceFlow.getTargetFlowElement().getId()); + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + dirtyRoads = findChildProcessAllDirtyRoad((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, dirtyRoads); + } + // 继续迭代 + 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 hasSequenceFlow, List targets, Boolean inChildProcess) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + inChildProcess = inChildProcess != null && inChildProcess; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null && !inChildProcess) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果发现目标点在子流程上存在,说明只到子流程为止 + if (targets.contains(sequenceFlow.getTargetFlowElement().getId())) { + inChildProcess = true; + break; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + inChildProcess = dirtyTargetInChildProcess((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, targets, inChildProcess); + } + // 继续迭代 + 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 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 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> findRoad(FlowElement source, List passRoads, Set hasSequenceFlow, List> roads) { + passRoads = passRoads == null ? new ArrayList<>() : passRoads; + roads = roads == null ? new ArrayList<>() : roads; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null && sequenceFlows.size() != 0) { + for (SequenceFlow sequenceFlow: sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 添加经过路线 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { + passRoads.add((UserTask) sequenceFlow.getSourceFlowElement()); + } + // 继续迭代 + roads = findRoad(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, roads); + } + } else { + // 添加路线 + roads.add(passRoads); + } + return roads; + } + + /** + * 历史节点数据清洗,清洗掉又回滚导致的脏数据 + * @param allElements 全部节点信息 + * @param historicTaskInstanceList 历史任务实例信息,数据采用开始时间升序 + * @return + */ + public static List historicTaskInstanceClean(Collection allElements, List historicTaskInstanceList) { + // 会签节点收集 + List multiTask = new ArrayList<>(); + allElements.forEach(flowElement -> { + if (flowElement instanceof UserTask) { + // 如果该节点的行为为会签行为,说明该节点为会签节点 + if (((UserTask) flowElement).getBehavior() instanceof ParallelMultiInstanceBehavior || ((UserTask) flowElement).getBehavior() instanceof SequentialMultiInstanceBehavior) { + multiTask.add(flowElement.getId()); + } + } + }); + // 循环放入栈,栈 LIFO:后进先出 + Stack stack = new Stack<>(); + historicTaskInstanceList.forEach(stack::push); + // 清洗后的历史任务实例 + List lastHistoricTaskInstanceList = new ArrayList<>(); + // 网关存在可能只走了部分分支情况,且还存在跳转废弃数据以及其他分支数据的干扰,因此需要对历史节点数据进行清洗 + // 临时用户任务 key + StringBuilder userTaskKey = null; + // 临时被删掉的任务 key,存在并行情况 + List deleteKeyList = new ArrayList<>(); + // 临时脏数据线路 + List> dirtyDataLineList = new ArrayList<>(); + // 由某个点跳到会签点,此时出现多个会签实例对应 1 个跳转情况,需要把这些连续脏数据都找到 + // 会签特殊处理下标 + int multiIndex = -1; + // 会签特殊处理 key + StringBuilder multiKey = null; + // 会签特殊处理操作标识 + boolean multiOpera = false; + while (!stack.empty()) { + // 从这里开始 userTaskKey 都还是上个栈的 key + // 是否是脏数据线路上的点 + final boolean[] isDirtyData = {false}; + for (Set oldDirtyDataLine : dirtyDataLineList) { + if (oldDirtyDataLine.contains(stack.peek().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 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; + } + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/conf/FlowServiceFactory.java b/src/main/java/com/qs/serve/modules/bpm/conf/FlowServiceFactory.java new file mode 100644 index 0000000..3157029 --- /dev/null +++ b/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; + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/conf/FlowableConfig.java b/src/main/java/com/qs/serve/modules/bpm/conf/FlowableConfig.java new file mode 100644 index 0000000..c627dfa --- /dev/null +++ b/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 { + + @Override + public void configure(SpringProcessEngineConfiguration engineConfiguration) { + engineConfiguration.setActivityFontName("宋体"); + engineConfiguration.setLabelFontName("宋体"); + engineConfiguration.setAnnotationFontName("宋体"); + } + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/conf/listener/UserTaskListener.java b/src/main/java/com/qs/serve/modules/bpm/conf/listener/UserTaskListener.java new file mode 100644 index 0000000..01d30f2 --- /dev/null +++ b/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()); + } +} diff --git a/src/main/java/com/qs/serve/modules/bpm/domian/dto/BpmnDto.java b/src/main/java/com/qs/serve/modules/bpm/domian/dto/BpmnDto.java new file mode 100644 index 0000000..8ddbb2b --- /dev/null +++ b/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; + } + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowCommentDto.java b/src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowCommentDto.java new file mode 100644 index 0000000..f076ee2 --- /dev/null +++ b/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; +} diff --git a/src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowFromFieldDTO.java b/src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowFromFieldDTO.java new file mode 100644 index 0000000..e8e2c2e --- /dev/null +++ b/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; +} diff --git a/src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowNextDto.java b/src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowNextDto.java new file mode 100644 index 0000000..a1c5f89 --- /dev/null +++ b/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 userList; + + private List roleList; + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowTaskDto.java b/src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowTaskDto.java new file mode 100644 index 0000000..abfaff9 --- /dev/null +++ b/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; + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowViewerDto.java b/src/main/java/com/qs/serve/modules/bpm/domian/dto/FlowViewerDto.java new file mode 100644 index 0000000..d5b0d9a --- /dev/null +++ b/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; + +} diff --git a/src/main/java/com/qs/serve/modules/bpm/domian/vo/FlowTaskVo.java b/src/main/java/com/qs/serve/modules/bpm/domian/vo/FlowTaskVo.java new file mode 100644 index 0000000..3dc7e1b --- /dev/null +++ b/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 values; + + /** + * 审批人 + */ + private String assignee; + + /** + * 候选人 + */ + private List candidateUsers; + + /** + * 审批组 + */ + private List candidateGroups; +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index c92699e..508937e 100644 --- a/src/main/resources/application-dev.yml +++ b/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: #上传映射地址