Pre Merge pull request !1349 from whc/dev

This commit is contained in:
whc 2025-06-08 03:50:08 +00:00 committed by Gitee
commit 2d568d72be
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
9 changed files with 212 additions and 6 deletions

View File

@ -59,7 +59,7 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
// 第二步获取任务的所有处理人 // 第二步获取任务的所有处理人
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
if (assigneeUserIds == null) { if (assigneeUserIds == null) {
assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
if (CollUtil.isEmpty(assigneeUserIds)) { if (CollUtil.isEmpty(assigneeUserIds)) {

View File

@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -41,11 +42,34 @@ public class BpmTaskAssignLeaderExpression {
* @param level 指定级别 * @param level 指定级别
* @return 指定级别的领导 * @return 指定级别的领导
*/ */
public Set<Long> calculateUsers(DelegateExecution execution, int level) { public Set<Long> calculateUsers(ExecutionEntityImpl execution, Long level) {
Assert.isTrue(level > 0, "level 必须大于 0");
// 获得发起人 // 获得发起人
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); return calculateUsers(NumberUtils.parseLong(processInstance.getStartUserId()), level);
}
/**
* 计算审批的候选人
*
* @param processInstanceId 流程实例id
* @param level 指定级别
* @return 指定级别的领导
*/
public Set<Long> calculateUsers(String processInstanceId, Long level) {
ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
return calculateUsers(NumberUtils.parseLong(processInstance.getStartUserId()), level);
}
/**
* 计算审批的候选人
*
* @param startUserId 发起人
* @param level 指定级别
* @return 指定级别的领导
*/
public Set<Long> calculateUsers(Long startUserId, Long level) {
Assert.isTrue(level > 0, "level 必须大于 0");
// 获得对应 leve 的部门 // 获得对应 leve 的部门
DeptRespDTO dept = null; DeptRespDTO dept = null;
for (int i = 0; i < level; i++) { for (int i = 0; i < level; i++) {

View File

@ -33,4 +33,26 @@ public class BpmTaskAssignStartUserExpression {
return SetUtils.asSet(startUserId); return SetUtils.asSet(startUserId);
} }
/**
* 计算审批的候选人
*
* @param startUserId 发起人id
* @return 发起人
*/
public Set<Long> calculateUsers(Long startUserId) {
return SetUtils.asSet(startUserId);
}
/**
* 计算审批的候选人
*
* @param processInstanceId 流程实例id
* @return 发起人
*/
public Set<Long> calculateUsers(String processInstanceId) {
ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
return SetUtils.asSet(startUserId);
}
} }

View File

@ -96,4 +96,8 @@ public class BpmnVariableConstants {
*/ */
public static final String TASK_SIGN_PIC_URL = "TASK_SIGN_PIC_URL"; public static final String TASK_SIGN_PIC_URL = "TASK_SIGN_PIC_URL";
/**
* 流程实例的变量 - 流程实例id
*/
public static final String PROCESS_INSTANCE_ID = "PROCESS_INSTANCE_ID";
} }

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
/**
* @Author whc
* @Date 2025/5/21 11:30
*/
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
public class DeleteExecutionCommand implements Command<Void> {
private final String executionId;
private final String reason;
public DeleteExecutionCommand(String executionId, String reason) {
this.executionId = executionId;
this.reason = reason;
}
@Override
public Void execute(CommandContext commandContext) {
ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
ExecutionEntity execution = executionEntityManager.findById(executionId);
if (execution != null) {
executionEntityManager.deleteExecutionAndRelatedData(execution, reason, false);
}
return null;
}
}

View File

@ -755,6 +755,48 @@ public class BpmnModelUtils {
return userTaskList; return userTaskList;
} }
/**
* 获取当前元素后可能执行到的所有userTask
*
* @param startElement 起始元素
* @param bpmnModel 流程模型
* @return 当前元素后可能执行到的所有userTask不包含自身
*/
public static Set<UserTask> findReachableUserTasks(FlowElement startElement, BpmnModel bpmnModel) {
Set<UserTask> userTasks = new HashSet<>();
Set<String> visited = new HashSet<>();
if (startElement instanceof FlowNode) {
List<SequenceFlow> outgoing = ((FlowNode) startElement).getOutgoingFlows();
for (SequenceFlow flow : outgoing) {
FlowElement target = bpmnModel.getMainProcess().getFlowElement(flow.getTargetRef());
dfs(target, bpmnModel, userTasks, visited);
}
}
return userTasks;
}
private static void dfs(FlowElement current, BpmnModel model, Set<UserTask> userTasks, Set<String> visited) {
if (current == null || visited.contains(current.getId())) {
return;
}
visited.add(current.getId());
if (current instanceof UserTask) {
userTasks.add((UserTask) current);
}
if (current instanceof FlowNode) {
List<SequenceFlow> outgoingFlows = ((FlowNode) current).getOutgoingFlows();
for (SequenceFlow flow : outgoingFlows) {
String targetRef = flow.getTargetRef();
FlowElement targetElement = model.getMainProcess().getFlowElement(targetRef);
dfs(targetElement, model, userTasks, visited);
}
}
}
// ========== BPMN 流程预测相关的方法 ========== // ========== BPMN 流程预测相关的方法 ==========
/** /**

View File

@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormFieldVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormFieldVO;
@ -30,6 +31,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@ -355,6 +358,33 @@ public class FlowableUtils {
} }
public static Object getExpressionValue(Map<String, Object> variable, String expressionString) { public static Object getExpressionValue(Map<String, Object> variable, String expressionString) {
// 替换方法参数中的 execution 为流程实例id或userId因为流程预测时获取不到execution对象
// 例如将 calculateUsers(execution, 1) 替换为 calculateUsers('xxx-xxx-xxx-xxx', 1)
Pattern pattern = Pattern.compile("\\(([^)]*)\\)");
Matcher matcher = pattern.matcher(expressionString);
StringBuilder sb = new StringBuilder();
while (matcher.find()) {
// 获取方法参数列表
String args = matcher.group(1);
// 用逗号分隔参数并逐个替换精确匹配 execution 的部分
String[] params = args.split(",");
for (int i = 0; i < params.length; i++) {
if ("execution".equals(params[i].trim())) {
// 流程已开始
if(ObjectUtil.isNotEmpty(variable.get(BpmnVariableConstants.PROCESS_INSTANCE_ID))) {
String processInstanceId = variable.get(BpmnVariableConstants.PROCESS_INSTANCE_ID).toString();
params[i] = "'" + processInstanceId + "'";
} else {
//流程未开始
params[i] = Objects.requireNonNull(SecurityFrameworkUtils.getLoginUserId()).toString();
}
}
}
String newArgs = String.join(", ", params);
matcher.appendReplacement(sb, "(" + newArgs + ")");
}
matcher.appendTail(sb);
expressionString = sb.toString();
VariableContainer variableContainer = new MapDelegateVariableContainer(variable, VariableContainer.empty()); VariableContainer variableContainer = new MapDelegateVariableContainer(variable, VariableContainer.empty());
return getExpressionValue(variableContainer, expressionString); return getExpressionValue(variableContainer, expressionString);
} }

View File

@ -46,6 +46,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
import org.flowable.engine.HistoryService; import org.flowable.engine.HistoryService;
@ -766,9 +767,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
.businessKey(businessKey) .businessKey(businessKey)
.variables(variables); .variables(variables);
// 3.1 创建流程 ID // 3.1 创建流程 ID
String processInstanceId = null;
BpmModelMetaInfoVO.ProcessIdRule processIdRule = processDefinitionInfo.getProcessIdRule(); BpmModelMetaInfoVO.ProcessIdRule processIdRule = processDefinitionInfo.getProcessIdRule();
if (processIdRule != null && Boolean.TRUE.equals(processIdRule.getEnable())) { if (processIdRule != null && Boolean.TRUE.equals(processIdRule.getEnable())) {
processInstanceBuilder.predefineProcessInstanceId(processIdRedisDAO.generate(processIdRule)); processInstanceId = processIdRedisDAO.generate(processIdRule);
processInstanceBuilder.predefineProcessInstanceId(processInstanceId);
} }
// 3.2 流程名称 // 3.2 流程名称
BpmModelMetaInfoVO.TitleSetting titleSetting = processDefinitionInfo.getTitleSetting(); BpmModelMetaInfoVO.TitleSetting titleSetting = processDefinitionInfo.getTitleSetting();
@ -784,6 +787,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
} }
// 3.3 发起流程实例 // 3.3 发起流程实例
ProcessInstance instance = processInstanceBuilder.start(); ProcessInstance instance = processInstanceBuilder.start();
// 将流程实例id保存到流程变量里用于无法获取execution时获取流程实例id
processInstanceId = StringUtils.defaultIfBlank(processInstanceId, instance.getId());
runtimeService.setVariable(instance.getProcessInstanceId(),BpmnVariableConstants.PROCESS_INSTANCE_ID,processInstanceId);
return instance.getId(); return instance.getId();
} }

View File

@ -24,6 +24,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.DeleteExecutionCommand;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
@ -646,6 +647,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
if (approveUserSelectAssignees == null) { if (approveUserSelectAssignees == null) {
approveUserSelectAssignees = new HashMap<>(); approveUserSelectAssignees = new HashMap<>();
} }
// 当多实例任务根据'审批人自选'策略多次选择时避免审批人被后一次选择覆盖
List<Long> nodeHadAssignees = approveUserSelectAssignees.get(nextFlowNode.getId());
if (nodeHadAssignees != null){
assignees = Stream.concat(nodeHadAssignees.stream(), assignees.stream())
.distinct()
.toList();
}
approveUserSelectAssignees.put(nextFlowNode.getId(), assignees); approveUserSelectAssignees.put(nextFlowNode.getId(), assignees);
Map<String,List<Long>> existingApproveUserSelectAssignees = (Map<String,List<Long>>) variables.get( Map<String,List<Long>> existingApproveUserSelectAssignees = (Map<String,List<Long>>) variables.get(
BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES); BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES);
@ -906,10 +914,47 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 4. 执行驳回 // 4. 执行驳回
// 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因 // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因
// 当多实例任务回退的时候有问题相关 issue: https://github.com/flowable/flowable-engine/issues/3944 // 当多实例任务回退的时候有问题相关 issue: https://github.com/flowable/flowable-engine/issues/3944
// 选择一个主 execution
String mainExecutionId = runExecutionIds.get(0);
// 删除其他 execution 解决多个execution会在移动的目标节点创建出多个任务的问题
for (int i = 1; i < runExecutionIds.size(); i++) {
String redundantId = runExecutionIds.get(i);
managementService.executeCommand(new DeleteExecutionCommand(redundantId, "退回清理冗余路径"));
}
runtimeService.createChangeActivityStateBuilder() runtimeService.createChangeActivityStateBuilder()
.processInstanceId(currentTask.getProcessInstanceId()) .processInstanceId(currentTask.getProcessInstanceId())
.moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) .moveExecutionToActivityId(mainExecutionId, reqVO.getTargetTaskDefinitionKey())
.changeState(); .changeState();
// 5. 清除'审批人自选'策略相关数据
// 移除退回后不会自动通过的节点的审批人数据
// 根据自动去重设置执行相关操作
BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo(currentTask.getProcessDefinitionId());
BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(currentTask.getProcessDefinitionId());
ProcessInstance processInstance = processInstanceService.getProcessInstance(currentTask.getProcessInstanceId());
if (BpmAutoApproveTypeEnum.NONE.getType().equals(processDefinitionInfo.getAutoApprovalType())) {
// 不自动通过清除 targetElement 后所有节点的审批人数据
Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance);
if (ObjectUtil.isNotEmpty(approveUserSelectAssignees)) {
Set<UserTask> reachableUserTasks = findReachableUserTasks(targetElement, bpmnModel);
for (UserTask reachableUserTask : reachableUserTasks) {
approveUserSelectAssignees.remove(reachableUserTask.getId());
}
runtimeService.setVariable(processInstance.getProcessInstanceId(),BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,approveUserSelectAssignees);
}
} else if (BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getType().equals(processDefinitionInfo.getAutoApprovalType())) {
// TODO 优化去重规则为连续审批的节点自动通过看退回到目标节点后会自动通过走到哪里将走到的位置之后的节点自选审批人数据清空
// 暂且清除 targetElement 后所有节点的审批人数据
// 自动通过时若候选人策略为'审批人自选'会因为缺失自选审批人报错需要重新选择审批人手动审批
Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance);
if (ObjectUtil.isNotEmpty(approveUserSelectAssignees)) {
Set<UserTask> reachableUserTasks = findReachableUserTasks(targetElement, bpmnModel);
for (UserTask reachableUserTask : reachableUserTasks) {
approveUserSelectAssignees.remove(reachableUserTask.getId());
}
runtimeService.setVariable(processInstance.getProcessInstanceId(),BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,approveUserSelectAssignees);
}
}
// 若为自动通过还会走到当前节点不清除节点自选审批人数据
} }
@Override @Override