Commit 8be3a3f87ef4065ab334f464d3b7a16793dc8df3

Authored by jason
1 parent 7925af99

perf: [BPM 工作流] 优化回退操作,流程预测不准确的问题

yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
@@ -43,14 +43,23 @@ public class BpmnVariableConstants { @@ -43,14 +43,23 @@ public class BpmnVariableConstants {
43 * @see ProcessInstance#getProcessVariables() 43 * @see ProcessInstance#getProcessVariables()
44 */ 44 */
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 * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} 48 * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id}
48 * 49 *
49 - * 目的是:驳回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过 50 + * 目的是:退回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过
50 * 51 *
51 * @see ProcessInstance#getProcessVariables() 52 * @see ProcessInstance#getProcessVariables()
52 */ 53 */
53 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 +
  56 + /**
  57 + * 流程实例的变量前缀 - 用于退回操作。记录需要预测的节点. 格式 NEED_SIMULATE_TASK_{节点定义 id}
  58 + *
  59 + * 目的是:退回操作,预测节点会不准,在流程变量中记录需要预测的节点,来辅助预测
  60 + */
  61 + public static final String PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX = "NEED_SIMULATE_TASK_";
  62 +
54 /** 63 /**
55 * 流程实例的变量 - 是否跳过表达式 64 * 流程实例的变量 - 是否跳过表达式
56 * 65 *
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -658,10 +658,11 @@ public class BpmnModelUtils { @@ -658,10 +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 if (CollUtil.isEmpty(sequenceFlows)) { 662 if (CollUtil.isEmpty(sequenceFlows)) {
662 - return true; 663 + return false;
663 } 664 }
664 - // 循环找到目标元素 665 + // 2.循环找目标元素, 找到目标节点
665 for (SequenceFlow sequenceFlow : sequenceFlows) { 666 for (SequenceFlow sequenceFlow : sequenceFlows) {
666 // 如果发现连线重复,说明循环了,跳过这个循环 667 // 如果发现连线重复,说明循环了,跳过这个循环
667 if (visitedElements.contains(sequenceFlow.getId())) { 668 if (visitedElements.contains(sequenceFlow.getId())) {
@@ -669,21 +670,22 @@ public class BpmnModelUtils { @@ -669,21 +670,22 @@ public class BpmnModelUtils {
669 } 670 }
670 // 添加已经走过的连线 671 // 添加已经走过的连线
671 visitedElements.add(sequenceFlow.getId()); 672 visitedElements.add(sequenceFlow.getId());
672 - // 这条线路存在目标节点,这条线路完成,进入下个线路 673 + // 这条线路存在目标节点,直接返回 true
673 FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement(); 674 FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
674 if (target.getId().equals(sourceFlowElement.getId())) { 675 if (target.getId().equals(sourceFlowElement.getId())) {
675 - continue; 676 + return true;
676 } 677 }
677 - // 如果目标节点为并行网关,则不继续 678 + // 如果目标节点为并行网关,跳过这个循环 (TODO 疑问:这个判断作用是防止回退到并行网关分支上的节点吗?)
678 if (sourceFlowElement instanceof ParallelGateway) { 679 if (sourceFlowElement instanceof ParallelGateway) {
679 - return false; 680 + continue;
680 } 681 }
681 - // 否则就继续迭代  
682 - if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) {  
683 - return false; 682 + // 继续迭代, 如果找到目标节点直接返回 true
  683 + if (isSequentialReachable(sourceFlowElement, target, visitedElements)) {
  684 + return true;
684 } 685 }
685 } 686 }
686 - return true; 687 + // 未找到返回 false
  688 + return false;
687 } 689 }
688 690
689 /** 691 /**
@@ -783,7 +785,6 @@ public class BpmnModelUtils { @@ -783,7 +785,6 @@ public class BpmnModelUtils {
783 return resultElements; 785 return resultElements;
784 } 786 }
785 787
786 - @SuppressWarnings("PatternVariableCanBeUsed")  
787 private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables, 788 private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables,
788 List<FlowElement> resultElements, Set<FlowElement> visitElements) { 789 List<FlowElement> resultElements, Set<FlowElement> visitElements) {
789 // 如果为空,或者已经遍历过,则直接结束 790 // 如果为空,或者已经遍历过,则直接结束
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.collection.ListUtil; 4 import cn.hutool.core.collection.ListUtil;
5 import cn.hutool.core.date.DateUtil; 5 import cn.hutool.core.date.DateUtil;
6 import cn.hutool.core.lang.Assert; 6 import cn.hutool.core.lang.Assert;
  7 +import cn.hutool.core.map.MapUtil;
7 import cn.hutool.core.util.*; 8 import cn.hutool.core.util.*;
8 import cn.iocoder.yudao.framework.common.pojo.PageResult; 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 10 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@@ -72,6 +73,7 @@ import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmA @@ -72,6 +73,7 @@ import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmA
72 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 73 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
73 import static cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum.REJECT_CHILD_PROCESS; 74 import static cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum.REJECT_CHILD_PROCESS;
74 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; 75 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
  76 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX;
75 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseNodeType; 77 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseNodeType;
76 import static java.util.Arrays.asList; 78 import static java.util.Arrays.asList;
77 import static java.util.Collections.singletonList; 79 import static java.util.Collections.singletonList;
@@ -218,10 +220,24 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -218,10 +220,24 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
218 220
219 // 3.1 计算当前登录用户的待办任务 221 // 3.1 计算当前登录用户的待办任务
220 BpmTaskRespVO todoTask = taskService.getTodoTask(loginUserId, reqVO.getTaskId(), reqVO.getProcessInstanceId()); 222 BpmTaskRespVO todoTask = taskService.getTodoTask(loginUserId, reqVO.getTaskId(), reqVO.getProcessInstanceId());
221 - // 3.2 预测未运行节点的审批信息 223 +
  224 + // 3.2 获取由于退回操作,需要预测的节点。 从流程变量中获取。 回退操作会设置这些变量
  225 + Set<String> needSimulateTaskDefKeysByReturn = new HashSet<>();
  226 + if (StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) {
  227 + Map<String, Object> variables = runtimeService.getVariables(reqVO.getProcessInstanceId());
  228 + Map<String, Object> simulateTaskVariables = MapUtil.filter(variables,
  229 + item -> item.getKey().startsWith(PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX));
  230 + simulateTaskVariables.forEach(
  231 + (key, value) -> needSimulateTaskDefKeysByReturn.add(StrUtil.removePrefix(key, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX)));
  232 + }
  233 + // 移除运行中的节点,运行中的节点无需预测
  234 + CollectionUtils.convertList(runActivityNodes, ActivityNode::getId).forEach(needSimulateTaskDefKeysByReturn::remove);
  235 +
  236 +
  237 + // 3.3 预测未运行节点的审批信息
222 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, 238 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
223 processDefinitionInfo, 239 processDefinitionInfo,
224 - processVariables, activities); 240 + processVariables, activities, needSimulateTaskDefKeysByReturn);
225 241
226 // 4. 拼接最终数据 242 // 4. 拼接最终数据
227 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, 243 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
@@ -461,7 +477,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -461,7 +477,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
461 } 477 }
462 478
463 /** 479 /**
464 - * 获取结束节点的状态 480 + * 获取结束节点的状态
465 */ 481 */
466 private Integer getEndActivityNodeStatus(HistoricTaskInstance task) { 482 private Integer getEndActivityNodeStatus(HistoricTaskInstance task) {
467 Integer status = FlowableUtils.getTaskStatus(task); 483 Integer status = FlowableUtils.getTaskStatus(task);
@@ -546,7 +562,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -546,7 +562,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
546 private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, 562 private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel,
547 BpmProcessDefinitionInfoDO processDefinitionInfo, 563 BpmProcessDefinitionInfoDO processDefinitionInfo,
548 Map<String, Object> processVariables, 564 Map<String, Object> processVariables,
549 - List<HistoricActivityInstance> activities) { 565 + List<HistoricActivityInstance> activities,
  566 + Set<String> needSimulateTaskDefKeysByReturn) {
550 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 567 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance
551 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录 568 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录
552 Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId); 569 Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId);
@@ -555,7 +572,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -555,7 +572,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
555 List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables); 572 List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);
556 return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn( 573 return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(
557 startUserId, bpmnModel, flowElements, 574 startUserId, bpmnModel, flowElements,
558 - processDefinitionInfo, processVariables, flowElement, runActivityIds)); 575 + processDefinitionInfo, processVariables, flowElement, runActivityIds, needSimulateTaskDefKeysByReturn));
559 } 576 }
560 // 情况二:SIMPLE 设计器 577 // 情况二:SIMPLE 设计器
561 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { 578 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) {
@@ -564,17 +581,19 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -564,17 +581,19 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
564 List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables); 581 List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);
565 return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple( 582 return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(
566 startUserId, bpmnModel, 583 startUserId, bpmnModel,
567 - processDefinitionInfo, processVariables, simpleNode, runActivityIds)); 584 + processDefinitionInfo, processVariables, simpleNode, runActivityIds, needSimulateTaskDefKeysByReturn));
568 } 585 }
569 throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); 586 throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());
570 } 587 }
571 588
572 private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel, 589 private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel,
573 BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, 590 BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
574 - BpmSimpleModelNodeVO node, Set<String> runActivityIds) { 591 + BpmSimpleModelNodeVO node, Set<String> runActivityIds,
  592 + Set<String> needSimulateTaskDefKeysByReturn) {
575 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 593 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance
576 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录 594 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录
577 - if (runActivityIds.contains(node.getId())) { 595 + // 回退操作时候,会记录需要预测的节点到流程变量中。即使在历史操作中,也需要预测。
  596 + if (!needSimulateTaskDefKeysByReturn.contains(node.getId()) && runActivityIds.contains(node.getId())) {
578 return null; 597 return null;
579 } 598 }
580 Integer status = BpmTaskStatusEnum.NOT_START.getStatus(); 599 Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
@@ -621,13 +640,17 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -621,13 +640,17 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
621 private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List<FlowElement> flowElements, 640 private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List<FlowElement> flowElements,
622 BpmProcessDefinitionInfoDO processDefinitionInfo, 641 BpmProcessDefinitionInfoDO processDefinitionInfo,
623 Map<String, Object> processVariables, 642 Map<String, Object> processVariables,
624 - FlowElement node, Set<String> runActivityIds) {  
625 - if (runActivityIds.contains(node.getId())) { 643 + FlowElement node, Set<String> runActivityIds,
  644 + Set<String> needSimulateTaskDefKeysByReturn) {
  645 +
  646 + // 回退操作时候,会记录需要预测的节点到流程变量中。即使节点在历史操作中,也需要预测。
  647 + if (!needSimulateTaskDefKeysByReturn.contains(node.getId()) && runActivityIds.contains(node.getId())) {
626 return null; 648 return null;
627 } 649 }
  650 +
628 Integer status = BpmTaskStatusEnum.NOT_START.getStatus(); 651 Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
629 // 如果节点被跳过,状态设置为跳过 652 // 如果节点被跳过,状态设置为跳过
630 - if(BpmnModelUtils.isSkipNode(node, processVariables)){ 653 + if (BpmnModelUtils.isSkipNode(node, processVariables)) {
631 status = BpmTaskStatusEnum.SKIP.getStatus(); 654 status = BpmTaskStatusEnum.SKIP.getStatus();
632 } 655 }
633 ActivityNode activityNode = new ActivityNode().setId(node.getId()) 656 ActivityNode activityNode = new ActivityNode().setId(node.getId())
@@ -967,7 +990,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -967,7 +990,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
967 if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_ROLLED_BACK)) { 990 if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_ROLLED_BACK)) {
968 return; 991 return;
969 } 992 }
970 - taskService.moveTaskToEnd(parentProcessInstance.getId(),REJECT_CHILD_PROCESS.getReason()); 993 + taskService.moveTaskToEnd(parentProcessInstance.getId(), REJECT_CHILD_PROCESS.getReason());
971 } 994 }
972 }); 995 });
973 } 996 }
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -2,10 +2,12 @@ package cn.iocoder.yudao.module.bpm.service.task; @@ -2,10 +2,12 @@ package cn.iocoder.yudao.module.bpm.service.task;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.convert.Convert; 4 import cn.hutool.core.convert.Convert;
  5 +import cn.hutool.core.date.DateUtil;
5 import cn.hutool.core.lang.Assert; 6 import cn.hutool.core.lang.Assert;
6 import cn.hutool.core.util.*; 7 import cn.hutool.core.util.*;
7 import cn.hutool.extra.spring.SpringUtil; 8 import cn.hutool.extra.spring.SpringUtil;
8 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;
9 import cn.iocoder.yudao.framework.common.util.date.DateUtils; 11 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
10 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 12 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
11 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 13 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@@ -68,8 +70,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU @@ -68,8 +70,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
68 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 70 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
69 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 71 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
70 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; 72 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
71 -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG;  
72 -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE; 73 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.*;
73 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; 74 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
74 75
75 /** 76 /**
@@ -590,7 +591,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -590,7 +591,11 @@ public class BpmTaskServiceImpl implements BpmTaskService {
590 bpmnModel, reqVO.getNextAssignees(), instance); 591 bpmnModel, reqVO.getNextAssignees(), instance);
591 runtimeService.setVariables(task.getProcessInstanceId(), variables); 592 runtimeService.setVariables(task.getProcessInstanceId(), variables);
592 593
593 - // 5. 调用 BPM complete 去完成任务 594 + // 5. 移除辅助预测的流程变量,这些变量在回退操作中设置
  595 + String simulateVariableName = StrUtil.concat(false, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, task.getTaskDefinitionKey());
  596 + runtimeService.removeVariable(task.getProcessInstanceId(), simulateVariableName);
  597 +
  598 + // 6. 调用 BPM complete 去完成任务
594 taskService.complete(task.getId(), variables, true); 599 taskService.complete(task.getId(), variables, true);
595 600
596 // 【加签专属】处理加签任务 601 // 【加签专属】处理加签任务
@@ -840,25 +845,26 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -840,25 +845,26 @@ public class BpmTaskServiceImpl implements BpmTaskService {
840 if (task.isSuspended()) { 845 if (task.isSuspended()) {
841 throw exception(TASK_IS_PENDING); 846 throw exception(TASK_IS_PENDING);
842 } 847 }
843 - // 1.2 校验源头和目标节点的关系,并返回目标元素  
844 - FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(),  
845 - reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId()); 848 + // 1.2 获取流程模型信息
  849 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
  850 + // 1.3 校验源头和目标节点的关系,并返回目标元素
  851 + FlowElement targetElement = validateTargetTaskCanReturn(bpmnModel, task.getTaskDefinitionKey(),
  852 + reqVO.getTargetTaskDefinitionKey());
846 853
847 // 2. 调用 Flowable 框架的退回逻辑 854 // 2. 调用 Flowable 框架的退回逻辑
848 - returnTask(userId, task, targetElement, reqVO); 855 + returnTask(userId, bpmnModel, task, targetElement, reqVO);
849 } 856 }
850 857
851 /** 858 /**
852 * 退回流程节点时,校验目标任务节点是否可退回 859 * 退回流程节点时,校验目标任务节点是否可退回
853 * 860 *
854 - * @param sourceKey 当前任务节点 Key  
855 - * @param targetKey 目标任务节点 key  
856 - * @param processDefinitionId 当前流程定义 ID 861 + * @param bpmnModel 流程模型
  862 + * @param sourceKey 当前任务节点 Key
  863 + * @param targetKey 目标任务节点 key
857 * @return 目标任务节点元素 864 * @return 目标任务节点元素
858 */ 865 */
859 - private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) {  
860 - // 1.1 获取流程模型信息  
861 - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); 866 + private FlowElement validateTargetTaskCanReturn(BpmnModel bpmnModel, String sourceKey, String targetKey) {
  867 +
862 // 1.3 获取当前任务节点元素 868 // 1.3 获取当前任务节点元素
863 FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); 869 FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey);
864 // 1.3 获取跳转的节点元素 870 // 1.3 获取跳转的节点元素
@@ -878,11 +884,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -878,11 +884,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
878 * 执行退回逻辑 884 * 执行退回逻辑
879 * 885 *
880 * @param userId 用户编号 886 * @param userId 用户编号
  887 + * @param bpmnModel 流程模型
881 * @param currentTask 当前退回的任务 888 * @param currentTask 当前退回的任务
882 * @param targetElement 需要退回到的目标任务 889 * @param targetElement 需要退回到的目标任务
883 * @param reqVO 前端参数封装 890 * @param reqVO 前端参数封装
884 */ 891 */
885 - public void returnTask(Long userId, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { 892 + public void returnTask(Long userId, BpmnModel bpmnModel, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
886 // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤 893 // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤
887 // 1.1 获取所有正常进行的任务节点 Key 894 // 1.1 获取所有正常进行的任务节点 Key
888 List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list(); 895 List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list();
@@ -915,18 +922,45 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -915,18 +922,45 @@ public class BpmTaskServiceImpl implements BpmTaskService {
915 } 922 }
916 }); 923 });
917 924
918 - // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过  
919 - runtimeService.setVariable(currentTask.getProcessInstanceId(),  
920 - String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE); 925 + // 3. 构建需要预测的任务流程变量
  926 + Set<String> taskDefinitionKeyList = needSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement);
  927 + Map<String, Object> needSimulateVariables = convertMap(taskDefinitionKeyList,
  928 + taskId -> StrUtil.concat(false, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, taskId), item -> Boolean.TRUE);
  929 +
921 // 4. 执行驳回 930 // 4. 执行驳回
922 // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因: 931 // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因:
923 // 当多实例任务回退的时候有问题。相关 issue: https://github.com/flowable/flowable-engine/issues/3944 932 // 当多实例任务回退的时候有问题。相关 issue: https://github.com/flowable/flowable-engine/issues/3944
924 runtimeService.createChangeActivityStateBuilder() 933 runtimeService.createChangeActivityStateBuilder()
925 .processInstanceId(currentTask.getProcessInstanceId()) 934 .processInstanceId(currentTask.getProcessInstanceId())
926 .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) 935 .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey())
  936 + // 设置需要预测的任务流程变量。用于辅助预测
  937 + .processVariables(needSimulateVariables)
  938 + // 设置流程变量(local)节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过
  939 + .localVariable(reqVO.getTargetTaskDefinitionKey(),
  940 + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE)
927 .changeState(); 941 .changeState();
928 } 942 }
929 943
  944 + private Set<String> needSimulateTaskDefinitionKeys(BpmnModel bpmnModel, Task currentTask, FlowElement targetElement) {
  945 + // 获取需要预测的任务的 definition key。 当前任务还没完成。也需要预测。
  946 + Set<String> taskDefinitionKeys = CollUtil.newHashSet(currentTask.getTaskDefinitionKey());
  947 + // 从已结束任务中找到要回退的目标任务。按时间倒序最近的一个目标任务
  948 + List<HistoricTaskInstance> endTaskList = CollectionUtils.filterList(
  949 + getTaskListByProcessInstanceId(currentTask.getProcessInstanceId(), Boolean.FALSE), item -> item.getEndTime() != null);
  950 + HistoricTaskInstance targetTask = findFirst(endTaskList,
  951 + item -> item.getTaskDefinitionKey().equals(targetElement.getId()));
  952 + endTaskList.forEach(item -> {
  953 + FlowElement element = getFlowElementById(bpmnModel, item.getTaskDefinitionKey());
  954 + // 如果已结束的任务在回退目标节点之后生成,且串行可达,则标记为需要预算节点。
  955 + // TODO 串行可达的方法需要和判断可回退节点 validateTargetTaskCanReturn 分开吗? 并行网关可能会有问题。
  956 + if (targetTask != null && DateUtil.compare(item.getCreateTime(), targetTask.getCreateTime()) > 0
  957 + && BpmnModelUtils.isSequentialReachable(element, targetElement, null)) {
  958 + taskDefinitionKeys.add(item.getTaskDefinitionKey());
  959 + }
  960 + });
  961 + return taskDefinitionKeys;
  962 + }
  963 +
930 @Override 964 @Override
931 @Transactional(rollbackFor = Exception.class) 965 @Transactional(rollbackFor = Exception.class)
932 public void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO) { 966 public void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO) {
@@ -1438,9 +1472,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -1438,9 +1472,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1438 return; 1472 return;
1439 } 1473 }
1440 FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); 1474 FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
1441 - // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略  
1442 - // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识  
1443 - Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), 1475 + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略, 使用 local variable
  1476 + Boolean returnTaskFlag = runtimeService.getVariableLocal(task.getExecutionId(),
1444 String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); 1477 String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
1445 Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(), 1478 Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(),
1446 PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); 1479 PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));