From deef88f56fc4dd1235f24a10c11e1991be73a034 Mon Sep 17 00:00:00 2001 From: lizhixian <18210040298@163.com> Date: Fri, 28 Feb 2025 17:10:59 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=AE=A1=E6=89=B9=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E6=97=B6=EF=BC=8C=E6=A0=A1=E9=AA=8C=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E4=B8=BA=E4=B8=8B=E4=B8=80=E4=B8=AA=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flowable/core/util/BpmnModelUtils.java | 119 ++++++++++++++++ .../bpm/service/task/BpmTaskServiceImpl.java | 133 ++++++------------ 2 files changed, 161 insertions(+), 91 deletions(-) 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 b73e0cc98d..7873681318 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 @@ -828,6 +828,125 @@ public class BpmnModelUtils { } } + /** + * 根据当前节点,获取下一个节点 + * + * @param currentElement 当前节点 + * @param bpmnModel BPMN模型 + * @param variables 流程变量 + */ + 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)){ + 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 variables, List 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 variables, List nextFlowNodes) { + Collection 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 variables, List nextFlowNodes) { + // 并行网关,遍历所有出口路径,获取目标节点 + gateway.getOutgoingFlows().forEach(flow -> { + FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef()); + if (targetElement instanceof FlowNode) { + nextFlowNodes.add((FlowNode) targetElement); + } + }); + } + /** * 计算条件表达式是否为 true 满足条件 * 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 04842ea51d..1a00f38617 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 @@ -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 variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables()); // 校验传递的参数中是否存在不是下一个执行的节点 - // 当前执行的流程节点,需根据该节点寻找下一个节点 - String taskDefinitionKey = task.getTaskDefinitionKey(); - List 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 getNextFlowNodes(String taskDefinitionKey, BpmnModel bpmnModel, Map 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 nextFlowNodes = new ArrayList<>(); - resolveNextNodes(currentNode, bpmnModel, variables, nextFlowNodes); - return nextFlowNodes; - } - - /** - * 递归解析下一个执行节点 - * @param currentNode 当前节点 - * @param bpmnModel BPMN 模型 - * @param variables 流程变量,用于条件判断 - * @param nextFlowNodes 存储下一个执行节点的列表 - */ - private void resolveNextNodes(FlowNode currentNode, BpmnModel bpmnModel, Map variables, List nextFlowNodes) { - List outgoingFlows = currentNode.getOutgoingFlows(); - for (SequenceFlow sequenceFlow : outgoingFlows) { - if (!shouldFollowSequenceFlow(currentNode, sequenceFlow, variables)) { - 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); - } - } - } - } - - /** - * 判断是否应该遵循当前序列流 - * @param currentNode 当前节点 - * @param sequenceFlow 序列流 - * @param variables 流程变量,用于条件判断 - * @return 是否应该遵循该序列流 - */ - private boolean shouldFollowSequenceFlow(FlowNode currentNode, SequenceFlow sequenceFlow, Map 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]} + * @param taskDefinitionKey 当前任务节点id + * @param variables 流程变量 + * @param bpmnModel 流程模型 + * @param nextActivityNodes 下一个节点审批人集合(参数) */ - private void validateNextAssignees(Long loginUserId, Map variables,String processInstanceId, - Map> nextActivityNodes){ - // 1、查询流程【预测】的全部信息 - BpmApprovalDetailRespVO approvalDetail = processInstanceService.getApprovalDetail(loginUserId, - new BpmApprovalDetailReqVO().setProcessVariables(variables).setProcessInstanceId(processInstanceId)); - // 2、获取预测节点的信息 - List activityNodes = approvalDetail.getActivityNodes(); - if (CollUtil.isNotEmpty(activityNodes)) { - // 2.1、获取节点中的审批人策略为【发起人自选】且状态为【未执行】的节点 - // TODO 获取下一个执行节点 - List 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> 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()); + private void validateNextAssignees(String taskDefinitionKey, Map variables, BpmnModel bpmnModel, + Map> nextActivityNodes,ProcessInstance processInstance){ + + // 1、获取当前任务节点的信息 + FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey); + // 2、获取下一个应该执行的节点集合 + List nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables); + // 3、比较前端传递的节点和预测的下一个节点是否匹配,匹配则将该节点设置上审批人 + for (FlowNode nextFlowNode : nextFlowNodes) { + // 获取下一个执行节点的属性 是否为 发起人自选 + Map> extensionElements = nextFlowNode.getExtensionElements(); + List elements = extensionElements.get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); + if (CollUtil.isEmpty(elements)){ + continue; + } + // 获取节点中的审批人策略 + Integer candidateStrategy = Integer.valueOf(elements.get(0).getElementText()); + // 获取流程实例中的发起人自选审批人 + Map> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables()); + List 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 nextAssignees = nextActivityNodes.get(nextFlowNode.getId()); + // 3.1、如果前端传递的节点为空,则抛出异常 + if (CollUtil.isEmpty(nextAssignees)) { + throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); } } + } + } /**