Commit deef88f56fc4dd1235f24a10c11e1991be73a034

Authored by lizhixian
1 parent 093e563b

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

yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -829,6 +829,125 @@ public class BpmnModelUtils { @@ -829,6 +829,125 @@ public class BpmnModelUtils {
829 } 829 }
830 830
831 /** 831 /**
  832 + * 根据当前节点,获取下一个节点
  833 + *
  834 + * @param currentElement 当前节点
  835 + * @param bpmnModel BPMN模型
  836 + * @param variables 流程变量
  837 + */
  838 + public static List<FlowNode> getNextFlowNodes(FlowElement currentElement, BpmnModel bpmnModel,
  839 + Map<String, Object> variables){
  840 + // 下一个执行的流程节点集合
  841 + List<FlowNode> nextFlowNodes = new ArrayList<>();
  842 + // 当前执行节点的基本属性
  843 + FlowNode currentNode = (FlowNode) currentElement;
  844 + // 获取当前节点的关联节点
  845 + List<SequenceFlow> outgoingFlows = currentNode.getOutgoingFlows();
  846 + if (CollUtil.isEmpty(outgoingFlows)){
  847 + log.warn("[getNextFlowNodes][当前节点({}) 的 outgoingFlows 为空]", currentNode.getId());
  848 + return nextFlowNodes;
  849 + }
  850 + // 遍历每个出口流
  851 + for (SequenceFlow outgoingFlow : outgoingFlows) {
  852 + // 获取目标节点的基本属性
  853 + FlowElement targetElement = bpmnModel.getFlowElement(outgoingFlow.getTargetRef());
  854 + if (targetElement == null){
  855 + continue;
  856 + }
  857 + if (targetElement instanceof Gateway gateway) {
  858 + // 处理不同类型的网关
  859 + if (gateway instanceof ExclusiveGateway) {
  860 + handleExclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
  861 + } else if (gateway instanceof InclusiveGateway) {
  862 + handleInclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
  863 + } else if (gateway instanceof ParallelGateway) {
  864 + handleParallelGateway(gateway, bpmnModel, variables, nextFlowNodes);
  865 + }
  866 + } else {
  867 + // 如果不是网关,直接添加到下一个节点列表
  868 + nextFlowNodes.add((FlowNode) targetElement);
  869 + }
  870 + }
  871 + return nextFlowNodes;
  872 + }
  873 +
  874 + /**
  875 + * 处理排他网关
  876 + *
  877 + * @param gateway 排他网关
  878 + * @param bpmnModel BPMN模型
  879 + * @param variables 流程变量
  880 + * @param nextFlowNodes 下一个执行的流程节点集合
  881 + */
  882 + private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
  883 + // TODO @小北: 这里findOne和simulateNextFlowElements中有重复代码,需要优化,@芋道:是否重构??
  884 + SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  885 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
  886 + && (evalConditionExpress(variables, flow.getConditionExpression())));
  887 + if (matchSequenceFlow == null) {
  888 + matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  889 + flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
  890 + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
  891 + if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
  892 + matchSequenceFlow = gateway.getOutgoingFlows().get(0);
  893 + }
  894 + }
  895 + // 遍历满足条件的 SequenceFlow 路径
  896 + if (matchSequenceFlow != null) {
  897 + FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef());
  898 + if (targetElement instanceof FlowNode) {
  899 + nextFlowNodes.add((FlowNode) targetElement);
  900 + }
  901 + }
  902 + }
  903 +
  904 + /**
  905 + * 处理包容网关
  906 + *
  907 + * @param gateway 排他网关
  908 + * @param bpmnModel BPMN模型
  909 + * @param variables 流程变量
  910 + * @param nextFlowNodes 下一个执行的流程节点集合
  911 + */
  912 + private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
  913 + Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
  914 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
  915 + && evalConditionExpress(variables, flow.getConditionExpression()));
  916 + if (CollUtil.isEmpty(matchSequenceFlows)) {
  917 + matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
  918 + flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
  919 + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
  920 + if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
  921 + matchSequenceFlows = gateway.getOutgoingFlows();
  922 + }
  923 + }
  924 + // 遍历满足条件的 SequenceFlow 路径,获取目标节点
  925 + matchSequenceFlows.forEach(flow -> {
  926 + FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
  927 + if (targetElement instanceof FlowNode) {
  928 + nextFlowNodes.add((FlowNode) targetElement);
  929 + }
  930 + });
  931 + }
  932 + /**
  933 + * 处理并行网关
  934 + *
  935 + * @param gateway 排他网关
  936 + * @param bpmnModel BPMN模型
  937 + * @param variables 流程变量
  938 + * @param nextFlowNodes 下一个执行的流程节点集合
  939 + */
  940 + private static void handleParallelGateway(Gateway gateway, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
  941 + // 并行网关,遍历所有出口路径,获取目标节点
  942 + gateway.getOutgoingFlows().forEach(flow -> {
  943 + FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
  944 + if (targetElement instanceof FlowNode) {
  945 + nextFlowNodes.add((FlowNode) targetElement);
  946 + }
  947 + });
  948 + }
  949 +
  950 + /**
832 * 计算条件表达式是否为 true 满足条件 951 * 计算条件表达式是否为 true 满足条件
833 * 952 *
834 * @param variables 流程实例 953 * @param variables 流程实例
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; @@ -23,6 +23,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
23 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; 23 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
24 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; 24 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
25 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 25 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  26 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
26 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; 27 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
27 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 28 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
28 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 29 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
@@ -522,14 +523,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -522,14 +523,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
522 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); 523 BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
523 // 2.3 调用 BPM complete 去完成任务 524 // 2.3 调用 BPM complete 去完成任务
524 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用 525 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
525 - if (CollUtil.isNotEmpty(reqVO.getVariables())) { 526 +// if (CollUtil.isNotEmpty(reqVO.getVariables())) {
526 Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables()); 527 Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
527 // 校验传递的参数中是否存在不是下一个执行的节点 528 // 校验传递的参数中是否存在不是下一个执行的节点
528 - // 当前执行的流程节点,需根据该节点寻找下一个节点  
529 - String taskDefinitionKey = task.getTaskDefinitionKey();  
530 - List<FlowNode> nextFlowNodes = getNextFlowNodes(taskDefinitionKey, bpmnModel, variables);  
531 - System.out.println(nextFlowNodes);  
532 -// validateNextAssignees(userId, reqVO.getVariables(), task.getProcessInstanceId(), reqVO.getNextAssignees()); 529 + validateNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(), bpmnModel, reqVO.getNextAssignees(), instance);
533 // 下个节点审批人如果不存在,则由前端传递 530 // 下个节点审批人如果不存在,则由前端传递
534 if (CollUtil.isNotEmpty(reqVO.getNextAssignees())) { 531 if (CollUtil.isNotEmpty(reqVO.getNextAssignees())) {
535 // 获取实例中的全部节点数据,避免后续节点的审批人被覆盖 532 // 获取实例中的全部节点数据,避免后续节点的审批人被覆盖
@@ -539,104 +536,58 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -539,104 +536,58 @@ public class BpmTaskServiceImpl implements BpmTaskService {
539 } 536 }
540 runtimeService.setVariables(task.getProcessInstanceId(), variables); 537 runtimeService.setVariables(task.getProcessInstanceId(), variables);
541 taskService.complete(task.getId(), variables, true); 538 taskService.complete(task.getId(), variables, true);
542 - } else {  
543 - taskService.complete(task.getId());  
544 - } 539 +// } else {
  540 +// taskService.complete(task.getId());
  541 +// }
545 542
546 // 【加签专属】处理加签任务 543 // 【加签专属】处理加签任务
547 handleParentTaskIfSign(task.getParentTaskId()); 544 handleParentTaskIfSign(task.getParentTaskId());
548 } 545 }
549 546
550 - /**  
551 - * 根据当前节点 ID 获取下一个执行的 FlowNode 列表  
552 - * @param taskDefinitionKey 当前节点 ID  
553 - * @param bpmnModel BPMN 模型  
554 - * @param variables 流程变量,用于条件判断  
555 - * @return 下一个执行的 FlowNode 列表  
556 - */  
557 - public List<FlowNode> getNextFlowNodes(String taskDefinitionKey, BpmnModel bpmnModel, Map<String, Object> variables) {  
558 - if (taskDefinitionKey == null || bpmnModel == null) {  
559 - throw new IllegalArgumentException("taskDefinitionKey and bpmnModel cannot be null");  
560 - }  
561 - FlowNode currentNode = (FlowNode) bpmnModel.getFlowElement(taskDefinitionKey);  
562 - if (currentNode == null) {  
563 - throw new IllegalArgumentException("FlowElement with given taskDefinitionKey not found in BpmnModel");  
564 - }  
565 - List<FlowNode> nextFlowNodes = new ArrayList<>();  
566 - resolveNextNodes(currentNode, bpmnModel, variables, nextFlowNodes);  
567 - return nextFlowNodes;  
568 - }  
569 547
570 /** 548 /**
571 - * 递归解析下一个执行节点  
572 - * @param currentNode 当前节点  
573 - * @param bpmnModel BPMN 模型  
574 - * @param variables 流程变量,用于条件判断  
575 - * @param nextFlowNodes 存储下一个执行节点的列表 549 + * 校验传递的参数中是否存在不是下一个执行的节点
  550 + *
  551 + * @param taskDefinitionKey 当前任务节点id
  552 + * @param variables 流程变量
  553 + * @param bpmnModel 流程模型
  554 + * @param nextActivityNodes 下一个节点审批人集合(参数)
576 */ 555 */
577 - private void resolveNextNodes(FlowNode currentNode, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {  
578 - List<SequenceFlow> outgoingFlows = currentNode.getOutgoingFlows();  
579 - for (SequenceFlow sequenceFlow : outgoingFlows) {  
580 - if (!shouldFollowSequenceFlow(currentNode, sequenceFlow, variables)) { 556 + private void validateNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
  557 + Map<String, List<Long>> nextActivityNodes,ProcessInstance processInstance){
  558 +
  559 + // 1、获取当前任务节点的信息
  560 + FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
  561 + // 2、获取下一个应该执行的节点集合
  562 + List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
  563 + // 3、比较前端传递的节点和预测的下一个节点是否匹配,匹配则将该节点设置上审批人
  564 + for (FlowNode nextFlowNode : nextFlowNodes) {
  565 + // 获取下一个执行节点的属性 是否为 发起人自选
  566 + Map<String, List<ExtensionElement>> extensionElements = nextFlowNode.getExtensionElements();
  567 + List<ExtensionElement> elements = extensionElements.get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
  568 + if (CollUtil.isEmpty(elements)){
581 continue; 569 continue;
582 } 570 }
583 - FlowElement targetElement = bpmnModel.getFlowElement(sequenceFlow.getTargetRef());  
584 - if (targetElement instanceof FlowNode targetNode) {  
585 - if (targetNode instanceof Gateway) {  
586 - // 如果目标节点是网关,递归处理  
587 - resolveNextNodes(targetNode, bpmnModel, variables, nextFlowNodes);  
588 - }else {  
589 - nextFlowNodes.add(targetNode); 571 + // 获取节点中的审批人策略
  572 + Integer candidateStrategy = Integer.valueOf(elements.get(0).getElementText());
  573 + // 获取流程实例中的发起人自选审批人
  574 + Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
  575 + List<Long> startUserSelectAssignee = startUserSelectAssignees.get(nextFlowNode.getId());
  576 + if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()) && CollUtil.isEmpty(startUserSelectAssignee)) {
  577 + // 先判断节点是否存在
  578 + if (!nextActivityNodes.containsKey(nextFlowNode.getId())){
  579 + throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName());
  580 + }
  581 + // 如果节点存在,则判断节点中的审批人策略是否为 发起人自选
  582 + List<Long> nextAssignees = nextActivityNodes.get(nextFlowNode.getId());
  583 + // 3.1、如果前端传递的节点为空,则抛出异常
  584 + if (CollUtil.isEmpty(nextAssignees)) {
  585 + throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
590 } 586 }
591 } 587 }
592 - }  
593 - }  
594 588
595 - /**  
596 - * 判断是否应该遵循当前序列流  
597 - * @param currentNode 当前节点  
598 - * @param sequenceFlow 序列流  
599 - * @param variables 流程变量,用于条件判断  
600 - * @return 是否应该遵循该序列流  
601 - */  
602 - private boolean shouldFollowSequenceFlow(FlowNode currentNode, SequenceFlow sequenceFlow, Map<String, Object> variables) {  
603 - if (currentNode instanceof ExclusiveGateway) {  
604 - String conditionExpression = sequenceFlow.getConditionExpression();  
605 - return conditionExpression == null || BpmnModelUtils.evalConditionExpress(variables, conditionExpression);  
606 } 589 }
607 - return true;  
608 - }  
609 590
610 -  
611 - /**  
612 - * 校验传递的参数中是否存在不是下一个执行的节点  
613 - *  
614 - * @param loginUserId 流程发起人  
615 - * @param processInstanceId 流程实例id  
616 - * @param nextActivityNodes 下一个执行节点信息 {节点id : [审批人id,审批人id]}  
617 - */  
618 - private void validateNextAssignees(Long loginUserId, Map<String, Object> variables,String processInstanceId,  
619 - Map<String, List<Long>> nextActivityNodes){  
620 - // 1、查询流程【预测】的全部信息  
621 - BpmApprovalDetailRespVO approvalDetail = processInstanceService.getApprovalDetail(loginUserId,  
622 - new BpmApprovalDetailReqVO().setProcessVariables(variables).setProcessInstanceId(processInstanceId));  
623 - // 2、获取预测节点的信息  
624 - List<BpmApprovalDetailRespVO.ActivityNode> activityNodes = approvalDetail.getActivityNodes();  
625 - if (CollUtil.isNotEmpty(activityNodes)) {  
626 - // 2.1、获取节点中的审批人策略为【发起人自选】且状态为【未执行】的节点  
627 - // TODO 获取下一个执行节点  
628 - List<BpmApprovalDetailRespVO.ActivityNode> notStartActivityNodes = activityNodes.stream().filter(node ->  
629 - BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy().equals(node.getCandidateStrategy())  
630 - && BpmTaskStatusEnum.NOT_START.getStatus().equals(node.getStatus())  
631 - && CollUtil.isEmpty(node.getCandidateUsers())).toList();  
632 - // 3、校验传递的参数中是否存在不是下一个节点的信息  
633 - for (Map.Entry<String, List<Long>> nextActivityNode : nextActivityNodes.entrySet()) {  
634 - if (notStartActivityNodes.stream().noneMatch(taskNode -> taskNode.getId().equals(nextActivityNode.getKey()))) {  
635 - log.error("[checkNextActivityNodes][ ({}) 不是下一个执行的流程节点!]", nextActivityNode.getKey());  
636 - throw exception(TASK_START_USER_SELECT_NODE_NOT_EXISTS, nextActivityNode.getKey());  
637 - }  
638 - }  
639 - }  
640 } 591 }
641 592
642 /** 593 /**