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

This commit is contained in:
YunaiV 2025-10-01 15:35:06 +08:00
parent 1dc5fd2c24
commit 5e46e0c4f8
4 changed files with 32 additions and 26 deletions

View File

@ -45,7 +45,7 @@ public class BpmnVariableConstants {
public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID";
/**
* 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id}
* 流程实例的变量 - 用于判断流程实例变量节点是否驳回格式 RETURN_FLAG_{节点 id}
*
* 目的是退回到发起节点时因为审批人与发起人相同所以被自动通过但是此时还是希望不要自动通过
*
@ -54,7 +54,7 @@ public class BpmnVariableConstants {
public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s";
/**
* 流程实例的变量前缀 - 用于退回操作记录需要预测的节点. 格式 NEED_SIMULATE_TASK_{节点定义 id}
* 流程实例的变量前缀 - 用于退回操作记录需要预测的节点格式 NEED_SIMULATE_TASK_{节点定义 id}
*
* 目的是退回操作预测节点会不准在流程变量中记录需要预测的节点来辅助预测
*/

View File

@ -658,11 +658,11 @@ public class BpmnModelUtils {
// 根据类型获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
// 1.没有入口连线则返回 false
// 1. 没有入口连线则返回 false
if (CollUtil.isEmpty(sequenceFlows)) {
return false;
}
// 2.循环找目标元素, 找到目标节点
// 2. 循环找目标元素, 找到目标节点
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复说明循环了跳过这个循环
if (visitedElements.contains(sequenceFlow.getId())) {
@ -679,7 +679,7 @@ public class BpmnModelUtils {
if (sourceFlowElement instanceof ParallelGateway) {
continue;
}
// 继续迭代 如果找到目标节点直接返回 true
// 继续迭代如果找到目标节点直接返回 true
if (isSequentialReachable(sourceFlowElement, target, visitedElements)) {
return true;
}

View File

@ -223,19 +223,19 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 3.1 计算当前登录用户的待办任务
BpmTaskRespVO todoTask = taskService.getTodoTask(loginUserId, reqVO.getTaskId(), reqVO.getProcessInstanceId());
// 3.2 获取由于退回操作需要预测的节点 从流程变量中获取 回退操作会设置这些变量
// 3.2 获取由于退回操作需要预测的节点从流程变量中获取回退操作会设置这些变量
Set<String> needSimulateTaskDefKeysByReturn = new HashSet<>();
if (StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) {
Map<String, Object> variables = runtimeService.getVariables(reqVO.getProcessInstanceId());
Map<String, Object> simulateTaskVariables = MapUtil.filter(variables,
item -> item.getKey().startsWith(PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX));
simulateTaskVariables.forEach(
(key, value) -> needSimulateTaskDefKeysByReturn.add(StrUtil.removePrefix(key, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX)));
simulateTaskVariables.forEach((key, value) ->
needSimulateTaskDefKeysByReturn.add(StrUtil.removePrefix(key, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX)));
}
// 移除运行中的节点运行中的节点无需预测
// TODO @jason是不是 foreach runActivityNodes然后移除 needSimulateTaskDefKeysByReturn 更好理解成本低一点
CollectionUtils.convertList(runActivityNodes, ActivityNode::getId).forEach(needSimulateTaskDefKeysByReturn::remove);
// 3.3 预测未运行节点的审批信息
List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
processDefinitionInfo,
@ -594,8 +594,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
Set<String> needSimulateTaskDefKeysByReturn) {
// TODO @芋艿可优化在驳回场景下未来的预测准确性不高原因是驳回后HistoricActivityInstance
// 包括了历史的操作不是只有 startEvent 到当前节点的记录
// 回退操作时候会记录需要预测的节点到流程变量中即使在历史操作中也需要预测
if (!needSimulateTaskDefKeysByReturn.contains(node.getId()) && runActivityIds.contains(node.getId())) {
if (runActivityIds.contains(node.getId())
&& !needSimulateTaskDefKeysByReturn.contains(node.getId())) { // 特殊回退操作时候会记录需要预测的节点到流程变量中即使在历史操作中也需要预测
return null;
}
Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
@ -644,7 +644,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
Map<String, Object> processVariables,
FlowElement node, Set<String> runActivityIds,
Set<String> needSimulateTaskDefKeysByReturn) {
// 回退操作时候会记录需要预测的节点到流程变量中即使节点在历史操作中也需要预测
if (!needSimulateTaskDefKeysByReturn.contains(node.getId()) && runActivityIds.contains(node.getId())) {
return null;

View File

@ -875,16 +875,15 @@ public class BpmTaskServiceImpl implements BpmTaskService {
* @return 目标任务节点元素
*/
private FlowElement validateTargetTaskCanReturn(BpmnModel bpmnModel, String sourceKey, String targetKey) {
// 1.3 获取当前任务节点元素
// 1.1 获取当前任务节点元素
FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey);
// 1.3 获取跳转的节点元素
// 1.2 获取跳转的节点元素
FlowElement target = BpmnModelUtils.getFlowElementById(bpmnModel, targetKey);
if (target == null) {
throw exception(TASK_TARGET_NODE_NOT_EXISTS);
}
// 2.2 只有串行可到达的节点才可以退回类似非串行子流程无法退回
// 2. 只有串行可到达的节点才可以退回类似非串行子流程无法退回
if (!BpmnModelUtils.isSequentialReachable(source, target, null)) {
throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR);
}
@ -934,7 +933,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
});
// 3. 构建需要预测的任务流程变量
Set<String> taskDefinitionKeyList = needSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement);
// TODO @jason驳回预测相关是不是搞成一个变量里面是 set 更简洁一点呀
Set<String> taskDefinitionKeyList = getNeedSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement);
Map<String, Object> needSimulateVariables = convertMap(taskDefinitionKeyList,
taskId -> StrUtil.concat(false, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, taskId), item -> Boolean.TRUE);
@ -944,27 +944,34 @@ public class BpmTaskServiceImpl implements BpmTaskService {
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(currentTask.getProcessInstanceId())
.moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey())
// 设置需要预测的任务流程变量用于辅助预测
// 设置需要预测的任务流程变量用于辅助预测
.processVariables(needSimulateVariables)
// 设置流程变量local节点退回标记, 用于退回到节点不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略导致自动通过
// 设置流程变量local节点退回标记, 用于退回到节点不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略导致自动通过
.localVariable(reqVO.getTargetTaskDefinitionKey(),
String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE)
.changeState();
}
private Set<String> needSimulateTaskDefinitionKeys(BpmnModel bpmnModel, Task currentTask, FlowElement targetElement) {
// 获取需要预测的任务的 definition key 当前任务还没完成也需要预测
private Set<String> getNeedSimulateTaskDefinitionKeys(BpmnModel bpmnModel, Task currentTask, FlowElement targetElement) {
// 1. 获取需要预测的任务的 definition key因为当前任务还没完成也需要预测
Set<String> taskDefinitionKeys = CollUtil.newHashSet(currentTask.getTaskDefinitionKey());
// 从已结束任务中找到要回退的目标任务按时间倒序最近的一个目标任务
// 2.1 从已结束任务中找到要回退的目标任务按时间倒序最近的一个目标任务
List<HistoricTaskInstance> endTaskList = CollectionUtils.filterList(
getTaskListByProcessInstanceId(currentTask.getProcessInstanceId(), Boolean.FALSE), item -> item.getEndTime() != null);
getTaskListByProcessInstanceId(currentTask.getProcessInstanceId(), Boolean.FALSE),
item -> item.getEndTime() != null);
// 2.2 遍历已结束的任务找到在 targetTask 之后生成的任务且串行可达的任务
HistoricTaskInstance targetTask = findFirst(endTaskList,
item -> item.getTaskDefinitionKey().equals(targetElement.getId()));
// TODO @jason驳回预测相关是不是 if targetTask 先判空
endTaskList.forEach(item -> {
FlowElement element = getFlowElementById(bpmnModel, item.getTaskDefinitionKey());
// 如果已结束的任务在回退目标节点之后生成且串行可达则标记为需要预算节点
// 如果已结束的任务在回退目标节点之后生成且串行可达则标记为需要预算节点
// TODO 串行可达的方法需要和判断可回退节点 validateTargetTaskCanReturn 分开吗 并行网关可能会有问题
if (targetTask != null && DateUtil.compare(item.getCreateTime(), targetTask.getCreateTime()) > 0
// TODO @jason驳回预测相关这里是不是判断 element
if (targetTask != null
// TODO @jason驳回预测相关这里直接 createTime compare 更简单因为不太会出现空哈
&& DateUtil.compare(item.getCreateTime(), targetTask.getCreateTime()) > 0
&& BpmnModelUtils.isSequentialReachable(element, targetElement, null)) {
taskDefinitionKeys.add(item.getTaskDefinitionKey());
}
@ -1483,7 +1490,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
return;
}
FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
// 判断是否为退回或者驳回如果是退回或者驳回不走这个策略, 使用 local variable
// 判断是否为退回或者驳回如果是退回或者驳回不走这个策略使用 local variable
Boolean returnTaskFlag = runtimeService.getVariableLocal(task.getExecutionId(),
String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(),