package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups; import cn.iocoder.yudao.module.bpm.enums.definition.*; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmTriggerTaskDelegate; import cn.iocoder.yudao.module.bpm.service.task.listener.BpmCallActivityListener; import cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.Process; import org.flowable.engine.delegate.ExecutionListener; import org.flowable.engine.delegate.TaskListener; import java.util.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; import static java.util.Arrays.asList; /** * 仿钉钉/飞书的模型相关的工具方法 *

* 1. 核心的逻辑实现,可见 {@link #buildBpmnModel(String, String, BpmSimpleModelNodeVO)} 方法 * 2. 所有的 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素,可见 {@link NodeConvert} 实现类 * * @author jason */ public class SimpleModelUtils { private static final Map NODE_CONVERTS = MapUtil.newHashMap(); static { List converts = asList(new StartNodeConvert(), new EndNodeConvert(), new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(), new DelayTimerNodeConvert(), new TriggerNodeConvert(), new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert(), new ChildProcessConvert()); converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert)); } /** * 仿钉钉流程设计模型数据结构(json)转换成 Bpmn Model *

* 整体逻辑如下: * 1. 创建:BpmnModel、Process 对象 * 2. 转换:将 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素 * 3. 连接:构建并添加节点之间的连线 Sequence Flow * * @param processId 流程标识 * @param processName 流程名称 * @param simpleModelNode 仿钉钉流程设计模型数据结构 * @return Bpmn Model */ public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { // 1. 创建 BpmnModel BpmnModel bpmnModel = new BpmnModel(); bpmnModel.setTargetNamespace(BpmnXMLConstants.BPMN2_NAMESPACE); // 设置命名空间。不加这个,解析 Message 会报 NPE 异常 // 创建 Process 对象 Process process = new Process(); process.setId(processId); process.setName(processName); process.setExecutable(Boolean.TRUE); bpmnModel.addProcess(process); // 2.1 创建 StartNode 节点 // 原因是:目前前端的第一个节点是“发起人节点”,所以这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点 BpmSimpleModelNodeVO startNode = buildStartNode(); startNode.setChildNode(simpleModelNode); // 2.2 将前端传递的 simpleModelNode 数据结构(json),转换成从 BPMN FlowNode 元素,并添加到 Main Process 中 traverseNodeToBuildFlowNode(startNode, process); // 3. 构建并添加节点之间的连线 Sequence Flow EndEvent endEvent = getEndEvent(bpmnModel); traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId()); // 4. 自动布局 new BpmnAutoLayout(bpmnModel).execute(); return bpmnModel; } private static BpmSimpleModelNodeVO buildStartNode() { return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID) .setName(BpmSimpleModelNodeTypeEnum.START_NODE.getName()) .setType(BpmSimpleModelNodeTypeEnum.START_NODE.getType()); } /** * 遍历节点,构建 FlowNode 元素 * * @param node SIMPLE 节点 * @param process BPMN 流程 */ private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) { // 1. 判断是否有效节点 if (!isValidNode(node)) { return; } BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型({})不支持", node.getType()); // 2. 处理当前节点 NodeConvert nodeConvert = NODE_CONVERTS.get(nodeType); Assert.notNull(nodeConvert, "模型节点类型的转换器({})不存在", node.getType()); List flowElements = nodeConvert.convertList(node); flowElements.forEach(process::addFlowElement); // 3.1 情况一:如果当前是分支节点,并且存在条件节点,则处理每个条件的子节点 if (BpmSimpleModelNodeTypeEnum.isBranchNode(node.getType()) && CollUtil.isNotEmpty(node.getConditionNodes())) { // 注意:这里的 item.getChildNode() 处理的是每个条件的子节点,不是处理条件 node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process)); } // 3.2 情况二:如果有“子”节点,则递归处理子节点 traverseNodeToBuildFlowNode(node.getChildNode(), process); } /** * 遍历节点,构建 SequenceFlow 元素 * * @param process Bpmn 流程 * @param node 当前节点 * @param targetNodeId 目标节点 ID */ private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { // 1.1 无效节点返回 if (!isValidNode(node)) { return; } // 1.2 END_NODE 直接返回 BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); if (nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) { return; } // 2.1 情况一:普通节点 if (!BpmSimpleModelNodeTypeEnum.isBranchNode(node.getType())) { traverseNormalNodeToBuildSequenceFlow(process, node, targetNodeId); } else { // 2.2 情况二:分支节点 traverseBranchNodeToBuildSequenceFlow(process, node, targetNodeId); } } /** * 遍历普通(非条件)节点,构建 SequenceFlow 元素 * * @param process Bpmn 流程 * @param node 当前节点 * @param targetNodeId 目标节点 ID */ private static void traverseNormalNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { BpmSimpleModelNodeVO childNode = node.getChildNode(); boolean isChildNodeValid = isValidNode(childNode); // 情况一:有“子”节点,则建立连线 // 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点 String finalTargetNodeId = isChildNodeValid ? childNode.getId() : targetNodeId; // 如果没有附加节点:则直接建立连线 if (StrUtil.isEmpty(node.getAttachNodeId())) { SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId); process.addFlowElement(sequenceFlow); } else { // 如果有附加节点:需要先建立和附加节点的连线,再建立附加节点和目标节点的连线。例如说,触发器节点(HTTP 回调) List sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), finalTargetNodeId); sequenceFlows.forEach(process::addFlowElement); } // 因为有子节点,递归调用后续子节点 if (isChildNodeValid) { traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); } } /** * 构建有附加节点的连线 * * @param nodeId 当前节点 ID * @param attachNodeId 附属节点 ID * @param targetNodeId 目标节点 ID */ private static List buildAttachNodeSequenceFlow(String nodeId, String attachNodeId, String targetNodeId) { SequenceFlow sequenceFlow = buildBpmnSequenceFlow(nodeId, attachNodeId, null, null, null); SequenceFlow attachSequenceFlow = buildBpmnSequenceFlow(attachNodeId, targetNodeId, null, null, null); return CollUtil.newArrayList(sequenceFlow, attachSequenceFlow); } /** * 遍历条件节点,构建 SequenceFlow 元素 * * @param process Bpmn 流程 * @param node 当前节点 * @param targetNodeId 目标节点 ID */ private static void traverseBranchNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(node.getType()); BpmSimpleModelNodeVO childNode = node.getChildNode(); List conditionNodes = node.getConditionNodes(); // TODO @芋艿 路由分支没有conditionNodes 这里注释会影响吗?@jason:一起帮忙瞅瞅! // Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空"); // 分支终点节点 ID String branchEndNodeId = null; if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE || nodeType == BpmSimpleModelNodeTypeEnum.ROUTER_BRANCH_NODE) { // 条件分支或路由分支 // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点 ID branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; } else if (nodeType == BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE || nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) { // 并行分支或包容分支 // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。 branchEndNodeId = buildGatewayJoinId(node.getId()); } Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空"); // 3. 遍历分支节点 if (nodeType == BpmSimpleModelNodeTypeEnum.ROUTER_BRANCH_NODE) { // 路由分支遍历 for (BpmSimpleModelNodeVO.RouterSetting router : node.getRouterGroups()) { SequenceFlow sequenceFlow = RouteBranchNodeConvert.buildSequenceFlow(node.getId(), router); process.addFlowElement(sequenceFlow); } } else { // 下面的注释,以如下情况举例子。分支 1:A->B->C->D->E,分支 2:A->D->E。其中,A 为分支节点, D 为 A 孩子节点 for (BpmSimpleModelNodeVO item : conditionNodes) { Assert.isTrue(Objects.equals(item.getType(), BpmSimpleModelNodeTypeEnum.CONDITION_NODE.getType()), "条件节点类型({})不符合", item.getType()); BpmSimpleModelNodeVO conditionChildNode = item.getChildNode(); // 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况 if (isValidNode(conditionChildNode)) { // 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线 SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), nodeType, item); process.addFlowElement(sequenceFlow); // 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线 traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId); } else { // 3.2 分支没有后续节点。例如说,建立 A->D 的连线 SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, nodeType, item); process.addFlowElement(sequenceFlow); } } } // 4.1 如果是并行分支、包容分支,由于是程序创建的聚合网关,需要手工创建聚合网关和下一个节点的连线 if (nodeType == BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE || nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) { String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId); process.addFlowElement(sequenceFlow); // 4.2 如果是路由分支,需要连接后续节点为默认路由 } else if (nodeType == BpmSimpleModelNodeTypeEnum.ROUTER_BRANCH_NODE) { SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), branchEndNodeId, node.getRouterDefaultFlowId(), null, null); process.addFlowElement(sequenceFlow); } // 5. 递归调用后续节点 继续递归。例如说,建立 D->E 的连线 traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); } private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId) { return buildBpmnSequenceFlow(sourceId, targetId, null, null, null); } private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String sequenceFlowId, String sequenceFlowName, String conditionExpression) { Assert.notEmpty(sourceId, "sourceId 不能为空"); Assert.notEmpty(targetId, "targetId 不能为空"); // TODO @jason:如果 sequenceFlowId 不存在的时候,是不是要生成一个默认的 sequenceFlowId? @芋艿: 貌似不需要,Flowable 会默认生成;TODO @jason:建议还是搞一个,主要是后续好排查问题。 // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧;TODO @jason:建议还是搞一个,主要是后续好排查问题。 SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); if (StrUtil.isNotEmpty(sequenceFlowId)) { sequenceFlow.setId(sequenceFlowId); } if (StrUtil.isNotEmpty(sequenceFlowName)) { sequenceFlow.setName(sequenceFlowName); } if (StrUtil.isNotEmpty(conditionExpression)) { sequenceFlow.setConditionExpression(conditionExpression); } return sequenceFlow; } public static boolean isValidNode(BpmSimpleModelNodeVO node) { return node != null && node.getId() != null; } public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) { return BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType().equals(node.getType()) && BpmUserTaskApproveMethodEnum.SEQUENTIAL.getMethod().equals(node.getApproveMethod()); } // ========== 各种 convert 节点的方法: BpmSimpleModelNodeVO => BPMN FlowElement ========== private interface NodeConvert { default List convertList(BpmSimpleModelNodeVO node) { return Collections.singletonList(convert(node)); } default FlowElement convert(BpmSimpleModelNodeVO node) { throw new UnsupportedOperationException("请实现该方法"); } BpmSimpleModelNodeTypeEnum getType(); } private static class StartNodeConvert implements NodeConvert { @Override public StartEvent convert(BpmSimpleModelNodeVO node) { StartEvent startEvent = new StartEvent(); startEvent.setId(node.getId()); startEvent.setName(node.getName()); return startEvent; } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.START_NODE; } } private static class EndNodeConvert implements NodeConvert { @Override public EndEvent convert(BpmSimpleModelNodeVO node) { EndEvent endEvent = new EndEvent(); endEvent.setId(node.getId()); endEvent.setName(node.getName()); // TODO @芋艿 + jason:要不要加一个终止定义? return endEvent; } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.END_NODE; } } private static class StartUserNodeConvert implements NodeConvert { @Override public UserTask convert(BpmSimpleModelNodeVO node) { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); // 人工审批 addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType()); // 候选人策略为发起人自己 addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask); // 添加表单字段权限属性元素 addFormFieldsPermission(node.getFieldsPermission(), userTask); // 添加操作按钮配置属性元素 addButtonsSetting(node.getButtonsSetting(), userTask); // 使用自动通过策略 // TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过; addAssignStartUserHandlerType(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType(), userTask); return userTask; } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.START_USER_NODE; } } private static class ApproveNodeConvert implements NodeConvert { @Override public List convertList(BpmSimpleModelNodeVO node) { List flowElements = new ArrayList<>(2); // 1. 构建用户任务 UserTask userTask = buildBpmnUserTask(node); flowElements.add(userTask); // 2. 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理 if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler()); flowElements.add(boundaryEvent); } return flowElements; } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.APPROVE_NODE; } /** * 添加 UserTask 用户的审批超时 BoundaryEvent 事件 * * @param userTask 审批任务 * @param timeoutHandler 超时处理器 * @return BoundaryEvent 超时事件 */ private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) { // 1. 创建 Timeout Boundary Event String timeCycle = null; if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) && timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { timeCycle = String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()); } BoundaryEvent boundaryEvent = buildTimeoutBoundaryEvent(userTask, BpmBoundaryEventTypeEnum.USER_TASK_TIMEOUT.getType(), timeoutHandler.getTimeDuration(), timeCycle, null); // 2 添加超时执行动作元素 addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType()); return boundaryEvent; } private UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); // 如果不是审批人节点,则直接返回 addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, node.getApproveType()); if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) { return userTask; } // 添加候选人元素 addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); // 添加表单字段权限属性元素 addFormFieldsPermission(node.getFieldsPermission(), userTask); // 添加操作按钮配置属性元素 addButtonsSetting(node.getButtonsSetting(), userTask); // 处理多实例(审批方式) processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); // 添加任务被拒绝的处理元素 addTaskRejectElements(node.getRejectHandler(), userTask); // 添加用户任务的审批人与发起人相同时的处理元素 addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask); // 添加用户任务的空处理元素 addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask); // 设置审批任务的截止时间 if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); } // 设置监听器 addUserTaskListener(node, userTask); // 添加是否需要签名 addSignEnable(node.getSignEnable(), userTask); // 审批意见 addReasonRequire(node.getReasonRequire(), userTask); // 节点类型 addNodeType(node.getType(), userTask); // 添加跳过表达式 if (StrUtil.isNotEmpty(node.getSkipExpression())) { userTask.setSkipExpression(node.getSkipExpression()); } return userTask; } private void addUserTaskListener(BpmSimpleModelNodeVO node, UserTask userTask) { List flowableListeners = new ArrayList<>(3); if (node.getTaskCreateListener() != null && Boolean.TRUE.equals(node.getTaskCreateListener().getEnable())) { FlowableListener flowableListener = new FlowableListener(); flowableListener.setEvent(TaskListener.EVENTNAME_CREATE); flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION); addListenerConfig(flowableListener, node.getTaskCreateListener()); flowableListeners.add(flowableListener); } if (node.getTaskAssignListener() != null && Boolean.TRUE.equals(node.getTaskAssignListener().getEnable())) { FlowableListener flowableListener = new FlowableListener(); flowableListener.setEvent(TaskListener.EVENTNAME_ASSIGNMENT); flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION); addListenerConfig(flowableListener, node.getTaskAssignListener()); flowableListeners.add(flowableListener); } if (node.getTaskCompleteListener() != null && Boolean.TRUE.equals(node.getTaskCompleteListener().getEnable())) { FlowableListener flowableListener = new FlowableListener(); flowableListener.setEvent(TaskListener.EVENTNAME_COMPLETE); flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION); addListenerConfig(flowableListener, node.getTaskCompleteListener()); flowableListeners.add(flowableListener); } if (CollUtil.isNotEmpty(flowableListeners)) { userTask.setTaskListeners(flowableListeners); } } private void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum); // 添加审批方式的扩展属性 addExtensionElement(userTask, USER_TASK_APPROVE_METHOD, approveMethod); if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) { // 随机审批,不需要设置多实例属性 return; } // 处理多实例审批方式 MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); // 设置 collectionVariable。本系统用不到,仅仅为了 Flowable 校验不报错 multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) { multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition()); multiInstanceCharacteristics.setSequential(false); } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) { multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition()); multiInstanceCharacteristics.setSequential(true); multiInstanceCharacteristics.setLoopCardinality("1"); } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) { Assert.notNull(approveRatio, "通过比例不能为空"); multiInstanceCharacteristics.setCompletionCondition( String.format(approveMethodEnum.getCompletionCondition(), String.format("%.2f", approveRatio / 100D))); multiInstanceCharacteristics.setSequential(false); } userTask.setLoopCharacteristics(multiInstanceCharacteristics); } } private static class TransactorNodeConvert extends ApproveNodeConvert { @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE; } } private static class CopyNodeConvert implements NodeConvert { @Override public ServiceTask convert(BpmSimpleModelNodeVO node) { ServiceTask serviceTask = new ServiceTask(); serviceTask.setId(node.getId()); serviceTask.setName(node.getName()); serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}"); // 添加抄送候选人元素 addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask); // 添加表单字段权限属性元素 addFormFieldsPermission(node.getFieldsPermission(), serviceTask); return serviceTask; } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.COPY_NODE; } } private static class ConditionBranchNodeConvert implements NodeConvert { @Override public ExclusiveGateway convert(BpmSimpleModelNodeVO node) { ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); exclusiveGateway.setId(node.getId()); // TODO @jason:setName // 设置默认的序列流(条件) BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), item -> BooleanUtil.isTrue(item.getConditionSetting().getDefaultFlow())); Assert.notNull(defaultSeqFlow, "条件分支节点({})的默认序列流不能为空", node.getId()); exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); return exclusiveGateway; } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE; } } private static class ParallelBranchNodeConvert implements NodeConvert { /** * 并行分支使用包容网关。需要设置所有出口条件表达式的值为 true 。原因是,解决 https://t.zsxq.com/m6GXh 反馈问题 * * @see {@link ConditionNodeConvert#buildSequenceFlow} */ @Override public List convertList(BpmSimpleModelNodeVO node) { InclusiveGateway inclusiveGateway = new InclusiveGateway(); inclusiveGateway.setId(node.getId()); // TODO @jason:setName // 合并网关 由程序创建,前端不需要传入 InclusiveGateway joinParallelGateway = new InclusiveGateway(); joinParallelGateway.setId(buildGatewayJoinId(node.getId())); // TODO @jason:setName return CollUtil.newArrayList(inclusiveGateway, joinParallelGateway); } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE; } } private static class InclusiveBranchNodeConvert implements NodeConvert { @Override public List convertList(BpmSimpleModelNodeVO node) { InclusiveGateway inclusiveGateway = new InclusiveGateway(); inclusiveGateway.setId(node.getId()); // 设置默认的序列流(条件) BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), item -> BooleanUtil.isTrue(item.getConditionSetting().getDefaultFlow())); Assert.notNull(defaultSeqFlow, "包容分支节点({})的默认序列流不能为空", node.getId()); inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); // TODO @jason:setName // 并行聚合网关由程序创建,前端不需要传入 InclusiveGateway joinInclusiveGateway = new InclusiveGateway(); joinInclusiveGateway.setId(buildGatewayJoinId(node.getId())); // TODO @jason:setName return CollUtil.newArrayList(inclusiveGateway, joinInclusiveGateway); } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE; } } public static class ConditionNodeConvert implements NodeConvert { @Override public List convertList(BpmSimpleModelNodeVO node) { // 原因是:正常情况下,它不会被调用到 throw new UnsupportedOperationException("条件节点不支持转换"); } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.CONDITION_NODE; } public static SequenceFlow buildSequenceFlow(String sourceId, String targetId, BpmSimpleModelNodeTypeEnum nodeType, BpmSimpleModelNodeVO node) { String conditionExpression; // 并行分支,使用包容网关实现,强制设置条件表达式为 true if (BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE == nodeType) { conditionExpression ="${true}"; } else { conditionExpression = buildConditionExpression(node.getConditionSetting()); } return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression); } } /** * 构造条件表达式 */ public static String buildConditionExpression(BpmSimpleModelNodeVO.ConditionSetting conditionSetting) { if (conditionSetting == null) { return null; } return buildConditionExpression(conditionSetting.getConditionType(), conditionSetting.getConditionExpression(), conditionSetting.getConditionGroups()); } public static String buildConditionExpression(BpmSimpleModelNodeVO.RouterSetting routerSetting) { return buildConditionExpression(routerSetting.getConditionType(), routerSetting.getConditionExpression(), routerSetting.getConditionGroups()); } public static String buildConditionExpression(Integer conditionType, String conditionExpression, ConditionGroups conditionGroups) { BpmSimpleModeConditionTypeEnum conditionTypeEnum = BpmSimpleModeConditionTypeEnum.valueOf(conditionType); if (conditionTypeEnum == BpmSimpleModeConditionTypeEnum.EXPRESSION) { return conditionExpression; } if (conditionTypeEnum == BpmSimpleModeConditionTypeEnum.RULE) { if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) { return null; } List strConditionGroups = convertList(conditionGroups.getConditions(), item -> { if (CollUtil.isEmpty(item.getRules())) { return ""; } // 构造规则表达式 List list = convertList(item.getRules(), (rule) -> { String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide() : "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号 return String.format(BpmConditionOpCodeEnum.fromCode(rule.getOpCode()).getSymbol(), rule.getLeftSide(), // 左侧:读取变量 rightSide); // 右侧:取值变量 }); // 构造条件组的表达式 Boolean and = item.getAnd(); return "(" + CollUtil.join(list, and ? " && " : " || ") + ")"; }); return String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || ")); } return null; } public static class DelayTimerNodeConvert implements NodeConvert { @Override public List convertList(BpmSimpleModelNodeVO node) { List flowElements = new ArrayList<>(2); // 1. 构建接收任务,通过接收任务可卡住节点 ReceiveTask receiveTask = new ReceiveTask(); receiveTask.setId(node.getId()); receiveTask.setName(node.getName()); flowElements.add(receiveTask); // 2. 添加接收任务的 Timer Boundary Event if (node.getDelaySetting() != null) { BoundaryEvent boundaryEvent = null; if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) { boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(), null, null, node.getDelaySetting().getDelayTime()); } else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) { boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(), node.getDelaySetting().getDelayTime(), null, null); } else { throw new UnsupportedOperationException("不支持的延迟类型:" + node.getDelaySetting()); } flowElements.add(boundaryEvent); } return flowElements; } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.DELAY_TIMER_NODE; } } public static class TriggerNodeConvert implements NodeConvert { @Override public List convertList(BpmSimpleModelNodeVO node) { Assert.notNull(node.getTriggerSetting(), "触发器节点设置不能为空"); List flowElements = new ArrayList<>(2); // HTTP 回调请求。需要附加一个 ReceiveTask、发起请求后、等待回调执行 if (BpmTriggerTypeEnum.HTTP_CALLBACK.getType().equals(node.getTriggerSetting().getType())) { Assert.notNull(node.getTriggerSetting().getHttpRequestSetting(), "触发器 HTTP 回调请求设置不能为空"); ReceiveTask receiveTask = new ReceiveTask(); receiveTask.setId("Activity_" + IdUtil.fastUUID()); receiveTask.setName("HTTP 回调"); node.setAttachNodeId(receiveTask.getId()); flowElements.add(receiveTask); // 重要:设置 callbackTaskDefineKey,用于 HTTP 回调 node.getTriggerSetting().getHttpRequestSetting().setCallbackTaskDefineKey(receiveTask.getId()); } // 触发器使用 ServiceTask 来实现 ServiceTask serviceTask = new ServiceTask(); serviceTask.setId(node.getId()); serviceTask.setName(node.getName()); serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); serviceTask.setImplementation("${" + BpmTriggerTaskDelegate.BEAN_NAME + "}"); addExtensionElement(serviceTask, TRIGGER_TYPE, node.getTriggerSetting().getType()); if (node.getTriggerSetting().getHttpRequestSetting() != null) { addExtensionElementJson(serviceTask, TRIGGER_PARAM, node.getTriggerSetting().getHttpRequestSetting()); } if (node.getTriggerSetting().getFormSettings() != null) { addExtensionElementJson(serviceTask, TRIGGER_PARAM, node.getTriggerSetting().getFormSettings()); } flowElements.add(serviceTask); return flowElements; } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.TRIGGER_NODE; } } public static class RouteBranchNodeConvert implements NodeConvert { @Override public ExclusiveGateway convert(BpmSimpleModelNodeVO node) { ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); exclusiveGateway.setId(node.getId()); // 设置默认的序列流(条件) node.setRouterDefaultFlowId("Flow_" + IdUtil.fastUUID()); exclusiveGateway.setDefaultFlow(node.getRouterDefaultFlowId()); return exclusiveGateway; } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.ROUTER_BRANCH_NODE; } public static SequenceFlow buildSequenceFlow(String nodeId, BpmSimpleModelNodeVO.RouterSetting router) { String conditionExpression = SimpleModelUtils.buildConditionExpression(router); return buildBpmnSequenceFlow(nodeId, router.getNodeId(), null, null, conditionExpression); } } private static class ChildProcessConvert implements NodeConvert { @Override public List convertList(BpmSimpleModelNodeVO node) { List flowElements = new ArrayList<>(2); BpmSimpleModelNodeVO.ChildProcessSetting childProcessSetting = node.getChildProcessSetting(); List inVariables = childProcessSetting.getInVariables() == null ? new ArrayList<>() : new ArrayList<>(childProcessSetting.getInVariables()); CallActivity callActivity = new CallActivity(); callActivity.setId(node.getId()); callActivity.setName(node.getName()); callActivity.setCalledElementType("key"); // 1. 是否异步 if (node.getChildProcessSetting().getAsync()) { callActivity.setAsynchronous(true); } // 2. 调用的子流程 callActivity.setCalledElement(childProcessSetting.getCalledProcessDefinitionKey()); callActivity.setProcessInstanceName(childProcessSetting.getCalledProcessDefinitionName()); // 3. 是否自动跳过子流程发起节点 IOParameter ioParameter = new IOParameter(); ioParameter.setSourceExpression(childProcessSetting.getSkipStartUserNode().toString()); ioParameter.setTarget(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE); inVariables.add(ioParameter); // 4. 【默认需要传递的一些变量】流程状态 ioParameter = new IOParameter(); ioParameter.setSource(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); ioParameter.setTarget(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); inVariables.add(ioParameter); // 5. 主→子变量传递、子->主变量传递 callActivity.setInParameters(inVariables); if (ArrayUtil.isNotEmpty(childProcessSetting.getOutVariables()) && ObjUtil.notEqual(childProcessSetting.getAsync(), Boolean.TRUE)) { callActivity.setOutParameters(childProcessSetting.getOutVariables()); } // 6. 子流程发起人配置 List executionListeners = new ArrayList<>(); FlowableListener flowableListener = new FlowableListener(); flowableListener.setEvent(ExecutionListener.EVENTNAME_START); flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); flowableListener.setImplementation(BpmCallActivityListener.DELEGATE_EXPRESSION); FieldExtension fieldExtension = new FieldExtension(); fieldExtension.setFieldName("listenerConfig"); fieldExtension.setStringValue(JsonUtils.toJsonString(childProcessSetting.getStartUserSetting())); flowableListener.getFieldExtensions().add(fieldExtension); executionListeners.add(flowableListener); callActivity.setExecutionListeners(executionListeners); // 7. 超时设置 if (childProcessSetting.getTimeoutSetting() != null && Boolean.TRUE.equals(childProcessSetting.getTimeoutSetting().getEnable())) { BoundaryEvent boundaryEvent = null; if (childProcessSetting.getTimeoutSetting().getType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) { boundaryEvent = buildTimeoutBoundaryEvent(callActivity, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(), childProcessSetting.getTimeoutSetting().getTimeExpression(), null, null); } else if (childProcessSetting.getTimeoutSetting().getType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) { boundaryEvent = buildTimeoutBoundaryEvent(callActivity, BpmBoundaryEventTypeEnum.CHILD_PROCESS_TIMEOUT.getType(), null, null, childProcessSetting.getTimeoutSetting().getTimeExpression()); } flowElements.add(boundaryEvent); } // 8. 多实例 if (childProcessSetting.getMultiInstanceSetting() != null && Boolean.TRUE.equals(childProcessSetting.getMultiInstanceSetting().getEnable())) { MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); multiInstanceCharacteristics.setSequential(childProcessSetting.getMultiInstanceSetting().getSequential()); if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY.getType())) { multiInstanceCharacteristics.setLoopCardinality(childProcessSetting.getMultiInstanceSetting().getSource()); } if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType()) || childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) { multiInstanceCharacteristics.setInputDataItem(childProcessSetting.getMultiInstanceSetting().getSource()); } multiInstanceCharacteristics.setCompletionCondition(String.format(BpmUserTaskApproveMethodEnum.RATIO.getCompletionCondition(), String.format("%.2f", childProcessSetting.getMultiInstanceSetting().getApproveRatio() / 100D))); callActivity.setLoopCharacteristics(multiInstanceCharacteristics); addExtensionElement(callActivity, CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE, childProcessSetting.getMultiInstanceSetting().getSourceType()); } // 添加节点类型 addNodeType(node.getType(), callActivity); flowElements.add(callActivity); return flowElements; } @Override public BpmSimpleModelNodeTypeEnum getType() { return BpmSimpleModelNodeTypeEnum.CHILD_PROCESS; } } private static String buildGatewayJoinId(String id) { return id + "_join"; } private static BoundaryEvent buildTimeoutBoundaryEvent(Activity attachedToRef, Integer type, String timeDuration, String timeCycle, String timeDate) { // 1.1 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); boundaryEvent.setId("Event-" + IdUtil.fastUUID()); boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断 boundaryEvent.setAttachedToRef(attachedToRef); // 1.2 定义超时时间表达式 TimerEventDefinition eventDefinition = new TimerEventDefinition(); if (ObjUtil.isNotNull(timeDuration)) { eventDefinition.setTimeDuration(timeDuration); } if (ObjUtil.isNotNull(timeDuration)) { eventDefinition.setTimeCycle(timeCycle); } if (ObjUtil.isNotNull(timeDate)) { eventDefinition.setTimeDate(timeDate); } boundaryEvent.addEventDefinition(eventDefinition); // 2. 添加定时器边界事件类型 addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, type); return boundaryEvent; } // ========== SIMPLE 流程预测相关的方法 ========== public static List simulateProcess(BpmSimpleModelNodeVO rootNode, Map variables) { List resultNodes = new ArrayList<>(); // 从头开始遍历 simulateNextNode(rootNode, variables, resultNodes); return resultNodes; } private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map variables, List resultNodes) { // 如果不合法(包括为空),则直接结束 if (!isValidNode(currentNode)) { return; } BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(currentNode.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE/TRANSACTOR_NODE if (nodeType == BpmSimpleModelNodeTypeEnum.START_NODE || nodeType == BpmSimpleModelNodeTypeEnum.START_USER_NODE || nodeType == BpmSimpleModelNodeTypeEnum.APPROVE_NODE || nodeType == BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) { // 添加此节点 resultNodes.add(currentNode); } // 情况:CONDITION_BRANCH_NODE 排它,只有一个满足条件的。如果没有,就走默认的 if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE) { // 查找满足条件的 BpmSimpleModelNodeVO 节点 BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) && evalConditionExpress(variables, conditionNode.getConditionSetting())); if (matchConditionNode == null) { matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); } Assert.notNull(matchConditionNode, "找不到条件节点({})", currentNode); // 遍历满足条件的 BpmSimpleModelNodeVO 节点 simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes); } // 情况:INCLUSIVE_BRANCH_NODE 包容,多个满足条件的。如果没有,就走默认的 if (nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) { // 查找满足条件的 BpmSimpleModelNodeVO 节点 Collection matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) && evalConditionExpress(variables, conditionNode.getConditionSetting())); if (CollUtil.isEmpty(matchConditionNodes)) { matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); } Assert.isTrue(!matchConditionNodes.isEmpty(), "找不到条件节点({})", currentNode); // 遍历满足条件的 BpmSimpleModelNodeVO 节点 matchConditionNodes.forEach(matchConditionNode -> simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes)); } // 情况:PARALLEL_BRANCH_NODE 并行,都满足,都走 if (nodeType == BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE) { // 遍历所有 BpmSimpleModelNodeVO 节点 currentNode.getConditionNodes().forEach(matchConditionNode -> simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes)); } // 遍历子节点 simulateNextNode(currentNode.getChildNode(), variables, resultNodes); } /** * 根据跳过表达式,判断是否跳过此节点 */ public static boolean isSkipNode(BpmSimpleModelNodeVO currentNode, Map variables) { if (StrUtil.isEmpty(currentNode.getSkipExpression())) { return false; } return BpmnModelUtils.evalConditionExpress(variables, currentNode.getSkipExpression()); } public static boolean evalConditionExpress(Map variables, BpmSimpleModelNodeVO.ConditionSetting conditionSetting) { return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting)); } }