Prechádzať zdrojové kódy

【代码评审】BPM:下一个审批人

YunaiV 5 mesiacov pred
rodič
commit
fedb9242b5

+ 0 - 1
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java

@@ -58,7 +58,6 @@ public interface ErrorCodeConstants {
     ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
     ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
     ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");
-    ErrorCode TASK_START_USER_SELECT_NODE_NOT_EXISTS = new ErrorCode(1_009_004_007, "({})不是下一个执行的流程节点!");
 
     // ========== 动态表单模块 1-009-010-000 ==========
     ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");

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

@@ -25,6 +25,6 @@ public class BpmTaskApproveReqVO {
     private Map<String, Object> variables;
 
     @Schema(description = "下一个节点审批人", example = "{nodeId:[1, 2]}")
-    private Map<String, List<Long>> nextAssignees;
+    private Map<String, List<Long>> nextAssignees; // 为什么是 Map,而不是 List 呢?因为下一个节点可能是多个,例如说并行网关的情况
 
 }

+ 7 - 37
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java

@@ -2,24 +2,21 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.d
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
 import com.google.common.collect.Sets;
 import jakarta.annotation.Resource;
 import org.flowable.bpmn.model.BpmnModel;
-import org.flowable.bpmn.model.ServiceTask;
-import org.flowable.bpmn.model.Task;
-import org.flowable.bpmn.model.UserTask;
 import org.flowable.engine.delegate.DelegateExecution;
 import org.flowable.engine.runtime.ProcessInstance;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 
-import java.util.*;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类
@@ -53,12 +50,9 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand
         Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
         Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空",
                 execution.getProcessInstanceId());
-        // 获得审批人,如果不存在,则直接返回空,fix: 用于节点预测时,如果该节点不存在发起人自选审批人,类型转换异常
+        // 获得审批人
         List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
-        if (CollUtil.isEmpty(assignees)){
-            return Sets.newLinkedHashSet();
-        }
-        return new LinkedHashSet<>(assignees);
+        return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
     }
 
     @Override
@@ -71,33 +65,9 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand
         if (startUserSelectAssignees == null) {
             return Sets.newLinkedHashSet();
         }
-        // 获得审批人,如果不存在,则直接返回空,fix: 用于节点预测时,如果该节点不存在发起人自选审批人,类型转换异常
+        // 获得审批人
         List<Long> assignees = startUserSelectAssignees.get(activityId);
-        if (CollUtil.isEmpty(assignees)){
-            return Sets.newLinkedHashSet();
-        }
-        return new LinkedHashSet<>(assignees);
-    }
-
-    /**
-     * 获得发起人自选审批人或抄送人的 Task 列表
-     *
-     * @param bpmnModel BPMN 模型
-     * @return Task 列表
-     */
-    public static List<Task> getStartUserSelectTaskList(BpmnModel bpmnModel) {
-        if (bpmnModel == null) {
-            return Collections.emptyList();
-        }
-        List<Task> tasks = new ArrayList<>();
-        tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class));
-        tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, ServiceTask.class));
-        if (CollUtil.isEmpty(tasks)) {
-            return Collections.emptyList();
-        }
-        tasks.removeIf(task -> ObjectUtil.notEqual(BpmnModelUtils.parseCandidateStrategy(task),
-                BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()));
-        return tasks;
+        return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
     }
 
 }

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

@@ -809,8 +809,7 @@ public class BpmnModelUtils {
         if (currentElement instanceof ExclusiveGateway) {
             // 查找满足条件的 SequenceFlow 路径
             Gateway gateway = (Gateway) currentElement;
-            // TODO @小北:当一个网关节点下存在多个满足的并行节点时,只查询一个节点流程流转会存在问题,需要优化,
-            // TODO 具体见issue:https://github.com/YunaiV/ruoyi-vue-pro/issues/761
+            // TODO @小北:当一个网关节点下存在多个满足的并行节点时,只查询一个节点流程流转会存在问题。需要优化,具体见issue:https://github.com/YunaiV/ruoyi-vue-pro/issues/761
             SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
                     flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
                             && (evalConditionExpress(variables, flow.getConditionExpression())));
@@ -866,27 +865,27 @@ public class BpmnModelUtils {
      * @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)){
+        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){
+            if (targetElement == null) {
                 continue;
             }
-            if (targetElement instanceof Gateway gateway) {
-                // 处理不同类型的网关
+            // 情况一:处理不同类型的网关
+            if (targetElement instanceof Gateway) {
+                Gateway gateway = (Gateway) targetElement;
                 if (gateway instanceof ExclusiveGateway) {
                     handleExclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
                 } else if (gateway instanceof InclusiveGateway) {
@@ -895,7 +894,7 @@ public class BpmnModelUtils {
                     handleParallelGateway(gateway, bpmnModel, variables, nextFlowNodes);
                 }
             } else {
-                // 如果不是网关,直接添加到下一个节点列表
+                // 情况二:如果不是网关,直接添加到下一个节点列表
                 nextFlowNodes.add((FlowNode) targetElement);
             }
         }
@@ -903,15 +902,17 @@ public class BpmnModelUtils {
     }
 
     /**
-     * 处理排网关
+     * 处理排网关
      *
      * @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 @小北: 这里和simulateNextFlowElements中有重复代码,是否重构??每个网关节点拆分出方法应该比较合理化,@芋道
+    private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
+                                               Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
+       // TODO @小北: 这里和 simulateNextFlowElements 中有重复代码,是否重构??每个网关节点拆分出方法应该比较合理化,@芋艿
+        // TODO @小北:ok,把 simulateNextFlowElements 里面处理网关的,复用这个方法,可以么?
         SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
                 flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
                         && (evalConditionExpress(variables, flow.getConditionExpression())));
@@ -940,7 +941,8 @@ public class BpmnModelUtils {
      * @param variables 流程变量
      * @param nextFlowNodes 下一个执行的流程节点集合
      */
-    private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> 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()));
@@ -960,6 +962,7 @@ public class BpmnModelUtils {
             }
         });
     }
+
     /**
      * 处理并行网关
      *
@@ -968,7 +971,8 @@ public class BpmnModelUtils {
      * @param variables 流程变量
      * @param nextFlowNodes 下一个执行的流程节点集合
      */
-    private static void handleParallelGateway(Gateway gateway, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> 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());

+ 2 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java

@@ -175,9 +175,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
             }
             startUserId = Long.valueOf(historicProcessInstance.getStartUserId());
             processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance);
-            // 如果流程变量不为空,则用前端传递的新变量值覆盖历史的流程变量
+            // 合并 DB 和前端传递的流量变量,以前端的为主
             Map<String, Object> historicVariables = historicProcessInstance.getProcessVariables();
-            if (null != processVariables) {
+            if (CollUtil.isNotEmpty(processVariables)) {
                 historicVariables.putAll(processVariables);
             }
             processVariables = historicVariables;

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

@@ -11,13 +11,10 @@ import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
-import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailReqVO;
-import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
-import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.bpm.enums.definition.*;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
@@ -63,7 +60,6 @@ import org.springframework.transaction.support.TransactionSynchronization;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import java.util.*;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -535,9 +531,15 @@ public class BpmTaskServiceImpl implements BpmTaskService {
             Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
             // 校验传递的参数中是否为下一个将要执行的任务节点
             validateNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(), bpmnModel, reqVO.getNextAssignees(), instance);
-            // 下个节点审批人如果不存在,则由前端传递
+            // 如果有下一个审批人,则设置到流程变量中
+            // TODO @小北:validateNextAssignees 升级成 validateAndSetNextAssignees,然后里面吧下面这一小段逻辑,抽进去如何?
             if (CollUtil.isNotEmpty(reqVO.getNextAssignees())) {
                 // 获取实例中的全部节点数据,避免后续节点的审批人被覆盖
+                // TODO @小北:这里有个需要讨论的点,微信哈;
+                // TODO 因为 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES 定位是发起人,那么审批人选择的,放在 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES。目前想到两个方案:
+                // TODO 方案一:增加一个 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,然后设置到这里面。然后,BpmTaskCandidateStartUserSelectStrategy 也从这里读
+                // TODO 方案二:也是增加一个 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,根据节点审批人类型,放到 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES、PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES
+                // TODO 方案三:融合成 PROCESS_INSTANCE_VARIABLE_USER_SELECT_ASSIGNEES,不再区分是发起人选择、还是审批人选择。
                 Map<String, List<Long>> hisProcessVariables = FlowableUtils.getStartUserSelectAssignees(instance.getProcessVariables());
                 hisProcessVariables.putAll(reqVO.getNextAssignees());
                 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, hisProcessVariables);
@@ -554,45 +556,52 @@ public class BpmTaskServiceImpl implements BpmTaskService {
 
 
     /**
-     * 校验传递的参数中是否为下一个将要执行的任务节点
+     * 校验选择的下一个节点的审批人,是否合法
      *
-     * @param taskDefinitionKey 当前任务节点id
+     * 1. 是否有漏选:没有选择审批人
+     * 2. 是否有多选:非下一个节点
+     *
+     * @param taskDefinitionKey 当前任务节点标识
      * @param variables 流程变量
      * @param bpmnModel 流程模型
-     * @param nextActivityNodes 下一个节点审批人集合(参数)
+     * @param nextAssignees 下一个节点审批人集合(参数)
+     * @param processInstance 流程实例
      */
     private void validateNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
-                                                Map<String, List<Long>> nextActivityNodes,ProcessInstance processInstance){
-        // 1获取当前任务节点的信息
+                                       Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
+        // 1. 获取当前任务节点的信息
         FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
-        // 2获取下一个将要执行的节点集合
+        // 2. 获取下一个将要执行的节点集合
         List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
-        // 3、循环下一个将要执行的节点集合
+
+        // 3. 循环下一个将要执行的节点集合
         for (FlowNode nextFlowNode : nextFlowNodes) {
-            // 3.1、获取下一个将要执行节点的属性(是否为自选审批人等)
+            // 3.1 获取下一个将要执行节点的属性(是否为自选审批人等)
+            // TODO @小北:public static Integer parseCandidateStrategy(FlowElement userTask) 使用这个工具方法哈。
             Map<String, List<ExtensionElement>> extensionElements = nextFlowNode.getExtensionElements();
             List<ExtensionElement> elements = extensionElements.get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
-            if (CollUtil.isEmpty(elements)){
+            if (CollUtil.isEmpty(elements)) {
                 continue;
             }
-            // 3.2获取节点中的审批人策略
+            // 3.2 获取节点中的审批人策略
             Integer candidateStrategy = Integer.valueOf(elements.get(0).getElementText());
-            // 3.3获取流程实例中的发起人自选审批人
+            // 3.3 获取流程实例中的发起人自选审批人
             Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
             List<Long> startUserSelectAssignee = startUserSelectAssignees.get(nextFlowNode.getId());
-            // 3.4如果节点中的审批人策略为 发起人自选,并且该节点的审批人为空
+            // 3.4 如果节点中的审批人策略为 发起人自选,并且该节点的审批人为空
             if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()) && CollUtil.isEmpty(startUserSelectAssignee)) {
                 // 先判断前端传递的参数节点节点是否为将要执行的节点
-                if (!nextActivityNodes.containsKey(nextFlowNode.getId())){
+                // TODO @小北:!nextAssignees.containsKey(nextFlowNode.getId())、和 CollUtil.isEmpty(nextAssignees.get(nextFlowNode.getId()))) 是不是等价的?
+                if (!nextAssignees.containsKey(nextFlowNode.getId())) {
                     throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName());
                 }
-                // 如果节点存在,则获取节点中的审批人
-                List<Long> nextAssignees = nextActivityNodes.get(nextFlowNode.getId());
                 // 如果前端传递的节点为空,则抛出异常
-                if (CollUtil.isEmpty(nextAssignees)) {
+                // TODO @小北:换一个错误码哈。
+                if (CollUtil.isEmpty(nextAssignees.get(nextFlowNode.getId()))) {
                     throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
                 }
             }
+            // TODO @小北:加一个“审批人选择”的校验;
         }
     }