打印了JENKINS节点日志
This commit is contained in:
parent
c5b4b6046a
commit
c3ee349760
@ -16,6 +16,11 @@ public enum DeployRecordStatusEnums {
|
||||
*/
|
||||
CREATED("CREATED", "已创建"),
|
||||
|
||||
/**
|
||||
* 待审批
|
||||
*/
|
||||
PENDING_APPROVAL("PENDING_APPROVAL", "待审批"),
|
||||
|
||||
/**
|
||||
* 运行中
|
||||
*/
|
||||
|
||||
@ -47,9 +47,7 @@ public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, Deplo
|
||||
String deployRemark
|
||||
) {
|
||||
// 检查是否已存在
|
||||
DeployRecord existing = deployRecordRepository
|
||||
.findByWorkflowInstanceIdAndDeletedFalse(workflowInstanceId)
|
||||
.orElse(null);
|
||||
DeployRecord existing = deployRecordRepository.findByWorkflowInstanceIdAndDeletedFalse(workflowInstanceId).orElse(null);
|
||||
|
||||
if (existing != null) {
|
||||
log.warn("部署记录已存在: workflowInstanceId={}", workflowInstanceId);
|
||||
@ -70,8 +68,7 @@ public class DeployRecordServiceImpl extends BaseServiceImpl<DeployRecord, Deplo
|
||||
record.setStartTime(LocalDateTime.now());
|
||||
|
||||
DeployRecord saved = deployRecordRepository.save(record);
|
||||
log.info("创建部署记录成功: id={}, workflowInstanceId={}, applicationId={}, environmentId={}",
|
||||
saved.getId(), workflowInstanceId, applicationId, environmentId);
|
||||
log.info("创建部署记录成功: id={}, workflowInstanceId={}, applicationId={}, environmentId={}", saved.getId(), workflowInstanceId, applicationId, environmentId);
|
||||
|
||||
return deployRecordConverter.toDto(saved);
|
||||
}
|
||||
|
||||
@ -503,6 +503,7 @@ public class DeployServiceImpl implements IDeployService {
|
||||
// 转换为 Map(Flowable 只支持基本类型)
|
||||
variables.put("jenkins", objectMapper.convertValue(jenkinsInput, Map.class));
|
||||
variables.put("approval", Map.of("required", true, "userIds", "admin"));
|
||||
variables.put("notification", Map.of("channelId", 1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,348 @@
|
||||
package com.qqchen.deploy.backend.framework.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.flowable.task.service.delegate.DelegateTask;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 嵌套 Map 工具类
|
||||
* 使用 Jackson JsonPointer (RFC 6901) 标准解析嵌套路径
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Slf4j
|
||||
public class NestedMapUtils {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 从嵌套 Map 中获取值
|
||||
* 支持多层嵌套路径访问,例如:outputs.status、a.b.c.d
|
||||
*
|
||||
* <p>示例:
|
||||
* <pre>
|
||||
* Map<String, Object> map = Map.of("outputs", Map.of("status", "SUCCESS"));
|
||||
* Object value = NestedMapUtils.getValue(map, "outputs.status");
|
||||
* // 返回: "SUCCESS"
|
||||
* </pre>
|
||||
*
|
||||
* @param map 源 Map
|
||||
* @param fieldPath 字段路径(点号分隔,如:outputs.status)
|
||||
* @return 字段值,如果路径不存在则返回 null
|
||||
*/
|
||||
public static Object getValue(Map<String, Object> map, String fieldPath) {
|
||||
if (fieldPath == null || fieldPath.isEmpty() || map == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 将 Map 转换为 JsonNode
|
||||
JsonNode rootNode = OBJECT_MAPPER.valueToTree(map);
|
||||
|
||||
// 2. 将点号路径转换为 JsonPointer 路径格式
|
||||
// 例如:outputs.status -> /outputs/status
|
||||
String jsonPointerPath = "/" + fieldPath.replace(".", "/");
|
||||
|
||||
// 3. 使用 JsonPointer 访问嵌套路径
|
||||
JsonNode resultNode = rootNode.at(jsonPointerPath);
|
||||
|
||||
// 4. 检查节点是否存在
|
||||
if (resultNode.isMissingNode()) {
|
||||
log.debug("嵌套路径不存在: {}", fieldPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 5. 将 JsonNode 转换回 Java 对象
|
||||
return OBJECT_MAPPER.convertValue(resultNode, Object.class);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("嵌套路径解析异常: {} - {}", fieldPath, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从嵌套 Map 中获取字符串值
|
||||
*
|
||||
* @param map 源 Map
|
||||
* @param fieldPath 字段路径(点号分隔)
|
||||
* @return 字符串值,如果路径不存在或值为 null 则返回 null
|
||||
*/
|
||||
public static String getString(Map<String, Object> map, String fieldPath) {
|
||||
Object value = getValue(map, fieldPath);
|
||||
return value != null ? value.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从嵌套 Map 中获取字符串值(带默认值)
|
||||
*
|
||||
* @param map 源 Map
|
||||
* @param fieldPath 字段路径(点号分隔)
|
||||
* @param defaultValue 默认值
|
||||
* @return 字符串值,如果路径不存在则返回默认值
|
||||
*/
|
||||
public static String getString(Map<String, Object> map, String fieldPath, String defaultValue) {
|
||||
String value = getString(map, fieldPath);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从嵌套 Map 中获取 Integer 值
|
||||
*
|
||||
* @param map 源 Map
|
||||
* @param fieldPath 字段路径(点号分隔)
|
||||
* @return Integer 值,如果路径不存在或转换失败则返回 null
|
||||
*/
|
||||
public static Integer getInteger(Map<String, Object> map, String fieldPath) {
|
||||
Object value = getValue(map, fieldPath);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
return (Integer) value;
|
||||
}
|
||||
try {
|
||||
return Integer.valueOf(value.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("无法将值转换为 Integer: {} = {}", fieldPath, value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从嵌套 Map 中获取 Long 值
|
||||
*
|
||||
* @param map 源 Map
|
||||
* @param fieldPath 字段路径(点号分隔)
|
||||
* @return Long 值,如果路径不存在或转换失败则返回 null
|
||||
*/
|
||||
public static Long getLong(Map<String, Object> map, String fieldPath) {
|
||||
Object value = getValue(map, fieldPath);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
return (Long) value;
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
return ((Integer) value).longValue();
|
||||
}
|
||||
try {
|
||||
return Long.valueOf(value.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("无法将值转换为 Long: {} = {}", fieldPath, value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从嵌套 Map 中获取 Boolean 值
|
||||
*
|
||||
* @param map 源 Map
|
||||
* @param fieldPath 字段路径(点号分隔)
|
||||
* @return Boolean 值,如果路径不存在则返回 null
|
||||
*/
|
||||
public static Boolean getBoolean(Map<String, Object> map, String fieldPath) {
|
||||
Object value = getValue(map, fieldPath);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
}
|
||||
return Boolean.valueOf(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从嵌套 Map 中获取指定类型的值
|
||||
*
|
||||
* @param map 源 Map
|
||||
* @param fieldPath 字段路径(点号分隔)
|
||||
* @param targetClass 目标类型
|
||||
* @param <T> 返回值类型
|
||||
* @return 指定类型的值,如果路径不存在或转换失败则返回 null
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getValue(Map<String, Object> map, String fieldPath, Class<T> targetClass) {
|
||||
Object value = getValue(map, fieldPath);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (targetClass.isInstance(value)) {
|
||||
return (T) value;
|
||||
}
|
||||
return OBJECT_MAPPER.convertValue(value, targetClass);
|
||||
} catch (Exception e) {
|
||||
log.warn("无法将值转换为 {}: {} = {}", targetClass.getSimpleName(), fieldPath, value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路径是否存在
|
||||
*
|
||||
* @param map 源 Map
|
||||
* @param fieldPath 字段路径(点号分隔)
|
||||
* @return true 如果路径存在,否则返回 false
|
||||
*/
|
||||
public static boolean hasPath(Map<String, Object> map, String fieldPath) {
|
||||
if (fieldPath == null || fieldPath.isEmpty() || map == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
JsonNode rootNode = OBJECT_MAPPER.valueToTree(map);
|
||||
String jsonPointerPath = "/" + fieldPath.replace(".", "/");
|
||||
JsonNode resultNode = rootNode.at(jsonPointerPath);
|
||||
return !resultNode.isMissingNode();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Flowable 集成方法 ====================
|
||||
|
||||
/**
|
||||
* 从 Flowable 执行上下文中解析变量表达式
|
||||
* 支持嵌套路径,例如:approval.userIds、sid_xxx.outputs.status
|
||||
*
|
||||
* <p>示例:
|
||||
* <pre>
|
||||
* // 流程变量中有:approval = {required: true, userIds: "admin"}
|
||||
* Object value = NestedMapUtils.getValueFromExecution(execution, "approval.userIds");
|
||||
* // 返回: "admin"
|
||||
* </pre>
|
||||
*
|
||||
* @param execution Flowable 执行上下文
|
||||
* @param variablePath 变量路径(点号分隔,如:approval.userIds)
|
||||
* @return 变量值,如果路径不存在则返回 null
|
||||
*/
|
||||
public static Object getValueFromExecution(DelegateExecution execution, String variablePath) {
|
||||
if (execution == null || variablePath == null || variablePath.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. 切分路径,获取顶层变量名
|
||||
String[] parts = variablePath.split("\\.", 2);
|
||||
String topLevelVar = parts[0];
|
||||
|
||||
// 2. 从 execution 中获取顶层变量
|
||||
Object value = execution.getVariable(topLevelVar);
|
||||
|
||||
// 3. 如果没有嵌套路径,直接返回
|
||||
if (parts.length == 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 4. 如果有嵌套路径,继续解析
|
||||
if (value instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) value;
|
||||
return getValue(map, parts[1]);
|
||||
} else {
|
||||
log.debug("变量 {} 不是 Map 类型,无法解析嵌套路径: {}", topLevelVar, variablePath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Flowable 任务上下文中解析变量表达式
|
||||
* 支持嵌套路径,例如:approval.userIds
|
||||
*
|
||||
* <p>示例:
|
||||
* <pre>
|
||||
* // 流程变量中有:approval = {required: true, userIds: "admin"}
|
||||
* Object value = NestedMapUtils.getValueFromTask(task, "approval.userIds");
|
||||
* // 返回: "admin"
|
||||
* </pre>
|
||||
*
|
||||
* @param task Flowable 任务上下文
|
||||
* @param variablePath 变量路径(点号分隔,如:approval.userIds)
|
||||
* @return 变量值,如果路径不存在则返回 null
|
||||
*/
|
||||
public static Object getValueFromTask(DelegateTask task, String variablePath) {
|
||||
if (task == null || variablePath == null || variablePath.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. 切分路径,获取顶层变量名
|
||||
String[] parts = variablePath.split("\\.", 2);
|
||||
String topLevelVar = parts[0];
|
||||
|
||||
// 2. 从 task 中获取顶层变量
|
||||
Object value = task.getVariable(topLevelVar);
|
||||
|
||||
// 3. 如果没有嵌套路径,直接返回
|
||||
if (parts.length == 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 4. 如果有嵌套路径,继续解析
|
||||
if (value instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) value;
|
||||
return getValue(map, parts[1]);
|
||||
} else {
|
||||
log.debug("变量 {} 不是 Map 类型,无法解析嵌套路径: {}", topLevelVar, variablePath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Flowable 上下文中解析变量表达式(支持 ${...} 格式)
|
||||
* 自动去除表达式包装,支持嵌套路径
|
||||
*
|
||||
* <p>示例:
|
||||
* <pre>
|
||||
* Object value1 = NestedMapUtils.resolveExpression(task, "${approval.userIds}");
|
||||
* Object value2 = NestedMapUtils.resolveExpression(task, "approval.userIds");
|
||||
* // 两种格式都能正确解析
|
||||
* </pre>
|
||||
*
|
||||
* @param task Flowable 任务上下文
|
||||
* @param expression 表达式(可以带 ${} 也可以不带)
|
||||
* @return 变量值,如果路径不存在则返回 null
|
||||
*/
|
||||
public static Object resolveExpression(DelegateTask task, String expression) {
|
||||
if (expression == null || expression.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 去除 ${} 包装
|
||||
String variablePath = expression;
|
||||
if (expression.startsWith("${") && expression.endsWith("}")) {
|
||||
variablePath = expression.substring(2, expression.length() - 1);
|
||||
}
|
||||
|
||||
return getValueFromTask(task, variablePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Flowable 执行上下文中解析变量表达式(支持 ${...} 格式)
|
||||
*
|
||||
* @param execution Flowable 执行上下文
|
||||
* @param expression 表达式(可以带 ${} 也可以不带)
|
||||
* @return 变量值,如果路径不存在则返回 null
|
||||
*/
|
||||
public static Object resolveExpression(DelegateExecution execution, String expression) {
|
||||
if (expression == null || expression.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 去除 ${} 包装
|
||||
String variablePath = expression;
|
||||
if (expression.startsWith("${") && expression.endsWith("}")) {
|
||||
variablePath = expression.substring(2, expression.length() - 1);
|
||||
}
|
||||
|
||||
return getValueFromExecution(execution, variablePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,8 +112,9 @@ public class ApprovalTaskListener extends BaseTaskListener<ApprovalInputMapping,
|
||||
String approverVariableExpression = input.getApproverVariable();
|
||||
log.info("解析审批人变量表达式: {}", approverVariableExpression);
|
||||
|
||||
// 解析表达式(如 ${approval.userIds})
|
||||
Object approverValue = resolveExpression(task, approverVariableExpression);
|
||||
// 使用工具类直接解析表达式(支持 ${approval.userIds} 或 approval.userIds)
|
||||
Object approverValue = com.qqchen.deploy.backend.framework.utils.NestedMapUtils
|
||||
.resolveExpression(task, approverVariableExpression);
|
||||
|
||||
if (approverValue != null) {
|
||||
log.info("解析得到审批人: {}", approverValue);
|
||||
@ -148,48 +149,6 @@ public class ApprovalTaskListener extends BaseTaskListener<ApprovalInputMapping,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析流程变量表达式
|
||||
* 支持简单表达式(如 ${approval.userIds})和直接变量名(如 approval.userIds)
|
||||
*/
|
||||
private Object resolveExpression(DelegateTask task, String expression) {
|
||||
try {
|
||||
// 如果是表达式格式(${...}),手动解析
|
||||
if (expression.startsWith("${") && expression.endsWith("}")) {
|
||||
// 去掉 ${ 和 }
|
||||
String variablePath = expression.substring(2, expression.length() - 1);
|
||||
return resolveVariablePath(task, variablePath);
|
||||
}
|
||||
|
||||
// 如果不是表达式格式,直接解析路径(如 approval.userIds)
|
||||
return resolveVariablePath(task, expression);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("解析表达式失败: {}", expression, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动解析变量路径(如 approval.userIds)
|
||||
*/
|
||||
private Object resolveVariablePath(DelegateTask task, String path) {
|
||||
String[] parts = path.split("\\.");
|
||||
Object value = task.getVariable(parts[0]);
|
||||
|
||||
// 如果是嵌套路径,继续解析
|
||||
for (int i = 1; i < parts.length && value != null; i++) {
|
||||
if (value instanceof Map) {
|
||||
value = ((Map<?, ?>) value).get(parts[i]);
|
||||
} else {
|
||||
log.warn("变量 {} 不是 Map 类型,无法继续解析路径", parts[i - 1]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将审批相关信息保存为任务变量
|
||||
* 这样前端查询任务时可以获取到这些信息
|
||||
|
||||
@ -16,10 +16,6 @@ import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
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;
|
||||
|
||||
|
||||
/**
|
||||
* 任务委派者基类
|
||||
@ -57,26 +53,24 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
|
||||
String currentNodeId = null;
|
||||
NodeContext<I, O> nodeContext = new NodeContext<>();
|
||||
Map<String, Object> configsMap = null;
|
||||
I inputMappingObj = null;
|
||||
|
||||
try {
|
||||
// 1. 获取节点ID
|
||||
currentNodeId = getFieldValue(nodeId, execution);
|
||||
log.info("Executing node: {}", currentNodeId);
|
||||
|
||||
// 2. 清除前一个节点的状态
|
||||
clearPreviousNodeStatus(execution);
|
||||
// 2. 解析配置(通用Map)
|
||||
configsMap = parseJsonField(configs, execution);
|
||||
|
||||
// 3. 解析并转换InputMapping(强类型)
|
||||
inputMappingObj = parseAndConvertInputMapping(execution);
|
||||
|
||||
// 3. 解析配置(通用Map)
|
||||
Map<String, Object> configsMap = parseJsonField(configs, execution);
|
||||
|
||||
// 4. 解析并转换InputMapping(强类型)
|
||||
I inputMappingObj = parseAndConvertInputMapping(execution);
|
||||
|
||||
// 5. 执行具体的业务逻辑,返回强类型输出
|
||||
// 4. 执行具体的业务逻辑,返回强类型输出
|
||||
O outputsObj = executeInternal(execution, configsMap, inputMappingObj);
|
||||
|
||||
// ✅ 6. 使用 NodeContext 保存节点数据
|
||||
|
||||
// 5. 使用 NodeContext 保存节点数据
|
||||
nodeContext.setConfigs(configsMap);
|
||||
nodeContext.setInputMapping(inputMappingObj);
|
||||
nodeContext.setOutputs(outputsObj);
|
||||
@ -84,16 +78,17 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
execution.setVariable(currentNodeId, nodeContext.toMap(objectMapper));
|
||||
log.info("Stored NodeContext for: {}", currentNodeId);
|
||||
|
||||
// 7. 设置节点执行状态为成功
|
||||
setExecutionStatus(execution, WORKFLOW_NODE_EXECUTION_STATE_SUCCESS);
|
||||
|
||||
} catch (Exception e) {
|
||||
// 即使失败,也保存完整的 NodeContext(包含 configs 和 inputMapping)
|
||||
BaseNodeOutputs failureNodeOutputs = new BaseNodeOutputs();
|
||||
failureNodeOutputs.setStatus(NodeExecutionStatusEnum.FAILURE);
|
||||
failureNodeOutputs.setMessage(e.getMessage());
|
||||
|
||||
nodeContext.setConfigs(configsMap); // 保存已解析的配置
|
||||
nodeContext.setInputMapping(inputMappingObj); // 保存已解析的输入
|
||||
nodeContext.setOutputs((O) failureNodeOutputs);
|
||||
|
||||
execution.setVariable(currentNodeId, nodeContext.toMap(objectMapper));
|
||||
// 设置失败状态
|
||||
setExecutionStatus(execution, WORKFLOW_NODE_EXECUTION_STATE_FAILURE);
|
||||
log.error("Task execution failed", e);
|
||||
}
|
||||
}
|
||||
@ -193,8 +188,9 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
String strValue = (String) value;
|
||||
if (strValue.contains("${")) {
|
||||
try {
|
||||
// 使用简单的变量替换:${sid_xxx.fieldName} -> 从execution中获取
|
||||
String resolvedValue = resolveVariableExpression(strValue, execution);
|
||||
// 使用工具类直接解析表达式
|
||||
Object resolvedValue = com.qqchen.deploy.backend.framework.utils.NestedMapUtils
|
||||
.resolveExpression(execution, strValue);
|
||||
log.debug("解析表达式: {} = {} -> {}", entry.getKey(), strValue, resolvedValue);
|
||||
resolvedMap.put(entry.getKey(), resolvedValue);
|
||||
} catch (Exception e) {
|
||||
@ -213,64 +209,6 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
return resolvedMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析变量表达式
|
||||
* 例如:${sid_xxx.buildNumber} -> 从 execution.getVariable("sid_xxx") 中获取 buildNumber 字段
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从execution中解析变量
|
||||
* 支持:sid_xxx.buildNumber 格式
|
||||
*/
|
||||
private Object resolveVariable(String varExpression, DelegateExecution execution) {
|
||||
String[] parts = varExpression.split("\\.", 2);
|
||||
|
||||
log.debug("解析变量表达式: {}, parts: {}", varExpression, java.util.Arrays.toString(parts));
|
||||
|
||||
if (parts.length == 1) {
|
||||
// 直接变量:${xxx}
|
||||
Object result = execution.getVariable(parts[0]);
|
||||
log.debug("直接变量 {} = {}", parts[0], result);
|
||||
return result;
|
||||
} else {
|
||||
// 对象属性:${sid_xxx.buildNumber} 或 ${jenkins.systemId}
|
||||
String varName = parts[0];
|
||||
String fieldName = parts[1];
|
||||
|
||||
Object varValue = execution.getVariable(varName);
|
||||
log.debug("获取变量 {} = {}, 类型: {}", varName, varValue, varValue != null ? varValue.getClass().getName() : "null");
|
||||
|
||||
if (varValue instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) varValue;
|
||||
Object fieldValue = map.get(fieldName);
|
||||
log.debug("从 Map 中获取字段 {} = {}", fieldName, fieldValue);
|
||||
return fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getFieldValue(Expression expression, DelegateExecution execution) {
|
||||
if (expression == null) return null;
|
||||
Object value = expression.getValue(execution);
|
||||
@ -291,13 +229,4 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
protected void setExecutionStatus(DelegateExecution execution, String status) {
|
||||
execution.setVariable(WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME, status);
|
||||
}
|
||||
|
||||
private void clearPreviousNodeStatus(DelegateExecution execution) {
|
||||
execution.removeVariable(WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME);
|
||||
log.debug("Cleared previous node status for node: {}", execution.getCurrentActivityId());
|
||||
}
|
||||
|
||||
}
|
||||
@ -19,5 +19,8 @@ public class BaseNodeOutputs {
|
||||
* 用于流程条件判断,所有节点统一使用此字段
|
||||
*/
|
||||
private NodeExecutionStatusEnum status;
|
||||
|
||||
private String message;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -22,9 +22,6 @@ 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;
|
||||
|
||||
/**
|
||||
* 审批任务结束监听器
|
||||
@ -79,13 +76,8 @@ public class ApprovalExecutionListener implements ExecutionListener {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,8 +19,6 @@ import jakarta.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
import static com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants.WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME;
|
||||
|
||||
@Slf4j
|
||||
@Component("globalNodeExecutionListener")
|
||||
public class GlobalNodeExecutionListener implements ExecutionListener {
|
||||
@ -50,24 +48,19 @@ public class GlobalNodeExecutionListener implements ExecutionListener {
|
||||
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)
|
||||
.orElse(null);
|
||||
switch (eventName) {
|
||||
case ExecutionListener.EVENTNAME_START:
|
||||
status = WorkflowNodeInstanceStatusEnums.RUNNING;
|
||||
startTime = now;
|
||||
break;
|
||||
case ExecutionListener.EVENTNAME_END:
|
||||
if (StringUtils.isEmpty(nodeExecutionStatus)) {
|
||||
status = WorkflowNodeInstanceStatusEnums.COMPLETED;
|
||||
} else {
|
||||
if (WorkFlowConstants.WORKFLOW_NODE_EXECUTION_STATE_SUCCESS.equals(nodeExecutionStatus)) {
|
||||
status = WorkflowNodeInstanceStatusEnums.COMPLETED;
|
||||
} else {
|
||||
// 从节点的 outputs.status 读取执行状态
|
||||
com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum nodeExecutionStatus = getNodeOutputStatus(execution, nodeId);
|
||||
|
||||
if (nodeExecutionStatus == com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum.FAILURE) {
|
||||
status = WorkflowNodeInstanceStatusEnums.FAILED;
|
||||
}
|
||||
} else {
|
||||
status = WorkflowNodeInstanceStatusEnums.COMPLETED;
|
||||
}
|
||||
|
||||
// ✅ 收集节点执行变量
|
||||
@ -98,6 +91,37 @@ public class GlobalNodeExecutionListener implements ExecutionListener {
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从节点的 outputs.status 获取执行状态
|
||||
*
|
||||
* @param execution Flowable执行上下文
|
||||
* @param nodeId 节点ID
|
||||
* @return 节点执行状态枚举,如果没有则返回 null
|
||||
*/
|
||||
private com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum getNodeOutputStatus(DelegateExecution execution, String nodeId) {
|
||||
try {
|
||||
// 使用工具类从节点变量中获取 outputs.status
|
||||
Object statusObj = com.qqchen.deploy.backend.framework.utils.NestedMapUtils
|
||||
.getValueFromExecution(execution, nodeId + ".outputs.status");
|
||||
|
||||
if (statusObj != null) {
|
||||
// 如果已经是枚举类型,直接返回
|
||||
if (statusObj instanceof com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum) {
|
||||
return (com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum) statusObj;
|
||||
}
|
||||
// 如果是字符串,转换为枚举
|
||||
try {
|
||||
return com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum.valueOf(statusObj.toString());
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("无法将 {} 转换为 NodeExecutionStatusEnum", statusObj);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("无法获取节点 {} 的 outputs.status: {}", nodeId, e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集节点执行变量
|
||||
*
|
||||
|
||||
@ -11,16 +11,21 @@ import java.util.Map;
|
||||
* 节点执行上下文(强类型)
|
||||
* 统一封装节点的配置、输入映射和输出结果
|
||||
*
|
||||
* <p>数据结构:
|
||||
* <p>数据结构(规范):
|
||||
* <pre>
|
||||
* {
|
||||
* "configs": { ... }, // 节点配置(来自 BPMN)
|
||||
* "inputMapping": { ... }, // 输入映射(运行时数据,强类型)
|
||||
* "outputs": { ... }, // 输出结果(完整嵌套,强类型,用于问题调查)
|
||||
* // ... outputs 的字段也会平铺到顶层(供 BPMN 表达式访问)
|
||||
* "outputs": { ... } // 输出结果(强类型,所有输出数据只存储在此)
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>BPMN 表达式访问规范:
|
||||
* <pre>
|
||||
* ${nodeId.outputs.字段名}
|
||||
* 示例:${sid_xxx.outputs.approvalResult == 'APPROVED'}
|
||||
* </pre>
|
||||
*
|
||||
* @param <I> InputMapping 类型
|
||||
* @param <O> Outputs 类型
|
||||
* @author qqchen
|
||||
@ -46,9 +51,14 @@ public class NodeContext<I, O> {
|
||||
/**
|
||||
* 转换为 Map(用于 Flowable 变量存储)
|
||||
*
|
||||
* 存储格式:
|
||||
* - configs、inputMapping、outputs 保留嵌套结构
|
||||
* - outputs 的字段同时平铺到顶层(供 BPMN 表达式访问)
|
||||
* 存储格式(严格遵守):
|
||||
* {
|
||||
* "configs": { ... }, // 节点配置
|
||||
* "inputMapping": { ... }, // 节点输入
|
||||
* "outputs": { ... } // 节点输出(所有输出数据只存储在此)
|
||||
* }
|
||||
*
|
||||
* BPMN 表达式访问:${nodeId.outputs.字段名}
|
||||
*/
|
||||
public Map<String, Object> toMap(ObjectMapper objectMapper) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
@ -67,11 +77,8 @@ public class NodeContext<I, O> {
|
||||
Map<String, Object> outputsMap = objectMapper.convertValue(outputs,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
|
||||
// 保存完整的嵌套 outputs(用于问题调查)
|
||||
// 只保存嵌套的 outputs(保持数据结构规范)
|
||||
map.put("outputs", outputsMap);
|
||||
|
||||
// 同时将 outputs 字段平铺到顶层(供 BPMN 表达式访问)
|
||||
map.putAll(outputsMap);
|
||||
}
|
||||
|
||||
return map;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user