Browse Source

【功能优化】工作流:基于 BPMN 的预测,Util 封装

YunaiV 9 months ago
parent
commit
3afe93a157

+ 1 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.http

@@ -6,8 +6,8 @@ Authorization: Bearer {{token}}
 
 ### 请求 /bpm/process-instance/get-bpmn 接口 => 失败
 #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911
 GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4
-#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4
 Content-Type: application/json
 tenant-id: 1
 Authorization: Bearer {{token}}

+ 97 - 128
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java

@@ -18,18 +18,13 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
 import com.google.common.collect.Maps;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
 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.api.variable.VariableContainer;
 import org.flowable.common.engine.impl.util.io.BytesStreamSource;
 import org.flowable.engine.ManagementService;
 
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.ScriptException;
 import java.util.*;
 
 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
@@ -40,8 +35,9 @@ import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_P
  * BPMN Model 操作工具类。目前分成三部分:
  *
  * 1. BPMN 修改 + 解析元素相关的方法
- * 2. BPM 简单查找相关的方法
- * 3. BPM 复杂遍历相关的方法
+ * 2. BPMN 简单查找相关的方法
+ * 3. BPMN 复杂遍历相关的方法
+ * 4. BPMN 流程预测相关的方法
  *
  * @author 芋道源码
  */
@@ -667,142 +663,115 @@ public class BpmnModelUtils {
         return userTaskList;
     }
 
-    // TODO FROM 欢欢
+    // ========== BPMN 流程预测相关的方法 ==========
 
-    public static void simulateProcess(BpmnModel bpmnModel, Map<String, Object> variables, List<FlowElement> flowElementList,
-                                       VariableContainer variableContainer) {
-        List<StartEvent> startEvents = bpmnModel.getMainProcess().findFlowElementsOfType(StartEvent.class);
-        //只处理一个开始节点的情况
-        simulateNextFlowElements(startEvents.get(0), variables, flowElementList, variableContainer);
-    }
+    /**
+     * 流程预测,返回 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 startEvent = (FlowNode) currentElement;
+            resultElements.add(startEvent);
+            // 遍历子节点
+            startEvent.getOutgoingFlows().forEach(
+                    nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
+            return;
+        }
 
-    private static void simulateNextFlowElements(FlowElement flowElement, Map<String, Object> variables, List<FlowElement> flowElementList,
-                                                 VariableContainer variableContainer) {
-        if (flowElement instanceof StartEvent) {
-            StartEvent startEvent = (StartEvent) flowElement;
-            flowElementList.add(startEvent);
-            List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
-            for (SequenceFlow flow : outgoingFlows) {
-                FlowElement targetFlowElement = flow.getTargetFlowElement();
-                simulateNextFlowElements(targetFlowElement, variables, flowElementList, variableContainer);
-            }
-        } else if (flowElement instanceof UserTask) {
-            UserTask userTask = (UserTask) flowElement;
-            flowElementList.add(userTask);
-            List<SequenceFlow> outgoingFlows = userTask.getOutgoingFlows();
-            for (SequenceFlow flow : outgoingFlows) {
-                FlowElement targetFlowElement = flow.getTargetFlowElement();
-                simulateNextFlowElements(targetFlowElement, variables, flowElementList, variableContainer);
-            }
-        } else if (flowElement instanceof ServiceTask) { // TODO 芋艿:待测试
-            ServiceTask serviceTask = (ServiceTask) flowElement;
-            flowElementList.add(serviceTask);
-            List<SequenceFlow> outgoingFlows = serviceTask.getOutgoingFlows();
-            for (SequenceFlow flow : outgoingFlows) {
-                FlowElement targetFlowElement = flow.getTargetFlowElement();
-                simulateNextFlowElements(targetFlowElement, variables, flowElementList, variableContainer);
-            }
-        } else if (flowElement instanceof ExclusiveGateway) {
-            Gateway gateway = (Gateway) flowElement;
-            boolean found = false;
-            for (SequenceFlow flow : gateway.getOutgoingFlows()) {
-                if (ObjUtil.equal(gateway.getDefaultFlow(), flow.getId())) {
-                    continue;
-                }
-                if (evalConditionExpress(variableContainer, flow.getConditionExpression())) {
-                    FlowElement targetFlowElement = flow.getTargetFlowElement();
-                    simulateNextFlowElements(targetFlowElement, variables, flowElementList, variableContainer);
-                    found = true;
-                    break;
-                }
-            }
-            if (!found) {
-                SequenceFlow targetFlowElement = CollUtil.findOne(gateway.getOutgoingFlows(),
-                        c -> ObjUtil.equal(gateway.getDefaultFlow(), c.getId()));
-                if (targetFlowElement == null && gateway.getOutgoingFlows().size() == 1) {
-                    targetFlowElement = gateway.getOutgoingFlows().get(0);
-                }
-                if (targetFlowElement != null && targetFlowElement.getTargetFlowElement() != null) {
-                    simulateNextFlowElements(targetFlowElement.getTargetFlowElement(), variables, flowElementList, variableContainer);
+        // 情况: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);
                 }
             }
-        } else if (flowElement instanceof InclusiveGateway) {
-            Gateway gateway = (Gateway) flowElement;
-            boolean found = false;
-            for (SequenceFlow flow : gateway.getOutgoingFlows()) {
-                if (ObjUtil.equal(gateway.getDefaultFlow(), flow.getId())) {
-                    continue;
-                }
-                if (evalConditionExpress(variableContainer, flow.getConditionExpression())) {
-                    FlowElement targetFlowElement = flow.getTargetFlowElement();
-                    simulateNextFlowElements(targetFlowElement, variables, flowElementList, variableContainer);
-                    found = true;
-                }
+            // 遍历满足条件的 SequenceFlow 路径
+            if (matchSequenceFlow != null) {
+                simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
             }
-            if (!found) {
-                SequenceFlow targetFlowElement = CollUtil.findOne(gateway.getOutgoingFlows(),
-                        c -> ObjUtil.equal(gateway.getDefaultFlow(), c.getId()));
-                if (targetFlowElement == null && gateway.getOutgoingFlows().size() == 1) {
-                    targetFlowElement = gateway.getOutgoingFlows().get(0);
-                }
-                if (targetFlowElement != null && targetFlowElement.getTargetFlowElement() != null) {
-                    simulateNextFlowElements(targetFlowElement.getTargetFlowElement(), variables, flowElementList, variableContainer);
-                }
-            }
-        } else if (flowElement instanceof ParallelGateway) {
-            Gateway gateway = (Gateway) flowElement;
-            for (SequenceFlow flow : gateway.getOutgoingFlows()) {
-                FlowElement targetFlowElement = flow.getTargetFlowElement();
-                simulateNextFlowElements(targetFlowElement, variables, flowElementList, variableContainer);
-            }
-        } else if (flowElement instanceof EndEvent) {
-            EndEvent endEvent = (EndEvent) flowElement;
-            flowElementList.add(endEvent);
+            return;
         }
-    }
 
-    private static boolean evaluateCondition(SequenceFlow flow, Map<String, Object> variables) {
-        String conditionExpression = flow.getConditionExpression();
-        if (StringUtils.isEmpty(conditionExpression)) {
-            return false;
-        }
-        try {
-            //兼容UEL和JS语法
-            if (conditionExpression.startsWith("${") && conditionExpression.endsWith("}")) {
-                conditionExpression = conditionExpression.substring(2, conditionExpression.length() - 1);
+        // 情况: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();
+                }
             }
-            return evaluateExpression(conditionExpression, variables);
-        } catch (ScriptException e) {
-            e.printStackTrace();
+            // 遍历满足条件的 SequenceFlow 路径
+            matchSequenceFlows.forEach(
+                    flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
         }
-        return false;
-    }
 
-    public static boolean evaluateExpression(String expression, Map<String, Object> variables) throws ScriptException {
-        ScriptEngineManager engineManager = new ScriptEngineManager();
-        ScriptEngine engine = engineManager.getEngineByName("JavaScript");
-
-        // 设置变量
-        for (Map.Entry<String, Object> entry : variables.entrySet()) {
-            engine.put(entry.getKey(), entry.getValue());
-        }
-
-        // 评估表达式
-        Object result = engine.eval(expression);
-        if (result instanceof Boolean) {
-            return (Boolean) result;
-        } else {
-            throw new ScriptException("Expression does not evaluate to boolean");
+        if (currentElement instanceof ParallelGateway) {
+            Gateway gateway = (Gateway) currentElement;
+            // 遍历子节点
+            gateway.getOutgoingFlows().forEach(
+                    nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
+            return;
         }
     }
 
     /**
-     * 计算条件表达式的值
+     * 计算条件表达式是否为 true 满足条件
      *
-     * @param variableContainer 流程实例
-     * @param express         条件表达式
+     * @param variables 流程实例
+     * @param express 条件表达式
+     * @return 是否满足条件
      */
-    private static Boolean evalConditionExpress(VariableContainer variableContainer, String express) {
+    private static boolean evalConditionExpress(Map<String, Object> variables, String express) {
         ManagementService managementService = SpringUtil.getBean(ManagementService.class);
         if (express == null) {
             return Boolean.FALSE;
@@ -810,7 +779,7 @@ public class BpmnModelUtils {
         // TODO @jason:疑问,为啥这里要在 managementService 里执行哈?
         Object result = managementService.executeCommand(context -> {
             try {
-                return FlowableUtils.getExpressionValue(variableContainer, express);
+                return FlowableUtils.getExpressionValue(variables, express);
             } catch (FlowableException ex) {
                 log.error("[evalConditionExpress][条件表达式({}) 解析报错", express, ex);
                 return Boolean.FALSE;

+ 7 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java

@@ -10,6 +10,7 @@ import org.flowable.common.engine.api.delegate.Expression;
 import org.flowable.common.engine.api.variable.VariableContainer;
 import org.flowable.common.engine.impl.el.ExpressionManager;
 import org.flowable.common.engine.impl.identity.Authentication;
+import org.flowable.common.engine.impl.variable.MapDelegateVariableContainer;
 import org.flowable.engine.ProcessEngineConfiguration;
 import org.flowable.engine.history.HistoricProcessInstance;
 import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
@@ -227,4 +228,10 @@ public class FlowableUtils {
         return expression.getValue(variableContainer);
     }
 
+    public static Object getExpressionValue(Map<String, Object> variable, String expressionString) {
+        VariableContainer variableContainer = new MapDelegateVariableContainer(variable,
+                VariableContainer.empty());
+        return getExpressionValue(variableContainer, expressionString);
+    }
+
 }

File diff suppressed because it is too large
+ 0 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java


Some files were not shown because too many files changed in this diff