Explorar o código

feat:审批通过时,校验节点是否为下一个执行节点

lizhixian hai 5 meses
pai
achega
deef88f56f

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

@@ -828,6 +828,125 @@ public class BpmnModelUtils {
         }
     }
 
+    /**
+     * 根据当前节点,获取下一个节点
+     *
+     * @param currentElement 当前节点
+     * @param bpmnModel  BPMN模型
+     * @param variables 流程变量
+     */
+    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 Gateway gateway) {
+                // 处理不同类型的网关
+                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;
+    }
+
+    /**
+     * 处理排他网关
+     *
+     * @param gateway 排他网关
+     * @param bpmnModel BPMN模型
+     * @param variables 流程变量
+     * @param nextFlowNodes 下一个执行的流程节点集合
+     */
+    private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
+       // TODO @小北: 这里findOne和simulateNextFlowElements中有重复代码,需要优化,@芋道:是否重构??
+        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);
+            }
+        }
+        // 遍历满足条件的 SequenceFlow 路径
+        if (matchSequenceFlow != null) {
+            FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef());
+            if (targetElement instanceof FlowNode) {
+                nextFlowNodes.add((FlowNode) targetElement);
+            }
+        }
+    }
+
+    /**
+     * 处理包容网关
+     *
+     * @param gateway 排他网关
+     * @param bpmnModel BPMN模型
+     * @param variables 流程变量
+     * @param nextFlowNodes 下一个执行的流程节点集合
+     */
+    private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
+        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();
+            }
+        }
+        // 遍历满足条件的 SequenceFlow 路径,获取目标节点
+        matchSequenceFlows.forEach(flow -> {
+            FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
+            if (targetElement instanceof FlowNode) {
+                nextFlowNodes.add((FlowNode) targetElement);
+            }
+        });
+    }
+    /**
+     * 处理并行网关
+     *
+     * @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 满足条件
      *

+ 40 - 89
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

@@ -23,6 +23,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
@@ -522,14 +523,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
                 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
         // 2.3 调用 BPM complete 去完成任务
         // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
-        if (CollUtil.isNotEmpty(reqVO.getVariables())) {
+//        if (CollUtil.isNotEmpty(reqVO.getVariables())) {
             Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
             // 校验传递的参数中是否存在不是下一个执行的节点
-            // 当前执行的流程节点,需根据该节点寻找下一个节点
-            String taskDefinitionKey = task.getTaskDefinitionKey();
-            List<FlowNode> nextFlowNodes = getNextFlowNodes(taskDefinitionKey, bpmnModel, variables);
-            System.out.println(nextFlowNodes);
-//            validateNextAssignees(userId, reqVO.getVariables(), task.getProcessInstanceId(), reqVO.getNextAssignees());
+            validateNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(), bpmnModel, reqVO.getNextAssignees(), instance);
             // 下个节点审批人如果不存在,则由前端传递
             if (CollUtil.isNotEmpty(reqVO.getNextAssignees())) {
                 // 获取实例中的全部节点数据,避免后续节点的审批人被覆盖
@@ -539,104 +536,58 @@ public class BpmTaskServiceImpl implements BpmTaskService {
             }
             runtimeService.setVariables(task.getProcessInstanceId(), variables);
             taskService.complete(task.getId(), variables, true);
-        } else {
-            taskService.complete(task.getId());
-        }
+//        } else {
+//            taskService.complete(task.getId());
+//        }
 
         // 【加签专属】处理加签任务
         handleParentTaskIfSign(task.getParentTaskId());
     }
 
-    /**
-     * 根据当前节点 ID 获取下一个执行的 FlowNode 列表
-     * @param taskDefinitionKey 当前节点 ID
-     * @param bpmnModel BPMN 模型
-     * @param variables 流程变量,用于条件判断
-     * @return 下一个执行的 FlowNode 列表
-     */
-    public List<FlowNode> getNextFlowNodes(String taskDefinitionKey, BpmnModel bpmnModel, Map<String, Object> variables) {
-        if (taskDefinitionKey == null || bpmnModel == null) {
-            throw new IllegalArgumentException("taskDefinitionKey and bpmnModel cannot be null");
-        }
-        FlowNode currentNode = (FlowNode) bpmnModel.getFlowElement(taskDefinitionKey);
-        if (currentNode == null) {
-            throw new IllegalArgumentException("FlowElement with given taskDefinitionKey not found in BpmnModel");
-        }
-        List<FlowNode> nextFlowNodes = new ArrayList<>();
-        resolveNextNodes(currentNode, bpmnModel, variables, nextFlowNodes);
-        return nextFlowNodes;
-    }
 
     /**
-     * 递归解析下一个执行节点
-     * @param currentNode 当前节点
-     * @param bpmnModel BPMN 模型
-     * @param variables 流程变量,用于条件判断
-     * @param nextFlowNodes 存储下一个执行节点的列表
+     * 校验传递的参数中是否存在不是下一个执行的节点
+     *
+     * @param taskDefinitionKey 当前任务节点id
+     * @param variables 流程变量
+     * @param bpmnModel 流程模型
+     * @param nextActivityNodes 下一个节点审批人集合(参数)
      */
-    private void resolveNextNodes(FlowNode currentNode, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
-        List<SequenceFlow> outgoingFlows = currentNode.getOutgoingFlows();
-        for (SequenceFlow sequenceFlow : outgoingFlows) {
-            if (!shouldFollowSequenceFlow(currentNode, sequenceFlow, variables)) {
+    private void validateNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
+                                                Map<String, List<Long>> nextActivityNodes,ProcessInstance processInstance){
+
+        // 1、获取当前任务节点的信息
+        FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
+        // 2、获取下一个应该执行的节点集合
+        List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
+        // 3、比较前端传递的节点和预测的下一个节点是否匹配,匹配则将该节点设置上审批人
+        for (FlowNode nextFlowNode : nextFlowNodes) {
+            // 获取下一个执行节点的属性 是否为 发起人自选
+            Map<String, List<ExtensionElement>> extensionElements = nextFlowNode.getExtensionElements();
+            List<ExtensionElement> elements = extensionElements.get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
+            if (CollUtil.isEmpty(elements)){
                 continue;
             }
-            FlowElement targetElement = bpmnModel.getFlowElement(sequenceFlow.getTargetRef());
-            if (targetElement instanceof FlowNode targetNode) {
-                if (targetNode instanceof Gateway) {
-                    // 如果目标节点是网关,递归处理
-                    resolveNextNodes(targetNode, bpmnModel, variables, nextFlowNodes);
-                }else {
-                    nextFlowNodes.add(targetNode);
+            // 获取节点中的审批人策略
+            Integer candidateStrategy = Integer.valueOf(elements.get(0).getElementText());
+            // 获取流程实例中的发起人自选审批人
+            Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
+            List<Long> startUserSelectAssignee = startUserSelectAssignees.get(nextFlowNode.getId());
+            if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()) && CollUtil.isEmpty(startUserSelectAssignee)) {
+                // 先判断节点是否存在
+                if (!nextActivityNodes.containsKey(nextFlowNode.getId())){
+                    throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName());
+                }
+                // 如果节点存在,则判断节点中的审批人策略是否为 发起人自选
+                List<Long> nextAssignees = nextActivityNodes.get(nextFlowNode.getId());
+                // 3.1、如果前端传递的节点为空,则抛出异常
+                if (CollUtil.isEmpty(nextAssignees)) {
+                    throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
                 }
             }
-        }
-    }
 
-    /**
-     * 判断是否应该遵循当前序列流
-     * @param currentNode 当前节点
-     * @param sequenceFlow 序列流
-     * @param variables 流程变量,用于条件判断
-     * @return 是否应该遵循该序列流
-     */
-    private boolean shouldFollowSequenceFlow(FlowNode currentNode, SequenceFlow sequenceFlow, Map<String, Object> variables) {
-        if (currentNode instanceof ExclusiveGateway) {
-            String conditionExpression = sequenceFlow.getConditionExpression();
-            return conditionExpression == null || BpmnModelUtils.evalConditionExpress(variables, conditionExpression);
         }
-        return true;
-    }
 
-
-    /**
-     * 校验传递的参数中是否存在不是下一个执行的节点
-     *
-     * @param loginUserId 流程发起人
-     * @param processInstanceId 流程实例id
-     * @param nextActivityNodes 下一个执行节点信息 {节点id : [审批人id,审批人id]}
-     */
-    private void validateNextAssignees(Long loginUserId, Map<String, Object> variables,String processInstanceId,
-                                                Map<String, List<Long>> nextActivityNodes){
-        // 1、查询流程【预测】的全部信息
-        BpmApprovalDetailRespVO approvalDetail = processInstanceService.getApprovalDetail(loginUserId,
-                new BpmApprovalDetailReqVO().setProcessVariables(variables).setProcessInstanceId(processInstanceId));
-        // 2、获取预测节点的信息
-        List<BpmApprovalDetailRespVO.ActivityNode> activityNodes = approvalDetail.getActivityNodes();
-        if (CollUtil.isNotEmpty(activityNodes)) {
-            // 2.1、获取节点中的审批人策略为【发起人自选】且状态为【未执行】的节点
-            // TODO 获取下一个执行节点
-            List<BpmApprovalDetailRespVO.ActivityNode> notStartActivityNodes = activityNodes.stream().filter(node ->
-                    BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy().equals(node.getCandidateStrategy())
-                    && BpmTaskStatusEnum.NOT_START.getStatus().equals(node.getStatus())
-                    && CollUtil.isEmpty(node.getCandidateUsers())).toList();
-            // 3、校验传递的参数中是否存在不是下一个节点的信息
-            for (Map.Entry<String, List<Long>> nextActivityNode : nextActivityNodes.entrySet()) {
-                if (notStartActivityNodes.stream().noneMatch(taskNode -> taskNode.getId().equals(nextActivityNode.getKey()))) {
-                    log.error("[checkNextActivityNodes][ ({}) 不是下一个执行的流程节点!]", nextActivityNode.getKey());
-                    throw exception(TASK_START_USER_SELECT_NODE_NOT_EXISTS, nextActivityNode.getKey());
-                }
-            }
-        }
     }
 
     /**