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,7 +24,7 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable<Integer> { | ||
| 24 | MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), | 24 | MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), |
| 25 | POST(22, "岗位"), | 25 | POST(22, "岗位"), |
| 26 | USER(30, "用户"), | 26 | USER(30, "用户"), |
| 27 | - APPROVE_USER_SELECT(34, "审批人自身"), // 当前审批人,可在审批时,选择下一个节点的审批人 | 27 | + APPROVE_USER_SELECT(34, "审批人自选"), // 当前审批人,可在审批时,选择下一个节点的审批人 |
| 28 | START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时,选择此节点的审批人 | 28 | START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时,选择此节点的审批人 |
| 29 | START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 | 29 | START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 |
| 30 | START_USER_DEPT_LEADER(37, "发起人部门负责人"), | 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,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 | * @param source 起始节点 | 1038 | * @param source 起始节点 |
| 964 | * @return 结果 | 1039 | * @return 结果 |
| 965 | */ | 1040 | */ |
| 966 | public static List<UserTask> getNextUserTasks(FlowElement source) { | 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 +7,6 @@ import cn.hutool.core.date.DateUtil; | ||
| 7 | import cn.hutool.core.lang.Assert; | 7 | import cn.hutool.core.lang.Assert; |
| 8 | import cn.hutool.core.util.*; | 8 | import cn.hutool.core.util.*; |
| 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||
| 11 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; | 10 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 12 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 11 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 13 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | 12 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| @@ -279,9 +278,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -279,9 +278,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 279 | 278 | ||
| 280 | // 3. 获取下一个将要执行的节点集合 | 279 | // 3. 获取下一个将要执行的节点集合 |
| 281 | FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); | 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 | List<ActivityNode> nextActivityNodes = convertList(nextUserTaskList, node -> new ActivityNode().setId(node.getId()) | 283 | List<ActivityNode> nextActivityNodes = convertList(nextUserTaskList, node -> new ActivityNode().setId(node.getId()) |
| 286 | .setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) | 284 | .setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) |
| 287 | .setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) | 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,10 +669,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 669 | } | 669 | } |
| 670 | // 1. 获取下一个将要执行的节点集合 | 670 | // 1. 获取下一个将要执行的节点集合 |
| 671 | FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey); | 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 | // 2. 校验选择的下一个节点的审批人,是否合法 | 674 | // 2. 校验选择的下一个节点的审批人,是否合法 |
| 675 | - for (FlowNode nextFlowNode : nextFlowNodes) { | 675 | + for (UserTask nextFlowNode : nextFlowNodes) { |
| 676 | Integer candidateStrategy = parseCandidateStrategy(nextFlowNode); | 676 | Integer candidateStrategy = parseCandidateStrategy(nextFlowNode); |
| 677 | // 2.1 情况一:如果节点中的审批人策略为 发起人自选 | 677 | // 2.1 情况一:如果节点中的审批人策略为 发起人自选 |
| 678 | if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) { | 678 | if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) { |
| @@ -698,8 +698,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -698,8 +698,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 698 | 698 | ||
| 699 | // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空 | 699 | // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空 |
| 700 | if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) { | 700 | if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) { |
| 701 | - // 如果节点存在,但未配置审批人 | 701 | + // 特殊:如果当前节点已经存在审批人,则不允许覆盖。 例如并行节点后,设置的审批人自选节点。 https://t.zsxq.com/daxv1 |
| 702 | Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables()); | 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 | List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null; | 707 | List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null; |
| 704 | if (CollUtil.isEmpty(assignees)) { | 708 | if (CollUtil.isEmpty(assignees)) { |
| 705 | throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); | 709 | throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); |