From ed8fc5f674544d3af63c6f68093ffc038385c493 Mon Sep 17 00:00:00 2001
From: dongyukun <1208714201@qq.com>
Date: 星期一, 16 十二月 2024 09:30:04 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java |  508 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 486 insertions(+), 22 deletions(-)

diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
index b0a4c83..2a79423 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -1,33 +1,353 @@
 package com.iailab.module.bpm.framework.flowable.core.util;
 
 import cn.hutool.core.collection.CollUtil;
+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 com.google.common.collect.Maps;
+import com.iailab.framework.common.util.collection.CollectionUtils;
 import com.iailab.framework.common.util.number.NumberUtils;
+import com.iailab.framework.common.util.string.StrUtils;
+import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import com.iailab.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum;
+import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
+import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
+import com.iailab.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
 import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
+import lombok.extern.slf4j.Slf4j;
 import org.flowable.bpmn.converter.BpmnXMLConverter;
 import org.flowable.bpmn.model.Process;
 import org.flowable.bpmn.model.*;
+import org.flowable.common.engine.api.FlowableException;
 import org.flowable.common.engine.impl.util.io.BytesStreamSource;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+
+import static com.iailab.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 iailab
  */
+@Slf4j
 public class BpmnModelUtils {
 
-    public static Integer parseCandidateStrategy(FlowElement userTask) {
-        return NumberUtils.parseInt(userTask.getAttributeValue(
-                BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
+    // ========== 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 String parseCandidateParam(FlowElement userTask) {
-        return userTask.getAttributeValue(
-                BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
+    public static void addExtensionElement(FlowElement element, String name, Integer value) {
+        if (value == null) {
+            return;
+        }
+        addExtensionElement(element, name, String.valueOf(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);
+            extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
+            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));
+    }
+
+    /**
+     * 添加任务拒绝处理元素
+     *
+     * @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 BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) {
+        Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
+        return BpmUserTaskRejectHandlerType.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(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE);
+            String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, 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(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
+            String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
+            String enable = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, 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);
+    }
+
+    // ========== BPM 简单查找相关的方法 ==========
 
     /**
      * 根据节点,获取入口连线
@@ -74,15 +394,14 @@
      * @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);
-                }
-            });
-        });
+        model.getProcesses().forEach(process -> process.getFlowElements().forEach(flowElement -> {
+            if (flowElement.getClass().isAssignableFrom(clazz)) {
+                result.add((T) flowElement);
+            }
+        }));
         return result;
     }
 
@@ -95,6 +414,12 @@
         }
         // 从 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) {
@@ -111,10 +436,17 @@
             return null;
         }
         BpmnXMLConverter converter = new BpmnXMLConverter();
-        return new String(converter.convertToXML(model));
+        return StrUtil.utf8Str(converter.convertToXML(model));
     }
 
-    // ========== 遍历相关的方法 ==========
+    public static String getBpmnXml(byte[] bpmnBytes) {
+        if (ArrayUtil.isEmpty(bpmnBytes)) {
+            return null;
+        }
+        return StrUtil.utf8Str(bpmnBytes);
+    }
+
+    // ========== BPMN 复杂遍历相关的方法 ==========
 
     /**
      * 找到 source 节点之前的所有用户任务节点
@@ -209,16 +541,16 @@
         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;
         // 不能是开始事件和子流程
@@ -329,4 +661,136 @@
         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;
+    }
+
+    @SuppressWarnings("PatternVariableCanBeUsed")
+    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 路径
+            Gateway gateway = (Gateway) currentElement;
+            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.notEqual(gateway.getDefaultFlow(), flow.getId()));
+                // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
+                if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
+                    matchSequenceFlow = gateway.getOutgoingFlows().get(0);
+                }
+            }
+            // 遍历满足条件的 SequenceFlow 路径
+            if (matchSequenceFlow != null) {
+                simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
+            }
+            return;
+        }
+
+        // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的
+        if (currentElement instanceof InclusiveGateway) {
+            // 查找满足条件的 SequenceFlow 路径
+            Gateway gateway = (Gateway) currentElement;
+            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.notEqual(gateway.getDefaultFlow(), flow.getId()));
+                // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
+                if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
+                    matchSequenceFlows = gateway.getOutgoingFlows();
+                }
+            }
+            // 遍历满足条件的 SequenceFlow 路径
+            matchSequenceFlows.forEach(
+                    flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
+        }
+
+        // 情况:ParallelGateway 并行,都满足,都走
+        if (currentElement instanceof ParallelGateway) {
+            Gateway gateway = (Gateway) currentElement;
+            // 遍历子节点
+            gateway.getOutgoingFlows().forEach(
+                    nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
+            return;
+        }
+    }
+
+    /**
+     * 计算条件表达式是否为 true 满足条件
+     *
+     * @param variables 流程实例
+     * @param express 条件表达式
+     * @return 是否满足条件
+     */
+    public static boolean evalConditionExpress(Map<String, Object> variables, String express) {
+        if (express == null) {
+            return Boolean.FALSE;
+        }
+        try {
+            Object result = FlowableUtils.getExpressionValue(variables, express);
+            return Boolean.TRUE.equals(result);
+        } catch (FlowableException ex) {
+            log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错", express, 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();
+    }
+
 }

--
Gitblit v1.9.3