Commit 5e46e0c4f894750514c4f30349b6dd500289922e
1 parent
1dc5fd2c
review:【bpm 工作流】驳回场景下的预测
Showing
4 changed files
with
32 additions
and
26 deletions
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
| @@ -45,7 +45,7 @@ public class BpmnVariableConstants { | @@ -45,7 +45,7 @@ public class BpmnVariableConstants { | ||
| 45 | public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID"; | 45 | public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID"; |
| 46 | 46 | ||
| 47 | /** | 47 | /** |
| 48 | - * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} | 48 | + * 流程实例的变量 - 用于判断流程实例变量节点是否驳回:格式 RETURN_FLAG_{节点 id} |
| 49 | * | 49 | * |
| 50 | * 目的是:退回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过 | 50 | * 目的是:退回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过 |
| 51 | * | 51 | * |
| @@ -54,7 +54,7 @@ public class BpmnVariableConstants { | @@ -54,7 +54,7 @@ public class BpmnVariableConstants { | ||
| 54 | public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; | 54 | public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; |
| 55 | 55 | ||
| 56 | /** | 56 | /** |
| 57 | - * 流程实例的变量前缀 - 用于退回操作。记录需要预测的节点. 格式 NEED_SIMULATE_TASK_{节点定义 id} | 57 | + * 流程实例的变量前缀 - 用于退回操作,记录需要预测的节点:格式 NEED_SIMULATE_TASK_{节点定义 id} |
| 58 | * | 58 | * |
| 59 | * 目的是:退回操作,预测节点会不准,在流程变量中记录需要预测的节点,来辅助预测 | 59 | * 目的是:退回操作,预测节点会不准,在流程变量中记录需要预测的节点,来辅助预测 |
| 60 | */ | 60 | */ |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
| @@ -658,11 +658,11 @@ public class BpmnModelUtils { | @@ -658,11 +658,11 @@ public class BpmnModelUtils { | ||
| 658 | 658 | ||
| 659 | // 根据类型,获取入口连线 | 659 | // 根据类型,获取入口连线 |
| 660 | List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source); | 660 | List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source); |
| 661 | - // 1.没有入口连线,则返回 false | 661 | + // 1. 没有入口连线,则返回 false |
| 662 | if (CollUtil.isEmpty(sequenceFlows)) { | 662 | if (CollUtil.isEmpty(sequenceFlows)) { |
| 663 | return false; | 663 | return false; |
| 664 | } | 664 | } |
| 665 | - // 2.循环找目标元素, 找到目标节点 | 665 | + // 2. 循环找目标元素, 找到目标节点 |
| 666 | for (SequenceFlow sequenceFlow : sequenceFlows) { | 666 | for (SequenceFlow sequenceFlow : sequenceFlows) { |
| 667 | // 如果发现连线重复,说明循环了,跳过这个循环 | 667 | // 如果发现连线重复,说明循环了,跳过这个循环 |
| 668 | if (visitedElements.contains(sequenceFlow.getId())) { | 668 | if (visitedElements.contains(sequenceFlow.getId())) { |
| @@ -679,7 +679,7 @@ public class BpmnModelUtils { | @@ -679,7 +679,7 @@ public class BpmnModelUtils { | ||
| 679 | if (sourceFlowElement instanceof ParallelGateway) { | 679 | if (sourceFlowElement instanceof ParallelGateway) { |
| 680 | continue; | 680 | continue; |
| 681 | } | 681 | } |
| 682 | - // 继续迭代, 如果找到目标节点直接返回 true | 682 | + // 继续迭代,如果找到目标节点直接返回 true |
| 683 | if (isSequentialReachable(sourceFlowElement, target, visitedElements)) { | 683 | if (isSequentialReachable(sourceFlowElement, target, visitedElements)) { |
| 684 | return true; | 684 | return true; |
| 685 | } | 685 | } |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
| @@ -223,19 +223,19 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -223,19 +223,19 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 223 | // 3.1 计算当前登录用户的待办任务 | 223 | // 3.1 计算当前登录用户的待办任务 |
| 224 | BpmTaskRespVO todoTask = taskService.getTodoTask(loginUserId, reqVO.getTaskId(), reqVO.getProcessInstanceId()); | 224 | BpmTaskRespVO todoTask = taskService.getTodoTask(loginUserId, reqVO.getTaskId(), reqVO.getProcessInstanceId()); |
| 225 | 225 | ||
| 226 | - // 3.2 获取由于退回操作,需要预测的节点。 从流程变量中获取。 回退操作会设置这些变量 | 226 | + // 3.2 获取由于退回操作,需要预测的节点。从流程变量中获取,回退操作会设置这些变量 |
| 227 | Set<String> needSimulateTaskDefKeysByReturn = new HashSet<>(); | 227 | Set<String> needSimulateTaskDefKeysByReturn = new HashSet<>(); |
| 228 | if (StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { | 228 | if (StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { |
| 229 | Map<String, Object> variables = runtimeService.getVariables(reqVO.getProcessInstanceId()); | 229 | Map<String, Object> variables = runtimeService.getVariables(reqVO.getProcessInstanceId()); |
| 230 | Map<String, Object> simulateTaskVariables = MapUtil.filter(variables, | 230 | Map<String, Object> simulateTaskVariables = MapUtil.filter(variables, |
| 231 | item -> item.getKey().startsWith(PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX)); | 231 | item -> item.getKey().startsWith(PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX)); |
| 232 | - simulateTaskVariables.forEach( | ||
| 233 | - (key, value) -> needSimulateTaskDefKeysByReturn.add(StrUtil.removePrefix(key, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX))); | 232 | + simulateTaskVariables.forEach((key, value) -> |
| 233 | + needSimulateTaskDefKeysByReturn.add(StrUtil.removePrefix(key, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX))); | ||
| 234 | } | 234 | } |
| 235 | // 移除运行中的节点,运行中的节点无需预测 | 235 | // 移除运行中的节点,运行中的节点无需预测 |
| 236 | + // TODO @jason:是不是 foreach runActivityNodes,然后移除 needSimulateTaskDefKeysByReturn 更好?(理解成本低一点) | ||
| 236 | CollectionUtils.convertList(runActivityNodes, ActivityNode::getId).forEach(needSimulateTaskDefKeysByReturn::remove); | 237 | CollectionUtils.convertList(runActivityNodes, ActivityNode::getId).forEach(needSimulateTaskDefKeysByReturn::remove); |
| 237 | 238 | ||
| 238 | - | ||
| 239 | // 3.3 预测未运行节点的审批信息 | 239 | // 3.3 预测未运行节点的审批信息 |
| 240 | List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, | 240 | List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, |
| 241 | processDefinitionInfo, | 241 | processDefinitionInfo, |
| @@ -594,8 +594,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -594,8 +594,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 594 | Set<String> needSimulateTaskDefKeysByReturn) { | 594 | Set<String> needSimulateTaskDefKeysByReturn) { |
| 595 | // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance | 595 | // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance |
| 596 | // 包括了历史的操作,不是只有 startEvent 到当前节点的记录 | 596 | // 包括了历史的操作,不是只有 startEvent 到当前节点的记录 |
| 597 | - // 回退操作时候,会记录需要预测的节点到流程变量中。即使在历史操作中,也需要预测。 | ||
| 598 | - if (!needSimulateTaskDefKeysByReturn.contains(node.getId()) && runActivityIds.contains(node.getId())) { | 597 | + if (runActivityIds.contains(node.getId()) |
| 598 | + && !needSimulateTaskDefKeysByReturn.contains(node.getId())) { // 特殊:回退操作时候,会记录需要预测的节点到流程变量中。即使在历史操作中,也需要预测 | ||
| 599 | return null; | 599 | return null; |
| 600 | } | 600 | } |
| 601 | Integer status = BpmTaskStatusEnum.NOT_START.getStatus(); | 601 | Integer status = BpmTaskStatusEnum.NOT_START.getStatus(); |
| @@ -644,7 +644,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -644,7 +644,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 644 | Map<String, Object> processVariables, | 644 | Map<String, Object> processVariables, |
| 645 | FlowElement node, Set<String> runActivityIds, | 645 | FlowElement node, Set<String> runActivityIds, |
| 646 | Set<String> needSimulateTaskDefKeysByReturn) { | 646 | Set<String> needSimulateTaskDefKeysByReturn) { |
| 647 | - | ||
| 648 | // 回退操作时候,会记录需要预测的节点到流程变量中。即使节点在历史操作中,也需要预测。 | 647 | // 回退操作时候,会记录需要预测的节点到流程变量中。即使节点在历史操作中,也需要预测。 |
| 649 | if (!needSimulateTaskDefKeysByReturn.contains(node.getId()) && runActivityIds.contains(node.getId())) { | 648 | if (!needSimulateTaskDefKeysByReturn.contains(node.getId()) && runActivityIds.contains(node.getId())) { |
| 650 | return null; | 649 | return null; |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
| @@ -875,16 +875,15 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -875,16 +875,15 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 875 | * @return 目标任务节点元素 | 875 | * @return 目标任务节点元素 |
| 876 | */ | 876 | */ |
| 877 | private FlowElement validateTargetTaskCanReturn(BpmnModel bpmnModel, String sourceKey, String targetKey) { | 877 | private FlowElement validateTargetTaskCanReturn(BpmnModel bpmnModel, String sourceKey, String targetKey) { |
| 878 | - | ||
| 879 | - // 1.3 获取当前任务节点元素 | 878 | + // 1.1 获取当前任务节点元素 |
| 880 | FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); | 879 | FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); |
| 881 | - // 1.3 获取跳转的节点元素 | 880 | + // 1.2 获取跳转的节点元素 |
| 882 | FlowElement target = BpmnModelUtils.getFlowElementById(bpmnModel, targetKey); | 881 | FlowElement target = BpmnModelUtils.getFlowElementById(bpmnModel, targetKey); |
| 883 | if (target == null) { | 882 | if (target == null) { |
| 884 | throw exception(TASK_TARGET_NODE_NOT_EXISTS); | 883 | throw exception(TASK_TARGET_NODE_NOT_EXISTS); |
| 885 | } | 884 | } |
| 886 | 885 | ||
| 887 | - // 2.2 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 | 886 | + // 2. 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 |
| 888 | if (!BpmnModelUtils.isSequentialReachable(source, target, null)) { | 887 | if (!BpmnModelUtils.isSequentialReachable(source, target, null)) { |
| 889 | throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); | 888 | throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); |
| 890 | } | 889 | } |
| @@ -934,7 +933,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -934,7 +933,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 934 | }); | 933 | }); |
| 935 | 934 | ||
| 936 | // 3. 构建需要预测的任务流程变量 | 935 | // 3. 构建需要预测的任务流程变量 |
| 937 | - Set<String> taskDefinitionKeyList = needSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement); | 936 | + // TODO @jason:【驳回预测相关】是不是搞成一个变量,里面是 set 更简洁一点呀? |
| 937 | + Set<String> taskDefinitionKeyList = getNeedSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement); | ||
| 938 | Map<String, Object> needSimulateVariables = convertMap(taskDefinitionKeyList, | 938 | Map<String, Object> needSimulateVariables = convertMap(taskDefinitionKeyList, |
| 939 | taskId -> StrUtil.concat(false, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, taskId), item -> Boolean.TRUE); | 939 | taskId -> StrUtil.concat(false, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, taskId), item -> Boolean.TRUE); |
| 940 | 940 | ||
| @@ -944,27 +944,34 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -944,27 +944,34 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 944 | runtimeService.createChangeActivityStateBuilder() | 944 | runtimeService.createChangeActivityStateBuilder() |
| 945 | .processInstanceId(currentTask.getProcessInstanceId()) | 945 | .processInstanceId(currentTask.getProcessInstanceId()) |
| 946 | .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) | 946 | .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) |
| 947 | - // 设置需要预测的任务流程变量。用于辅助预测 | 947 | + // 设置需要预测的任务流程变量,用于辅助预测 |
| 948 | .processVariables(needSimulateVariables) | 948 | .processVariables(needSimulateVariables) |
| 949 | - // 设置流程变量(local)节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过 | 949 | + // 设置流程变量(local)节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 |
| 950 | .localVariable(reqVO.getTargetTaskDefinitionKey(), | 950 | .localVariable(reqVO.getTargetTaskDefinitionKey(), |
| 951 | String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE) | 951 | String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE) |
| 952 | .changeState(); | 952 | .changeState(); |
| 953 | } | 953 | } |
| 954 | 954 | ||
| 955 | - private Set<String> needSimulateTaskDefinitionKeys(BpmnModel bpmnModel, Task currentTask, FlowElement targetElement) { | ||
| 956 | - // 获取需要预测的任务的 definition key。 当前任务还没完成。也需要预测。 | 955 | + private Set<String> getNeedSimulateTaskDefinitionKeys(BpmnModel bpmnModel, Task currentTask, FlowElement targetElement) { |
| 956 | + // 1. 获取需要预测的任务的 definition key。因为当前任务还没完成,也需要预测 | ||
| 957 | Set<String> taskDefinitionKeys = CollUtil.newHashSet(currentTask.getTaskDefinitionKey()); | 957 | Set<String> taskDefinitionKeys = CollUtil.newHashSet(currentTask.getTaskDefinitionKey()); |
| 958 | - // 从已结束任务中找到要回退的目标任务。按时间倒序最近的一个目标任务 | 958 | + |
| 959 | + // 2.1 从已结束任务中找到要回退的目标任务,按时间倒序最近的一个目标任务 | ||
| 959 | List<HistoricTaskInstance> endTaskList = CollectionUtils.filterList( | 960 | List<HistoricTaskInstance> endTaskList = CollectionUtils.filterList( |
| 960 | - getTaskListByProcessInstanceId(currentTask.getProcessInstanceId(), Boolean.FALSE), item -> item.getEndTime() != null); | 961 | + getTaskListByProcessInstanceId(currentTask.getProcessInstanceId(), Boolean.FALSE), |
| 962 | + item -> item.getEndTime() != null); | ||
| 963 | + // 2.2 遍历已结束的任务,找到在 targetTask 之后生成的任务,且串行可达的任务 | ||
| 961 | HistoricTaskInstance targetTask = findFirst(endTaskList, | 964 | HistoricTaskInstance targetTask = findFirst(endTaskList, |
| 962 | item -> item.getTaskDefinitionKey().equals(targetElement.getId())); | 965 | item -> item.getTaskDefinitionKey().equals(targetElement.getId())); |
| 966 | + // TODO @jason:【驳回预测相关】是不是 if targetTask 先判空? | ||
| 963 | endTaskList.forEach(item -> { | 967 | endTaskList.forEach(item -> { |
| 964 | FlowElement element = getFlowElementById(bpmnModel, item.getTaskDefinitionKey()); | 968 | FlowElement element = getFlowElementById(bpmnModel, item.getTaskDefinitionKey()); |
| 965 | - // 如果已结束的任务在回退目标节点之后生成,且串行可达,则标记为需要预算节点。 | 969 | + // 如果已结束的任务在回退目标节点之后生成,且串行可达,则标记为需要预算节点 |
| 966 | // TODO 串行可达的方法需要和判断可回退节点 validateTargetTaskCanReturn 分开吗? 并行网关可能会有问题。 | 970 | // TODO 串行可达的方法需要和判断可回退节点 validateTargetTaskCanReturn 分开吗? 并行网关可能会有问题。 |
| 967 | - if (targetTask != null && DateUtil.compare(item.getCreateTime(), targetTask.getCreateTime()) > 0 | 971 | + // TODO @jason:【驳回预测相关】这里是不是判断 element 哈? |
| 972 | + if (targetTask != null | ||
| 973 | + // TODO @jason:【驳回预测相关】这里直接 createTime 的 compare 更简单?因为不太会出现空哈。 | ||
| 974 | + && DateUtil.compare(item.getCreateTime(), targetTask.getCreateTime()) > 0 | ||
| 968 | && BpmnModelUtils.isSequentialReachable(element, targetElement, null)) { | 975 | && BpmnModelUtils.isSequentialReachable(element, targetElement, null)) { |
| 969 | taskDefinitionKeys.add(item.getTaskDefinitionKey()); | 976 | taskDefinitionKeys.add(item.getTaskDefinitionKey()); |
| 970 | } | 977 | } |
| @@ -1483,7 +1490,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -1483,7 +1490,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 1483 | return; | 1490 | return; |
| 1484 | } | 1491 | } |
| 1485 | FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | 1492 | FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |
| 1486 | - // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略, 使用 local variable | 1493 | + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略(使用 local variable) |
| 1487 | Boolean returnTaskFlag = runtimeService.getVariableLocal(task.getExecutionId(), | 1494 | Boolean returnTaskFlag = runtimeService.getVariableLocal(task.getExecutionId(), |
| 1488 | String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); | 1495 | String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); |
| 1489 | Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(), | 1496 | Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(), |