feat:审批通过时,校验节点是否为下一个执行节点

This commit is contained in:
lizhixian 2025-02-28 17:10:59 +08:00
parent 093e563b80
commit deef88f56f
2 changed files with 161 additions and 91 deletions

View File

@ -828,6 +828,125 @@ public class BpmnModelUtils {
} }
} }
/**
* 根据当前节点获取下一个节点
*
* @param currentElement 当前节点
* @param bpmnModel BPMN模型
* @param variables 流程变量
*/
public static List<FlowNode> getNextFlowNodes(FlowElement currentElement, BpmnModel bpmnModel,
Map<String, Object> variables){
// 下一个执行的流程节点集合
List<FlowNode> nextFlowNodes = new ArrayList<>();
// 当前执行节点的基本属性
FlowNode currentNode = (FlowNode) currentElement;
// 获取当前节点的关联节点
List<SequenceFlow> outgoingFlows = currentNode.getOutgoingFlows();
if (CollUtil.isEmpty(outgoingFlows)){
log.warn("[getNextFlowNodes][当前节点({}) 的 outgoingFlows 为空]", currentNode.getId());
return nextFlowNodes;
}
// 遍历每个出口流
for (SequenceFlow outgoingFlow : outgoingFlows) {
// 获取目标节点的基本属性
FlowElement targetElement = bpmnModel.getFlowElement(outgoingFlow.getTargetRef());
if (targetElement == null){
continue;
}
if (targetElement instanceof Gateway gateway) {
// 处理不同类型的网关
if (gateway instanceof ExclusiveGateway) {
handleExclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
} else if (gateway instanceof InclusiveGateway) {
handleInclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
} else if (gateway instanceof ParallelGateway) {
handleParallelGateway(gateway, bpmnModel, variables, nextFlowNodes);
}
} else {
// 如果不是网关直接添加到下一个节点列表
nextFlowNodes.add((FlowNode) targetElement);
}
}
return nextFlowNodes;
}
/**
* 处理排他网关
*
* @param gateway 排他网关
* @param bpmnModel BPMN模型
* @param variables 流程变量
* @param nextFlowNodes 下一个执行的流程节点集合
*/
private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
// TODO @小北 这里findOne和simulateNextFlowElements中有重复代码需要优化@芋道是否重构
SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& (evalConditionExpress(variables, flow.getConditionExpression())));
if (matchSequenceFlow == null) {
matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
// 特殊没有默认的情况下并且只有 1 个条件则认为它是默认的
if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
matchSequenceFlow = gateway.getOutgoingFlows().get(0);
}
}
// 遍历满足条件的 SequenceFlow 路径
if (matchSequenceFlow != null) {
FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef());
if (targetElement instanceof FlowNode) {
nextFlowNodes.add((FlowNode) targetElement);
}
}
}
/**
* 处理包容网关
*
* @param gateway 排他网关
* @param bpmnModel BPMN模型
* @param variables 流程变量
* @param nextFlowNodes 下一个执行的流程节点集合
*/
private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& evalConditionExpress(variables, flow.getConditionExpression()));
if (CollUtil.isEmpty(matchSequenceFlows)) {
matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
// 特殊没有默认的情况下并且只有 1 个条件则认为它是默认的
if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
matchSequenceFlows = gateway.getOutgoingFlows();
}
}
// 遍历满足条件的 SequenceFlow 路径获取目标节点
matchSequenceFlows.forEach(flow -> {
FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
if (targetElement instanceof FlowNode) {
nextFlowNodes.add((FlowNode) targetElement);
}
});
}
/**
* 处理并行网关
*
* @param gateway 排他网关
* @param bpmnModel BPMN模型
* @param variables 流程变量
* @param nextFlowNodes 下一个执行的流程节点集合
*/
private static void handleParallelGateway(Gateway gateway, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
// 并行网关遍历所有出口路径获取目标节点
gateway.getOutgoingFlows().forEach(flow -> {
FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
if (targetElement instanceof FlowNode) {
nextFlowNodes.add((FlowNode) targetElement);
}
});
}
/** /**
* 计算条件表达式是否为 true 满足条件 * 计算条件表达式是否为 true 满足条件
* *

View File

@ -23,6 +23,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; 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.BpmnModelConstants;
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.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;
@ -522,14 +523,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
// 2.3 调用 BPM complete 去完成任务 // 2.3 调用 BPM complete 去完成任务
// 其中variables 是存储动态表单到 local 任务级别过滤一下避免 ProcessInstance 系统级的变量被占用 // 其中variables 是存储动态表单到 local 任务级别过滤一下避免 ProcessInstance 系统级的变量被占用
if (CollUtil.isNotEmpty(reqVO.getVariables())) { // if (CollUtil.isNotEmpty(reqVO.getVariables())) {
Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables()); Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
// 校验传递的参数中是否存在不是下一个执行的节点 // 校验传递的参数中是否存在不是下一个执行的节点
// 当前执行的流程节点需根据该节点寻找下一个节点 validateNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(), bpmnModel, reqVO.getNextAssignees(), instance);
String taskDefinitionKey = task.getTaskDefinitionKey();
List<FlowNode> nextFlowNodes = getNextFlowNodes(taskDefinitionKey, bpmnModel, variables);
System.out.println(nextFlowNodes);
// validateNextAssignees(userId, reqVO.getVariables(), task.getProcessInstanceId(), reqVO.getNextAssignees());
// 下个节点审批人如果不存在则由前端传递 // 下个节点审批人如果不存在则由前端传递
if (CollUtil.isNotEmpty(reqVO.getNextAssignees())) { if (CollUtil.isNotEmpty(reqVO.getNextAssignees())) {
// 获取实例中的全部节点数据避免后续节点的审批人被覆盖 // 获取实例中的全部节点数据避免后续节点的审批人被覆盖
@ -539,104 +536,58 @@ public class BpmTaskServiceImpl implements BpmTaskService {
} }
runtimeService.setVariables(task.getProcessInstanceId(), variables); runtimeService.setVariables(task.getProcessInstanceId(), variables);
taskService.complete(task.getId(), variables, true); taskService.complete(task.getId(), variables, true);
} else { // } else {
taskService.complete(task.getId()); // taskService.complete(task.getId());
} // }
// 加签专属处理加签任务 // 加签专属处理加签任务
handleParentTaskIfSign(task.getParentTaskId()); handleParentTaskIfSign(task.getParentTaskId());
} }
/**
* 根据当前节点 ID 获取下一个执行的 FlowNode 列表
* @param taskDefinitionKey 当前节点 ID
* @param bpmnModel BPMN 模型
* @param variables 流程变量用于条件判断
* @return 下一个执行的 FlowNode 列表
*/
public List<FlowNode> getNextFlowNodes(String taskDefinitionKey, BpmnModel bpmnModel, Map<String, Object> variables) {
if (taskDefinitionKey == null || bpmnModel == null) {
throw new IllegalArgumentException("taskDefinitionKey and bpmnModel cannot be null");
}
FlowNode currentNode = (FlowNode) bpmnModel.getFlowElement(taskDefinitionKey);
if (currentNode == null) {
throw new IllegalArgumentException("FlowElement with given taskDefinitionKey not found in BpmnModel");
}
List<FlowNode> nextFlowNodes = new ArrayList<>();
resolveNextNodes(currentNode, bpmnModel, variables, nextFlowNodes);
return nextFlowNodes;
}
/**
* 递归解析下一个执行节点
* @param currentNode 当前节点
* @param bpmnModel BPMN 模型
* @param variables 流程变量用于条件判断
* @param nextFlowNodes 存储下一个执行节点的列表
*/
private void resolveNextNodes(FlowNode currentNode, BpmnModel bpmnModel, Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
List<SequenceFlow> outgoingFlows = currentNode.getOutgoingFlows();
for (SequenceFlow sequenceFlow : outgoingFlows) {
if (!shouldFollowSequenceFlow(currentNode, sequenceFlow, variables)) {
continue;
}
FlowElement targetElement = bpmnModel.getFlowElement(sequenceFlow.getTargetRef());
if (targetElement instanceof FlowNode targetNode) {
if (targetNode instanceof Gateway) {
// 如果目标节点是网关递归处理
resolveNextNodes(targetNode, bpmnModel, variables, nextFlowNodes);
}else {
nextFlowNodes.add(targetNode);
}
}
}
}
/**
* 判断是否应该遵循当前序列流
* @param currentNode 当前节点
* @param sequenceFlow 序列流
* @param variables 流程变量用于条件判断
* @return 是否应该遵循该序列流
*/
private boolean shouldFollowSequenceFlow(FlowNode currentNode, SequenceFlow sequenceFlow, Map<String, Object> variables) {
if (currentNode instanceof ExclusiveGateway) {
String conditionExpression = sequenceFlow.getConditionExpression();
return conditionExpression == null || BpmnModelUtils.evalConditionExpress(variables, conditionExpression);
}
return true;
}
/** /**
* 校验传递的参数中是否存在不是下一个执行的节点 * 校验传递的参数中是否存在不是下一个执行的节点
* *
* @param loginUserId 流程发起人 * @param taskDefinitionKey 当前任务节点id
* @param processInstanceId 流程实例id * @param variables 流程变量
* @param nextActivityNodes 下一个执行节点信息 {节点id : [审批人id,审批人id]} * @param bpmnModel 流程模型
* @param nextActivityNodes 下一个节点审批人集合参数
*/ */
private void validateNextAssignees(Long loginUserId, Map<String, Object> variables,String processInstanceId, private void validateNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
Map<String, List<Long>> nextActivityNodes){ Map<String, List<Long>> nextActivityNodes,ProcessInstance processInstance){
// 1查询流程预测的全部信息
BpmApprovalDetailRespVO approvalDetail = processInstanceService.getApprovalDetail(loginUserId, // 1获取当前任务节点的信息
new BpmApprovalDetailReqVO().setProcessVariables(variables).setProcessInstanceId(processInstanceId)); FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
// 2获取预测节点的信息 // 2获取下一个应该执行的节点集合
List<BpmApprovalDetailRespVO.ActivityNode> activityNodes = approvalDetail.getActivityNodes(); List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
if (CollUtil.isNotEmpty(activityNodes)) { // 3比较前端传递的节点和预测的下一个节点是否匹配匹配则将该节点设置上审批人
// 2.1获取节点中的审批人策略为发起人自选且状态为未执行的节点 for (FlowNode nextFlowNode : nextFlowNodes) {
// TODO 获取下一个执行节点 // 获取下一个执行节点的属性 是否为 发起人自选
List<BpmApprovalDetailRespVO.ActivityNode> notStartActivityNodes = activityNodes.stream().filter(node -> Map<String, List<ExtensionElement>> extensionElements = nextFlowNode.getExtensionElements();
BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()) List<ExtensionElement> elements = extensionElements.get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
&& BpmTaskStatusEnum.NOT_START.getStatus().equals(node.getStatus()) if (CollUtil.isEmpty(elements)){
&& CollUtil.isEmpty(node.getCandidateUsers())).toList(); continue;
// 3校验传递的参数中是否存在不是下一个节点的信息 }
for (Map.Entry<String, List<Long>> nextActivityNode : nextActivityNodes.entrySet()) { // 获取节点中的审批人策略
if (notStartActivityNodes.stream().noneMatch(taskNode -> taskNode.getId().equals(nextActivityNode.getKey()))) { Integer candidateStrategy = Integer.valueOf(elements.get(0).getElementText());
log.error("[checkNextActivityNodes][ ({}) 不是下一个执行的流程节点!]", nextActivityNode.getKey()); // 获取流程实例中的发起人自选审批人
throw exception(TASK_START_USER_SELECT_NODE_NOT_EXISTS, nextActivityNode.getKey()); Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
List<Long> startUserSelectAssignee = startUserSelectAssignees.get(nextFlowNode.getId());
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()) && CollUtil.isEmpty(startUserSelectAssignee)) {
// 先判断节点是否存在
if (!nextActivityNodes.containsKey(nextFlowNode.getId())){
throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName());
}
// 如果节点存在则判断节点中的审批人策略是否为 发起人自选
List<Long> nextAssignees = nextActivityNodes.get(nextFlowNode.getId());
// 3.1如果前端传递的节点为空则抛出异常
if (CollUtil.isEmpty(nextAssignees)) {
throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
} }
} }
} }
} }
/** /**