【同步】BOOT 和 CLOUD 的功能

This commit is contained in:
YunaiV
2025-08-16 19:02:44 +08:00
parent 2382c3d844
commit 110c38bf6e
49 changed files with 574 additions and 262 deletions

View File

@@ -72,6 +72,9 @@ public class BpmModelMetaInfoVO {
@Schema(description = "允许撤销审批中的申请", example = "true")
private Boolean allowCancelRunningProcess;
@Schema(description = "允许允许审批人撤回任务", example = "false")
private Boolean allowWithdrawTask;
@Schema(description = "流程 ID 规则", example = "{}")
private ProcessIdRule processIdRule;

View File

@@ -219,6 +219,14 @@ public class BpmTaskController {
return success(true);
}
@PutMapping("/withdraw")
@Operation(summary = "撤回任务")
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<Boolean> withdrawTask(@RequestParam("taskId") String taskId) {
taskService.withdrawTask(getLoginUserId(), taskId);
return success(true);
}
@GetMapping("/list-by-parent-task-id")
@Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
@Parameter(name = "parentTaskId", description = "父级任务编号", required = true)

View File

@@ -172,6 +172,11 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
*/
private Boolean allowCancelRunningProcess;
/**
* 是否允许审批人撤回任务
*/
private Boolean allowWithdrawTask;
/**
* 流程 ID 规则
*/

View File

@@ -797,9 +797,9 @@ public class BpmnModelUtils {
// 情况StartEvent/EndEvent/UserTask/ServiceTask
if (currentElement instanceof StartEvent
|| currentElement instanceof EndEvent
|| currentElement instanceof UserTask
|| currentElement instanceof ServiceTask) {
|| currentElement instanceof EndEvent
|| currentElement instanceof UserTask
|| currentElement instanceof ServiceTask) {
// 添加节点
FlowNode flowNode = (FlowNode) currentElement;
resultElements.add(flowNode);
@@ -908,6 +908,49 @@ public class BpmnModelUtils {
return nextFlowNodes;
}
/**
* 查找起始节点下一个用户任务列表列表
*
* @param source 起始节点
* @return 结果
*/
public static List<UserTask> getNextUserTasks(FlowElement source) {
return getNextUserTasks(source, null, null);
}
/**
* 查找起始节点下一个用户任务列表列表
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 用户任务列表
* @return 结果
*/
public static List<UserTask> getNextUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>());
userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>());
// 获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (!sequenceFlows.isEmpty()) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
if (targetFlowElement instanceof UserTask) {
// 若节点为用户任务,加入到结果列表中
userTaskList.add((UserTask) targetFlowElement);
} else {
// 若节点非用户任务,继续递归查找下一个节点
getNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList);
}
}
}
return userTaskList;
}
/**
* 处理排它网关
*
@@ -938,8 +981,8 @@ public class BpmnModelUtils {
*/
private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map<String, Object> variables) {
SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& (evalConditionExpress(variables, flow.getConditionExpression())));
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()));

View File

@@ -67,7 +67,6 @@ import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
@@ -221,11 +220,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
processDefinitionInfo,
processVariables, activities);
// 3.3 如果是发起动作activityId 为开始节点,不校验审批人自选节点
if (ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
simulateActivityNodes.removeIf(node ->
BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()));
}
// 4. 拼接最终数据
return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
@@ -415,7 +409,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
endActivities.forEach(activity -> {
// StartEvent只处理 BPMN 的场景。因为SIMPLE 情况下,已经有 START_USER_NODE 节点
if (ELEMENT_EVENT_START.equals(activity.getActivityType())
&& BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) {
&& BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())
&& !CollUtil.contains(activities, // 特殊:如果已经存在用户手动创建的 START_USER_NODE_ID 节点,则忽略 StartEvent
historicActivity -> historicActivity.getActivityId().equals(START_USER_NODE_ID))) {
ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID)
.setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus());
ActivityNode startNode = new ActivityNode().setId(startTask.getId())
@@ -555,7 +551,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 情况一BPMN 设计器
if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) {
List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);
return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel,
return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(
startUserId, bpmnModel, flowElements,
processDefinitionInfo, processVariables, flowElement, runActivityIds));
}
// 情况二SIMPLE 设计器
@@ -563,7 +560,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(),
BpmSimpleModelNodeVO.class);
List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);
return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel,
return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(
startUserId, bpmnModel,
processDefinitionInfo, processVariables, simpleNode, runActivityIds));
}
throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());
@@ -618,8 +616,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
return null;
}
private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List<FlowElement> flowElements,
BpmProcessDefinitionInfoDO processDefinitionInfo,
Map<String, Object> processVariables,
FlowElement node, Set<String> runActivityIds) {
if (runActivityIds.contains(node.getId())) {
return null;
@@ -634,6 +633,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 1. 开始节点
if (node instanceof StartEvent) {
if (CollUtil.contains(flowElements, // 特殊:如果已经存在用户手动创建的 START_USER_NODE_ID 节点,则忽略 StartEvent
flowElement -> flowElement.getId().equals(START_USER_NODE_ID))) {
return null;
}
return activityNode.setName(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getName())
.setNodeType(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType());
}

View File

@@ -250,6 +250,14 @@ public interface BpmTaskService {
*/
void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO);
/**
* 撤回任务
*
* @param userId 用户编号
* @param taskId 任务编号
*/
void withdrawTask(Long userId, String taskId);
// ========== Event 事件相关方法 ==========
/**

View File

@@ -196,7 +196,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/**
* 获得用户指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务
*
* @param userId 用户编号
* @param userId 用户编号
* @param processInstanceId 流程编号
* @return 任务
*/
@@ -599,15 +599,15 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/**
* 校验选择的下一个节点的审批人,是否合法
*
* <p>
* 1. 是否有漏选:没有选择审批人
* 2. 是否有多选:非下一个节点
*
* @param taskDefinitionKey 当前任务节点标识
* @param variables 流程变量
* @param bpmnModel 流程模型
* @param nextAssignees 下一个节点审批人集合(参数)
* @param processInstance 流程实例
* @param variables 流程变量
* @param bpmnModel 流程模型
* @param nextAssignees 下一个节点审批人集合(参数)
* @param processInstance 流程实例
*/
@SuppressWarnings("unchecked")
private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
@@ -659,7 +659,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
approveUserSelectAssignees = new HashMap<>();
}
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);
if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) {
approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees);
@@ -1177,6 +1177,63 @@ public class BpmTaskServiceImpl implements BpmTaskService {
processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void withdrawTask(Long userId, String taskId) {
// 1.1 查询本人已办任务
HistoricTaskInstance taskInstance = historyService.createHistoricTaskInstanceQuery()
.taskId(taskId).taskAssignee(userId.toString()).finished().singleResult();
if (ObjUtil.isNull(taskInstance)) {
throw exception(TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS);
}
// 1.2 校验流程是否结束
ProcessInstance processInstance = processInstanceService.getProcessInstance(taskInstance.getProcessInstanceId());
if (ObjUtil.isNull(processInstance)) {
throw exception(TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING);
}
// 1.3 判断此流程是否允许撤回
BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo(
processInstance.getProcessDefinitionId());
if (ObjUtil.isNull(processDefinitionInfo) || !Boolean.TRUE.equals(processDefinitionInfo.getAllowWithdrawTask())) {
throw exception(TASK_WITHDRAW_FAIL_NOT_ALLOW);
}
// 1.4 判断下一个节点是否被审批过,如果是则无法撤回
BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskInstance.getProcessDefinitionId());
UserTask userTask = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, taskInstance.getTaskDefinitionKey());
List<String> nextUserTaskKeys = convertList(BpmnModelUtils.getNextUserTasks(userTask), UserTask::getId);
if (CollUtil.isEmpty(nextUserTaskKeys)) {
throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
}
// TODO @芋艿是否选择升级flowable版本解决taskCreatedAfter、taskCreatedBefore问题升级7.1.0可以;包括 todo 和 done 那边的查询哇??? 是的!
long nextUserTaskFinishedCount = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstance.getProcessInstanceId()).taskDefinitionKeys(nextUserTaskKeys)
.taskCreatedAfter(taskInstance.getEndTime()).finished().count();
if (nextUserTaskFinishedCount > 0) {
throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
}
// 1.5 获取需要撤回的运行任务
List<Task> runningTasks = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId())
.taskDefinitionKeys(nextUserTaskKeys).active().list();
if (CollUtil.isEmpty(runningTasks)) {
throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
}
// 2.1 取消当前任务
List<String> withdrawExecutionIds = new ArrayList<>();
for (Task task : runningTasks) {
// 标记撤回任务为取消
taskService.addComment(task.getId(), taskInstance.getProcessInstanceId(), BpmCommentTypeEnum.CANCEL.getType(),
BpmCommentTypeEnum.CANCEL.formatComment("前一节点撤回"));
updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_WITHDRAW.getReason());
withdrawExecutionIds.add(task.getExecutionId());
}
// 2.2 执行撤回操作
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(processInstance.getProcessInstanceId())
.moveExecutionsToSingleActivityId(withdrawExecutionIds, taskInstance.getTaskDefinitionKey())
.changeState();
}
/**
* 校验任务是否能被减签
*
@@ -1223,7 +1280,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
}
// 2. 任务前置通知
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())) {
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting();
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
@@ -1350,7 +1407,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
.taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus())
.finished();
if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType())
&& sameAssigneeQuery.count() > 0) {
&& sameAssigneeQuery.count() > 0) {
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
.setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName()));
return;
@@ -1362,7 +1419,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
return;
}
List<String> sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点
BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())),
BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())),
SequenceFlow::getSourceRef);
if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) {
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
@@ -1387,7 +1444,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
if (userTaskElement.getId().equals(START_USER_NODE_ID)
&& (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核
|| BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
|| BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
&& ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
.setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
@@ -1456,7 +1513,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
}
// 任务后置通知
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())) {
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting();
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());