2026-07-01 6b5f7c66fc40d7f6099d561e31a34fbd50dd20d3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
 
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import cn.iocoder.yudao.module.bpm.enums.definition.*;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
 
import java.util.*;
 
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
 
/**
 * BPMN Model 操作工具类。目前分成三部分:
 *
 * 1. BPMN 修改 + 解析元素相关的方法
 * 2. BPMN 简单查找相关的方法
 * 3. BPMN 复杂遍历相关的方法
 * 4. BPMN 流程预测相关的方法
 *
 * @author 芋道源码
 */
@Slf4j
public class BpmnModelUtils {
 
    // ========== BPMN 修改 + 解析元素相关的方法 ==========
 
    public static void addExtensionElement(FlowElement element, String name, String value) {
        if (value == null) {
            return;
        }
        ExtensionElement extensionElement = new ExtensionElement();
        extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
        extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
        extensionElement.setElementText(value);
        extensionElement.setName(name);
        element.addExtensionElement(extensionElement);
    }
 
    public static void addExtensionElement(FlowElement element, String name, Integer value) {
        if (value == null) {
            return;
        }
        addExtensionElement(element, name, String.valueOf(value));
    }
 
    public static void addExtensionElementJson(FlowElement element, String name, Object value) {
        if (value == null) {
            return;
        }
        addExtensionElement(element, name, JsonUtils.toJsonString(value));
    }
 
    public static void addExtensionElement(FlowElement element, String name, Map<String, String> attributes) {
        if (attributes == null) {
            return;
        }
        ExtensionElement extensionElement = new ExtensionElement();
        extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
        extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
        extensionElement.setName(name);
        attributes.forEach((key, value) -> {
            ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value);
            extensionElement.addAttribute(extensionAttribute);
        });
        element.addExtensionElement(extensionElement);
    }
 
    /**
     * 解析扩展元素
     *
     * @param flowElement 节点
     * @param elementName 元素名称
     * @return 扩展元素
     */
    public static String parseExtensionElement(FlowElement flowElement, String elementName) {
        if (flowElement == null) {
            return null;
        }
        ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName));
        return element != null ? element.getElementText() : null;
    }
 
    /**
     * 给节点添加候选人元素
     *
     * @param candidateStrategy 候选人策略
     * @param candidateParam 候选人参数,允许空
     * @param flowElement 节点
     */
    public static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) {
        addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
                candidateStrategy == null ? null : candidateStrategy.toString());
        addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam);
    }
 
    /**
     * 解析候选人策略
     *
     * @param userTask 任务节点
     * @return 候选人策略
     */
    public static Integer parseCandidateStrategy(FlowElement userTask) {
        Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue(
                BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
        // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限
        if (candidateStrategy == null) {
            ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
            candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null;
        }
        return candidateStrategy;
    }
 
    /**
     * 解析候选人参数
     *
     * @param userTask 任务节点
     * @return 候选人参数
     */
    public static String parseCandidateParam(FlowElement userTask) {
        String candidateParam = userTask.getAttributeValue(
                BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
        if (candidateParam == null) {
            ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
            candidateParam = element != null ? element.getElementText() : null;
        }
        return candidateParam;
    }
 
    /**
     * 解析审批类型
     *
     * @see BpmUserTaskApproveTypeEnum
     * @param userTask 任务节点
     * @return 审批类型
     */
    public static Integer parseApproveType(FlowElement userTask) {
        return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE));
    }
 
    /**
     * 解析子流程多实例来源类型
     *
     * @see BpmChildProcessMultiInstanceSourceTypeEnum
     * @param element 任务节点
     * @return 多实例来源类型
     */
    public static Integer parseMultiInstanceSourceType(FlowElement element) {
        return NumberUtils.parseInt(parseExtensionElement(element, BpmnModelConstants.CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE));
    }
 
    /**
     * 添加任务拒绝处理元素
     *
     * @param rejectHandler 任务拒绝处理
     * @param userTask 任务节点
     */
    public static void addTaskRejectElements(BpmSimpleModelNodeVO.RejectHandler rejectHandler, UserTask userTask) {
        if (rejectHandler == null) {
            return;
        }
        addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType()));
        addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId());
    }
 
    /**
     * 解析任务拒绝处理类型
     *
     * @param userTask 任务节点
     * @return 任务拒绝处理类型
     */
    public static BpmUserTaskRejectHandlerTypeEnum parseRejectHandlerType(FlowElement userTask) {
        Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
        return BpmUserTaskRejectHandlerTypeEnum.typeOf(rejectHandlerType);
    }
 
    /**
     * 解析任务拒绝返回任务节点 ID
     *
     * @param flowElement 任务节点
     * @return 任务拒绝返回任务节点 ID
     */
    public static String parseReturnTaskId(FlowElement flowElement) {
        return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
    }
 
    /**
     * 给节点添加用户任务的审批人与发起人相同时,处理类型枚举
     *
     * @see BpmUserTaskAssignStartUserHandlerTypeEnum
     * @param assignStartUserHandlerType 发起人处理类型
     * @param userTask 任务节点
     */
    public static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) {
        if (assignStartUserHandlerType == null) {
            return;
        }
        addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString());
    }
 
    /**
     * 给节点添加用户任务的审批人为空时,处理类型枚举
     *
     * @see BpmUserTaskAssignEmptyHandlerTypeEnum
     * @param emptyHandler 空处理
     * @param userTask 任务节点
     */
    public static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) {
        if (emptyHandler == null) {
            return;
        }
        addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType()));
        addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds()));
    }
 
    /**
     * 解析用户任务的审批人与发起人相同时,处理类型枚举
     *
     * @param userTask 任务节点
     * @return 处理类型枚举
     */
    public static Integer parseAssignStartUserHandlerType(FlowElement userTask) {
        return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE));
    }
 
    /**
     * 解析用户任务的审批人为空时,处理类型枚举
     *
     * @param userTask 任务节点
     * @return 处理类型枚举
     */
    public static Integer parseAssignEmptyHandlerType(FlowElement userTask) {
        return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE));
    }
 
    /**
     * 解析用户任务的审批人为空时,处理用户 ID 数组
     *
     * @param userTask 任务节点
     * @return 处理用户 ID 数组
     */
    public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) {
        return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ",");
    }
 
    /**
     * 给节点添加表单字段权限元素
     *
     * @param fieldsPermissions 表单字段权限
     * @param flowElement 节点
     */
    public static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) {
        if (CollUtil.isNotEmpty(fieldsPermissions)) {
            fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item));
        }
    }
 
    /**
     * 解析表单字段权限
     *
     * @param bpmnModel bpmnModel 对象
     * @param flowElementId 元素 ID
     * @return 表单字段权限
     */
    public static Map<String, String> parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) {
        if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) {
            return null;
        }
        FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
        if (flowElement == null) {
            return null;
        }
        List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(FORM_FIELD_PERMISSION_ELEMENT);
        if (CollUtil.isEmpty(extensionElements)) {
            return null;
        }
        Map<String, String> fieldsPermission = MapUtil.newHashMap();
        extensionElements.forEach(element -> {
            String field = element.getAttributeValue(null, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE);
            String permission = element.getAttributeValue(null, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE);
            if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) {
                fieldsPermission.put(field, permission);
            }
        });
        return fieldsPermission;
    }
 
    /**
     * 给节点添加操作按钮设置元素
     */
    public static void addButtonsSetting(List<BpmSimpleModelNodeVO.OperationButtonSetting> buttonsSetting, UserTask userTask) {
        if (CollUtil.isNotEmpty(buttonsSetting)) {
            List<Map<String, String>> list = CollectionUtils.convertList(buttonsSetting, item -> {
                Map<String, String> settingMap = Maps.newHashMapWithExpectedSize(3);
                settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId()));
                settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName());
                settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable()));
                return settingMap;
            });
            list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item));
        }
    }
 
    /**
     * 解析操作按钮设置
     *
     * @param bpmnModel bpmnModel 对象
     * @param flowElementId 元素 ID
     * @return 操作按钮设置
     */
    public static Map<Integer, BpmTaskRespVO.OperationButtonSetting> parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) {
        FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
        if (flowElement == null) {
            return null;
        }
        List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(BUTTON_SETTING_ELEMENT);
        if (CollUtil.isEmpty(extensionElements)) {
            return null;
        }
        Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size());
        extensionElements.forEach(element -> {
            String id = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
            String displayName = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
            String enable = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE);
            if (StrUtil.isNotEmpty(id)) {
                BpmTaskRespVO.OperationButtonSetting setting = new BpmTaskRespVO.OperationButtonSetting();
                buttonSettings.put(Integer.valueOf(id), setting.setDisplayName(displayName).setEnable(Boolean.parseBoolean(enable)));
            }
        });
        return buttonSettings;
    }
 
    /**
     * 解析边界事件扩展元素
     *
     * @param boundaryEvent 边界事件
     * @param customElement 元素
     * @return 扩展元素
     */
    public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) {
        if (boundaryEvent == null) {
            return null;
        }
        ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement));
        return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null);
    }
 
    public static void addSignEnable(Boolean signEnable, FlowElement userTask) {
        addExtensionElement(userTask, SIGN_ENABLE,
                ObjUtil.isNotNull(signEnable) ? signEnable.toString() : Boolean.FALSE.toString());
    }
 
    public static Boolean parseSignEnable(BpmnModel bpmnModel, String flowElementId) {
        FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
        if (flowElement == null) {
            return false;
        }
        List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(SIGN_ENABLE);
        if (CollUtil.isEmpty(extensionElements)) {
            return false;
        }
        return Convert.toBool(extensionElements.get(0).getElementText(), false);
    }
 
    public static void addReasonRequire(Boolean reasonRequire, FlowElement userTask) {
        addExtensionElement(userTask, REASON_REQUIRE,
                ObjUtil.isNotNull(reasonRequire) ? reasonRequire.toString() : Boolean.FALSE.toString());
    }
 
    public static Boolean parseReasonRequire(BpmnModel bpmnModel, String flowElementId) {
        FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
        if (flowElement == null) {
            return false;
        }
        List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(REASON_REQUIRE);
        if (CollUtil.isEmpty(extensionElements)) {
            return false;
        }
        return Convert.toBool(extensionElements.get(0).getElementText(), false);
    }
 
    public static void addListenerConfig(FlowableListener flowableListener, BpmSimpleModelNodeVO.ListenerHandler handler) {
        FieldExtension fieldExtension = new FieldExtension();
        fieldExtension.setFieldName("listenerConfig");
        fieldExtension.setStringValue(JsonUtils.toJsonString(handler));
        flowableListener.getFieldExtensions().add(fieldExtension);
    }
 
    public static BpmSimpleModelNodeVO.ListenerHandler parseListenerConfig(Expression fixedValue) {
        String expressionText = fixedValue.getExpressionText();
        Assert.notNull(expressionText, "监听器扩展字段({})不能为空", expressionText);
        return JsonUtils.parseObject(expressionText, BpmSimpleModelNodeVO.ListenerHandler.class);
    }
 
    public static BpmTriggerTypeEnum parserTriggerType(FlowElement flowElement) {
        Integer triggerType = NumberUtils.parseInt(parseExtensionElement(flowElement, TRIGGER_TYPE));
        return BpmTriggerTypeEnum.typeOf(triggerType);
    }
 
    public static String parserTriggerParam(FlowElement flowElement) {
        return parseExtensionElement(flowElement, TRIGGER_PARAM);
    }
 
    /**
     * 给节点添加节点类型
     *
     * @param nodeType 节点类型
     * @param flowElement 节点
     */
    public static void addNodeType(Integer nodeType, FlowElement flowElement) {
        addExtensionElement(flowElement, BpmnModelConstants.NODE_TYPE, nodeType);
    }
 
    /**
     * 解析节点类型
     *
     * @param flowElement 节点
     * @return 节点类型
     */
    public static Integer parseNodeType(FlowElement flowElement) {
        return NumberUtils.parseInt(parseExtensionElement(flowElement, BpmnModelConstants.NODE_TYPE));
    }
 
    // ========== BPM 简单查找相关的方法 ==========
 
    /**
     * 根据节点,获取入口连线
     *
     * @param source 起始节点
     * @return 入口连线列表
     */
    public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
        if (source instanceof FlowNode) {
            return ((FlowNode) source).getIncomingFlows();
        }
        return new ArrayList<>();
    }
 
    /**
     * 根据节点,递归获取上游 source 为 UserTask 的入口连线
     *
     * 1. 如果当前节点的直接入口连线 source 就是 UserTask,则直接返回该连线
     * 2. 如果当前节点的直接入口连线 source 不是 UserTask,则继续向上递归查找
     * 3. 如果递归过程中遇到 StartEvent 或 SubProcess,则停止该分支继续向上查找
     *
     * @param source 起始节点
     * @return 上游连接 UserTask 的入口连线列表
     */
    public static List<SequenceFlow> getElementIncomingUserTaskFlows(FlowElement source) {
        List<SequenceFlow> result = new ArrayList<>();
        collectElementIncomingUserTaskFlows(source, new HashSet<>(), new HashSet<>(), result);
        return result;
    }
 
    private static void collectElementIncomingUserTaskFlows(FlowElement source, Set<String> visitedSequenceFlowIds,
                                                            Set<String> resultSequenceFlowIds, List<SequenceFlow> result) {
        // 如果是开始节点或子流程,则停止该分支向上查找
        if (source == null || source instanceof StartEvent || source instanceof SubProcess) {
            return;
        }
        // 获取入口连线
        List<SequenceFlow> incomingFlows = getElementIncomingFlows(source);
        if (CollUtil.isEmpty(incomingFlows)) {
            return;
        }
 
        // 循环找到目标元素
        for (SequenceFlow incomingFlow : incomingFlows) {
            // 如果发现连线重复,说明连线已经走过。跳过
            if (incomingFlow == null || !visitedSequenceFlowIds.add(incomingFlow.getId())) {
                continue;
            }
            // 如果 source 是 UserTask,则添加到结果中
            FlowElement sourceFlowElement = incomingFlow.getSourceFlowElement();
            if (sourceFlowElement instanceof UserTask) {
                if (resultSequenceFlowIds.add(incomingFlow.getId())) {
                    result.add(incomingFlow);
                }
                continue;
            }
            // 递归向上查找 UserTask
            collectElementIncomingUserTaskFlows(sourceFlowElement, visitedSequenceFlowIds,
                    resultSequenceFlowIds, result);
        }
    }
 
    /**
     * 根据节点,获取出口连线
     *
     * @param source 起始节点
     * @return 出口连线列表
     */
    public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
        if (source instanceof FlowNode) {
            return ((FlowNode) source).getOutgoingFlows();
        }
        return new ArrayList<>();
    }
 
    /**
     * 获取流程元素信息
     *
     * @param model         bpmnModel 对象
     * @param flowElementId 元素 ID
     * @return 元素信息
     */
    public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
        Process process = model.getMainProcess();
        FlowElement flowElement = process.getFlowElement(flowElementId);
        if (flowElement != null) {
            return flowElement;
        }
        return model.getFlowElement(flowElementId);
    }
 
    /**
     * 获得 BPMN 流程中,指定的元素们
     *
     * @param model 模型
     * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
     * @return 元素们
     */
    @SuppressWarnings("unchecked")
    public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
        List<T> result = new ArrayList<>();
        model.getProcesses().forEach(process -> process.getFlowElements().forEach(flowElement -> {
            if (flowElement.getClass().isAssignableFrom(clazz)) {
                result.add((T) flowElement);
            }
        }));
        return result;
    }
 
    public static StartEvent getStartEvent(BpmnModel model) {
        Process process = model.getMainProcess();
        // 从 initialFlowElement 找
        FlowElement startElement = process.getInitialFlowElement();
        if (startElement instanceof StartEvent) {
            return (StartEvent) startElement;
        }
        // 从 flowElementList 找
        return (StartEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof StartEvent);
    }
 
    public static EndEvent getEndEvent(BpmnModel model) {
        Process process = model.getMainProcess();
        // 从 flowElementList 找 endEvent
        return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent);
    }
 
    public static BpmnModel getBpmnModel(byte[] bpmnBytes) {
        if (ArrayUtil.isEmpty(bpmnBytes)) {
            return null;
        }
        BpmnXMLConverter converter = new BpmnXMLConverter();
        // 补充说明:由于在 Flowable 中自定义了属性,所以 validateSchema 传递 false
        return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), false, false);
    }
 
    public static String getBpmnXml(BpmnModel model) {
        if (model == null) {
            return null;
        }
        BpmnXMLConverter converter = new BpmnXMLConverter();
        return StrUtil.utf8Str(converter.convertToXML(model));
    }
 
    public static String getBpmnXml(byte[] bpmnBytes) {
        if (ArrayUtil.isEmpty(bpmnBytes)) {
            return null;
        }
        return StrUtil.utf8Str(bpmnBytes);
    }
 
    // ========== BPMN 复杂遍历相关的方法 ==========
 
    /**
     * 找到 source 节点之前的所有用户任务节点
     *
     * @param source          起始节点
     * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
     * @param userTaskList    已找到的用户任务节点
     * @return 用户任务节点 数组
     */
    public static List<UserTask> getPreviousUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList);
        }
 
        // 根据类型,获取入口连线
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows == null) {
            return userTaskList;
        }
        // 循环找到目标元素
        for (SequenceFlow sequenceFlow : sequenceFlows) {
            // 如果发现连线重复,说明循环了,跳过这个循环
            if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                continue;
            }
            // 添加已经走过的连线
            hasSequenceFlow.add(sequenceFlow.getId());
            // 类型为用户节点,则新增父级节点
            if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
                userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
            }
            // 类型为子流程,则添加子流程开始节点出口处相连的节点
            if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
                // 获取子流程用户任务节点
                List<UserTask> childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
                // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
                if (CollUtil.isNotEmpty(childUserTaskList)) {
                    userTaskList.addAll(childUserTaskList);
                }
            }
            // 继续迭代
            userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
        }
        return userTaskList;
    }
 
    /**
     * 迭代获取子流程用户任务节点
     *
     * @param source          起始节点
     * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
     * @param userTaskList    需要撤回的用户任务列表
     * @return 用户任务节点
     */
    public static List<UserTask> findChildProcessUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
 
        // 根据类型,获取出口连线
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows == null) {
            return userTaskList;
        }
        // 循环找到目标元素
        for (SequenceFlow sequenceFlow : sequenceFlows) {
            // 如果发现连线重复,说明循环了,跳过这个循环
            if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                continue;
            }
            // 添加已经走过的连线
            hasSequenceFlow.add(sequenceFlow.getId());
            // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
            if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
                userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
                continue;
            }
            // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
            if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                List<UserTask> childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
                // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
                if (CollUtil.isNotEmpty(childUserTaskList)) {
                    userTaskList.addAll(childUserTaskList);
                    continue;
                }
            }
            // 继续迭代
            userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
        }
        return userTaskList;
    }
 
    /**
     * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
     * 不存在直接退回到子流程中的情况,但存在从子流程出去到父流程情况
     *
     * @param source          起始节点
     * @param target          目标节点
     * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复
     * @return 结果
     */
    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
        visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
        // 不能是开始事件和子流程
        if (source instanceof StartEvent && isInEventSubprocess(source)) {
            return false;
        }
 
        // 根据类型,获取入口连线
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        // 1. 没有入口连线,则返回 false
        if (CollUtil.isEmpty(sequenceFlows)) {
            return false;
        }
        // 2. 循环找目标元素, 找到目标节点
        for (SequenceFlow sequenceFlow : sequenceFlows) {
            // 如果发现连线重复,说明循环了,跳过这个循环
            if (visitedElements.contains(sequenceFlow.getId())) {
                continue;
            }
            // 添加已经走过的连线
            visitedElements.add(sequenceFlow.getId());
            // 这条线路存在目标节点,直接返回 true
            FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
            if (target.getId().equals(sourceFlowElement.getId())) {
               return true;
            }
            // 如果目标节点为并行网关,跳过这个循环 (TODO 疑问:这个判断作用是防止回退到并行网关分支上的节点吗?)
            if (sourceFlowElement instanceof ParallelGateway) {
                continue;
            }
            // 继续迭代,如果找到目标节点直接返回 true
            if (isSequentialReachable(sourceFlowElement, target, visitedElements)) {
                return true;
            }
        }
        // 未找到返回 false
        return false;
    }
 
    /**
     * 判断当前节点是否属于不同的子流程
     *
     * @param flowElement 被判断的节点
     * @return true 表示属于子流程
     */
    private static boolean isInEventSubprocess(FlowElement flowElement) {
        FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
        while (flowElementsContainer != null) {
            if (flowElementsContainer instanceof EventSubProcess) {
                return true;
            }
 
            if (flowElementsContainer instanceof FlowElement) {
                flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
            } else {
                flowElementsContainer = null;
            }
        }
        return false;
    }
 
    /**
     * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找
     *
     * @param source          起始节点
     * @param runTaskKeyList  正在运行的任务 Key,用于校验任务节点是否是正在运行的节点
     * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
     * @param userTaskList    需要撤回的用户任务列表
     * @return 子级任务节点列表
     */
    public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList,
                                                            Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
        // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
        }
 
        // 根据类型,获取出口连线
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (sequenceFlows == null) {
            return userTaskList;
        }
        // 循环找到目标元素
        for (SequenceFlow sequenceFlow : sequenceFlows) {
            // 如果发现连线重复,说明循环了,跳过这个循环
            if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                continue;
            }
            // 添加已经走过的连线
            hasSequenceFlow.add(sequenceFlow.getId());
            // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
            if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
                userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
                continue;
            }
            // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
            if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
                // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
                if (CollUtil.isNotEmpty(childUserTaskList)) {
                    userTaskList.addAll(childUserTaskList);
                    continue;
                }
            }
            // 继续迭代
            userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
        }
        return userTaskList;
    }
 
    // ========== BPMN 流程预测相关的方法 ==========
 
    /**
     * 流程预测,返回 StartEvent、UserTask、ServiceTask、EndEvent 节点元素,最终是 List 串行结果
     *
     * @param bpmnModel BPMN 图
     * @param variables 变量
     * @return 节点元素数组
     */
    public static List<FlowElement> simulateProcess(BpmnModel bpmnModel, Map<String, Object> variables) {
        List<FlowElement> resultElements = new ArrayList<>();
        Set<FlowElement> visitElements = new HashSet<>();
 
        // 从 StartEvent 开始遍历
        StartEvent startEvent = getStartEvent(bpmnModel);
        simulateNextFlowElements(startEvent, variables, resultElements, visitElements);
 
        // 将 EndEvent 放在末尾。原因是,DFS 遍历,可能 EndEvent 在 resultElements 中
        List<FlowElement> endEvents = CollUtil.removeWithAddIf(resultElements,
                flowElement -> flowElement instanceof EndEvent);
        resultElements.addAll(endEvents);
        return resultElements;
    }
 
    private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables,
                                                 List<FlowElement> resultElements, Set<FlowElement> visitElements) {
        // 如果为空,或者已经遍历过,则直接结束
        if (currentElement == null) {
            return;
        }
        if (visitElements.contains(currentElement)) {
            return;
        }
        visitElements.add(currentElement);
 
        // 情况:StartEvent/EndEvent/UserTask/ServiceTask
        if (currentElement instanceof StartEvent
            || currentElement instanceof EndEvent
            || currentElement instanceof UserTask
            || currentElement instanceof ServiceTask) {
            // 添加节点
            FlowNode flowNode = (FlowNode) currentElement;
            resultElements.add(flowNode);
 
            // 遍历子节点
            flowNode.getOutgoingFlows().forEach(
                    nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
            return;
        }
 
        // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的
        if (currentElement instanceof ExclusiveGateway) {
            // 查找满足条件的 SequenceFlow 路径
            SequenceFlow matchSequenceFlow = findMatchSequenceFlowByExclusiveGateway((Gateway) currentElement, variables);
            // 遍历满足条件的 SequenceFlow 路径
            if (matchSequenceFlow != null) {
                simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
            }
        }
        // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的
        else if (currentElement instanceof InclusiveGateway) {
            // 查找满足条件的 SequenceFlow 路径
            Collection<SequenceFlow> matchSequenceFlows = findMatchSequenceFlowsByInclusiveGateway((Gateway) currentElement, variables);
            // 遍历满足条件的 SequenceFlow 路径
            matchSequenceFlows.forEach(
                    flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
        }
        // 情况:ParallelGateway 并行,都满足,都走
        else if (currentElement instanceof ParallelGateway) {
            Gateway gateway = (Gateway) currentElement;
            // 遍历子节点
            gateway.getOutgoingFlows().forEach(
                    nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
        }
    }
 
    /**
     * 判断是否跳过此节点
     *
     * @param flowNode 节点
     * @param variables 流程变量
     */
    public static boolean isSkipNode(FlowElement flowNode, Map<String, Object> variables) {
        // 1. 检查节点是否有跳过表达式(支持多种任务节点类型)
        String skipExpression = null;
        if (flowNode instanceof UserTask) {
            skipExpression = ((UserTask) flowNode).getSkipExpression();
        } else if (flowNode instanceof ServiceTask) {
            skipExpression = ((ServiceTask) flowNode).getSkipExpression();
        } else if (flowNode instanceof ScriptTask) {
            skipExpression = ((ScriptTask) flowNode).getSkipExpression();
        }
 
        if (StrUtil.isEmpty(skipExpression)) {
            return false;
        }
 
        // 2. 计算跳过表达式的值
        return evalConditionExpress(variables, skipExpression);
    }
 
    /**
     * 根据当前节点,获取下一个节点
     *
     * @param currentElement 当前节点
     * @param bpmnModel  BPMN模型
     * @param variables 流程变量
     */
    @SuppressWarnings("PatternVariableCanBeUsed")
    public static List<FlowNode> getNextFlowNodes(FlowElement currentElement, BpmnModel bpmnModel,
                                                  Map<String, Object> variables){
        List<FlowNode> nextFlowNodes = new ArrayList<>(); // 下一个执行的流程节点集合
        FlowNode currentNode = (FlowNode) currentElement;  // 当前执行节点的基本属性
        List<SequenceFlow> outgoingFlows = currentNode.getOutgoingFlows();  // 当前节点的关联节点
        if (CollUtil.isEmpty(outgoingFlows)) {
            log.warn("[getNextFlowNodes][当前节点({}) 的 outgoingFlows 为空]", currentNode.getId());
            return nextFlowNodes;
        }
 
        // 遍历每个出口流
        for (SequenceFlow outgoingFlow : outgoingFlows) {
            // 获取目标节点的基本属性
            FlowElement targetElement = bpmnModel.getFlowElement(outgoingFlow.getTargetRef());
            if (targetElement == null) {
                continue;
            }
            // 如果是结束节点,直接返回
            if (targetElement instanceof EndEvent) {
                break;
            }
            // 情况一:处理不同类型的网关
            if (targetElement instanceof Gateway) {
                Gateway gateway = (Gateway) targetElement;
                if (gateway instanceof ExclusiveGateway) {
                    handleExclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
                } else if (gateway instanceof InclusiveGateway) {
                    handleInclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
                } else if (gateway instanceof ParallelGateway) {
                    handleParallelGateway(gateway, bpmnModel, variables, nextFlowNodes);
                }
            } else {
                // 情况二:如果不是网关,直接添加到下一个节点列表
                nextFlowNodes.add((FlowNode) targetElement);
            }
        }
        return nextFlowNodes;
    }
 
    /**
     * 查找起始节点下一个用户任务列表, 该方法会递归向下查找:跳过非 UserTask 节点, 支持网关条件判断
     *
     * <ul>
     *     <li>排他网关:走满足条件的唯一一条路径(含默认路径兜底)</li>
     *     <li>包容网关:走所有满足条件的路径</li>
     *     <li>并行网关:走所有出口路径</li>
     * </ul>
     *
     * @param currentElement 当前节点
     * @param bpmnModel      BPMN 模型
     * @param variables      流程变量(用于网关条件判断)
     * @return 下一个用户任务节点列表
     */
    public static List<UserTask> getNextUserTasks(FlowElement currentElement, BpmnModel bpmnModel,
                                                      Map<String, Object> variables) {
        return getNextUserTasks(currentElement, bpmnModel, variables, new HashSet<>(), new ArrayList<>());
    }
 
    private static List<UserTask> getNextUserTasks(FlowElement currentElement, BpmnModel bpmnModel,
                                                       Map<String, Object> variables,
                                                       Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        // 1. 根据节点类型决定要遍历的出口连线
        //    网关需要根据条件表达式筛选;其它节点直接取所有 outgoing flows
        List<SequenceFlow> outgoingFlows;
        if (currentElement instanceof Gateway) {
            outgoingFlows = getGatewayOutgoingFlows((Gateway) currentElement, variables);
        } else {
            outgoingFlows = getElementOutgoingFlows(currentElement);
        }
        if (CollUtil.isEmpty(outgoingFlows)) {
            return userTaskList;
        }
        // 2. 遍历出口连线,递归查找用户任务
        for (SequenceFlow outgoingFlow : outgoingFlows) {
            // 防止连线成环导致死循环
            if (hasSequenceFlow.contains(outgoingFlow.getId())) {
                continue;
            }
            hasSequenceFlow.add(outgoingFlow.getId());
            // 获取目标节点
            FlowElement targetElement = bpmnModel.getFlowElement(outgoingFlow.getTargetRef());
            if (targetElement == null || targetElement instanceof EndEvent) {
                continue;
            }
            if (targetElement instanceof UserTask) {
                // 找到用户任务:加入结果
                userTaskList.add((UserTask) targetElement);
            } else {
                // 非用户任务(网关、服务任务、中间事件等):继续递归向下查找
                getNextUserTasks(targetElement, bpmnModel, variables, hasSequenceFlow, userTaskList);
            }
        }
        return userTaskList;
    }
 
    /**
     * 根据网关类型与流程变量,筛选网关的出口连线
     *
     * @param gateway   网关节点
     * @param variables 流程变量
     * @return 命中的出口连线列表
     */
    private static List<SequenceFlow> getGatewayOutgoingFlows(Gateway gateway, Map<String, Object> variables) {
        if (gateway instanceof ExclusiveGateway) {
            SequenceFlow matchFlow = findMatchSequenceFlowByExclusiveGateway(gateway, variables);
            return matchFlow == null ? Collections.emptyList() : Collections.singletonList(matchFlow);
        }
        if (gateway instanceof InclusiveGateway) {
            return new ArrayList<>(findMatchSequenceFlowsByInclusiveGateway(gateway, variables));
        }
        // 默认(并行网关等):走所有出口
        return gateway.getOutgoingFlows();
    }
 
    /**
     * 查找起始节点下一个用户任务列表列表, 不判断网关条件
     *
     * @param source 起始节点
     * @return 结果
     */
    public static List<UserTask> getNextUserTasks(FlowElement source) {
        return getNextUserTasks(source, new HashSet<>(), new ArrayList<>());
    }
 
    /**
     * 查找起始节点下一个用户任务列表列表
     * @param source 起始节点
     * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
     * @param userTaskList 用户任务列表
     * @return 结果
     */
    public static List<UserTask> getNextUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>());
        userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>());
        // 获取出口连线
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (!sequenceFlows.isEmpty()) {
            for (SequenceFlow sequenceFlow : sequenceFlows) {
                // 如果发现连线重复,说明循环了,跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // 添加已经走过的连线
                hasSequenceFlow.add(sequenceFlow.getId());
                FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
                if (targetFlowElement instanceof UserTask) {
                    // 若节点为用户任务,加入到结果列表中
                    userTaskList.add((UserTask) targetFlowElement);
                } else {
                    // 若节点非用户任务,继续递归查找下一个节点
                    getNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList);
                }
            }
        }
        return userTaskList;
    }
 
    /**
     * 处理排它网关
     *
     * @param gateway 排他网关
     * @param bpmnModel BPMN模型
     * @param variables 流程变量
     * @param nextFlowNodes 下一个执行的流程节点集合
     */
    private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
                                               Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
        // 查找满足条件的 SequenceFlow 路径
        SequenceFlow matchSequenceFlow = findMatchSequenceFlowByExclusiveGateway(gateway, variables);
        // 遍历满足条件的 SequenceFlow 路径
        if (matchSequenceFlow != null) {
            FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef());
            if (targetElement instanceof FlowNode) {
                nextFlowNodes.add((FlowNode) targetElement);
            }
        }
    }
 
    /**
     * 处理排它网关(Exclusive Gateway),选择符合条件的路径
     *
     * @param gateway 排他网关
     * @param variables 流程变量
     * @return 符合条件的路径
     */
    private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map<String, Object> variables) {
        SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
                    flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
                            && (evalConditionExpress(variables, flow.getConditionExpression())));
        if (matchSequenceFlow == null) {
            matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
                    flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
            // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
            if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
                matchSequenceFlow = gateway.getOutgoingFlows().get(0);
            }
        }
        return matchSequenceFlow;
    }
 
    /**
     * 处理包容网关
     *
     * @param gateway 排他网关
     * @param bpmnModel BPMN模型
     * @param variables 流程变量
     * @param nextFlowNodes 下一个执行的流程节点集合
     */
    private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
                                               Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
        // 查找满足条件的 SequenceFlow 路径集合
        Collection<SequenceFlow> matchSequenceFlows = findMatchSequenceFlowsByInclusiveGateway(gateway, variables);
        // 遍历满足条件的 SequenceFlow 路径,获取目标节点
        matchSequenceFlows.forEach(flow -> {
            FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
            if (targetElement instanceof FlowNode) {
                nextFlowNodes.add((FlowNode) targetElement);
            }
        });
    }
 
    /**
     * 处理排它网关(Inclusive Gateway),选择符合条件的路径
     *
     * @param gateway 排他网关
     * @param variables 流程变量
     * @return 符合条件的路径
     */
    private static Collection<SequenceFlow> findMatchSequenceFlowsByInclusiveGateway(Gateway gateway, Map<String, Object> variables) {
        // 查找满足条件的 SequenceFlow 路径
        Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
                flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
                        && evalConditionExpress(variables, flow.getConditionExpression()));
        if (CollUtil.isEmpty(matchSequenceFlows)) {
            matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
                    flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
            // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
            if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
                matchSequenceFlows = gateway.getOutgoingFlows();
            }
        }
        return matchSequenceFlows;
    }
 
 
    /**
     * 处理并行网关
     *
     * @param gateway 排他网关
     * @param bpmnModel BPMN模型
     * @param variables 流程变量
     * @param nextFlowNodes 下一个执行的流程节点集合
     */
    private static void handleParallelGateway(Gateway gateway, BpmnModel bpmnModel,
                                              Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
        // 并行网关,遍历所有出口路径,获取目标节点
        gateway.getOutgoingFlows().forEach(flow -> {
            FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
            if (targetElement instanceof FlowNode) {
                nextFlowNodes.add((FlowNode) targetElement);
            }
        });
    }
 
    /**
     * 计算条件表达式是否为 true 满足条件
     *
     * @param variables 流程实例
     * @param expression 条件表达式
     * @return 是否满足条件
     */
    public static boolean evalConditionExpress(Map<String, Object> variables, String expression) {
        if (StrUtil.isEmpty(expression)) {
            return Boolean.FALSE;
        }
        // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables
        if (variables == null) {
            variables = new HashMap<>();
        }
 
        // 执行计算
        try {
            Object result = FlowableUtils.getExpressionValue(variables, expression);
            return Boolean.TRUE.equals(result);
        } catch (FlowableException ex) {
            // 为什么使用 info 日志?原因是,expression 如果从 variables 取不到值,会报错。实际这种情况下,可以忽略
            log.info("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", expression, variables, ex);
            return Boolean.FALSE;
        }
    }
 
    @SuppressWarnings("PatternVariableCanBeUsed")
    public static boolean isSequentialUserTask(FlowElement flowElement) {
        if (!(flowElement instanceof UserTask)) {
            return false;
        }
        UserTask userTask = (UserTask) flowElement;
        MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics();
        return loopCharacteristics != null && loopCharacteristics.isSequential();
    }
 
}