提交 | 用户 | 时间
|
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 |
} |