潘志宝
3 天以前 b8a0affd03b5fa9fa33cd6f870e90394c2df86c7
提交 | 用户 | 时间
bb2880 1 package com.iailab.module.bpm.framework.flowable.core.util;
H 2
3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.map.MapUtil;
6 import cn.hutool.core.util.*;
7 import com.iailab.framework.common.util.collection.CollectionUtils;
8 import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
9 import com.iailab.module.bpm.enums.definition.*;
10 import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
11 import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
12 import com.iailab.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
13 import org.flowable.bpmn.BpmnAutoLayout;
14 import org.flowable.bpmn.constants.BpmnXMLConstants;
15 import org.flowable.bpmn.model.Process;
16 import org.flowable.bpmn.model.*;
17
18 import java.util.*;
19
20 import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
21 import static com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
22 import static java.util.Arrays.asList;
23
24 /**
25  * 仿钉钉/飞书的模型相关的工具方法
26  * <p>
27  * 1. 核心的逻辑实现,可见 {@link #buildBpmnModel(String, String, BpmSimpleModelNodeVO)} 方法
28  * 2. 所有的 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素,可见 {@link NodeConvert} 实现类
29  *
30  * @author jason
31  */
32 public class SimpleModelUtils {
33
34     private static final Map<BpmSimpleModelNodeType, NodeConvert> NODE_CONVERTS = MapUtil.newHashMap();
35
36     static {
37         List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(),
38                 new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(),
39                 new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert());
40         converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert));
41     }
42
43     /**
44      * 仿钉钉流程设计模型数据结构(json)转换成 Bpmn Model
45      * <p>
46      * 整体逻辑如下:
47      * 1. 创建:BpmnModel、Process 对象
48      * 2. 转换:将 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素
49      * 3. 连接:构建并添加节点之间的连线 Sequence Flow
50      *
51      * @param processId       流程标识
52      * @param processName     流程名称
53      * @param simpleModelNode 仿钉钉流程设计模型数据结构
54      * @return Bpmn Model
55      */
56     public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
57         // 1. 创建 BpmnModel
58         BpmnModel bpmnModel = new BpmnModel();
59         bpmnModel.setTargetNamespace(BpmnXMLConstants.BPMN2_NAMESPACE); // 设置命名空间。不加这个,解析 Message 会报 NPE 异常
60         // 创建 Process 对象
61         Process process = new Process();
62         process.setId(processId);
63         process.setName(processName);
64         process.setExecutable(Boolean.TRUE);
65         bpmnModel.addProcess(process);
66
67         // 2.1 创建 StartNode 节点
68         // 原因是:目前前端的第一个节点是“发起人节点”,所以这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点
69         BpmSimpleModelNodeVO startNode = buildStartNode();
70         startNode.setChildNode(simpleModelNode);
71         // 2.2 将前端传递的 simpleModelNode 数据结构(json),转换成从 BPMN FlowNode 元素,并添加到 Main Process 中
72         traverseNodeToBuildFlowNode(startNode, process);
73
74         // 3. 构建并添加节点之间的连线 Sequence Flow
75         EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
76         traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
77
78         // 4. 自动布局
79         new BpmnAutoLayout(bpmnModel).execute();
80         return bpmnModel;
81     }
82
83     private static BpmSimpleModelNodeVO buildStartNode() {
84         return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID)
f4f940 85                 .setName(BpmSimpleModelNodeType.START_NODE.getName())
bb2880 86                 .setType(BpmSimpleModelNodeType.START_NODE.getType());
H 87     }
88
89     /**
90      * 遍历节点,构建 FlowNode 元素
91      *
92      * @param node SIMPLE 节点
93      * @param process BPMN 流程
94      */
95     private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) {
96         // 1. 判断是否有效节点
97         if (!isValidNode(node)) {
98             return;
99         }
100         BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
101         Assert.notNull(nodeType, "模型节点类型({})不支持", node.getType());
102
103         // 2. 处理当前节点
104         NodeConvert nodeConvert = NODE_CONVERTS.get(nodeType);
105         Assert.notNull(nodeConvert, "模型节点类型的转换器({})不存在", node.getType());
106         List<? extends FlowElement> flowElements = nodeConvert.convertList(node);
107         flowElements.forEach(process::addFlowElement);
108
109         // 3.1 情况一:如果当前是分支节点,并且存在条件节点,则处理每个条件的子节点
110         if (BpmSimpleModelNodeType.isBranchNode(node.getType())
111                 && CollUtil.isNotEmpty(node.getConditionNodes())) {
112             // 注意:这里的 item.getChildNode() 处理的是每个条件的子节点,不是处理条件
113             node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process));
114         }
115
116         // 3.2 情况二:如果有“子”节点,则递归处理子节点
117         traverseNodeToBuildFlowNode(node.getChildNode(), process);
118     }
119
120     /**
121      * 遍历节点,构建 SequenceFlow 元素
122      *
123      * @param process Bpmn 流程
124      * @param node 当前节点
125      * @param targetNodeId 目标节点 ID
126      */
127     private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
128         // 1.1 无效节点返回
129         if (!isValidNode(node)) {
130             return;
131         }
132         // 1.2 END_NODE 直接返回
133         BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
134         Assert.notNull(nodeType, "模型节点类型不支持");
135         if (nodeType == BpmSimpleModelNodeType.END_NODE) {
136             return;
137         }
138
139         // 2.1 情况一:普通节点
140         if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) {
141             traverseNormalNodeToBuildSequenceFlow(process, node, targetNodeId);
142         } else {
143             // 2.2 情况二:分支节点
144             traverseBranchNodeToBuildSequenceFlow(process, node, targetNodeId);
145         }
146     }
147
148     /**
149      * 遍历普通(非条件)节点,构建 SequenceFlow 元素
150      *
151      * @param process Bpmn 流程
152      * @param node 当前节点
153      * @param targetNodeId 目标节点 ID
154      */
155     private static void traverseNormalNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
156         BpmSimpleModelNodeVO childNode = node.getChildNode();
157         boolean isChildNodeValid = isValidNode(childNode);
158         // 情况一:有“子”节点,则建立连线
159         // 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点
160         String finalTargetNodeId = isChildNodeValid? childNode.getId() : targetNodeId;
161         SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId);
162         process.addFlowElement(sequenceFlow);
163
164         // 因为有子节点,递归调用后续子节点
165         if (isChildNodeValid) {
166             traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
167         }
168     }
169
170     /**
171      * 遍历条件节点,构建 SequenceFlow 元素
172      *
173      * @param process Bpmn 流程
174      * @param node 当前节点
175      * @param targetNodeId 目标节点 ID
176      */
177     private static void traverseBranchNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
178         BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
179         BpmSimpleModelNodeVO childNode = node.getChildNode();
180         List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
181         Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空");
182         // 分支终点节点 ID
183         String branchEndNodeId = null;
184         if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) { // 条件分支
185             // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点 ID
186             branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
187         } else if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE
188                 || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) {  // 并行分支或包容分支
189             // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。
190             branchEndNodeId = buildGatewayJoinId(node.getId());
191         }
192         Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空");
193
194         // 3. 遍历分支节点
195         // 下面的注释,以如下情况举例子。分支 1:A->B->C->D->E,分支 2:A->D->E。其中,A 为分支节点, D 为 A 孩子节点
196         for (BpmSimpleModelNodeVO item : conditionNodes) {
197             Assert.isTrue(Objects.equals(item.getType(), BpmSimpleModelNodeType.CONDITION_NODE.getType()),
198                     "条件节点类型({})不符合", item.getType());
199             BpmSimpleModelNodeVO conditionChildNode = item.getChildNode();
200             // 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况
201             if (isValidNode(conditionChildNode)) {
202                 // 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线
203                 SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), item);
204                 process.addFlowElement(sequenceFlow);
205                 // 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线
206                 traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId);
207             } else {
208                 // 3.2 分支没有后续节点。例如说,建立 A->D 的连线
209                 SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, item);
210                 process.addFlowElement(sequenceFlow);
211             }
212         }
213
214         // 4. 如果是并行分支、包容分支,由于是程序创建的聚合网关,需要手工创建聚合网关和下一个节点的连线
215         if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE
216                 || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE ) {
217             String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
218             SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId);
219             process.addFlowElement(sequenceFlow);
220         }
221
222         // 5. 递归调用后续节点 继续递归。例如说,建立 D->E 的连线
223         traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
224     }
225
226     private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId) {
227         return buildBpmnSequenceFlow(sourceId, targetId, null, null, null);
228     }
229
230     private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId,
231                                                       String sequenceFlowId, String sequenceFlowName,
232                                                       String conditionExpression) {
233         Assert.notEmpty(sourceId, "sourceId 不能为空");
234         Assert.notEmpty(targetId, "targetId 不能为空");
235         // TODO @jason:如果 sequenceFlowId 不存在的时候,是不是要生成一个默认的 sequenceFlowId? @芋艿: 貌似不需要,Flowable 会默认生成;TODO @jason:建议还是搞一个,主要是后续好排查问题。
236         // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧;TODO @jason:建议还是搞一个,主要是后续好排查问题。
237         SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
238         if (StrUtil.isNotEmpty(sequenceFlowId)) {
239             sequenceFlow.setId(sequenceFlowId);
240         }
241         if (StrUtil.isNotEmpty(sequenceFlowName)) {
242             sequenceFlow.setName(sequenceFlowName);
243         }
244         if (StrUtil.isNotEmpty(conditionExpression)) {
245             sequenceFlow.setConditionExpression(conditionExpression);
246         }
247         return sequenceFlow;
248     }
249
250     public static boolean isValidNode(BpmSimpleModelNodeVO node) {
251         return node != null && node.getId() != null;
252     }
253
254     public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) {
255         return BpmSimpleModelNodeType.APPROVE_NODE.getType().equals(node.getType())
256                 && BpmUserTaskApproveMethodEnum.SEQUENTIAL.getMethod().equals(node.getApproveMethod());
257     }
258
259     // ========== 各种 convert 节点的方法: BpmSimpleModelNodeVO => BPMN FlowElement ==========
260
261     private interface NodeConvert {
262
263         default List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) {
264             return Collections.singletonList(convert(node));
265         }
266
267         default FlowElement convert(BpmSimpleModelNodeVO node) {
268             throw new UnsupportedOperationException("请实现该方法");
269         }
270
271         BpmSimpleModelNodeType getType();
272
273     }
274
275     private static class StartNodeConvert implements NodeConvert {
276
277         @Override
278         public StartEvent convert(BpmSimpleModelNodeVO node) {
279             StartEvent startEvent = new StartEvent();
280             startEvent.setId(node.getId());
281             startEvent.setName(node.getName());
282             return startEvent;
283         }
284
285         @Override
286         public BpmSimpleModelNodeType getType() {
287             return BpmSimpleModelNodeType.START_NODE;
288         }
289
290     }
291
292     private static class EndNodeConvert implements NodeConvert {
293
294         @Override
295         public EndEvent convert(BpmSimpleModelNodeVO node) {
296             EndEvent endEvent = new EndEvent();
297             endEvent.setId(node.getId());
298             endEvent.setName(node.getName());
299             // TODO @芋艿 + jason:要不要加一个终止定义?
300             return endEvent;
301         }
302
303         @Override
304         public BpmSimpleModelNodeType getType() {
305             return BpmSimpleModelNodeType.END_NODE;
306         }
307
308     }
309
310     private static class StartUserNodeConvert implements NodeConvert {
311
312         @Override
313         public UserTask convert(BpmSimpleModelNodeVO node) {
314             UserTask userTask = new UserTask();
315             userTask.setId(node.getId());
316             userTask.setName(node.getName());
317
318             // 人工审批
f4f940 319             addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
bb2880 320             // 候选人策略为发起人自己
H 321             addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask);
322             // 添加表单字段权限属性元素
323             addFormFieldsPermission(node.getFieldsPermission(), userTask);
324             // 添加操作按钮配置属性元素
325             addButtonsSetting(node.getButtonsSetting(), userTask);
326             // 使用自动通过策略
327             // TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过;
328             addAssignStartUserHandlerType(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType(), userTask);
329             return userTask;
330         }
331
332         @Override
333         public BpmSimpleModelNodeType getType() {
334             return BpmSimpleModelNodeType.START_USER_NODE;
335         }
336
337     }
338
339     private static class ApproveNodeConvert implements NodeConvert {
340
341         @Override
342         public List<FlowElement> convertList(BpmSimpleModelNodeVO node) {
343             List<FlowElement> flowElements = new ArrayList<>(2);
344             // 1. 构建用户任务
345             UserTask userTask = buildBpmnUserTask(node);
346             flowElements.add(userTask);
347
348             // 2. 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理
349             if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
350                 BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler());
351                 flowElements.add(boundaryEvent);
352             }
353             return flowElements;
354         }
355
356         @Override
357         public BpmSimpleModelNodeType getType() {
358             return BpmSimpleModelNodeType.APPROVE_NODE;
359         }
360
361         /**
362          * 添加 UserTask 用户的审批超时 BoundaryEvent 事件
363          *
364          * @param userTask       审批任务
365          * @param timeoutHandler 超时处理器
366          * @return BoundaryEvent 超时事件
367          */
368         private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask,
369                                                                 BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) {
370             // 1.1 定时器边界事件
371             BoundaryEvent boundaryEvent = new BoundaryEvent();
372             boundaryEvent.setId("Event-" + IdUtil.fastUUID());
373             boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
374             boundaryEvent.setAttachedToRef(userTask);
375             // 1.2 定义超时时间、最大提醒次数
376             TimerEventDefinition eventDefinition = new TimerEventDefinition();
377             eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
378             if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) &&
379                     timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
380                 eventDefinition.setTimeCycle(String.format("R%d/%s",
381                         timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
382             }
383             boundaryEvent.addEventDefinition(eventDefinition);
384
385             // 2.1 添加定时器边界事件类型
386             addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType());
387             // 2.2 添加超时执行动作元素
388             addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType());
389             return boundaryEvent;
390         }
391
392         private UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) {
393             UserTask userTask = new UserTask();
394             userTask.setId(node.getId());
395             userTask.setName(node.getName());
396
397             // 如果不是审批人节点,则直接返回
398             addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, node.getApproveType());
399             if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) {
400                 return userTask;
401             }
402
403             // 添加候选人元素
404             addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask);
405             // 添加表单字段权限属性元素
406             addFormFieldsPermission(node.getFieldsPermission(), userTask);
407             // 添加操作按钮配置属性元素
408             addButtonsSetting(node.getButtonsSetting(), userTask);
409             // 处理多实例(审批方式)
410             processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask);
411             // 添加任务被拒绝的处理元素
412             addTaskRejectElements(node.getRejectHandler(), userTask);
413             // 添加用户任务的审批人与发起人相同时的处理元素
414             addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask);
415             // 添加用户任务的空处理元素
416             addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask);
417             //  设置审批任务的截止时间
418             if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
419                 userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
420             }
421             return userTask;
422         }
423
424         private void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) {
425             BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
426             Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum);
427             // 添加审批方式的扩展属性
428             addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod);
429             if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) {
430                 // 随机审批,不需要设置多实例属性
431                 return;
432             }
433
434             // 处理多实例审批方式
435             MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
436             // 设置 collectionVariable。本系统用不到,仅仅为了 Flowable 校验不报错
437             multiInstanceCharacteristics.setInputDataItem("${coll_userList}");
438             if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) {
439                 multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition());
440                 multiInstanceCharacteristics.setSequential(false);
441             } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) {
442                 multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition());
443                 multiInstanceCharacteristics.setSequential(true);
444                 multiInstanceCharacteristics.setLoopCardinality("1");
445             } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) {
446                 Assert.notNull(approveRatio, "通过比例不能为空");
447                 multiInstanceCharacteristics.setCompletionCondition(
448                         String.format(approveMethodEnum.getCompletionCondition(), String.format("%.2f", approveRatio / 100D)));
449                 multiInstanceCharacteristics.setSequential(false);
450             }
451             userTask.setLoopCharacteristics(multiInstanceCharacteristics);
452         }
453
454     }
455
456     private static class CopyNodeConvert implements NodeConvert {
457
458         @Override
459         public ServiceTask convert(BpmSimpleModelNodeVO node) {
460             ServiceTask serviceTask = new ServiceTask();
461             serviceTask.setId(node.getId());
462             serviceTask.setName(node.getName());
463             serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
464             serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}");
465
466             // 添加抄送候选人元素
467             addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask);
468             // 添加表单字段权限属性元素
469             addFormFieldsPermission(node.getFieldsPermission(), serviceTask);
470             return serviceTask;
471         }
472
473         @Override
474         public BpmSimpleModelNodeType getType() {
475             return BpmSimpleModelNodeType.COPY_NODE;
476         }
477
478     }
479
480     private static class ConditionBranchNodeConvert implements NodeConvert {
481
482         @Override
483         public ExclusiveGateway convert(BpmSimpleModelNodeVO node) {
484             ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
485             exclusiveGateway.setId(node.getId());
486             // TODO @jason:setName
487
488             // 设置默认的序列流(条件)
489             BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
490                     item -> BooleanUtil.isTrue(item.getDefaultFlow()));
491             Assert.notNull(defaultSeqFlow, "条件分支节点({})的默认序列流不能为空", node.getId());
492             exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
493             return exclusiveGateway;
494         }
495
496         @Override
497         public BpmSimpleModelNodeType getType() {
498             return BpmSimpleModelNodeType.CONDITION_BRANCH_NODE;
499         }
500
501     }
502
503     private static class ParallelBranchNodeConvert implements NodeConvert {
504
505         @Override
506         public List<ParallelGateway> convertList(BpmSimpleModelNodeVO node) {
507             ParallelGateway parallelGateway = new ParallelGateway();
508             parallelGateway.setId(node.getId());
509             // TODO @jason:setName
510
511             // 并行聚合网关由程序创建,前端不需要传入
512             ParallelGateway joinParallelGateway = new ParallelGateway();
513             joinParallelGateway.setId(buildGatewayJoinId(node.getId()));
514             // TODO @jason:setName
515             return CollUtil.newArrayList(parallelGateway, joinParallelGateway);
516         }
517
518         @Override
519         public BpmSimpleModelNodeType getType() {
520             return BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE;
521         }
522
523     }
524
525     private static class InclusiveBranchNodeConvert implements NodeConvert {
526
527         @Override
528         public List<InclusiveGateway> convertList(BpmSimpleModelNodeVO node) {
529             InclusiveGateway inclusiveGateway = new InclusiveGateway();
530             inclusiveGateway.setId(node.getId());
531             // 设置默认的序列流(条件)
532             BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
533                     item -> BooleanUtil.isTrue(item.getDefaultFlow()));
534             Assert.notNull(defaultSeqFlow, "包容分支节点({})的默认序列流不能为空", node.getId());
535             inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
536             // TODO @jason:setName
537
538             // 并行聚合网关由程序创建,前端不需要传入
539             InclusiveGateway joinInclusiveGateway = new InclusiveGateway();
540             joinInclusiveGateway.setId(buildGatewayJoinId(node.getId()));
541             // TODO @jason:setName
542             return CollUtil.newArrayList(inclusiveGateway, joinInclusiveGateway);
543         }
544
545         @Override
546         public BpmSimpleModelNodeType getType() {
547             return BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE;
548         }
549
550     }
551
552     public static class ConditionNodeConvert implements NodeConvert {
553
554         @Override
555         public List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) {
556             // 原因是:正常情况下,它不会被调用到
557             throw new UnsupportedOperationException("条件节点不支持转换");
558         }
559
560         @Override
561         public BpmSimpleModelNodeType getType() {
562             return BpmSimpleModelNodeType.CONDITION_NODE;
563         }
564
565         public static SequenceFlow buildSequenceFlow(String sourceId, String targetId,
566                                                      BpmSimpleModelNodeVO node) {
567             String conditionExpression = buildConditionExpression(node);
568             return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression);
569         }
570
571         /**
572          * 构造条件表达式
573          *
574          * @param node 条件节点
575          */
576         public static String buildConditionExpression(BpmSimpleModelNodeVO node) {
577             BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(node.getConditionType());
578             if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) {
579                 return node.getConditionExpression();
580             }
581             if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) {
582                 BpmSimpleModelNodeVO.ConditionGroups conditionGroups = node.getConditionGroups();
583                 if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) {
584                     return null;
585                 }
586                 List<String> strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> {
587                     if (CollUtil.isEmpty(item.getRules())) {
588                         return "";
589                     }
590                     // 构造规则表达式
591                     List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> {
592                         String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide()
593                                 : "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号
594                         return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide);
595                     });
596                     // 构造条件组的表达式
597                     Boolean and = item.getAnd();
598                     return "(" + CollUtil.join(list, and ? " && " : " || ") + ")";
599                 });
600                 return String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || "));
601             }
602             return null;
603         }
604
605     }
606
607     private static String buildGatewayJoinId(String id) {
608         return id + "_join";
609     }
610
611     // ========== SIMPLE 流程预测相关的方法 ==========
612
613     public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) {
614         List<BpmSimpleModelNodeVO> resultNodes = new ArrayList<>();
615
616         // 从头开始遍历
617         simulateNextNode(rootNode, variables, resultNodes);
618         return resultNodes;
619     }
620
621     private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables,
f4f940 622                                          List<BpmSimpleModelNodeVO> resultNodes) {
bb2880 623         // 如果不合法(包括为空),则直接结束
H 624         if (!isValidNode(currentNode)) {
625             return;
626         }
627         BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentNode.getType());
628         Assert.notNull(nodeType, "模型节点类型不支持");
629
630         // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE
631         if (nodeType == BpmSimpleModelNodeType.START_NODE
f4f940 632                 || nodeType == BpmSimpleModelNodeType.START_USER_NODE
H 633                 || nodeType == BpmSimpleModelNodeType.APPROVE_NODE
634                 || nodeType == BpmSimpleModelNodeType.COPY_NODE
635                 || nodeType == BpmSimpleModelNodeType.END_NODE) {
bb2880 636             // 添加元素
H 637             resultNodes.add(currentNode);
638         }
639
640         // 情况:CONDITION_BRANCH_NODE 排它,只有一个满足条件的。如果没有,就走默认的
641         if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) {
642             // 查找满足条件的 BpmSimpleModelNodeVO 节点
643             BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
644                     conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow())
f4f940 645                             && evalConditionExpress(variables, conditionNode));
bb2880 646             if (matchConditionNode == null) {
H 647                 matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
648                         conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow()));
649             }
650             Assert.notNull(matchConditionNode, "找不到条件节点({})", currentNode);
651             // 遍历满足条件的 BpmSimpleModelNodeVO 节点
652             simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes);
653         }
654
655         // 情况:INCLUSIVE_BRANCH_NODE 包容,多个满足条件的。如果没有,就走默认的
656         if (nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) {
657             // 查找满足条件的 BpmSimpleModelNodeVO 节点
658             Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
659                     conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow())
660                             && evalConditionExpress(variables, conditionNode));
661             if (CollUtil.isEmpty(matchConditionNodes)) {
662                 matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
663                         conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow()));
664             }
665             Assert.isTrue(!matchConditionNodes.isEmpty(), "找不到条件节点({})", currentNode);
666             // 遍历满足条件的 BpmSimpleModelNodeVO 节点
667             matchConditionNodes.forEach(matchConditionNode ->
668                     simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes));
669         }
670
671         // 情况:PARALLEL_BRANCH_NODE 并行,都满足,都走
672         if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE) {
673             // 遍历所有 BpmSimpleModelNodeVO 节点
674             currentNode.getConditionNodes().forEach(matchConditionNode ->
675                     simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes));
676         }
677
678         // 遍历子节点
679         simulateNextNode(currentNode.getChildNode(), variables, resultNodes);
680     }
681
682     public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO conditionNode) {
683         return BpmnModelUtils.evalConditionExpress(variables, ConditionNodeConvert.buildConditionExpression(conditionNode));
684     }
685
686 }