houzhongyi
2024-07-11 e7c1260db32209a078a962aaa0ad5492c35774fb
提交 | 用户 | 时间
e7c126 1 package com.iailab.module.bpm.framework.flowable.core.util;
H 2
3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ArrayUtil;
5 import com.iailab.framework.common.util.number.NumberUtils;
6 import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
7 import org.flowable.bpmn.converter.BpmnXMLConverter;
8 import org.flowable.bpmn.model.Process;
9 import org.flowable.bpmn.model.*;
10 import org.flowable.common.engine.impl.util.io.BytesStreamSource;
11
12 import java.util.ArrayList;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Set;
16
17 /**
18  * 流程模型转操作工具类
19  */
20 public class BpmnModelUtils {
21
22     public static Integer parseCandidateStrategy(FlowElement userTask) {
23         return NumberUtils.parseInt(userTask.getAttributeValue(
24                 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
25     }
26
27     public static String parseCandidateParam(FlowElement userTask) {
28         return userTask.getAttributeValue(
29                 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
30     }
31
32     /**
33      * 根据节点,获取入口连线
34      *
35      * @param source 起始节点
36      * @return 入口连线列表
37      */
38     public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
39         if (source instanceof FlowNode) {
40             return ((FlowNode) source).getIncomingFlows();
41         }
42         return new ArrayList<>();
43     }
44
45     /**
46      * 根据节点,获取出口连线
47      *
48      * @param source 起始节点
49      * @return 出口连线列表
50      */
51     public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
52         if (source instanceof FlowNode) {
53             return ((FlowNode) source).getOutgoingFlows();
54         }
55         return new ArrayList<>();
56     }
57
58     /**
59      * 获取流程元素信息
60      *
61      * @param model         bpmnModel 对象
62      * @param flowElementId 元素 ID
63      * @return 元素信息
64      */
65     public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
66         Process process = model.getMainProcess();
67         return process.getFlowElement(flowElementId);
68     }
69
70     /**
71      * 获得 BPMN 流程中,指定的元素们
72      *
73      * @param model 模型
74      * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
75      * @return 元素们
76      */
77     public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
78         List<T> result = new ArrayList<>();
79         model.getProcesses().forEach(process -> {
80             process.getFlowElements().forEach(flowElement -> {
81                 if (flowElement.getClass().isAssignableFrom(clazz)) {
82                     result.add((T) flowElement);
83                 }
84             });
85         });
86         return result;
87     }
88
89     public static StartEvent getStartEvent(BpmnModel model) {
90         Process process = model.getMainProcess();
91         // 从 initialFlowElement 找
92         FlowElement startElement = process.getInitialFlowElement();
93         if (startElement instanceof StartEvent) {
94             return (StartEvent) startElement;
95         }
96         // 从 flowElementList 找
97         return (StartEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof StartEvent);
98     }
99
100     public static BpmnModel getBpmnModel(byte[] bpmnBytes) {
101         if (ArrayUtil.isEmpty(bpmnBytes)) {
102             return null;
103         }
104         BpmnXMLConverter converter = new BpmnXMLConverter();
105         // 补充说明:由于在 Flowable 中自定义了属性,所以 validateSchema 传递 false
106         return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), false, false);
107     }
108
109     public static String getBpmnXml(BpmnModel model) {
110         if (model == null) {
111             return null;
112         }
113         BpmnXMLConverter converter = new BpmnXMLConverter();
114         return new String(converter.convertToXML(model));
115     }
116
117     // ========== 遍历相关的方法 ==========
118
119     /**
120      * 找到 source 节点之前的所有用户任务节点
121      *
122      * @param source          起始节点
123      * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
124      * @param userTaskList    已找到的用户任务节点
125      * @return 用户任务节点 数组
126      */
127     public static List<UserTask> getPreviousUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
128         userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
129         hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
130         // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
131         if (source instanceof StartEvent && source.getSubProcess() != null) {
132             userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList);
133         }
134
135         // 根据类型,获取入口连线
136         List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
137         if (sequenceFlows == null) {
138             return userTaskList;
139         }
140         // 循环找到目标元素
141         for (SequenceFlow sequenceFlow : sequenceFlows) {
142             // 如果发现连线重复,说明循环了,跳过这个循环
143             if (hasSequenceFlow.contains(sequenceFlow.getId())) {
144                 continue;
145             }
146             // 添加已经走过的连线
147             hasSequenceFlow.add(sequenceFlow.getId());
148             // 类型为用户节点,则新增父级节点
149             if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
150                 userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
151             }
152             // 类型为子流程,则添加子流程开始节点出口处相连的节点
153             if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
154                 // 获取子流程用户任务节点
155                 List<UserTask> childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
156                 // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
157                 if (CollUtil.isNotEmpty(childUserTaskList)) {
158                     userTaskList.addAll(childUserTaskList);
159                 }
160             }
161             // 继续迭代
162             userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
163         }
164         return userTaskList;
165     }
166
167     /**
168      * 迭代获取子流程用户任务节点
169      *
170      * @param source          起始节点
171      * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
172      * @param userTaskList    需要撤回的用户任务列表
173      * @return 用户任务节点
174      */
175     public static List<UserTask> findChildProcessUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
176         hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
177         userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
178
179         // 根据类型,获取出口连线
180         List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
181         if (sequenceFlows == null) {
182             return userTaskList;
183         }
184         // 循环找到目标元素
185         for (SequenceFlow sequenceFlow : sequenceFlows) {
186             // 如果发现连线重复,说明循环了,跳过这个循环
187             if (hasSequenceFlow.contains(sequenceFlow.getId())) {
188                 continue;
189             }
190             // 添加已经走过的连线
191             hasSequenceFlow.add(sequenceFlow.getId());
192             // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
193             if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
194                 userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
195                 continue;
196             }
197             // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
198             if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
199                 List<UserTask> childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
200                 // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
201                 if (CollUtil.isNotEmpty(childUserTaskList)) {
202                     userTaskList.addAll(childUserTaskList);
203                     continue;
204                 }
205             }
206             // 继续迭代
207             userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
208         }
209         return userTaskList;
210     }
211
212
213     /**
214      * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
215      * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况
216      *
217      * @param source          起始节点
218      * @param target          目标节点
219      * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复
220      * @return 结果
221      */
222     public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
223         visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
224         // 不能是开始事件和子流程
225         if (source instanceof StartEvent && isInEventSubprocess(source)) {
226             return false;
227         }
228
229         // 根据类型,获取入口连线
230         List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
231         if (CollUtil.isEmpty(sequenceFlows)) {
232             return true;
233         }
234         // 循环找到目标元素
235         for (SequenceFlow sequenceFlow : sequenceFlows) {
236             // 如果发现连线重复,说明循环了,跳过这个循环
237             if (visitedElements.contains(sequenceFlow.getId())) {
238                 continue;
239             }
240             // 添加已经走过的连线
241             visitedElements.add(sequenceFlow.getId());
242             // 这条线路存在目标节点,这条线路完成,进入下个线路
243             FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
244             if (target.getId().equals(sourceFlowElement.getId())) {
245                 continue;
246             }
247             // 如果目标节点为并行网关,则不继续
248             if (sourceFlowElement instanceof ParallelGateway) {
249                 return false;
250             }
251             // 否则就继续迭代
252             if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) {
253                 return false;
254             }
255         }
256         return true;
257     }
258
259     /**
260      * 判断当前节点是否属于不同的子流程
261      *
262      * @param flowElement 被判断的节点
263      * @return true 表示属于子流程
264      */
265     private static boolean isInEventSubprocess(FlowElement flowElement) {
266         FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
267         while (flowElementsContainer != null) {
268             if (flowElementsContainer instanceof EventSubProcess) {
269                 return true;
270             }
271
272             if (flowElementsContainer instanceof FlowElement) {
273                 flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
274             } else {
275                 flowElementsContainer = null;
276             }
277         }
278         return false;
279     }
280
281     /**
282      * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找
283      *
284      * @param source          起始节点
285      * @param runTaskKeyList  正在运行的任务 Key,用于校验任务节点是否是正在运行的节点
286      * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
287      * @param userTaskList    需要撤回的用户任务列表
288      * @return 子级任务节点列表
289      */
290     public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList,
291                                                             Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
292         hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
293         userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
294         // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
295         if (source instanceof StartEvent && source.getSubProcess() != null) {
296             userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
297         }
298
299         // 根据类型,获取出口连线
300         List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
301         if (sequenceFlows == null) {
302             return userTaskList;
303         }
304         // 循环找到目标元素
305         for (SequenceFlow sequenceFlow : sequenceFlows) {
306             // 如果发现连线重复,说明循环了,跳过这个循环
307             if (hasSequenceFlow.contains(sequenceFlow.getId())) {
308                 continue;
309             }
310             // 添加已经走过的连线
311             hasSequenceFlow.add(sequenceFlow.getId());
312             // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
313             if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
314                 userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
315                 continue;
316             }
317             // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
318             if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
319                 List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
320                 // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
321                 if (CollUtil.isNotEmpty(childUserTaskList)) {
322                     userTaskList.addAll(childUserTaskList);
323                     continue;
324                 }
325             }
326             // 继续迭代
327             userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
328         }
329         return userTaskList;
330     }
331
332 }