Commit 5e46e0c4f894750514c4f30349b6dd500289922e

Authored by YunaiV
1 parent 1dc5fd2c

review:【bpm 工作流】驳回场景下的预测

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(),