Commit ca815b4c30afb731d2a2ea84325c528e401b4aca
Committed by
Gitee
!1563 fix: 并行分支后的,审核人自选问题修复。<a href="https://gitee.com/link?target=https%3A%2F%2…
…Ft.zsxq.com%2Fdaxv1">https://t.zsxq.com/daxv1</a> Merge pull request !1563 from 芋道源码/master-jdk17-bpm-bug-fix
Showing
4 changed files
with
87 additions
and
10 deletions
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
| ... | ... | @@ -24,7 +24,7 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable<Integer> { |
| 24 | 24 | MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), |
| 25 | 25 | POST(22, "岗位"), |
| 26 | 26 | USER(30, "用户"), |
| 27 | - APPROVE_USER_SELECT(34, "审批人自身"), // 当前审批人,可在审批时,选择下一个节点的审批人 | |
| 27 | + APPROVE_USER_SELECT(34, "审批人自选"), // 当前审批人,可在审批时,选择下一个节点的审批人 | |
| 28 | 28 | START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时,选择此节点的审批人 |
| 29 | 29 | START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 |
| 30 | 30 | START_USER_DEPT_LEADER(37, "发起人部门负责人"), | ... | ... |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
| ... | ... | @@ -958,13 +958,88 @@ public class BpmnModelUtils { |
| 958 | 958 | } |
| 959 | 959 | |
| 960 | 960 | /** |
| 961 | - * 查找起始节点下一个用户任务列表列表 | |
| 961 | + * 查找起始节点下一个用户任务列表, 该方法会递归向下查找:跳过非 UserTask 节点, 支持网关条件判断 | |
| 962 | + * | |
| 963 | + * <ul> | |
| 964 | + * <li>排他网关:走满足条件的唯一一条路径(含默认路径兜底)</li> | |
| 965 | + * <li>包容网关:走所有满足条件的路径</li> | |
| 966 | + * <li>并行网关:走所有出口路径</li> | |
| 967 | + * </ul> | |
| 968 | + * | |
| 969 | + * @param currentElement 当前节点 | |
| 970 | + * @param bpmnModel BPMN 模型 | |
| 971 | + * @param variables 流程变量(用于网关条件判断) | |
| 972 | + * @return 下一个用户任务节点列表 | |
| 973 | + */ | |
| 974 | + public static List<UserTask> getNextUserTasks(FlowElement currentElement, BpmnModel bpmnModel, | |
| 975 | + Map<String, Object> variables) { | |
| 976 | + return getNextUserTasks(currentElement, bpmnModel, variables, new HashSet<>(), new ArrayList<>()); | |
| 977 | + } | |
| 978 | + | |
| 979 | + private static List<UserTask> getNextUserTasks(FlowElement currentElement, BpmnModel bpmnModel, | |
| 980 | + Map<String, Object> variables, | |
| 981 | + Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | |
| 982 | + // 1. 根据节点类型决定要遍历的出口连线 | |
| 983 | + // 网关需要根据条件表达式筛选;其它节点直接取所有 outgoing flows | |
| 984 | + List<SequenceFlow> outgoingFlows; | |
| 985 | + if (currentElement instanceof Gateway) { | |
| 986 | + outgoingFlows = getGatewayOutgoingFlows((Gateway) currentElement, variables); | |
| 987 | + } else { | |
| 988 | + outgoingFlows = getElementOutgoingFlows(currentElement); | |
| 989 | + } | |
| 990 | + if (CollUtil.isEmpty(outgoingFlows)) { | |
| 991 | + return userTaskList; | |
| 992 | + } | |
| 993 | + // 2. 遍历出口连线,递归查找用户任务 | |
| 994 | + for (SequenceFlow outgoingFlow : outgoingFlows) { | |
| 995 | + // 防止连线成环导致死循环 | |
| 996 | + if (hasSequenceFlow.contains(outgoingFlow.getId())) { | |
| 997 | + continue; | |
| 998 | + } | |
| 999 | + hasSequenceFlow.add(outgoingFlow.getId()); | |
| 1000 | + | |
| 1001 | + FlowElement targetElement = bpmnModel.getFlowElement(outgoingFlow.getTargetRef()); | |
| 1002 | + if (targetElement == null || targetElement instanceof EndEvent) { | |
| 1003 | + continue; | |
| 1004 | + } | |
| 1005 | + if (targetElement instanceof UserTask) { | |
| 1006 | + // 找到用户任务:加入结果 | |
| 1007 | + userTaskList.add((UserTask) targetElement); | |
| 1008 | + } else { | |
| 1009 | + // 非用户任务(网关、服务任务、中间事件等):继续递归向下查找 | |
| 1010 | + getNextUserTasks(targetElement, bpmnModel, variables, hasSequenceFlow, userTaskList); | |
| 1011 | + } | |
| 1012 | + } | |
| 1013 | + return userTaskList; | |
| 1014 | + } | |
| 1015 | + | |
| 1016 | + /** | |
| 1017 | + * 根据网关类型与流程变量,筛选网关的出口连线 | |
| 1018 | + * | |
| 1019 | + * @param gateway 网关节点 | |
| 1020 | + * @param variables 流程变量 | |
| 1021 | + * @return 命中的出口连线列表 | |
| 1022 | + */ | |
| 1023 | + private static List<SequenceFlow> getGatewayOutgoingFlows(Gateway gateway, Map<String, Object> variables) { | |
| 1024 | + if (gateway instanceof ExclusiveGateway) { | |
| 1025 | + SequenceFlow matchFlow = findMatchSequenceFlowByExclusiveGateway(gateway, variables); | |
| 1026 | + return matchFlow == null ? Collections.emptyList() : Collections.singletonList(matchFlow); | |
| 1027 | + } | |
| 1028 | + if (gateway instanceof InclusiveGateway) { | |
| 1029 | + return new ArrayList<>(findMatchSequenceFlowsByInclusiveGateway(gateway, variables)); | |
| 1030 | + } | |
| 1031 | + // 默认(并行网关等):走所有出口 | |
| 1032 | + return gateway.getOutgoingFlows(); | |
| 1033 | + } | |
| 1034 | + | |
| 1035 | + /** | |
| 1036 | + * 查找起始节点下一个用户任务列表列表, 不判断网关条件 | |
| 962 | 1037 | * |
| 963 | 1038 | * @param source 起始节点 |
| 964 | 1039 | * @return 结果 |
| 965 | 1040 | */ |
| 966 | 1041 | public static List<UserTask> getNextUserTasks(FlowElement source) { |
| 967 | - return getNextUserTasks(source, null, null); | |
| 1042 | + return getNextUserTasks(source, new HashSet<>(), new ArrayList<>()); | |
| 968 | 1043 | } |
| 969 | 1044 | |
| 970 | 1045 | /** | ... | ... |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
| ... | ... | @@ -7,7 +7,6 @@ import cn.hutool.core.date.DateUtil; |
| 7 | 7 | import cn.hutool.core.lang.Assert; |
| 8 | 8 | import cn.hutool.core.util.*; |
| 9 | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 11 | 10 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 12 | 11 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 13 | 12 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| ... | ... | @@ -279,9 +278,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService |
| 279 | 278 | |
| 280 | 279 | // 3. 获取下一个将要执行的节点集合 |
| 281 | 280 | FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); |
| 282 | - List<FlowNode> nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables); | |
| 283 | - // 仅仅获取 UserTask 节点 TODO add from jason:如果网关节点和网关节点相连,获取下个 UserTask. 貌似有点不准。 | |
| 284 | - List<FlowNode> nextUserTaskList = CollectionUtils.filterList(nextFlowNodes, node -> node instanceof UserTask); | |
| 281 | + // 获取 UserTask 节点 | |
| 282 | + List<UserTask> nextUserTaskList = BpmnModelUtils.getNextUserTasks(flowElement, bpmnModel, processVariables); | |
| 285 | 283 | List<ActivityNode> nextActivityNodes = convertList(nextUserTaskList, node -> new ActivityNode().setId(node.getId()) |
| 286 | 284 | .setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) |
| 287 | 285 | .setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) | ... | ... |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
| ... | ... | @@ -669,10 +669,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 669 | 669 | } |
| 670 | 670 | // 1. 获取下一个将要执行的节点集合 |
| 671 | 671 | FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey); |
| 672 | - List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables); | |
| 672 | + List<UserTask> nextFlowNodes = getNextUserTasks(flowElement, bpmnModel, variables); | |
| 673 | 673 | |
| 674 | 674 | // 2. 校验选择的下一个节点的审批人,是否合法 |
| 675 | - for (FlowNode nextFlowNode : nextFlowNodes) { | |
| 675 | + for (UserTask nextFlowNode : nextFlowNodes) { | |
| 676 | 676 | Integer candidateStrategy = parseCandidateStrategy(nextFlowNode); |
| 677 | 677 | // 2.1 情况一:如果节点中的审批人策略为 发起人自选 |
| 678 | 678 | if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) { |
| ... | ... | @@ -698,8 +698,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 698 | 698 | |
| 699 | 699 | // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空 |
| 700 | 700 | if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) { |
| 701 | - // 如果节点存在,但未配置审批人 | |
| 701 | + // 特殊:如果当前节点已经存在审批人,则不允许覆盖。 例如并行节点后,设置的审批人自选节点。 https://t.zsxq.com/daxv1 | |
| 702 | 702 | Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables()); |
| 703 | + if (approveUserSelectAssignees != null && CollUtil.isNotEmpty(approveUserSelectAssignees.get(nextFlowNode.getId()))) { | |
| 704 | + continue; | |
| 705 | + } | |
| 706 | + // 如果节点存在,但未配置审批人 | |
| 703 | 707 | List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null; |
| 704 | 708 | if (CollUtil.isEmpty(assignees)) { |
| 705 | 709 | throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); | ... | ... |