Commit deef88f56fc4dd1235f24a10c11e1991be73a034
1 parent
093e563b
feat:审批通过时,校验节点是否为下一个执行节点
Showing
2 changed files
with
159 additions
and
89 deletions
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 | 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 | 951 | * 计算条件表达式是否为 true 满足条件 |
| 833 | 952 | * |
| 834 | 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 | 23 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; |
| 24 | 24 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; |
| 25 | 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 | 27 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; |
| 27 | 28 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 28 | 29 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| ... | ... | @@ -522,14 +523,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 522 | 523 | BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); |
| 523 | 524 | // 2.3 调用 BPM complete 去完成任务 |
| 524 | 525 | // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用 |
| 525 | - if (CollUtil.isNotEmpty(reqVO.getVariables())) { | |
| 526 | +// if (CollUtil.isNotEmpty(reqVO.getVariables())) { | |
| 526 | 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 | 531 | if (CollUtil.isNotEmpty(reqVO.getNextAssignees())) { |
| 535 | 532 | // 获取实例中的全部节点数据,避免后续节点的审批人被覆盖 |
| ... | ... | @@ -539,104 +536,58 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 539 | 536 | } |
| 540 | 537 | runtimeService.setVariables(task.getProcessInstanceId(), variables); |
| 541 | 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 | 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 | 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 | /** | ... | ... |