添加通知管理功能
This commit is contained in:
parent
175e33ce4f
commit
813a4e4350
@ -0,0 +1,70 @@
|
||||
package com.qqchen.deploy.backend.workflow.controller;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.api.Response;
|
||||
import com.qqchen.deploy.backend.workflow.dto.request.ApprovalTaskRequest;
|
||||
import com.qqchen.deploy.backend.workflow.dto.response.ApprovalTaskDTO;
|
||||
import com.qqchen.deploy.backend.workflow.service.IApprovalTaskService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 审批任务管理接口
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/approval-tasks")
|
||||
@Tag(name = "审批任务管理", description = "审批任务相关接口")
|
||||
public class ApprovalTaskApiController {
|
||||
|
||||
@Resource
|
||||
private IApprovalTaskService approvalTaskService;
|
||||
|
||||
@Operation(summary = "查询我的待办审批任务")
|
||||
@GetMapping("/my-tasks")
|
||||
public Response<List<ApprovalTaskDTO>> getMyTasks(
|
||||
@Parameter(description = "用户名", required = true) @RequestParam String username
|
||||
) {
|
||||
List<ApprovalTaskDTO> tasks = approvalTaskService.getMyTasks(username);
|
||||
return Response.success(tasks);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询流程实例的审批任务")
|
||||
@GetMapping("/process/{processInstanceId}")
|
||||
public Response<List<ApprovalTaskDTO>> getTasksByProcessInstance(
|
||||
@Parameter(description = "流程实例ID", required = true)
|
||||
@PathVariable String processInstanceId
|
||||
) {
|
||||
List<ApprovalTaskDTO> tasks = approvalTaskService.getTasksByProcessInstance(processInstanceId);
|
||||
return Response.success(tasks);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询审批任务详情")
|
||||
@GetMapping("/{taskId}")
|
||||
public Response<ApprovalTaskDTO> getTaskById(
|
||||
@Parameter(description = "任务ID", required = true)
|
||||
@PathVariable String taskId
|
||||
) {
|
||||
ApprovalTaskDTO task = approvalTaskService.getTaskById(taskId);
|
||||
return Response.success(task);
|
||||
}
|
||||
|
||||
@Operation(summary = "完成审批任务(通过/拒绝)")
|
||||
@PostMapping("/complete")
|
||||
public Response<Void> completeTask(
|
||||
@Valid @RequestBody ApprovalTaskRequest request
|
||||
) {
|
||||
approvalTaskService.completeTask(request);
|
||||
return Response.success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,32 +1,227 @@
|
||||
package com.qqchen.deploy.backend.workflow.delegate;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.ApprovalInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.outputs.ApprovalOutputs;
|
||||
import jakarta.annotation.Resource;
|
||||
import com.qqchen.deploy.backend.workflow.enums.ApprovalResultEnum;
|
||||
import com.qqchen.deploy.backend.workflow.enums.ApproverTypeEnum;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.flowable.engine.delegate.BpmnError;
|
||||
import org.flowable.common.engine.api.delegate.Expression;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.flowable.engine.delegate.TaskListener;
|
||||
import org.flowable.task.service.delegate.DelegateTask;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 审批节点委派
|
||||
* 负责创建Flowable用户任务,并配置审批相关参数
|
||||
*
|
||||
* ✅ 实现 TaskListener 接口,在任务创建时(create event)被调用
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
* @since 2025-10-23
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("approvalDelegate")
|
||||
public class ApprovalNodeDelegate extends BaseNodeDelegate<ApprovalInputMapping, ApprovalOutputs> {
|
||||
public class ApprovalNodeDelegate extends BaseNodeDelegate<ApprovalInputMapping, ApprovalOutputs> implements TaskListener {
|
||||
|
||||
@Resource
|
||||
private TaskService taskService;
|
||||
/**
|
||||
* ✅ TaskListener 接口方法
|
||||
* 在任务创建时被 Flowable 调用
|
||||
*
|
||||
* ⚠️ 重要:必须在这里直接设置 Task 的 assignee/candidateGroups,
|
||||
* 而不是设置流程变量,因为任务已经创建了!
|
||||
*
|
||||
* @param delegateTask Flowable 任务代理对象
|
||||
*/
|
||||
@Override
|
||||
public void notify(DelegateTask delegateTask) {
|
||||
log.info("TaskListener: Approval task created - taskId: {}", delegateTask.getId());
|
||||
|
||||
try {
|
||||
// ✅ 1. 解析 field 字段中的 configs 和 inputMapping
|
||||
// DelegateTask 继承了 VariableScope,可以直接传递给 Expression.getValue()
|
||||
Map<String, Object> configs = parseJsonFieldFromTask(this.configs, delegateTask);
|
||||
ApprovalInputMapping input = parseInputMappingFromTask(delegateTask);
|
||||
|
||||
// ✅ 2. 直接设置任务的审批人(而不是设置流程变量)
|
||||
configureTaskAssignee(delegateTask, input);
|
||||
|
||||
// ✅ 3. 设置任务的基本信息
|
||||
if (configs != null && configs.get("nodeName") != null) {
|
||||
delegateTask.setName((String) configs.get("nodeName"));
|
||||
}
|
||||
if (input.getApprovalTitle() != null) {
|
||||
delegateTask.setDescription(input.getApprovalTitle());
|
||||
}
|
||||
|
||||
// ✅ 4. 设置超时时间
|
||||
if (input.getTimeoutDuration() != null && input.getTimeoutDuration() > 0) {
|
||||
java.util.Date dueDate = java.sql.Timestamp.valueOf(
|
||||
LocalDateTime.now().plusHours(input.getTimeoutDuration())
|
||||
);
|
||||
delegateTask.setDueDate(dueDate);
|
||||
}
|
||||
|
||||
// ✅ 5. 将审批相关信息保存为任务的局部变量,方便前端查询时使用
|
||||
saveApprovalInfoToTaskVariables(delegateTask, input);
|
||||
|
||||
log.info("Approval task configuration completed - assignee: {}", delegateTask.getAssignee());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to configure approval task", e);
|
||||
throw new RuntimeException("Failed to configure approval task: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将审批相关信息保存为任务变量
|
||||
* 这样前端查询任务时可以获取到这些信息
|
||||
*/
|
||||
private void saveApprovalInfoToTaskVariables(DelegateTask delegateTask, ApprovalInputMapping input) {
|
||||
// 保存审批标题和内容
|
||||
if (input.getApprovalTitle() != null) {
|
||||
delegateTask.setVariableLocal("approvalTitle", input.getApprovalTitle());
|
||||
}
|
||||
if (input.getApprovalContent() != null) {
|
||||
delegateTask.setVariableLocal("approvalContent", input.getApprovalContent());
|
||||
}
|
||||
|
||||
// 保存审批模式
|
||||
if (input.getApprovalMode() != null) {
|
||||
delegateTask.setVariableLocal("approvalMode", input.getApprovalMode().getCode());
|
||||
}
|
||||
|
||||
// 保存审批配置
|
||||
if (input.getAllowDelegate() != null) {
|
||||
delegateTask.setVariableLocal("allowDelegate", input.getAllowDelegate());
|
||||
}
|
||||
if (input.getAllowAddSign() != null) {
|
||||
delegateTask.setVariableLocal("allowAddSign", input.getAllowAddSign());
|
||||
}
|
||||
if (input.getRequireComment() != null) {
|
||||
delegateTask.setVariableLocal("requireComment", input.getRequireComment());
|
||||
}
|
||||
|
||||
// 保存候选组(如果有)
|
||||
if (input.getApproverRoles() != null && !input.getApproverRoles().isEmpty()) {
|
||||
delegateTask.setVariableLocal("candidateGroups", String.join(",", input.getApproverRoles()));
|
||||
} else if (input.getApproverDepartments() != null && !input.getApproverDepartments().isEmpty()) {
|
||||
delegateTask.setVariableLocal("candidateGroups", String.join(",", input.getApproverDepartments()));
|
||||
}
|
||||
|
||||
log.debug("Saved approval info to task variables for taskId: {}", delegateTask.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 TaskListener 中解析 JSON field
|
||||
* DelegateTask 继承了 VariableScope,可以直接传递给 Expression.getValue()
|
||||
*/
|
||||
private Map<String, Object> parseJsonFieldFromTask(Expression expression, DelegateTask task) {
|
||||
if (expression == null) {
|
||||
return new java.util.HashMap<>();
|
||||
}
|
||||
|
||||
String jsonStr = expression.getValue(task) != null ? expression.getValue(task).toString() : null;
|
||||
if (jsonStr == null || jsonStr.isEmpty()) {
|
||||
return new java.util.HashMap<>();
|
||||
}
|
||||
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return objectMapper.readValue(jsonStr, new com.fasterxml.jackson.core.type.TypeReference<Map<String, Object>>() {});
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse JSON field: {}", jsonStr, e);
|
||||
return new java.util.HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 TaskListener 中解析 InputMapping
|
||||
*/
|
||||
private ApprovalInputMapping parseInputMappingFromTask(DelegateTask task) {
|
||||
try {
|
||||
String inputMappingJson = this.inputMapping != null && this.inputMapping.getValue(task) != null
|
||||
? this.inputMapping.getValue(task).toString()
|
||||
: null;
|
||||
|
||||
if (inputMappingJson == null || inputMappingJson.isEmpty()) {
|
||||
return new ApprovalInputMapping();
|
||||
}
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return objectMapper.readValue(inputMappingJson, ApprovalInputMapping.class);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse input mapping", e);
|
||||
return new ApprovalInputMapping();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置任务的审批人
|
||||
* 根据 approverType 不同,设置不同的审批人
|
||||
*/
|
||||
private void configureTaskAssignee(DelegateTask task, ApprovalInputMapping input) {
|
||||
if (input.getApproverType() == null) {
|
||||
log.warn("ApproverType is null, skip setting assignee");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (input.getApproverType()) {
|
||||
case USER:
|
||||
// 指定用户审批
|
||||
if (input.getApprovers() != null && !input.getApprovers().isEmpty()) {
|
||||
// ✅ 单人审批:设置 assignee
|
||||
if ("SINGLE".equals(input.getApprovalMode() != null ? input.getApprovalMode().getCode() : "")) {
|
||||
task.setAssignee(input.getApprovers().get(0));
|
||||
log.info("Set single assignee: {}", input.getApprovers().get(0));
|
||||
} else {
|
||||
// ✅ 会签/或签:设置候选人
|
||||
for (String approver : input.getApprovers()) {
|
||||
task.addCandidateUser(approver);
|
||||
}
|
||||
log.info("Set candidate users: {}", input.getApprovers());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ROLE:
|
||||
// 指定角色审批:设置候选组
|
||||
if (input.getApproverRoles() != null && !input.getApproverRoles().isEmpty()) {
|
||||
for (String role : input.getApproverRoles()) {
|
||||
task.addCandidateGroup(role);
|
||||
}
|
||||
log.info("Set candidate groups (roles): {}", input.getApproverRoles());
|
||||
}
|
||||
break;
|
||||
|
||||
case DEPARTMENT:
|
||||
// 指定部门审批:设置候选组
|
||||
if (input.getApproverDepartments() != null && !input.getApproverDepartments().isEmpty()) {
|
||||
for (String dept : input.getApproverDepartments()) {
|
||||
task.addCandidateGroup(dept);
|
||||
}
|
||||
log.info("Set candidate groups (departments): {}", input.getApproverDepartments());
|
||||
}
|
||||
break;
|
||||
|
||||
case VARIABLE:
|
||||
// 从流程变量中获取审批人
|
||||
if (input.getApproverVariable() != null) {
|
||||
Object approverValue = task.getVariable(input.getApproverVariable());
|
||||
if (approverValue != null) {
|
||||
task.setAssignee(approverValue.toString());
|
||||
log.info("Set assignee from variable {}: {}", input.getApproverVariable(), approverValue);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ApprovalOutputs executeInternal(
|
||||
@ -34,58 +229,119 @@ public class ApprovalNodeDelegate extends BaseNodeDelegate<ApprovalInputMapping,
|
||||
Map<String, Object> configs,
|
||||
ApprovalInputMapping input
|
||||
) {
|
||||
log.info("Creating approval task - assignee: {}, candidateGroup: {}",
|
||||
input.getAssignee(), input.getCandidateGroup());
|
||||
log.info("Creating approval task - mode: {}, approverType: {}",
|
||||
input.getApprovalMode(), input.getApproverType());
|
||||
|
||||
try {
|
||||
// 创建用户任务
|
||||
Task task = taskService.createTaskQuery()
|
||||
.processInstanceId(execution.getProcessInstanceId())
|
||||
.taskDefinitionKey(execution.getCurrentActivityId())
|
||||
.singleResult();
|
||||
// 设置审批任务的相关变量到流程中,供Flowable UserTask使用
|
||||
setApprovalVariables(execution, configs, input);
|
||||
|
||||
if (task == null) {
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Failed to create approval task");
|
||||
}
|
||||
|
||||
// 从configs中获取任务名称和描述
|
||||
String taskName = (String) configs.get("nodeName");
|
||||
String taskDesc = (String) configs.get("description");
|
||||
|
||||
task.setName(taskName != null ? taskName : "审批任务");
|
||||
task.setDescription(taskDesc);
|
||||
|
||||
// 设置审批人
|
||||
if (input.getAssignee() != null) {
|
||||
task.setAssignee(input.getAssignee());
|
||||
}
|
||||
|
||||
// 设置候选组
|
||||
if (input.getCandidateGroup() != null) {
|
||||
taskService.addCandidateGroup(task.getId(), input.getCandidateGroup());
|
||||
}
|
||||
|
||||
// 设置超时时间
|
||||
if (input.getTimeoutHours() != null) {
|
||||
Date dueDate = java.sql.Timestamp.valueOf(
|
||||
LocalDateTime.now().plusHours(input.getTimeoutHours()));
|
||||
task.setDueDate(dueDate);
|
||||
}
|
||||
|
||||
// 更新任务
|
||||
taskService.saveTask(task);
|
||||
|
||||
log.info("Created approval task: {}", task.getId());
|
||||
|
||||
// 返回输出(审批任务是异步的,这里先返回pending状态)
|
||||
// 审批任务是异步的,这里先返回PENDING状态
|
||||
ApprovalOutputs outputs = new ApprovalOutputs();
|
||||
outputs.setResult("PENDING");
|
||||
outputs.setApprovalResult(ApprovalResultEnum.PENDING);
|
||||
outputs.setStatus(NodeExecutionStatusEnum.SUCCESS); // 节点创建成功
|
||||
|
||||
log.info("Created approval task configuration successfully");
|
||||
return outputs;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create approval task", e);
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR,
|
||||
"Failed to create approval task: " + e.getMessage());
|
||||
log.error("Failed to create approval task configuration", e);
|
||||
ApprovalOutputs outputs = new ApprovalOutputs();
|
||||
outputs.setApprovalResult(ApprovalResultEnum.REJECTED);
|
||||
outputs.setStatus(NodeExecutionStatusEnum.FAILURE);
|
||||
return outputs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置审批任务相关变量到流程执行上下文
|
||||
*/
|
||||
private void setApprovalVariables(DelegateExecution execution,
|
||||
Map<String, Object> configs,
|
||||
ApprovalInputMapping input) {
|
||||
// 审批模式
|
||||
if (input.getApprovalMode() != null) {
|
||||
execution.setVariable("approvalMode", input.getApprovalMode().getCode());
|
||||
}
|
||||
|
||||
// 审批人类型
|
||||
if (input.getApproverType() != null) {
|
||||
execution.setVariable("approverType", input.getApproverType().getCode());
|
||||
}
|
||||
|
||||
// 根据审批人类型设置对应的审批人
|
||||
if (input.getApproverType() != null) {
|
||||
switch (input.getApproverType()) {
|
||||
case USER:
|
||||
if (input.getApprovers() != null && !input.getApprovers().isEmpty()) {
|
||||
// 设置审批人列表(用户名)
|
||||
execution.setVariable("approvers",
|
||||
String.join(",", input.getApprovers()));
|
||||
// 设置第一个审批人为默认assignee(单人审批时)
|
||||
execution.setVariable("assignee", input.getApprovers().get(0));
|
||||
}
|
||||
break;
|
||||
case ROLE:
|
||||
if (input.getApproverRoles() != null && !input.getApproverRoles().isEmpty()) {
|
||||
// 设置审批角色列表
|
||||
execution.setVariable("approverRoles",
|
||||
String.join(",", input.getApproverRoles()));
|
||||
// 设置候选组(角色)
|
||||
execution.setVariable("candidateGroups",
|
||||
String.join(",", input.getApproverRoles()));
|
||||
}
|
||||
break;
|
||||
case DEPARTMENT:
|
||||
if (input.getApproverDepartments() != null && !input.getApproverDepartments().isEmpty()) {
|
||||
// 设置审批部门列表
|
||||
execution.setVariable("approverDepartments",
|
||||
String.join(",", input.getApproverDepartments()));
|
||||
// 设置候选组(部门)
|
||||
execution.setVariable("candidateGroups",
|
||||
String.join(",", input.getApproverDepartments()));
|
||||
}
|
||||
break;
|
||||
case VARIABLE:
|
||||
if (input.getApproverVariable() != null) {
|
||||
execution.setVariable("approverVariable", input.getApproverVariable());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 审批标题和内容
|
||||
if (input.getApprovalTitle() != null) {
|
||||
execution.setVariable("approvalTitle", input.getApprovalTitle());
|
||||
}
|
||||
if (input.getApprovalContent() != null) {
|
||||
execution.setVariable("approvalContent", input.getApprovalContent());
|
||||
}
|
||||
|
||||
// 超时配置
|
||||
if (input.getTimeoutDuration() != null) {
|
||||
execution.setVariable("timeoutDuration", input.getTimeoutDuration());
|
||||
}
|
||||
if (input.getTimeoutAction() != null) {
|
||||
execution.setVariable("timeoutAction", input.getTimeoutAction().getCode());
|
||||
}
|
||||
|
||||
// 审批配置
|
||||
if (input.getAllowDelegate() != null) {
|
||||
execution.setVariable("allowDelegate", input.getAllowDelegate());
|
||||
}
|
||||
if (input.getAllowAddSign() != null) {
|
||||
execution.setVariable("allowAddSign", input.getAllowAddSign());
|
||||
}
|
||||
if (input.getRequireComment() != null) {
|
||||
execution.setVariable("requireComment", input.getRequireComment());
|
||||
}
|
||||
|
||||
// 从configs设置任务名称和描述
|
||||
if (configs.get("nodeName") != null) {
|
||||
execution.setVariable("taskName", configs.get("nodeName"));
|
||||
}
|
||||
if (configs.get("description") != null) {
|
||||
execution.setVariable("taskDescription", configs.get("description"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
}
|
||||
|
||||
// 7. 设置节点执行状态为成功
|
||||
setExecutionStatus(execution, WORKFLOW_NODE_EXECUTION_STATE_SUCCESS);
|
||||
setExecutionStatus(execution, WORKFLOW_NODE_EXECUTION_STATE_SUCCESS);
|
||||
|
||||
} catch (Exception e) {
|
||||
setExecutionStatus(execution, WORKFLOW_NODE_EXECUTION_STATE_FAILURE);
|
||||
|
||||
@ -1,29 +1,87 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.inputmapping;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.enums.ApprovalModeEnum;
|
||||
import com.qqchen.deploy.backend.workflow.enums.ApproverTypeEnum;
|
||||
import com.qqchen.deploy.backend.workflow.enums.TimeoutActionEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 审批节点输入映射
|
||||
*
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@Data
|
||||
public class ApprovalInputMapping {
|
||||
|
||||
/**
|
||||
* 审批人
|
||||
*/
|
||||
private String assignee;
|
||||
|
||||
/**
|
||||
* 候选组
|
||||
*/
|
||||
private String candidateGroup;
|
||||
|
||||
/**
|
||||
* 超时时间(小时)
|
||||
*/
|
||||
private Integer timeoutHours;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批模式
|
||||
*/
|
||||
private ApprovalModeEnum approvalMode;
|
||||
|
||||
/**
|
||||
* 审批人类型
|
||||
*/
|
||||
private ApproverTypeEnum approverType;
|
||||
|
||||
/**
|
||||
* 审批人列表(当 approverType = USER 时使用)
|
||||
* 使用用户名,如 ["admin", "zhangsan"]
|
||||
*/
|
||||
private List<String> approvers;
|
||||
|
||||
/**
|
||||
* 审批角色列表(当 approverType = ROLE 时使用)
|
||||
* 使用角色编码,如 ["ROLE_ADMIN", "ROLE_MANAGER"]
|
||||
*/
|
||||
private List<String> approverRoles;
|
||||
|
||||
/**
|
||||
* 审批部门列表(当 approverType = DEPARTMENT 时使用)
|
||||
* 使用部门编码,如 ["DEPT_RD", "DEPT_OPS"]
|
||||
*/
|
||||
private List<String> approverDepartments;
|
||||
|
||||
/**
|
||||
* 审批人变量(当 approverType = VARIABLE 时使用)
|
||||
* 格式:${变量名}
|
||||
*/
|
||||
private String approverVariable;
|
||||
|
||||
/**
|
||||
* 审批标题
|
||||
*/
|
||||
private String approvalTitle;
|
||||
|
||||
/**
|
||||
* 审批内容
|
||||
*/
|
||||
private String approvalContent;
|
||||
|
||||
/**
|
||||
* 超时时间(小时),0表示不限制
|
||||
*/
|
||||
private Integer timeoutDuration;
|
||||
|
||||
/**
|
||||
* 超时处理方式
|
||||
*/
|
||||
private TimeoutActionEnum timeoutAction;
|
||||
|
||||
/**
|
||||
* 是否允许转交
|
||||
*/
|
||||
private Boolean allowDelegate;
|
||||
|
||||
/**
|
||||
* 是否允许加签
|
||||
*/
|
||||
private Boolean allowAddSign;
|
||||
|
||||
/**
|
||||
* 是否必须填写意见
|
||||
*/
|
||||
private Boolean requireComment;
|
||||
}
|
||||
|
||||
@ -1,31 +1,66 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.outputs;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.enums.ApprovalResultEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 审批节点输出
|
||||
*
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApprovalOutputs extends BaseNodeOutputs {
|
||||
|
||||
|
||||
/**
|
||||
* 审批结果
|
||||
*/
|
||||
private String result;
|
||||
|
||||
private ApprovalResultEnum approvalResult;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
private String approver;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
private Long approverUserId;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
private LocalDateTime approvalTime;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
private String comment;
|
||||
|
||||
/**
|
||||
* 审批人
|
||||
*/
|
||||
private String approver;
|
||||
}
|
||||
private String approvalComment;
|
||||
|
||||
/**
|
||||
* 审批用时(秒)
|
||||
*/
|
||||
private Long approvalDuration;
|
||||
|
||||
/**
|
||||
* 所有审批人列表(会签/或签时)
|
||||
*/
|
||||
private List<ApproverInfo> allApprovers;
|
||||
|
||||
/**
|
||||
* 审批人信息
|
||||
*/
|
||||
@Data
|
||||
public static class ApproverInfo {
|
||||
private Long userId;
|
||||
private String userName;
|
||||
private String result;
|
||||
private String comment;
|
||||
private LocalDateTime time;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 审批任务请求
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "审批任务请求")
|
||||
public class ApprovalTaskRequest {
|
||||
|
||||
@NotBlank(message = "任务ID不能为空")
|
||||
@Schema(description = "Flowable任务ID", required = true)
|
||||
private String taskId;
|
||||
|
||||
@NotNull(message = "审批结果不能为空")
|
||||
@Schema(description = "审批结果: true=通过, false=拒绝", required = true)
|
||||
private Boolean approved;
|
||||
|
||||
@Schema(description = "审批意见")
|
||||
private String comment;
|
||||
}
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 审批任务响应DTO
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "审批任务信息")
|
||||
public class ApprovalTaskDTO {
|
||||
|
||||
@Schema(description = "任务ID")
|
||||
private String taskId;
|
||||
|
||||
@Schema(description = "任务名称")
|
||||
private String taskName;
|
||||
|
||||
@Schema(description = "任务描述")
|
||||
private String taskDescription;
|
||||
|
||||
@Schema(description = "流程实例ID")
|
||||
private String processInstanceId;
|
||||
|
||||
@Schema(description = "流程定义ID")
|
||||
private String processDefinitionId;
|
||||
|
||||
@Schema(description = "审批人")
|
||||
private String assignee;
|
||||
|
||||
@Schema(description = "候选组")
|
||||
private String candidateGroups;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "到期时间")
|
||||
private LocalDateTime dueDate;
|
||||
|
||||
@Schema(description = "审批标题")
|
||||
private String approvalTitle;
|
||||
|
||||
@Schema(description = "审批内容")
|
||||
private String approvalContent;
|
||||
|
||||
@Schema(description = "审批模式")
|
||||
private String approvalMode;
|
||||
|
||||
@Schema(description = "是否允许转交")
|
||||
private Boolean allowDelegate;
|
||||
|
||||
@Schema(description = "是否允许加签")
|
||||
private Boolean allowAddSign;
|
||||
|
||||
@Schema(description = "是否必须填写意见")
|
||||
private Boolean requireComment;
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
package com.qqchen.deploy.backend.workflow.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 审批模式枚举
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ApprovalModeEnum {
|
||||
|
||||
/**
|
||||
* 单人审批
|
||||
*/
|
||||
SINGLE("SINGLE", "单人审批"),
|
||||
|
||||
/**
|
||||
* 会签(所有人同意)
|
||||
*/
|
||||
ALL("ALL", "会签"),
|
||||
|
||||
/**
|
||||
* 或签(任一人同意)
|
||||
*/
|
||||
ANY("ANY", "或签");
|
||||
|
||||
@JsonValue
|
||||
private final String code;
|
||||
private final String description;
|
||||
}
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
package com.qqchen.deploy.backend.workflow.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 审批结果枚举
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ApprovalResultEnum {
|
||||
|
||||
/**
|
||||
* 待审批
|
||||
*/
|
||||
PENDING("PENDING", "待审批"),
|
||||
|
||||
/**
|
||||
* 通过
|
||||
*/
|
||||
APPROVED("APPROVED", "通过"),
|
||||
|
||||
/**
|
||||
* 拒绝
|
||||
*/
|
||||
REJECTED("REJECTED", "拒绝"),
|
||||
|
||||
/**
|
||||
* 超时
|
||||
*/
|
||||
TIMEOUT("TIMEOUT", "超时");
|
||||
|
||||
@JsonValue
|
||||
private final String code;
|
||||
private final String description;
|
||||
}
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
package com.qqchen.deploy.backend.workflow.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 审批人类型枚举
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ApproverTypeEnum {
|
||||
|
||||
/**
|
||||
* 指定用户
|
||||
*/
|
||||
USER("USER", "指定用户"),
|
||||
|
||||
/**
|
||||
* 指定角色
|
||||
*/
|
||||
ROLE("ROLE", "指定角色"),
|
||||
|
||||
/**
|
||||
* 指定部门
|
||||
*/
|
||||
DEPARTMENT("DEPARTMENT", "指定部门"),
|
||||
|
||||
/**
|
||||
* 流程变量
|
||||
*/
|
||||
VARIABLE("VARIABLE", "流程变量");
|
||||
|
||||
@JsonValue
|
||||
private final String code;
|
||||
private final String description;
|
||||
}
|
||||
|
||||
@ -85,12 +85,12 @@ public enum NodeTypeEnums {
|
||||
NodeCategoryEnums.TASK,
|
||||
"通知节点"
|
||||
),
|
||||
APPROVAL_NODE(
|
||||
"APPROVAL_NODE",
|
||||
APPROVAL(
|
||||
"APPROVAL",
|
||||
"审批节点",
|
||||
BpmnNodeTypeEnums.USER_TASK,
|
||||
NodeCategoryEnums.TASK,
|
||||
"审批任务"
|
||||
"人工审批节点,支持多种审批模式"
|
||||
);
|
||||
//
|
||||
// /**
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
package com.qqchen.deploy.backend.workflow.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 超时处理方式枚举
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum TimeoutActionEnum {
|
||||
|
||||
/**
|
||||
* 无操作
|
||||
*/
|
||||
NONE("NONE", "无操作"),
|
||||
|
||||
/**
|
||||
* 自动通过
|
||||
*/
|
||||
AUTO_APPROVE("AUTO_APPROVE", "自动通过"),
|
||||
|
||||
/**
|
||||
* 自动拒绝
|
||||
*/
|
||||
AUTO_REJECT("AUTO_REJECT", "自动拒绝"),
|
||||
|
||||
/**
|
||||
* 仅通知
|
||||
*/
|
||||
NOTIFY("NOTIFY", "仅通知");
|
||||
|
||||
@JsonValue
|
||||
private final String code;
|
||||
private final String description;
|
||||
}
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
package com.qqchen.deploy.backend.workflow.service;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.dto.request.ApprovalTaskRequest;
|
||||
import com.qqchen.deploy.backend.workflow.dto.response.ApprovalTaskDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 审批任务服务接口
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
public interface IApprovalTaskService {
|
||||
|
||||
/**
|
||||
* 查询当前用户的待办审批任务
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 待办任务列表
|
||||
*/
|
||||
List<ApprovalTaskDTO> getMyTasks(String username);
|
||||
|
||||
/**
|
||||
* 查询流程实例的所有审批任务
|
||||
*
|
||||
* @param processInstanceId 流程实例ID
|
||||
* @return 任务列表
|
||||
*/
|
||||
List<ApprovalTaskDTO> getTasksByProcessInstance(String processInstanceId);
|
||||
|
||||
/**
|
||||
* 根据任务ID查询审批任务详情
|
||||
*
|
||||
* @param taskId 任务ID
|
||||
* @return 任务详情
|
||||
*/
|
||||
ApprovalTaskDTO getTaskById(String taskId);
|
||||
|
||||
/**
|
||||
* 处理审批任务(通过/拒绝)
|
||||
*
|
||||
* @param request 审批请求
|
||||
*/
|
||||
void completeTask(ApprovalTaskRequest request);
|
||||
}
|
||||
|
||||
@ -0,0 +1,161 @@
|
||||
package com.qqchen.deploy.backend.workflow.service.impl;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.workflow.dto.request.ApprovalTaskRequest;
|
||||
import com.qqchen.deploy.backend.workflow.dto.response.ApprovalTaskDTO;
|
||||
import com.qqchen.deploy.backend.workflow.service.IApprovalTaskService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 审批任务服务实现
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ApprovalTaskServiceImpl implements IApprovalTaskService {
|
||||
|
||||
@Resource
|
||||
private TaskService taskService;
|
||||
|
||||
@Override
|
||||
public List<ApprovalTaskDTO> getMyTasks(String username) {
|
||||
log.info("查询用户 {} 的待办任务", username);
|
||||
|
||||
List<Task> tasks = taskService.createTaskQuery()
|
||||
.taskAssignee(username)
|
||||
.orderByTaskCreateTime()
|
||||
.desc()
|
||||
.list();
|
||||
|
||||
return tasks.stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ApprovalTaskDTO> getTasksByProcessInstance(String processInstanceId) {
|
||||
log.info("查询流程实例 {} 的所有任务", processInstanceId);
|
||||
|
||||
List<Task> tasks = taskService.createTaskQuery()
|
||||
.processInstanceId(processInstanceId)
|
||||
.orderByTaskCreateTime()
|
||||
.desc()
|
||||
.list();
|
||||
|
||||
return tasks.stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApprovalTaskDTO getTaskById(String taskId) {
|
||||
log.info("查询任务详情: {}", taskId);
|
||||
|
||||
Task task = taskService.createTaskQuery()
|
||||
.taskId(taskId)
|
||||
.singleResult();
|
||||
|
||||
if (task == null) {
|
||||
throw new BusinessException(ResponseCode.DATA_NOT_FOUND);
|
||||
}
|
||||
|
||||
return convertToDTO(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void completeTask(ApprovalTaskRequest request) {
|
||||
log.info("处理审批任务: taskId={}, approved={}", request.getTaskId(), request.getApproved());
|
||||
|
||||
// 检查任务是否存在
|
||||
Task task = taskService.createTaskQuery()
|
||||
.taskId(request.getTaskId())
|
||||
.singleResult();
|
||||
|
||||
if (task == null) {
|
||||
throw new BusinessException(ResponseCode.DATA_NOT_FOUND);
|
||||
}
|
||||
|
||||
// ✅ 获取节点ID(taskDefinitionKey 就是 BPMN 中的节点 ID)
|
||||
String nodeId = task.getTaskDefinitionKey();
|
||||
|
||||
// ✅ 构建审批输出对象(ApprovalOutputs)
|
||||
Map<String, Object> approvalOutputs = new HashMap<>();
|
||||
approvalOutputs.put("status", "SUCCESS");
|
||||
approvalOutputs.put("approvalResult", request.getApproved() ? "APPROVED" : "REJECTED");
|
||||
approvalOutputs.put("approver", task.getAssignee());
|
||||
approvalOutputs.put("approvalTime", LocalDateTime.now().toString());
|
||||
approvalOutputs.put("approvalComment", request.getComment());
|
||||
|
||||
// ✅ 以节点ID为变量名存储审批结果(这样条件表达式才能正确解析)
|
||||
// 例如:${sid_7353fb85_562b_441d_add6_23915ab8e843.approvalResult == 'APPROVED'}
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
variables.put(nodeId, approvalOutputs);
|
||||
|
||||
// 添加任务评论
|
||||
if (request.getComment() != null) {
|
||||
taskService.addComment(request.getTaskId(), task.getProcessInstanceId(), request.getComment());
|
||||
}
|
||||
|
||||
// 完成任务
|
||||
taskService.complete(request.getTaskId(), variables);
|
||||
|
||||
log.info("审批任务完成: taskId={}, nodeId={}, result={}",
|
||||
request.getTaskId(), nodeId, request.getApproved() ? "APPROVED" : "REJECTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 Task 为 DTO
|
||||
*/
|
||||
private ApprovalTaskDTO convertToDTO(Task task) {
|
||||
ApprovalTaskDTO dto = new ApprovalTaskDTO();
|
||||
dto.setTaskId(task.getId());
|
||||
dto.setTaskName(task.getName());
|
||||
dto.setTaskDescription(task.getDescription());
|
||||
dto.setProcessInstanceId(task.getProcessInstanceId());
|
||||
dto.setProcessDefinitionId(task.getProcessDefinitionId());
|
||||
dto.setAssignee(task.getAssignee());
|
||||
|
||||
if (task.getCreateTime() != null) {
|
||||
dto.setCreateTime(LocalDateTime.ofInstant(
|
||||
task.getCreateTime().toInstant(),
|
||||
ZoneId.systemDefault()));
|
||||
}
|
||||
|
||||
if (task.getDueDate() != null) {
|
||||
dto.setDueDate(LocalDateTime.ofInstant(
|
||||
task.getDueDate().toInstant(),
|
||||
ZoneId.systemDefault()));
|
||||
}
|
||||
|
||||
// 获取任务变量
|
||||
Map<String, Object> variables = taskService.getVariables(task.getId());
|
||||
if (variables != null) {
|
||||
dto.setApprovalTitle((String) variables.get("approvalTitle"));
|
||||
dto.setApprovalContent((String) variables.get("approvalContent"));
|
||||
dto.setApprovalMode((String) variables.get("approvalMode"));
|
||||
dto.setCandidateGroups((String) variables.get("candidateGroups"));
|
||||
dto.setAllowDelegate((Boolean) variables.get("allowDelegate"));
|
||||
dto.setAllowAddSign((Boolean) variables.get("allowAddSign"));
|
||||
dto.setRequireComment((Boolean) variables.get("requireComment"));
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,23 +128,32 @@ public class BpmnConverter {
|
||||
*/
|
||||
private void convertSingleNode(WorkflowDefinitionGraphNode node, Process process, Map<String, String> idMapping) {
|
||||
try {
|
||||
// 步骤2.3:获取节点对应的BPMN元素类型
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends FlowElement> instanceClass = (Class<? extends FlowElement>) NodeTypeEnums.valueOf(node.getNodeCode())
|
||||
.getBpmnType()
|
||||
.getInstance();
|
||||
|
||||
// 步骤2.4:创建节点实例并设置基本属性
|
||||
FlowElement element = instanceClass.getDeclaredConstructor().newInstance();
|
||||
String validId = sanitizeId(node.getId());
|
||||
idMapping.put(node.getId(), validId);
|
||||
|
||||
// 如果是网关节点,需要特殊处理
|
||||
if (element instanceof Gateway) {
|
||||
element = createGatewayElement(node, validId);
|
||||
|
||||
FlowElement element;
|
||||
|
||||
// ✅ 步骤2.3:检查是否为审批节点,特殊处理
|
||||
if ("APPROVAL".equals(node.getNodeCode()) || "APPROVAL_NODE".equals(node.getNodeCode())) {
|
||||
// 创建 UserTask 而不是 ServiceTask
|
||||
element = createUserTask(node, validId);
|
||||
} else {
|
||||
element.setId(validId);
|
||||
element.setName(node.getNodeName());
|
||||
// 其他节点按原有逻辑处理
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends FlowElement> instanceClass = (Class<? extends FlowElement>) NodeTypeEnums.valueOf(node.getNodeCode())
|
||||
.getBpmnType()
|
||||
.getInstance();
|
||||
|
||||
// 步骤2.4:创建节点实例并设置基本属性
|
||||
element = instanceClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
// 如果是网关节点,需要特殊处理
|
||||
if (element instanceof Gateway) {
|
||||
element = createGatewayElement(node, validId);
|
||||
} else {
|
||||
element.setId(validId);
|
||||
element.setName(node.getNodeName());
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤2.5:配置节点的特定属性(传递原始节点ID和sanitized ID)
|
||||
@ -182,7 +191,10 @@ public class BpmnConverter {
|
||||
Map<String, List<ExtensionElement>> extensionElements = configureExecutionListeners(element);
|
||||
|
||||
// 步骤2:根据节点类型进行特定配置
|
||||
if (element instanceof ServiceTask) {
|
||||
if (element instanceof UserTask) {
|
||||
// ✅ 审批节点(UserTask)的特殊配置
|
||||
configureUserTask((UserTask) element, node, extensionElements, validId);
|
||||
} else if (element instanceof ServiceTask) {
|
||||
configureServiceTask((ServiceTask) element, node, process, extensionElements, validId);
|
||||
} else {
|
||||
element.setExtensionElements(extensionElements);
|
||||
@ -233,6 +245,88 @@ public class BpmnConverter {
|
||||
return extensionElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建审批节点(UserTask)
|
||||
*
|
||||
* @param node 工作流节点定义
|
||||
* @param validId sanitized 后的节点 ID
|
||||
* @return UserTask 实例
|
||||
*/
|
||||
private UserTask createUserTask(WorkflowDefinitionGraphNode node, String validId) {
|
||||
UserTask userTask = new UserTask();
|
||||
userTask.setId(validId);
|
||||
userTask.setName(node.getNodeName());
|
||||
|
||||
// ⚠️ 不在这里设置 assignee,而是在 TaskListener 中动态设置
|
||||
// TaskListener 会解析 inputMapping 并直接调用 task.setAssignee()
|
||||
|
||||
return userTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置审批节点(UserTask)
|
||||
*
|
||||
* @param userTask 审批任务节点
|
||||
* @param node 工作流节点定义
|
||||
* @param extensionElements 扩展元素
|
||||
* @param validId sanitized 后的节点 ID
|
||||
*/
|
||||
private void configureUserTask(UserTask userTask, WorkflowDefinitionGraphNode node, Map<String, List<ExtensionElement>> extensionElements, String validId) {
|
||||
try {
|
||||
// ✅ 1. 创建 TaskListener(在任务创建时调用 ApprovalNodeDelegate)
|
||||
ExtensionElement taskListener = new ExtensionElement();
|
||||
taskListener.setName("taskListener");
|
||||
taskListener.setNamespace("http://flowable.org/bpmn");
|
||||
taskListener.setNamespacePrefix("flowable");
|
||||
taskListener.addAttribute(createAttribute("event", "create"));
|
||||
taskListener.addAttribute(createAttribute("delegateExpression", "${approvalDelegate}"));
|
||||
|
||||
// ✅ 2. 将 field 字段作为 TaskListener 的子元素添加(而不是 UserTask 的子元素)
|
||||
// 这样 ApprovalNodeDelegate 才能通过 @field 注解注入这些字段
|
||||
addFieldsToTaskListener(taskListener, node, validId);
|
||||
|
||||
// ✅ 3. 将 TaskListener 添加到扩展元素
|
||||
extensionElements.computeIfAbsent("taskListener", k -> new ArrayList<>()).add(taskListener);
|
||||
|
||||
// ✅ 4. 设置扩展元素
|
||||
userTask.setExtensionElements(extensionElements);
|
||||
|
||||
log.debug("配置审批节点完成: {}", node.getNodeName());
|
||||
} catch (Exception e) {
|
||||
log.error("配置审批节点失败: {}", node.getNodeName(), e);
|
||||
throw new RuntimeException("配置审批节点失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向 TaskListener 添加 field 子元素
|
||||
*
|
||||
* @param taskListener TaskListener 扩展元素
|
||||
* @param node 工作流节点定义
|
||||
* @param validId sanitized 后的节点 ID
|
||||
*/
|
||||
private void addFieldsToTaskListener(ExtensionElement taskListener, WorkflowDefinitionGraphNode node, String validId) {
|
||||
try {
|
||||
// ✅ 1. 添加 nodeId field
|
||||
taskListener.addChildElement(createFieldElement("nodeId", validId));
|
||||
|
||||
// ✅ 2. 添加 configs field
|
||||
if (node.getConfigs() != null) {
|
||||
String configsJson = objectMapper.writeValueAsString(node.getConfigs());
|
||||
taskListener.addChildElement(createFieldElement("configs", configsJson));
|
||||
}
|
||||
|
||||
// ✅ 3. 添加 inputMapping field
|
||||
if (node.getInputMapping() != null) {
|
||||
String inputMappingJson = objectMapper.writeValueAsString(node.getInputMapping());
|
||||
taskListener.addChildElement(createFieldElement("inputMapping", inputMappingJson));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("添加 TaskListener field 字段失败: {}", node.getNodeName(), e);
|
||||
throw new RuntimeException("添加 TaskListener field 字段失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置服务任务节点
|
||||
*
|
||||
@ -274,6 +368,7 @@ public class BpmnConverter {
|
||||
return "${notificationDelegate}";
|
||||
case "SCRIPT_NODE":
|
||||
return "${shellDelegate}";
|
||||
case "APPROVAL":
|
||||
case "APPROVAL_NODE":
|
||||
return "${approvalDelegate}";
|
||||
case "DEPLOY_NODE":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user