From fedb9242b59b9846951d49e475b718c5a87a406e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 1 Mar 2025 16:34:30 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84=E5=AE=A1?= =?UTF-8?q?=E3=80=91BPM=EF=BC=9A=E4=B8=8B=E4=B8=80=E4=B8=AA=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/enums/ErrorCodeConstants.java | 1 - .../task/vo/task/BpmTaskApproveReqVO.java | 2 +- ...mTaskCandidateStartUserSelectStrategy.java | 44 +++------------- .../flowable/core/util/BpmnModelUtils.java | 40 ++++++++------- .../task/BpmProcessInstanceServiceImpl.java | 4 +- .../bpm/service/task/BpmTaskServiceImpl.java | 51 +++++++++++-------- 6 files changed, 62 insertions(+), 80 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index a1e2d45aa9..3a50bba523 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/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, "动态表单不存在"); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java index a0751c12e6..0969fda135 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java +++ b/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 variables; @Schema(description = "下一个节点审批人", example = "{nodeId:[1, 2]}") - private Map> nextAssignees; + private Map> nextAssignees; // 为什么是 Map,而不是 List 呢?因为下一个节点可能是多个,例如说并行网关的情况 } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java index f4efa549e5..9304d288ac 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java +++ b/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> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", execution.getProcessInstanceId()); - // 获得审批人,如果不存在,则直接返回空,fix: 用于节点预测时,如果该节点不存在发起人自选审批人,类型转换异常 + // 获得审批人 List 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 assignees = startUserSelectAssignees.get(activityId); - if (CollUtil.isEmpty(assignees)){ - return Sets.newLinkedHashSet(); - } - return new LinkedHashSet<>(assignees); - } - - /** - * 获得发起人自选审批人或抄送人的 Task 列表 - * - * @param bpmnModel BPMN 模型 - * @return Task 列表 - */ - public static List getStartUserSelectTaskList(BpmnModel bpmnModel) { - if (bpmnModel == null) { - return Collections.emptyList(); - } - List 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(); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 31c27c2b2a..1eea893f76 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/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 getNextFlowNodes(FlowElement currentElement, BpmnModel bpmnModel, Map variables){ - // 下一个执行的流程节点集合 - List nextFlowNodes = new ArrayList<>(); - // 当前执行节点的基本属性 - FlowNode currentNode = (FlowNode) currentElement; - // 获取当前节点的关联节点 - List outgoingFlows = currentNode.getOutgoingFlows(); - if (CollUtil.isEmpty(outgoingFlows)){ + List nextFlowNodes = new ArrayList<>(); // 下一个执行的流程节点集合 + FlowNode currentNode = (FlowNode) currentElement; // 当前执行节点的基本属性 + List 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 variables, List nextFlowNodes) { - // TODO @小北: 这里和simulateNextFlowElements中有重复代码,是否重构??每个网关节点拆分出方法应该比较合理化,@芋道 + private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel, + Map variables, List 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 variables, List nextFlowNodes) { + private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel, + Map variables, List nextFlowNodes) { Collection 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 variables, List nextFlowNodes) { + private static void handleParallelGateway(Gateway gateway, BpmnModel bpmnModel, + Map variables, List nextFlowNodes) { // 并行网关,遍历所有出口路径,获取目标节点 gateway.getOutgoingFlows().forEach(flow -> { FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 2b8e67c147..9da2610cb6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/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 historicVariables = historicProcessInstance.getProcessVariables(); - if (null != processVariables) { + if (CollUtil.isNotEmpty(processVariables)) { historicVariables.putAll(processVariables); } processVariables = historicVariables; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index ab9238dc10..0b5a70ab08 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/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 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> 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 variables, BpmnModel bpmnModel, - Map> nextActivityNodes,ProcessInstance processInstance){ - // 1、获取当前任务节点的信息 + Map> nextAssignees, ProcessInstance processInstance) { + // 1. 获取当前任务节点的信息 FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey); - // 2、获取下一个将要执行的节点集合 + // 2. 获取下一个将要执行的节点集合 List nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables); - // 3、循环下一个将要执行的节点集合 + + // 3. 循环下一个将要执行的节点集合 for (FlowNode nextFlowNode : nextFlowNodes) { - // 3.1、获取下一个将要执行节点的属性(是否为自选审批人等) + // 3.1 获取下一个将要执行节点的属性(是否为自选审批人等) + // TODO @小北:public static Integer parseCandidateStrategy(FlowElement userTask) 使用这个工具方法哈。 Map> extensionElements = nextFlowNode.getExtensionElements(); List 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> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables()); List 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 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 @小北:加一个“审批人选择”的校验; } }