添加通知管理功能
This commit is contained in:
parent
4b859989ee
commit
6eb5a83f58
@ -1,4 +1,4 @@
|
||||
package com.qqchen.deploy.backend.workflow.controller;
|
||||
package com.qqchen.deploy.backend.workflow.api;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.api.Response;
|
||||
import com.qqchen.deploy.backend.workflow.dto.request.ApprovalTaskRequest;
|
||||
@ -1,347 +0,0 @@
|
||||
package com.qqchen.deploy.backend.workflow.delegate;
|
||||
|
||||
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 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.common.engine.api.delegate.Expression;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
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.Map;
|
||||
|
||||
/**
|
||||
* 审批节点委派
|
||||
* 负责创建Flowable用户任务,并配置审批相关参数
|
||||
*
|
||||
* ✅ 实现 TaskListener 接口,在任务创建时(create event)被调用
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-23
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("approvalDelegate")
|
||||
public class ApprovalNodeDelegate extends BaseNodeDelegate<ApprovalInputMapping, ApprovalOutputs> implements TaskListener {
|
||||
|
||||
/**
|
||||
* ✅ 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(
|
||||
DelegateExecution execution,
|
||||
Map<String, Object> configs,
|
||||
ApprovalInputMapping input
|
||||
) {
|
||||
log.info("Creating approval task - mode: {}, approverType: {}",
|
||||
input.getApprovalMode(), input.getApproverType());
|
||||
|
||||
try {
|
||||
// 设置审批任务的相关变量到流程中,供Flowable UserTask使用
|
||||
setApprovalVariables(execution, configs, input);
|
||||
|
||||
// 审批任务是异步的,这里先返回PENDING状态
|
||||
ApprovalOutputs outputs = new ApprovalOutputs();
|
||||
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 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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
package com.qqchen.deploy.backend.workflow.delegate;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.ApprovalInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.outputs.ApprovalOutputs;
|
||||
import com.qqchen.deploy.backend.workflow.enums.ApprovalModeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.task.service.delegate.DelegateTask;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 审批任务监听器
|
||||
* 在 UserTask 创建时被调用,负责配置审批人和任务基本信息
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-23
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("approvalTaskListener")
|
||||
public class ApprovalTaskListener extends BaseTaskListener<ApprovalInputMapping, ApprovalOutputs> {
|
||||
|
||||
@Override
|
||||
protected void configureTask(
|
||||
DelegateTask delegateTask,
|
||||
Map<String, Object> configs,
|
||||
ApprovalInputMapping inputMapping
|
||||
) {
|
||||
log.info("Configuring approval task: {}", delegateTask.getId());
|
||||
|
||||
// 1. 配置审批人
|
||||
configureTaskAssignee(delegateTask, inputMapping);
|
||||
|
||||
// 2. 设置任务基本信息
|
||||
if (configs.get("nodeName") != null) {
|
||||
delegateTask.setName((String) configs.get("nodeName"));
|
||||
}
|
||||
if (inputMapping.getApprovalTitle() != null) {
|
||||
delegateTask.setDescription(inputMapping.getApprovalTitle());
|
||||
}
|
||||
|
||||
// 3. 设置超时时间
|
||||
if (inputMapping.getTimeoutDuration() != null && inputMapping.getTimeoutDuration() > 0) {
|
||||
java.util.Date dueDate = java.sql.Timestamp.valueOf(
|
||||
LocalDateTime.now().plusHours(inputMapping.getTimeoutDuration())
|
||||
);
|
||||
delegateTask.setDueDate(dueDate);
|
||||
}
|
||||
|
||||
// 4. 保存审批信息到任务局部变量(供前端查询)
|
||||
saveApprovalInfoToTaskVariables(delegateTask, inputMapping);
|
||||
|
||||
log.info("Approval task configured - assignee: {}", delegateTask.getAssignee());
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置任务的审批人
|
||||
* 根据 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()) {
|
||||
// ✅ 使用枚举比较而不是字符串比较
|
||||
if (input.getApprovalMode() == ApprovalModeEnum.SINGLE) {
|
||||
// 单人审批:设置 assignee
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将审批相关信息保存为任务变量
|
||||
* 这样前端查询任务时可以获取到这些信息
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.workflow.delegate;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.workflow.model.NodeContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.flowable.engine.delegate.JavaDelegate;
|
||||
@ -20,31 +21,38 @@ import static com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants.WOR
|
||||
|
||||
/**
|
||||
* 任务委派者基类
|
||||
|
||||
* 负责处理节点配置、输入映射和输出结果的统一处理
|
||||
*
|
||||
*
|
||||
* @param <I> 输入映射类型 (InputMapping)
|
||||
* @param <O> 输出类型 (Outputs)
|
||||
*
|
||||
* @author qqchen
|
||||
*/
|
||||
@Slf4j
|
||||
|
||||
public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
|
||||
// Flowable自动注入的字段
|
||||
protected Expression nodeId;
|
||||
|
||||
protected Expression configs;
|
||||
|
||||
protected Expression inputMapping;
|
||||
|
||||
protected Expression outputs;
|
||||
|
||||
// 缓存泛型类型
|
||||
private Class<I> inputMappingClass;
|
||||
|
||||
private Class<O> outputsClass;
|
||||
|
||||
@Override
|
||||
public void execute(DelegateExecution execution) {
|
||||
|
||||
String currentNodeId = null;
|
||||
try {
|
||||
// 1. 获取节点ID
|
||||
@ -54,45 +62,49 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
// 2. 清除前一个节点的状态
|
||||
clearPreviousNodeStatus(execution);
|
||||
|
||||
|
||||
// 3. 解析配置(通用Map)
|
||||
Map<String, Object> configsMap = parseJsonField(configs, execution);
|
||||
|
||||
|
||||
// 4. 解析并转换InputMapping(强类型)
|
||||
I inputMappingObj = parseAndConvertInputMapping(execution);
|
||||
|
||||
// 5. 执行具体的业务逻辑,返回强类型输出
|
||||
O outputsObj = executeInternal(execution, configsMap, inputMappingObj);
|
||||
|
||||
// 6. 关键:将输出转换为Map并存储为变量
|
||||
if (outputsObj != null) {
|
||||
Map<String, Object> outputsMap = objectMapper.convertValue(outputsObj,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
execution.setVariable(currentNodeId, outputsMap);
|
||||
log.info("Stored node outputs: {} = {}", currentNodeId, outputsMap);
|
||||
}
|
||||
// ✅ 6. 使用 NodeContext 保存节点数据
|
||||
NodeContext<I, O> nodeContext = new NodeContext<>();
|
||||
nodeContext.setConfigs(configsMap);
|
||||
nodeContext.setInputMapping(inputMappingObj);
|
||||
nodeContext.setOutputs(outputsObj);
|
||||
|
||||
execution.setVariable(currentNodeId, nodeContext.toMap(objectMapper));
|
||||
log.info("Stored NodeContext for: {}", currentNodeId);
|
||||
|
||||
// 7. 设置节点执行状态为成功
|
||||
setExecutionStatus(execution, WORKFLOW_NODE_EXECUTION_STATE_SUCCESS);
|
||||
|
||||
} catch (Exception e) {
|
||||
setExecutionStatus(execution, WORKFLOW_NODE_EXECUTION_STATE_FAILURE);
|
||||
|
||||
log.error("Node execution failed: {}", currentNodeId, e);
|
||||
throw new RuntimeException("Node execution failed: " + currentNodeId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* 执行具体的业务逻辑(子类实现)
|
||||
*
|
||||
* @param execution Flowable执行上下文
|
||||
* @param configs 节点配置
|
||||
* @param inputMapping 输入映射(强类型)
|
||||
* @param execution Flowable执行上下文
|
||||
* @param configs 节点配置
|
||||
* @param inputMapping 输入映射(强类型)
|
||||
* @return 节点输出结果(强类型)
|
||||
*/
|
||||
protected abstract O executeInternal(
|
||||
DelegateExecution execution,
|
||||
Map<String, Object> configs,
|
||||
I inputMapping
|
||||
DelegateExecution execution,
|
||||
Map<String, Object> configs,
|
||||
I inputMapping
|
||||
);
|
||||
|
||||
/**
|
||||
@ -136,15 +148,16 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
try {
|
||||
String inputMappingJson = getFieldValue(inputMapping, execution);
|
||||
Class<I> inputClass = getInputMappingClass();
|
||||
|
||||
|
||||
if (inputMappingJson == null || inputMappingJson.isEmpty()) {
|
||||
return inputClass.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
|
||||
// 先解析为Map
|
||||
Map<String, Object> inputMap = objectMapper.readValue(
|
||||
inputMappingJson,
|
||||
new TypeReference<Map<String, Object>>() {}
|
||||
inputMappingJson,
|
||||
new TypeReference<Map<String, Object>>() {
|
||||
}
|
||||
);
|
||||
|
||||
// 处理表达式
|
||||
@ -152,7 +165,7 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
|
||||
// 转换为强类型对象
|
||||
return objectMapper.convertValue(resolvedMap, inputClass);
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse input mapping", e);
|
||||
throw new RuntimeException("Failed to parse input mapping", e);
|
||||
@ -165,10 +178,10 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
*/
|
||||
protected Map<String, Object> resolveExpressions(Map<String, Object> inputMap, DelegateExecution execution) {
|
||||
Map<String, Object> resolvedMap = new HashMap<>();
|
||||
|
||||
|
||||
for (Map.Entry<String, Object> entry : inputMap.entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
|
||||
|
||||
if (value instanceof String) {
|
||||
String strValue = (String) value;
|
||||
if (strValue.contains("${")) {
|
||||
@ -187,7 +200,7 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
resolvedMap.put(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return resolvedMap;
|
||||
}
|
||||
|
||||
@ -197,21 +210,21 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
*/
|
||||
private String resolveVariableExpression(String expression, DelegateExecution execution) {
|
||||
String result = expression;
|
||||
|
||||
|
||||
// 匹配 ${xxx.yyy} 或 ${xxx} 格式
|
||||
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\$\\{([^}]+)\\}");
|
||||
java.util.regex.Matcher matcher = pattern.matcher(expression);
|
||||
|
||||
|
||||
while (matcher.find()) {
|
||||
String varExpression = matcher.group(1); // 例如:sid_xxx.buildNumber
|
||||
Object value = resolveVariable(varExpression, execution);
|
||||
|
||||
|
||||
if (value != null) {
|
||||
// 替换 ${xxx.yyy} 为实际值
|
||||
result = result.replace("${" + varExpression + "}", value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -221,7 +234,7 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
*/
|
||||
private Object resolveVariable(String varExpression, DelegateExecution execution) {
|
||||
String[] parts = varExpression.split("\\.", 2);
|
||||
|
||||
|
||||
if (parts.length == 1) {
|
||||
// 直接变量:${xxx}
|
||||
return execution.getVariable(parts[0]);
|
||||
@ -229,7 +242,7 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
// 对象属性:${sid_xxx.buildNumber}
|
||||
String varName = parts[0];
|
||||
String fieldName = parts[1];
|
||||
|
||||
|
||||
Object varValue = execution.getVariable(varName);
|
||||
if (varValue instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -237,7 +250,7 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
return map.get(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -253,7 +266,8 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
return new HashMap<>();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(jsonStr, new TypeReference<Map<String, Object>>() {});
|
||||
return objectMapper.readValue(jsonStr, new TypeReference<>() {
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse JSON field: {}", jsonStr, e);
|
||||
return new HashMap<>();
|
||||
@ -268,4 +282,5 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
execution.removeVariable(WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME);
|
||||
log.debug("Cleared previous node status for node: {}", execution.getCurrentActivityId());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
package com.qqchen.deploy.backend.workflow.delegate;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.workflow.model.NodeContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.common.engine.api.delegate.Expression;
|
||||
import org.flowable.engine.delegate.TaskListener;
|
||||
import org.flowable.task.service.delegate.DelegateTask;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* TaskListener 基类
|
||||
* 用于 UserTask 的任务创建阶段,统一处理输入映射解析和任务配置
|
||||
*
|
||||
* @param <I> 输入映射类型 (InputMapping)
|
||||
* @param <O> 输出类型 (Outputs) - 用于类型标识
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-23
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class BaseTaskListener<I, O> implements TaskListener {
|
||||
|
||||
@Autowired
|
||||
protected ObjectMapper objectMapper;
|
||||
|
||||
// Flowable 自动注入的字段
|
||||
protected Expression nodeId;
|
||||
protected Expression configs;
|
||||
protected Expression inputMapping;
|
||||
|
||||
private Class<I> inputMappingClass;
|
||||
|
||||
@Override
|
||||
public void notify(DelegateTask delegateTask) {
|
||||
String currentNodeId = null;
|
||||
try {
|
||||
// 1. 获取节点ID
|
||||
currentNodeId = getFieldValue(nodeId, delegateTask);
|
||||
log.info("TaskListener: Creating task for node: {}", currentNodeId);
|
||||
|
||||
// 2. 解析配置和输入映射
|
||||
Map<String, Object> configsMap = parseJsonField(configs, delegateTask);
|
||||
I inputMappingObj = parseInputMapping(delegateTask);
|
||||
|
||||
// 3. 将节点ID保存到任务局部变量(供 ExecutionListener 使用)
|
||||
delegateTask.setVariableLocal("nodeId", currentNodeId);
|
||||
|
||||
// ✅ 4. 初始化 NodeContext(configs + inputMapping)
|
||||
NodeContext<I, O> nodeContext = new NodeContext<>();
|
||||
nodeContext.setConfigs(configsMap);
|
||||
nodeContext.setInputMapping(inputMappingObj);
|
||||
delegateTask.setVariable(currentNodeId, nodeContext.toMap(objectMapper));
|
||||
log.debug("Initialized NodeContext for: {}", currentNodeId);
|
||||
|
||||
// 5. 执行具体的任务配置逻辑(子类实现)
|
||||
configureTask(delegateTask, configsMap, inputMappingObj);
|
||||
|
||||
log.info("Task configuration completed for node: {}", currentNodeId);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to configure task for node: {}", currentNodeId, e);
|
||||
throw new RuntimeException("Failed to configure task: " + currentNodeId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置任务(子类实现)
|
||||
*
|
||||
* @param delegateTask Flowable 任务对象
|
||||
* @param configs 节点配置
|
||||
* @param inputMapping 输入映射(强类型)
|
||||
*/
|
||||
protected abstract void configureTask(
|
||||
DelegateTask delegateTask,
|
||||
Map<String, Object> configs,
|
||||
I inputMapping
|
||||
);
|
||||
|
||||
/**
|
||||
* 解析输入映射
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected I parseInputMapping(DelegateTask task) {
|
||||
try {
|
||||
String inputMappingJson = getFieldValue(inputMapping, task);
|
||||
Class<I> inputClass = getInputMappingClass();
|
||||
|
||||
if (inputMappingJson == null || inputMappingJson.isEmpty()) {
|
||||
return inputClass.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
|
||||
return objectMapper.readValue(inputMappingJson, inputClass);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse input mapping", e);
|
||||
throw new RuntimeException("Failed to parse input mapping", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射获取 InputMapping 的类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<I> getInputMappingClass() {
|
||||
if (inputMappingClass == null) {
|
||||
Type genericSuperclass = getClass().getGenericSuperclass();
|
||||
if (genericSuperclass instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
|
||||
inputMappingClass = (Class<I>) parameterizedType.getActualTypeArguments()[0];
|
||||
}
|
||||
}
|
||||
return inputMappingClass;
|
||||
}
|
||||
|
||||
protected String getFieldValue(Expression expression, DelegateTask task) {
|
||||
if (expression == null) return null;
|
||||
Object value = expression.getValue(task);
|
||||
return value != null ? value.toString() : null;
|
||||
}
|
||||
|
||||
protected Map<String, Object> parseJsonField(Expression expression, DelegateTask task) {
|
||||
String jsonStr = getFieldValue(expression, task);
|
||||
if (jsonStr == null || jsonStr.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(jsonStr, new TypeReference<Map<String, Object>>() {});
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse JSON field: {}", jsonStr, e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,4 +27,15 @@ public class WorkflowNodeInstanceStatusChangeEvent {
|
||||
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 节点执行变量(JSON格式)
|
||||
* 包含节点的输入、输出、上下文等信息
|
||||
*/
|
||||
private String variables;
|
||||
|
||||
/**
|
||||
* 错误信息(节点执行失败时)
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 审批节点输出
|
||||
*
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-23
|
||||
*/
|
||||
@ -57,10 +57,15 @@ public class ApprovalOutputs extends BaseNodeOutputs {
|
||||
*/
|
||||
@Data
|
||||
public static class ApproverInfo {
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String userName;
|
||||
|
||||
private String result;
|
||||
|
||||
private String comment;
|
||||
|
||||
private LocalDateTime time;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.request;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.enums.ApprovalResultEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@ -20,8 +21,8 @@ public class ApprovalTaskRequest {
|
||||
private String taskId;
|
||||
|
||||
@NotNull(message = "审批结果不能为空")
|
||||
@Schema(description = "审批结果: true=通过, false=拒绝", required = true)
|
||||
private Boolean approved;
|
||||
@Schema(description = "审批结果: APPROVED=通过, REJECTED=拒绝", required = true, example = "APPROVED")
|
||||
private ApprovalResultEnum result;
|
||||
|
||||
@Schema(description = "审批意见")
|
||||
private String comment;
|
||||
|
||||
@ -76,12 +76,6 @@ public class WorkflowDefinition extends Entity<Long> {
|
||||
@Column(name = "graph", columnDefinition = "json")
|
||||
private WorkflowDefinitionGraph graph;
|
||||
|
||||
/**
|
||||
* 表单配置
|
||||
*/
|
||||
@Type(JsonType.class)
|
||||
@Column(name = "local_variables_schema", columnDefinition = "json")
|
||||
private JsonNode localVariablesSchema;
|
||||
|
||||
/**
|
||||
* 流程状态
|
||||
@ -96,18 +90,6 @@ public class WorkflowDefinition extends Entity<Long> {
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否可执行
|
||||
*/
|
||||
@Column(name = "is_executable", nullable = false)
|
||||
private Boolean isExecutable = true;
|
||||
|
||||
/**
|
||||
* 目标命名空间
|
||||
*/
|
||||
@Column(name = "target_namespace")
|
||||
private String targetNamespace = "http://www.flowable.org/test";
|
||||
|
||||
|
||||
/**
|
||||
* 流程标签,用于分组和过滤
|
||||
|
||||
@ -92,139 +92,6 @@ public enum NodeTypeEnums {
|
||||
NodeCategoryEnums.TASK,
|
||||
"人工审批节点,支持多种审批模式"
|
||||
);
|
||||
//
|
||||
// /**
|
||||
// * 用户任务节点
|
||||
// * 需要人工处理的任务节点
|
||||
// * 特点:
|
||||
// * - 需要用户交互
|
||||
// * - 可以分配给特定用户或角色
|
||||
// * - 支持审批、填写表单等操作
|
||||
// */
|
||||
// USER_TASK(
|
||||
// "USER_TASK",
|
||||
// "用户任务",
|
||||
// "人工处理任务",
|
||||
// new WorkflowNodeGraph()
|
||||
// .setShape("rect")
|
||||
// .setSize(120, 60)
|
||||
// .setStyle("#ffffff", "#1890ff", "user")
|
||||
// .configPorts(Arrays.asList("in", "out"))
|
||||
// ),
|
||||
//
|
||||
// /**
|
||||
// * 服务任务节点
|
||||
// * 自动执行的系统服务任务
|
||||
// * 特点:
|
||||
// * - 自动执行,无需人工干预
|
||||
// * - 可以调用外部服务或系统API
|
||||
// * - 支持异步执行
|
||||
// */
|
||||
// SERVICE_TASK(
|
||||
// "SERVICE_TASK",
|
||||
// "服务任务",
|
||||
// "系统服务调用",
|
||||
// new WorkflowNodeGraph()
|
||||
// .setShape("rectangle")
|
||||
// .setSize(120, 60)
|
||||
// .setStyle("#ffffff", "#1890ff", "api")
|
||||
// .configPorts(Arrays.asList("in", "out"))
|
||||
// ),
|
||||
//
|
||||
// /**
|
||||
// * 脚本任务节点
|
||||
// * 执行自定义脚本的任务节点
|
||||
// * 特点:
|
||||
// * - 支持多种脚本语言(如Shell、Python等<EFBFBD><EFBFBD><EFBFBD>
|
||||
// * - 可以执行自定义业务逻辑
|
||||
// * - 适合复杂的数据处理和计算
|
||||
// */
|
||||
// SCRIPT_TASK(
|
||||
// "SCRIPT_TASK",
|
||||
// "脚本任务",
|
||||
// "脚本执行任务",
|
||||
// new WorkflowNodeGraph()
|
||||
// .setShape("rectangle")
|
||||
// .setSize(120, 60)
|
||||
// .setStyle("#ffffff", "#1890ff", "code")
|
||||
// .configPorts(Arrays.asList("in", "out"))
|
||||
// ),
|
||||
//
|
||||
// /**
|
||||
// * 排他网关
|
||||
// * 基于条件的分支控制
|
||||
// * 特点:
|
||||
// * - 只会选择一个分支执行
|
||||
// * - 需要设置分支条件
|
||||
// * - 适合互斥的业务场景
|
||||
// */
|
||||
// EXCLUSIVE_GATEWAY(
|
||||
// "EXCLUSIVE_GATEWAY",
|
||||
// "排他网关",
|
||||
// "条件分支控制",
|
||||
// new WorkflowNodeGraph()
|
||||
// .setShape("diamond")
|
||||
// .setSize(50, 50)
|
||||
// .setStyle("#fff7e6", "#faad14", "fork")
|
||||
// .configPorts(Arrays.asList("in", "out"))
|
||||
// ),
|
||||
//
|
||||
// /**
|
||||
// * 并行网关
|
||||
// * 并行执行多个分支
|
||||
// * 特点:
|
||||
// * - 所有分支同时执行
|
||||
// * - 等待所有分支完成才继续
|
||||
// * - 适合并行处理的业务场景
|
||||
// */
|
||||
// PARALLEL_GATEWAY(
|
||||
// "PARALLEL_GATEWAY",
|
||||
// "并行网关",
|
||||
// "<EFBFBD><EFBFBD>行分支控制",
|
||||
// new WorkflowNodeGraph()
|
||||
// .setShape("diamond")
|
||||
// .setSize(50, 50)
|
||||
// .setStyle("#fff7e6", "#faad14", "branches")
|
||||
// .configPorts(Arrays.asList("in", "out"))
|
||||
// ),
|
||||
//
|
||||
// /**
|
||||
// * 子流程节点
|
||||
// * 嵌套的子流程
|
||||
// * 特点:
|
||||
// * - 可以包含完整的子流程
|
||||
// * - 支持流程的模块化和复用
|
||||
// * - 可以独立部署和版本控制
|
||||
// */
|
||||
// SUBPROCESS(
|
||||
// "SUB_PROCESS",
|
||||
// "子流程",
|
||||
// "嵌入式子流程",
|
||||
// new WorkflowNodeGraph()
|
||||
// .setShape("rectangle")
|
||||
// .setSize(120, 60)
|
||||
// .setStyle("#ffffff", "#1890ff", "apartment")
|
||||
// .configPorts(Arrays.asList("in", "out"))
|
||||
// ),
|
||||
//
|
||||
// /**
|
||||
// * 调用活动节点
|
||||
// * 调用外部定义的流程
|
||||
// * 特点:
|
||||
// * - 可以调用其他已定义的流程
|
||||
// * - 支持流程的复用
|
||||
// * - 可以传递参数和接收返回值
|
||||
// */
|
||||
// CALL_ACTIVITY(
|
||||
// "CALL_ACTIVITY",
|
||||
// "调用活动",
|
||||
// "调用外部定义的流程,支持跨系统流程调用和参数传递",
|
||||
// new WorkflowNodeGraph()
|
||||
// .setShape("rectangle")
|
||||
// .setSize(120, 60)
|
||||
// .setStyle("#ffffff", "#1890ff", "api")
|
||||
// .configPorts(Arrays.asList("in", "out"))
|
||||
// );
|
||||
|
||||
@JsonValue
|
||||
private final String code; // 节点类型编码
|
||||
|
||||
@ -0,0 +1,238 @@
|
||||
package com.qqchen.deploy.backend.workflow.listener.flowable.execution;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
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 com.qqchen.deploy.backend.workflow.enums.ApprovalResultEnum;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum;
|
||||
import com.qqchen.deploy.backend.workflow.model.NodeContext;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.HistoryService;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.flowable.engine.delegate.ExecutionListener;
|
||||
import org.flowable.task.api.history.HistoricTaskInstance;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants.WORKFLOW_NODE_EXECUTION_STATE_FAILURE;
|
||||
import static com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants.WORKFLOW_NODE_EXECUTION_STATE_SUCCESS;
|
||||
import static com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants.WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME;
|
||||
|
||||
/**
|
||||
* 审批任务结束监听器
|
||||
* 在 UserTask 结束时自动装配上下文信息并构建 ApprovalOutputs
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-23
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("approvalExecutionListener")
|
||||
public class ApprovalExecutionListener implements ExecutionListener {
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Resource
|
||||
private HistoryService historyService;
|
||||
|
||||
@Override
|
||||
public void notify(DelegateExecution execution) {
|
||||
String nodeId = execution.getCurrentActivityId();
|
||||
try {
|
||||
log.info("ApprovalExecutionListener: Building outputs for node: {}", nodeId);
|
||||
|
||||
// 1. 读取现有的 NodeContext
|
||||
Object nodeDataObj = execution.getVariable(nodeId);
|
||||
if (!(nodeDataObj instanceof Map)) {
|
||||
log.warn("NodeContext not found for node: {}, skipping ApprovalExecutionListener", nodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> nodeDataMap = (Map<String, Object>) nodeDataObj;
|
||||
NodeContext<ApprovalInputMapping, ApprovalOutputs> nodeContext =
|
||||
NodeContext.fromMap(nodeDataMap, ApprovalInputMapping.class, ApprovalOutputs.class, objectMapper);
|
||||
|
||||
// 2. 检查是否已经有临时的 outputs(由 ApprovalTaskServiceImpl 设置)
|
||||
ApprovalOutputs outputs = nodeContext.getOutputs();
|
||||
if (outputs == null) {
|
||||
log.warn("Outputs not found in NodeContext for node: {}, skipping", nodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 自动装配上下文信息(计算审批用时、历史任务ID等)
|
||||
enrichApprovalOutputs(execution, nodeId, outputs);
|
||||
|
||||
// 4. 更新 NodeContext 的 outputs
|
||||
nodeContext.setOutputs(outputs);
|
||||
|
||||
// 5. 保存回流程变量
|
||||
execution.setVariable(nodeId, nodeContext.toMap(objectMapper));
|
||||
|
||||
log.info("Stored approval outputs for node: {}, result: {}", nodeId, outputs.getApprovalResult());
|
||||
|
||||
// 6. 设置节点执行状态为成功(供 GlobalNodeExecutionListener 使用)
|
||||
execution.setVariable(WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME, WORKFLOW_NODE_EXECUTION_STATE_SUCCESS);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to build approval outputs for node: {}", nodeId, e);
|
||||
execution.setVariable(WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME,
|
||||
WORKFLOW_NODE_EXECUTION_STATE_FAILURE);
|
||||
throw new RuntimeException("Failed to build approval outputs: " + nodeId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 丰富 ApprovalOutputs
|
||||
* 自动装配上下文信息(审批用时等)
|
||||
*/
|
||||
private void enrichApprovalOutputs(DelegateExecution execution, String nodeId, ApprovalOutputs outputs) {
|
||||
// 1. ⚙️ 自动装配:审批用时(从任务历史计算)
|
||||
Long duration = calculateApprovalDuration(execution, nodeId);
|
||||
outputs.setApprovalDuration(duration);
|
||||
|
||||
// TODO: 未来可以添加更多自动装配逻辑(如历史任务ID、审批状态等)
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 ApprovalOutputs(弃用,保留以防万一)
|
||||
* 自动装配所有上下文信息
|
||||
* @deprecated 使用 enrichApprovalOutputs 代替
|
||||
*/
|
||||
@Deprecated
|
||||
private ApprovalOutputs buildApprovalOutputs(DelegateExecution execution, String nodeId, Map<String, Object> approvalData) {
|
||||
ApprovalOutputs outputs = new ApprovalOutputs();
|
||||
|
||||
// 1. 设置审批结果(从 approvalData 读取)
|
||||
String approvalResultStr = (String) approvalData.get("approvalResult");
|
||||
try {
|
||||
outputs.setApprovalResult(ApprovalResultEnum.valueOf(approvalResultStr));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Invalid approval result: {}, defaulting to REJECTED", approvalResultStr);
|
||||
outputs.setApprovalResult(ApprovalResultEnum.REJECTED);
|
||||
}
|
||||
|
||||
// 2. 设置审批意见(从 approvalData 读取)
|
||||
String comment = (String) approvalData.get("approvalComment");
|
||||
outputs.setApprovalComment(comment);
|
||||
|
||||
// 3. 设置审批人(从 approvalData 读取)
|
||||
String approver = (String) approvalData.get("approver");
|
||||
outputs.setApprover(approver);
|
||||
|
||||
// 4. ⚙️ 自动装配:审批人ID(如果有)
|
||||
Object approverUserIdObj = approvalData.get("approverUserId");
|
||||
if (approverUserIdObj != null) {
|
||||
outputs.setApproverUserId(Long.valueOf(approverUserIdObj.toString()));
|
||||
}
|
||||
|
||||
// 5. 设置审批时间(从 approvalData 读取)
|
||||
Object approvalTimeObj = approvalData.get("approvalTime");
|
||||
if (approvalTimeObj instanceof LocalDateTime) {
|
||||
outputs.setApprovalTime((LocalDateTime) approvalTimeObj);
|
||||
} else if (approvalTimeObj != null) {
|
||||
// 尝试从其他格式转换
|
||||
outputs.setApprovalTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
// 6. ⚙️ 自动装配:审批用时(从任务历史计算)
|
||||
Long duration = calculateApprovalDuration(execution, nodeId);
|
||||
outputs.setApprovalDuration(duration);
|
||||
|
||||
// 7. ⚙️ 自动装配:所有审批人列表(会签/或签场景)
|
||||
List<ApprovalOutputs.ApproverInfo> allApprovers = getAllApprovers(execution, nodeId);
|
||||
if (allApprovers != null && !allApprovers.isEmpty()) {
|
||||
outputs.setAllApprovers(allApprovers);
|
||||
}
|
||||
|
||||
// 8. ⚙️ 自动装配:节点执行状态
|
||||
outputs.setStatus(NodeExecutionStatusEnum.SUCCESS);
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算审批用时(秒)
|
||||
*/
|
||||
private Long calculateApprovalDuration(DelegateExecution execution, String nodeId) {
|
||||
try {
|
||||
// 查询任务历史
|
||||
List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery()
|
||||
.processInstanceId(execution.getProcessInstanceId())
|
||||
.taskDefinitionKey(nodeId)
|
||||
.orderByHistoricTaskInstanceEndTime()
|
||||
.desc()
|
||||
.list();
|
||||
|
||||
if (!tasks.isEmpty()) {
|
||||
HistoricTaskInstance task = tasks.get(0);
|
||||
if (task.getCreateTime() != null && task.getEndTime() != null) {
|
||||
long durationMillis = task.getEndTime().getTime() - task.getCreateTime().getTime();
|
||||
return durationMillis / 1000; // 转换为秒
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to calculate approval duration", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有审批人列表(会签/或签场景)
|
||||
*/
|
||||
private List<ApprovalOutputs.ApproverInfo> getAllApprovers(DelegateExecution execution, String nodeId) {
|
||||
try {
|
||||
// 查询该节点的所有历史任务
|
||||
List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery()
|
||||
.processInstanceId(execution.getProcessInstanceId())
|
||||
.taskDefinitionKey(nodeId)
|
||||
.finished()
|
||||
.list();
|
||||
|
||||
if (tasks.size() > 1) {
|
||||
// 多个任务说明是会签/或签场景
|
||||
List<ApprovalOutputs.ApproverInfo> approvers = new ArrayList<>();
|
||||
for (HistoricTaskInstance task : tasks) {
|
||||
ApprovalOutputs.ApproverInfo info = new ApprovalOutputs.ApproverInfo();
|
||||
info.setUserName(task.getAssignee());
|
||||
|
||||
// 从任务变量中获取审批结果和意见
|
||||
Map<String, Object> taskVariables = task.getTaskLocalVariables();
|
||||
if (taskVariables != null) {
|
||||
info.setResult((String) taskVariables.get("taskApprovalResult"));
|
||||
info.setComment((String) taskVariables.get("taskApprovalComment"));
|
||||
}
|
||||
|
||||
if (task.getEndTime() != null) {
|
||||
info.setTime(convertToLocalDateTime(task.getEndTime()));
|
||||
}
|
||||
|
||||
approvers.add(info);
|
||||
}
|
||||
return approvers;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to get all approvers", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Date 转 LocalDateTime
|
||||
*/
|
||||
private LocalDateTime convertToLocalDateTime(Date date) {
|
||||
if (date == null) return null;
|
||||
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.qqchen.deploy.backend.workflow.listener.flowable.execution;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowNodeInstanceStatusEnums;
|
||||
import com.qqchen.deploy.backend.workflow.dto.event.WorkflowNodeInstanceStatusChangeEvent;
|
||||
@ -15,7 +17,7 @@ import org.springframework.stereotype.Component;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
import static com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants.WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME;
|
||||
|
||||
@ -27,6 +29,9 @@ public class GlobalNodeExecutionListener implements ExecutionListener {
|
||||
@Lazy
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public void notify(DelegateExecution execution) {
|
||||
// 获取当前节点信息
|
||||
@ -42,6 +47,9 @@ public class GlobalNodeExecutionListener implements ExecutionListener {
|
||||
WorkflowNodeInstanceStatusEnums status = null;
|
||||
LocalDateTime startTime = null;
|
||||
LocalDateTime endTime = null;
|
||||
String variablesJson = null;
|
||||
String errorMessage = null;
|
||||
|
||||
String nodeExecutionStatus = Optional.ofNullable(execution.getVariables())
|
||||
.map(vars -> vars.get(WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME))
|
||||
.map(Object::toString)
|
||||
@ -61,6 +69,15 @@ public class GlobalNodeExecutionListener implements ExecutionListener {
|
||||
status = WorkflowNodeInstanceStatusEnums.FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 收集节点执行变量
|
||||
variablesJson = collectNodeVariables(execution, nodeId, status);
|
||||
|
||||
// ✅ 如果失败,提取错误信息
|
||||
if (status == WorkflowNodeInstanceStatusEnums.FAILED) {
|
||||
errorMessage = extractErrorMessage(execution, nodeId);
|
||||
}
|
||||
|
||||
endTime = now;
|
||||
break;
|
||||
default:
|
||||
@ -76,6 +93,58 @@ public class GlobalNodeExecutionListener implements ExecutionListener {
|
||||
.status(status)
|
||||
.startTime(startTime)
|
||||
.endTime(endTime)
|
||||
.variables(variablesJson)
|
||||
.errorMessage(errorMessage)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集节点执行变量
|
||||
*
|
||||
* @param execution Flowable执行上下文
|
||||
* @param nodeId 节点ID
|
||||
* @param status 节点执行状态
|
||||
* @return JSON格式的变量字符串
|
||||
*/
|
||||
private String collectNodeVariables(DelegateExecution execution, String nodeId,
|
||||
WorkflowNodeInstanceStatusEnums status) {
|
||||
try {
|
||||
// ✅ 直接获取所有流程变量
|
||||
Map<String, Object> allVariables = execution.getVariables();
|
||||
|
||||
if (allVariables == null || allVariables.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return objectMapper.writeValueAsString(allVariables);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to collect node variables for nodeId: {}", nodeId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取错误信息
|
||||
*
|
||||
* @param execution Flowable执行上下文
|
||||
* @param nodeId 节点ID
|
||||
* @return 错误信息字符串
|
||||
*/
|
||||
private String extractErrorMessage(DelegateExecution execution, String nodeId) {
|
||||
try {
|
||||
Object nodeOutput = execution.getVariable(nodeId);
|
||||
if (nodeOutput instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> outputMap = (Map<String, Object>) nodeOutput;
|
||||
Object errorMsg = outputMap.get("errorMessage");
|
||||
if (errorMsg != null) {
|
||||
return errorMsg.toString();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to extract error message for nodeId: {}", nodeId, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
package com.qqchen.deploy.backend.workflow.model;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 节点执行上下文(强类型)
|
||||
* 统一封装节点的配置、输入映射和输出结果
|
||||
*
|
||||
* <p>数据结构:
|
||||
* <pre>
|
||||
* {
|
||||
* "configs": { ... }, // 节点配置(来自 BPMN)
|
||||
* "inputMapping": { ... }, // 输入映射(运行时数据,强类型)
|
||||
* "outputs": { ... }, // 输出结果(完整嵌套,强类型,用于问题调查)
|
||||
* // ... outputs 的字段也会平铺到顶层(供 BPMN 表达式访问)
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param <I> InputMapping 类型
|
||||
* @param <O> Outputs 类型
|
||||
* @author qqchen
|
||||
*/
|
||||
@Data
|
||||
public class NodeContext<I, O> {
|
||||
|
||||
/**
|
||||
* 节点配置(来自 BPMN XML 的 configs 字段)
|
||||
*/
|
||||
private Map<String, Object> configs;
|
||||
|
||||
/**
|
||||
* 输入映射(运行时动态数据,强类型)
|
||||
*/
|
||||
private I inputMapping;
|
||||
|
||||
/**
|
||||
* 输出结果(节点执行后生成,强类型)
|
||||
*/
|
||||
private O outputs;
|
||||
|
||||
/**
|
||||
* 转换为 Map(用于 Flowable 变量存储)
|
||||
*
|
||||
* 存储格式:
|
||||
* - configs、inputMapping、outputs 保留嵌套结构
|
||||
* - outputs 的字段同时平铺到顶层(供 BPMN 表达式访问)
|
||||
*/
|
||||
public Map<String, Object> toMap(ObjectMapper objectMapper) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
if (configs != null) {
|
||||
map.put("configs", configs);
|
||||
}
|
||||
|
||||
if (inputMapping != null) {
|
||||
Map<String, Object> inputMap = objectMapper.convertValue(inputMapping,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
map.put("inputMapping", inputMap);
|
||||
}
|
||||
|
||||
if (outputs != null) {
|
||||
Map<String, Object> outputsMap = objectMapper.convertValue(outputs,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
|
||||
// 保存完整的嵌套 outputs(用于问题调查)
|
||||
map.put("outputs", outputsMap);
|
||||
|
||||
// 同时将 outputs 字段平铺到顶层(供 BPMN 表达式访问)
|
||||
map.putAll(outputsMap);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Map 恢复 NodeContext
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <I, O> NodeContext<I, O> fromMap(
|
||||
Map<String, Object> map,
|
||||
Class<I> inputClass,
|
||||
Class<O> outputClass,
|
||||
ObjectMapper objectMapper
|
||||
) {
|
||||
NodeContext<I, O> context = new NodeContext<>();
|
||||
|
||||
if (map.containsKey("configs")) {
|
||||
context.setConfigs((Map<String, Object>) map.get("configs"));
|
||||
}
|
||||
|
||||
if (map.containsKey("inputMapping") && inputClass != null) {
|
||||
Object inputObj = map.get("inputMapping");
|
||||
I input = objectMapper.convertValue(inputObj, inputClass);
|
||||
context.setInputMapping(input);
|
||||
}
|
||||
|
||||
if (map.containsKey("outputs") && outputClass != null) {
|
||||
Object outputsObj = map.get("outputs");
|
||||
O output = objectMapper.convertValue(outputsObj, outputClass);
|
||||
context.setOutputs(output);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
package com.qqchen.deploy.backend.workflow.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.ApprovalInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.outputs.ApprovalOutputs;
|
||||
import com.qqchen.deploy.backend.workflow.dto.request.ApprovalTaskRequest;
|
||||
import com.qqchen.deploy.backend.workflow.dto.response.ApprovalTaskDTO;
|
||||
import com.qqchen.deploy.backend.workflow.model.NodeContext;
|
||||
import com.qqchen.deploy.backend.workflow.service.IApprovalTaskService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -31,6 +35,9 @@ public class ApprovalTaskServiceImpl implements IApprovalTaskService {
|
||||
|
||||
@Resource
|
||||
private TaskService taskService;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public List<ApprovalTaskDTO> getMyTasks(String username) {
|
||||
@ -80,7 +87,7 @@ public class ApprovalTaskServiceImpl implements IApprovalTaskService {
|
||||
@Override
|
||||
@Transactional
|
||||
public void completeTask(ApprovalTaskRequest request) {
|
||||
log.info("处理审批任务: taskId={}, approved={}", request.getTaskId(), request.getApproved());
|
||||
log.info("处理审批任务: taskId={}, result={}", request.getTaskId(), request.getResult());
|
||||
|
||||
// 检查任务是否存在
|
||||
Task task = taskService.createTaskQuery()
|
||||
@ -91,32 +98,48 @@ public class ApprovalTaskServiceImpl implements IApprovalTaskService {
|
||||
throw new BusinessException(ResponseCode.DATA_NOT_FOUND);
|
||||
}
|
||||
|
||||
// ✅ 获取节点ID(taskDefinitionKey 就是 BPMN 中的节点 ID)
|
||||
// ✅ 获取节点ID并更新 NodeContext
|
||||
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());
|
||||
// 读取现有 NodeContext
|
||||
Object nodeDataObj = taskService.getVariable(task.getId(), nodeId);
|
||||
NodeContext<ApprovalInputMapping, ApprovalOutputs> nodeContext;
|
||||
if (nodeDataObj instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> nodeDataMap = (Map<String, Object>) nodeDataObj;
|
||||
nodeContext = NodeContext.fromMap(nodeDataMap,
|
||||
ApprovalInputMapping.class, ApprovalOutputs.class, objectMapper);
|
||||
} else {
|
||||
nodeContext = new NodeContext<>();
|
||||
}
|
||||
|
||||
// ✅ 以节点ID为变量名存储审批结果(这样条件表达式才能正确解析)
|
||||
// 例如:${sid_7353fb85_562b_441d_add6_23915ab8e843.approvalResult == 'APPROVED'}
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
variables.put(nodeId, approvalOutputs);
|
||||
// 创建临时审批数据(稍后由 ApprovalExecutionListener 完善)
|
||||
ApprovalOutputs tempOutputs = new ApprovalOutputs();
|
||||
tempOutputs.setApprovalResult(request.getResult());
|
||||
tempOutputs.setApprover(task.getAssignee());
|
||||
tempOutputs.setApprovalTime(LocalDateTime.now());
|
||||
tempOutputs.setApprovalComment(request.getComment());
|
||||
|
||||
// 添加任务评论
|
||||
// 暂时设置为 outputs(ApprovalExecutionListener 会完善)
|
||||
nodeContext.setOutputs(tempOutputs);
|
||||
|
||||
// 保存回流程变量
|
||||
taskService.setVariable(task.getId(), nodeId, nodeContext.toMap(objectMapper));
|
||||
|
||||
// 5. 添加任务评论(供历史查询)
|
||||
if (request.getComment() != null) {
|
||||
taskService.addComment(request.getTaskId(), task.getProcessInstanceId(), request.getComment());
|
||||
}
|
||||
|
||||
// 完成任务
|
||||
taskService.complete(request.getTaskId(), variables);
|
||||
// 6. 完成任务(触发 ApprovalExecutionListener)
|
||||
// ApprovalExecutionListener 会:
|
||||
// - 读取上面设置的变量
|
||||
// - 自动装配其他上下文信息(approvalDuration、allApprovers 等)
|
||||
// - 构建完整的 ApprovalOutputs 对象
|
||||
// - 以 nodeId 为 key 存储到流程变量
|
||||
taskService.complete(request.getTaskId());
|
||||
|
||||
log.info("审批任务完成: taskId={}, nodeId={}, result={}",
|
||||
request.getTaskId(), nodeId, request.getApproved() ? "APPROVED" : "REJECTED");
|
||||
log.info("审批任务完成: taskId={}, result={}", request.getTaskId(), request.getResult());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -67,13 +67,23 @@ public class WorkflowNodeInstanceServiceImpl extends BaseServiceImpl<WorkflowNod
|
||||
.orElseThrow(() -> new RuntimeException("Node instance not found for processInstanceId: " + nodeInstance.getProcessInstanceId()));
|
||||
WorkflowNodeInstance workflowNodeInstance = workflowNodeInstanceRepository.findByProcessInstanceIdAndNodeId(workflowInstance.getProcessInstanceId(), event.getNodeId());
|
||||
if (workflowNodeInstance == null) {
|
||||
// 新建节点实例
|
||||
nodeInstance.setWorkflowInstanceId(workflowInstance.getId());
|
||||
nodeInstance.setWorkflowDefinitionId(workflowInstance.getWorkflowDefinitionId());
|
||||
// ✅ 保存节点执行变量
|
||||
nodeInstance.setVariables(event.getVariables());
|
||||
// ✅ 保存错误信息(如果有)
|
||||
nodeInstance.setErrorMessage(event.getErrorMessage());
|
||||
super.repository.save(nodeInstance);
|
||||
return;
|
||||
}
|
||||
// 更新节点实例
|
||||
workflowNodeInstance.setEndTime(event.getEndTime());
|
||||
workflowNodeInstance.setStatus(event.getStatus());
|
||||
// ✅ 更新节点执行变量
|
||||
workflowNodeInstance.setVariables(event.getVariables());
|
||||
// ✅ 更新错误信息(如果有)
|
||||
workflowNodeInstance.setErrorMessage(event.getErrorMessage());
|
||||
super.repository.save(workflowNodeInstance);
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +134,7 @@ public class BpmnConverter {
|
||||
FlowElement element;
|
||||
|
||||
// ✅ 步骤2.3:检查是否为审批节点,特殊处理
|
||||
if ("APPROVAL".equals(node.getNodeCode()) || "APPROVAL_NODE".equals(node.getNodeCode())) {
|
||||
if ("APPROVAL".equals(node.getNodeCode())) {
|
||||
// 创建 UserTask 而不是 ServiceTask
|
||||
element = createUserTask(node, validId);
|
||||
} else {
|
||||
@ -273,22 +273,26 @@ public class BpmnConverter {
|
||||
*/
|
||||
private void configureUserTask(UserTask userTask, WorkflowDefinitionGraphNode node, Map<String, List<ExtensionElement>> extensionElements, String validId) {
|
||||
try {
|
||||
// ✅ 1. 创建 TaskListener(在任务创建时调用 ApprovalNodeDelegate)
|
||||
// ✅ 1. 创建 TaskListener(在任务创建时调用 ApprovalTaskListener)
|
||||
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}"));
|
||||
taskListener.addAttribute(createAttribute("delegateExpression", "${approvalTaskListener}"));
|
||||
|
||||
// ✅ 2. 将 field 字段作为 TaskListener 的子元素添加(而不是 UserTask 的子元素)
|
||||
// 这样 ApprovalNodeDelegate 才能通过 @field 注解注入这些字段
|
||||
// 这样 ApprovalTaskListener 才能通过 @field 注解注入这些字段
|
||||
addFieldsToTaskListener(taskListener, node, validId);
|
||||
|
||||
// ✅ 3. 将 TaskListener 添加到扩展元素
|
||||
extensionElements.computeIfAbsent("taskListener", k -> new ArrayList<>()).add(taskListener);
|
||||
|
||||
// ✅ 4. 设置扩展元素
|
||||
// ✅ 4. 添加 ApprovalExecutionListener(在任务结束时构建 ApprovalOutputs)
|
||||
// 确保它在 globalNodeExecutionListener 之前执行,这样状态变量才能正确设置
|
||||
addApprovalExecutionListener(extensionElements);
|
||||
|
||||
// ✅ 5. 设置扩展元素
|
||||
userTask.setExtensionElements(extensionElements);
|
||||
|
||||
log.debug("配置审批节点完成: {}", node.getNodeName());
|
||||
@ -327,6 +331,43 @@ public class BpmnConverter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 ApprovalExecutionListener(审批任务结束监听器)
|
||||
* 确保它在 globalNodeExecutionListener 之前执行
|
||||
*
|
||||
* @param extensionElements 扩展元素
|
||||
*/
|
||||
private void addApprovalExecutionListener(Map<String, List<ExtensionElement>> extensionElements) {
|
||||
List<ExtensionElement> executionListeners =
|
||||
extensionElements.computeIfAbsent("executionListener", k -> new ArrayList<>());
|
||||
|
||||
// 找到 end 事件的 globalNodeExecutionListener 的位置
|
||||
int endListenerIndex = -1;
|
||||
for (int i = 0; i < executionListeners.size(); i++) {
|
||||
ExtensionElement listener = executionListeners.get(i);
|
||||
String event = listener.getAttributeValue(null, "event");
|
||||
String delegateExpr = listener.getAttributeValue(null, "delegateExpression");
|
||||
|
||||
if ("end".equals(event) && "${globalNodeExecutionListener}".equals(delegateExpr)) {
|
||||
endListenerIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 在 globalNodeExecutionListener 之前插入 approvalExecutionListener
|
||||
// 这样审批结果会先被构建,状态变量也会被设置
|
||||
ExtensionElement approvalListener = createExecutionListener("end", "${approvalExecutionListener}");
|
||||
|
||||
if (endListenerIndex >= 0) {
|
||||
executionListeners.add(endListenerIndex, approvalListener);
|
||||
} else {
|
||||
// 如果没有找到 globalNodeExecutionListener,直接添加到列表末尾
|
||||
executionListeners.add(approvalListener);
|
||||
}
|
||||
|
||||
log.debug("Added ApprovalExecutionListener before GlobalNodeExecutionListener");
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置服务任务节点
|
||||
*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user