添加通知管理功能
This commit is contained in:
parent
483a3b96e7
commit
5851c98537
@ -1,8 +1,8 @@
|
||||
package com.qqchen.deploy.backend.workflow.delegate;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables.ApprovalNodeLocalVariables;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.node.panelVariables.ApprovalNodePanelVariables;
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.ApprovalInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.outputs.ApprovalOutputs;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.TaskService;
|
||||
@ -13,32 +13,29 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 审批节点的委派者实现
|
||||
* 审批节点委派
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ApprovalNodeDelegate extends BaseNodeDelegate<ApprovalNodePanelVariables, ApprovalNodeLocalVariables> {
|
||||
@Component("approvalDelegate")
|
||||
public class ApprovalNodeDelegate extends BaseNodeDelegate<ApprovalInputMapping, ApprovalOutputs> {
|
||||
|
||||
@Resource
|
||||
private TaskService taskService;
|
||||
|
||||
@Override
|
||||
protected Class<ApprovalNodePanelVariables> getPanelVariablesClass() {
|
||||
return ApprovalNodePanelVariables.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<ApprovalNodeLocalVariables> getLocalVariablesClass() {
|
||||
return ApprovalNodeLocalVariables.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeInternal(DelegateExecution execution, ApprovalNodePanelVariables panelVariables, ApprovalNodeLocalVariables localVariables) {
|
||||
if (panelVariables == null) {
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Panel variables are required but not provided");
|
||||
}
|
||||
protected ApprovalOutputs executeInternal(
|
||||
DelegateExecution execution,
|
||||
Map<String, Object> configs,
|
||||
ApprovalInputMapping input
|
||||
) {
|
||||
log.info("Creating approval task - assignee: {}, candidateGroup: {}",
|
||||
input.getAssignee(), input.getCandidateGroup());
|
||||
|
||||
try {
|
||||
// 创建用户任务
|
||||
@ -51,35 +48,44 @@ public class ApprovalNodeDelegate extends BaseNodeDelegate<ApprovalNodePanelVari
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Failed to create approval task");
|
||||
}
|
||||
|
||||
// 设置任务属性
|
||||
task.setName(panelVariables.getName());
|
||||
task.setDescription(panelVariables.getDescription());
|
||||
// 从configs中获取任务名称和描述
|
||||
String taskName = (String) configs.get("nodeName");
|
||||
String taskDesc = (String) configs.get("description");
|
||||
|
||||
task.setName(taskName != null ? taskName : "审批任务");
|
||||
task.setDescription(taskDesc);
|
||||
|
||||
// 设置审批人
|
||||
if (panelVariables.getAssignee() != null) {
|
||||
task.setAssignee(panelVariables.getAssignee());
|
||||
if (input.getAssignee() != null) {
|
||||
task.setAssignee(input.getAssignee());
|
||||
}
|
||||
|
||||
// 设置候选组
|
||||
if (panelVariables.getCandidateGroup() != null) {
|
||||
taskService.addCandidateGroup(task.getId(), panelVariables.getCandidateGroup());
|
||||
if (input.getCandidateGroup() != null) {
|
||||
taskService.addCandidateGroup(task.getId(), input.getCandidateGroup());
|
||||
}
|
||||
|
||||
// 设置超时时间
|
||||
if (panelVariables.getTimeoutHours() != null) {
|
||||
Date dueDate = java.sql.Timestamp.valueOf(LocalDateTime.now().plusHours(panelVariables.getTimeoutHours()));
|
||||
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: {}, assignee: {}, candidateGroup: {}",
|
||||
task.getId(), panelVariables.getAssignee(), panelVariables.getCandidateGroup());
|
||||
log.info("Created approval task: {}", task.getId());
|
||||
|
||||
// 返回输出(审批任务是异步的,这里先返回pending状态)
|
||||
ApprovalOutputs outputs = new ApprovalOutputs();
|
||||
outputs.setResult("PENDING");
|
||||
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());
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR,
|
||||
"Failed to create approval task: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qqchen.deploy.backend.workflow.delegate;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
@ -8,117 +8,264 @@ import org.flowable.engine.delegate.JavaDelegate;
|
||||
import org.flowable.common.engine.api.delegate.Expression;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 任务委派者基类
|
||||
* 负责处理panelVariables和localVariables的转换和注入
|
||||
* 负责处理节点配置、输入映射和输出结果的统一处理
|
||||
*
|
||||
* @param <I> 输入映射类型 (InputMapping)
|
||||
* @param <O> 输出类型 (Outputs)
|
||||
*
|
||||
* @param <P> Panel变量类型
|
||||
* @param <L> Local变量类型
|
||||
* <p>
|
||||
* <p>
|
||||
* panelVariables: 节点配置,一次配置长期有效
|
||||
* localVariables: 运行时变量,每次执行都会变化
|
||||
* @author qqchen
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class BaseNodeDelegate<P, L> implements JavaDelegate {
|
||||
|
||||
public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
// 字段注入,由Flowable自动设置
|
||||
protected Expression panelVariables;
|
||||
// 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
|
||||
currentNodeId = getFieldValue(nodeId, execution);
|
||||
log.info("Executing node: {}", currentNodeId);
|
||||
|
||||
// 2. 清除前一个节点的状态
|
||||
clearPreviousNodeStatus(execution);
|
||||
// 获取并转换Panel变量
|
||||
P panelVars = null;
|
||||
if (panelVariables != null) {
|
||||
String panelVarsJson = panelVariables.getValue(execution).toString();
|
||||
JsonNode panelVarsNode = objectMapper.readTree(panelVarsJson);
|
||||
panelVars = objectMapper.treeToValue(panelVarsNode, getPanelVariablesClass());
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 获取并转换Local变量
|
||||
L localVars = objectMapper.convertValue(execution.getVariables(), getLocalVariablesClass());
|
||||
// 执行具体的任务逻辑
|
||||
executeInternal(execution, panelVars, localVars);
|
||||
|
||||
// 如果节点没有设置状态,默认设置成功状态
|
||||
String status = getExecutionStatus(execution);
|
||||
if (status == null) {
|
||||
setExecutionStatus(execution, WORKFLOW_NODE_EXECUTION_STATE_SUCCESS);
|
||||
}
|
||||
// 7. 设置节点执行状态为成功
|
||||
setExecutionStatus(execution, WORKFLOW_NODE_EXECUTION_STATE_SUCCESS);
|
||||
|
||||
} catch (Exception e) {
|
||||
// 设置失败状态
|
||||
setExecutionStatus(execution, WORKFLOW_NODE_EXECUTION_STATE_FAILURE);
|
||||
log.error("Task execution failed", e);
|
||||
log.error("Node execution failed: {}", currentNodeId, e);
|
||||
throw new RuntimeException("Node execution failed: " + currentNodeId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置执行状态
|
||||
* 执行具体的业务逻辑(子类实现)
|
||||
*
|
||||
* @param execution 执行上下文
|
||||
* @param status 状态值
|
||||
* @param execution Flowable执行上下文
|
||||
* @param configs 节点配置
|
||||
* @param inputMapping 输入映射(强类型)
|
||||
* @return 节点输出结果(强类型)
|
||||
*/
|
||||
protected void setExecutionStatus(DelegateExecution execution, String status) {
|
||||
execution.setVariable(WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME, status);
|
||||
log.debug("Set execution status: {}", status);
|
||||
protected abstract O executeInternal(
|
||||
DelegateExecution execution,
|
||||
Map<String, Object> configs,
|
||||
I inputMapping
|
||||
);
|
||||
|
||||
/**
|
||||
* 通过反射获取InputMapping的类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<I> getInputMappingClass() {
|
||||
if (inputMappingClass == null) {
|
||||
Type genericSuperclass = getClass().getGenericSuperclass();
|
||||
if (genericSuperclass instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
|
||||
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
|
||||
// 第一个泛型参数是 I (InputMapping)
|
||||
inputMappingClass = (Class<I>) actualTypeArguments[0];
|
||||
}
|
||||
}
|
||||
return inputMappingClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Panel变量的类型
|
||||
*
|
||||
* @return Panel变量的Class对象
|
||||
* 通过反射获取Outputs的类型(预留,未来可能需要)
|
||||
*/
|
||||
protected abstract Class<P> getPanelVariablesClass();
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<O> getOutputsClass() {
|
||||
if (outputsClass == null) {
|
||||
Type genericSuperclass = getClass().getGenericSuperclass();
|
||||
if (genericSuperclass instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
|
||||
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
|
||||
// 第二个泛型参数是 O (Outputs)
|
||||
outputsClass = (Class<O>) actualTypeArguments[1];
|
||||
}
|
||||
}
|
||||
return outputsClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Local变量的类型
|
||||
*
|
||||
* @return Local变量的Class对象
|
||||
* 解析并转换InputMapping
|
||||
*/
|
||||
protected abstract Class<L> getLocalVariablesClass();
|
||||
protected I parseAndConvertInputMapping(DelegateExecution execution) {
|
||||
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>>() {}
|
||||
);
|
||||
|
||||
// 处理表达式
|
||||
Map<String, Object> resolvedMap = resolveExpressions(inputMap, execution);
|
||||
|
||||
// 转换为强类型对象
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行具体的任务逻辑
|
||||
*
|
||||
* @param execution DelegateExecution对象
|
||||
* @param panelVariables 转换后的Panel变量
|
||||
* @param localVariables 转换后的Local变量
|
||||
* 解析Map中的表达式
|
||||
* 使用简单的字符串替换方式,从execution变量中获取值
|
||||
*/
|
||||
protected abstract void executeInternal(DelegateExecution execution, P panelVariables, L localVariables);
|
||||
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("${")) {
|
||||
try {
|
||||
// 使用简单的变量替换:${sid_xxx.fieldName} -> 从execution中获取
|
||||
String resolvedValue = resolveVariableExpression(strValue, execution);
|
||||
resolvedMap.put(entry.getKey(), resolvedValue);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to resolve expression: {}, using original value", strValue);
|
||||
resolvedMap.put(entry.getKey(), value);
|
||||
}
|
||||
} else {
|
||||
resolvedMap.put(entry.getKey(), value);
|
||||
}
|
||||
} else {
|
||||
resolvedMap.put(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (parts.length == 1) {
|
||||
// 直接变量:${xxx}
|
||||
return execution.getVariable(parts[0]);
|
||||
} else {
|
||||
// 对象属性:${sid_xxx.buildNumber}
|
||||
String varName = parts[0];
|
||||
String fieldName = parts[1];
|
||||
|
||||
Object varValue = execution.getVariable(varName);
|
||||
if (varValue instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) varValue;
|
||||
return map.get(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getFieldValue(Expression expression, DelegateExecution execution) {
|
||||
if (expression == null) return null;
|
||||
Object value = expression.getValue(execution);
|
||||
return value != null ? value.toString() : null;
|
||||
}
|
||||
|
||||
protected Map<String, Object> parseJsonField(Expression expression, DelegateExecution execution) {
|
||||
String jsonStr = getFieldValue(expression, execution);
|
||||
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<>();
|
||||
}
|
||||
}
|
||||
|
||||
protected void setExecutionStatus(DelegateExecution execution, String status) {
|
||||
execution.setVariable(WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME, status);
|
||||
}
|
||||
|
||||
private void clearPreviousNodeStatus(DelegateExecution execution) {
|
||||
// 只有在当前节点是ServiceTask时才清除状态
|
||||
execution.removeVariable(WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME);
|
||||
log.debug("Cleared previous node status for node: {}", execution.getCurrentActivityId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取执行状态
|
||||
*
|
||||
* @param execution 执行上下文
|
||||
* @return 执行状态,如果不存在则返回null
|
||||
*/
|
||||
protected String getExecutionStatus(DelegateExecution execution) {
|
||||
return Optional.ofNullable(execution.getVariables())
|
||||
.map(vars -> vars.get(WORKFLOW_PREVIOUS_NODE_EXECUTION_STATE_VARIABLE_NAME))
|
||||
.map(Object::toString)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
@ -3,72 +3,79 @@ package com.qqchen.deploy.backend.workflow.delegate;
|
||||
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
|
||||
import com.qqchen.deploy.backend.deploy.entity.JenkinsJob;
|
||||
import com.qqchen.deploy.backend.deploy.enums.JenkinsBuildStatus;
|
||||
import com.qqchen.deploy.backend.deploy.integration.IExternalSystemIntegration;
|
||||
import com.qqchen.deploy.backend.deploy.integration.IJenkinsServiceIntegration;
|
||||
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsQueueBuildInfoResponse;
|
||||
import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository;
|
||||
import com.qqchen.deploy.backend.deploy.repository.IJenkinsJobRepository;
|
||||
import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables.DeployNodeLocalVariables;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.node.panelVariables.DeployNodePanelVariables;
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.JenkinsBuildInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.outputs.JenkinsBuildOutputs;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.delegate.BpmnError;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Shell脚本任务的委派者实现
|
||||
* Jenkins构建任务委派
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeployNodeDelegate extends BaseNodeDelegate<DeployNodePanelVariables, DeployNodeLocalVariables> {
|
||||
@Component("jenkinsBuildDelegate")
|
||||
public class JenkinsBuildDelegate extends BaseNodeDelegate<JenkinsBuildInputMapping, JenkinsBuildOutputs> {
|
||||
|
||||
@Resource
|
||||
private IJenkinsServiceIntegration jenkinsServiceIntegration;
|
||||
|
||||
private static final int QUEUE_POLL_INTERVAL = 10; // 10秒
|
||||
|
||||
private static final int MAX_QUEUE_POLLS = 30; // 最多等待5分钟
|
||||
|
||||
// 轮询间隔(秒)
|
||||
private static final int BUILD_POLL_INTERVAL = 10;
|
||||
|
||||
// 最大轮询次数
|
||||
private static final int MAX_BUILD_POLLS = 180; // 30分钟超时
|
||||
|
||||
@Resource
|
||||
private IExternalSystemRepository externalSystemRepository;
|
||||
|
||||
@Resource
|
||||
private IJenkinsJobRepository jenkinsJobRepository;
|
||||
|
||||
@Override
|
||||
protected Class<DeployNodePanelVariables> getPanelVariablesClass() {
|
||||
return DeployNodePanelVariables.class;
|
||||
}
|
||||
private static final int QUEUE_POLL_INTERVAL = 10; // 10秒
|
||||
private static final int MAX_QUEUE_POLLS = 30; // 最多等待5分钟
|
||||
private static final int BUILD_POLL_INTERVAL = 10; // 轮询间隔(秒)
|
||||
private static final int MAX_BUILD_POLLS = 180; // 30分钟超时
|
||||
|
||||
@Override
|
||||
protected Class<DeployNodeLocalVariables> getLocalVariablesClass() {
|
||||
return DeployNodeLocalVariables.class;
|
||||
}
|
||||
protected JenkinsBuildOutputs executeInternal(
|
||||
DelegateExecution execution,
|
||||
Map<String, Object> configs,
|
||||
JenkinsBuildInputMapping input
|
||||
) {
|
||||
log.info("Jenkins Build - serverId: {}, project: {}",
|
||||
input.getJenkinsServerId(), input.getProject());
|
||||
|
||||
@Override
|
||||
protected void executeInternal(DelegateExecution execution, DeployNodePanelVariables panelVariables, DeployNodeLocalVariables localVariables) {
|
||||
ExternalSystem externalSystem = externalSystemRepository.findById(localVariables.getExternalSystemId()).orElseThrow(() -> new RuntimeException("ExternalSystem not found!!!"));
|
||||
JenkinsJob jenkinsJob = jenkinsJobRepository.findById(localVariables.getJobId()).orElseThrow(() -> new RuntimeException("Jenkins job not found!!!"));
|
||||
// Pipeline脚本模板
|
||||
// 1. 获取外部系统和Jenkins Job
|
||||
ExternalSystem externalSystem = externalSystemRepository.findById(input.getJenkinsServerId().longValue())
|
||||
.orElseThrow(() -> new RuntimeException("Jenkins服务器不存在: " + input.getJenkinsServerId()));
|
||||
|
||||
// 2. 触发构建(这里简化处理,实际需要根据project获取jenkinsJob)
|
||||
// TODO: 根据input.getProject()查找对应的JenkinsJob
|
||||
Map<String, String> parameters = new HashMap<>();
|
||||
parameters.put("PIPELINE_SCRIPT", localVariables.getScript());
|
||||
String queueId = jenkinsServiceIntegration.buildWithParameters(externalSystem, jenkinsJob.getJobName(), parameters);
|
||||
JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(queueId);
|
||||
// 3. 轮询构建状态
|
||||
pollBuildStatus(externalSystem, jenkinsJob.getJobName(), buildInfo.getBuildNumber());
|
||||
// parameters.put("PROJECT", input.getProject());
|
||||
|
||||
// String queueId = jenkinsServiceIntegration.buildWithParameters(
|
||||
// externalSystem, input.getProject(), parameters);
|
||||
// JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(queueId);
|
||||
// JenkinsBuildStatus status = pollBuildStatus(externalSystem, input.getProject(), buildInfo.getBuildNumber());
|
||||
|
||||
// 3. 构造输出结果
|
||||
JenkinsBuildOutputs outputs = new JenkinsBuildOutputs();
|
||||
outputs.setBuildStatus("SUCCESS"); // TODO: 从实际构建状态获取
|
||||
outputs.setBuildNumber(123); // TODO: 从buildInfo获取
|
||||
outputs.setBuildUrl("http://jenkins.example.com/job/" + input.getProject() + "/123/");
|
||||
outputs.setGitCommitId("a3f5e8d2c4b1a5e9f2d3e7b8c9d1a2f3e4b5c6d7");
|
||||
outputs.setBuildDuration(120);
|
||||
outputs.setArtifactUrl("http://jenkins.example.com/job/" + input.getProject() + "/123/artifact/target/app.jar");
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
private JenkinsQueueBuildInfoResponse waitForBuildToStart(String queueId) {
|
||||
@ -1,18 +1,9 @@
|
||||
package com.qqchen.deploy.backend.workflow.delegate;
|
||||
|
||||
import com.qqchen.deploy.backend.deploy.enums.JenkinsBuildStatus;
|
||||
import com.qqchen.deploy.backend.deploy.integration.IJenkinsServiceIntegration;
|
||||
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsQueueBuildInfoResponse;
|
||||
import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables.DeployNodeLocalVariables;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables.NotificationNodeLocalVariables;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.node.panelVariables.DeployNodePanelVariables;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.node.panelVariables.NotificationNodePanelVariables;
|
||||
import jakarta.annotation.Resource;
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.NotificationInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.outputs.NotificationOutputs;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.delegate.BpmnError;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@ -21,47 +12,69 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Shell脚本任务的委派者实现
|
||||
* 通知任务委派
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NotificationNodeDelegate extends BaseNodeDelegate<NotificationNodePanelVariables, NotificationNodeLocalVariables> {
|
||||
@Component("notificationDelegate")
|
||||
public class NotificationNodeDelegate extends BaseNodeDelegate<NotificationInputMapping, NotificationOutputs> {
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
// TODO: 从数据库中读取webhook配置
|
||||
private final String WX_HOOK_API = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=614b110b-8957-4be8-95b9-4eca84c15028";
|
||||
|
||||
// 用于存储实时输出的Map
|
||||
private static final Map<String, StringBuilder> outputMap = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Map<String, StringBuilder> errorMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
protected Class<NotificationNodePanelVariables> getPanelVariablesClass() {
|
||||
return NotificationNodePanelVariables.class;
|
||||
protected NotificationOutputs executeInternal(
|
||||
DelegateExecution execution,
|
||||
Map<String, Object> configs,
|
||||
NotificationInputMapping input
|
||||
) {
|
||||
log.info("Sending notification - channel: {}, title: {}, content: {}",
|
||||
input.getNotificationChannel(), input.getTitle(), input.getContent());
|
||||
|
||||
// 1. 根据notificationChannel获取webhook配置
|
||||
// TODO: 从数据库查询通知渠道配置
|
||||
|
||||
// 2. 发送通知
|
||||
try {
|
||||
sendWeChatNotification(input.getTitle(), input.getContent());
|
||||
|
||||
// 3. 返回成功结果
|
||||
NotificationOutputs outputs = new NotificationOutputs();
|
||||
outputs.setStatus("SUCCESS");
|
||||
return outputs;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send notification", e);
|
||||
NotificationOutputs outputs = new NotificationOutputs();
|
||||
outputs.setStatus("FAILURE");
|
||||
return outputs;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<NotificationNodeLocalVariables> getLocalVariablesClass() {
|
||||
return NotificationNodeLocalVariables.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeInternal(DelegateExecution execution, NotificationNodePanelVariables panelVariables, NotificationNodeLocalVariables localVariables) {
|
||||
/**
|
||||
* 发送企业微信通知
|
||||
*/
|
||||
private void sendWeChatNotification(String title, String content) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
String format = String.format("{\n" +
|
||||
String message = String.format("%s\n%s", title, content);
|
||||
String body = String.format(
|
||||
"{\n" +
|
||||
" \"msgtype\": \"text\",\n" +
|
||||
" \"text\": {\n" +
|
||||
" \"content\": \"%s\"\n" +
|
||||
" }\n" +
|
||||
"}", panelVariables.getText() + ":这是一个面板填写的变量");
|
||||
"}", message
|
||||
);
|
||||
|
||||
HttpEntity<String> entity = new HttpEntity<>(format, headers);
|
||||
HttpEntity<String> entity = new HttpEntity<>(body, headers);
|
||||
|
||||
restTemplate.exchange(
|
||||
WX_HOOK_API,
|
||||
@ -69,6 +82,7 @@ public class NotificationNodeDelegate extends BaseNodeDelegate<NotificationNodeP
|
||||
entity,
|
||||
String.class
|
||||
);
|
||||
|
||||
log.info("WeChat notification sent successfully");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,176 +1,45 @@
|
||||
package com.qqchen.deploy.backend.workflow.delegate;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables.ScriptNodeLocalVariables;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.node.panelVariables.ScriptNodePanelVariables;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeLogTypeEnums;
|
||||
import com.qqchen.deploy.backend.workflow.dto.event.ShellLogEvent;
|
||||
import jakarta.annotation.Resource;
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.ShellInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.outputs.ShellOutputs;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.delegate.BpmnError;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Shell脚本任务的委派者实现
|
||||
* Shell脚本节点委派
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ShellNodeDelegate extends BaseNodeDelegate<ScriptNodePanelVariables, ScriptNodeLocalVariables> {
|
||||
|
||||
@Resource
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
// 用于存储实时输出的Map
|
||||
private static final Map<String, StringBuilder> outputMap = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Map<String, StringBuilder> errorMap = new ConcurrentHashMap<>();
|
||||
@Component("shellDelegate")
|
||||
public class ShellNodeDelegate extends BaseNodeDelegate<ShellInputMapping, ShellOutputs> {
|
||||
|
||||
@Override
|
||||
protected Class<ScriptNodePanelVariables> getPanelVariablesClass() {
|
||||
return ScriptNodePanelVariables.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<ScriptNodeLocalVariables> getLocalVariablesClass() {
|
||||
return ScriptNodeLocalVariables.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeInternal(DelegateExecution execution,
|
||||
ScriptNodePanelVariables panelVariables,
|
||||
ScriptNodeLocalVariables localVariables) {
|
||||
if (panelVariables == null || panelVariables.getScript() == null) {
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Script is required but not provided");
|
||||
}
|
||||
|
||||
// try {
|
||||
// log.info("准备执行脚本: {}", panelVariables.getScript());
|
||||
// // 使用processInstanceId而不是executionId
|
||||
// String processInstanceId = execution.getProcessInstanceId();
|
||||
// outputMap.put(processInstanceId, new StringBuilder());
|
||||
// errorMap.put(processInstanceId, new StringBuilder());
|
||||
//
|
||||
// // 创建进程构建器
|
||||
// ProcessBuilder processBuilder = new ProcessBuilder();
|
||||
//
|
||||
// // 根据操作系统选择合适的shell
|
||||
// String os = System.getProperty("os.name").toLowerCase();
|
||||
// if (os.contains("win")) {
|
||||
// // Windows系统使用cmd
|
||||
// processBuilder.command("cmd", "/c", panelVariables.getScript());
|
||||
// } else {
|
||||
// // Unix-like系统使用bash
|
||||
// processBuilder.command("bash", "-c", panelVariables.getScript());
|
||||
// }
|
||||
//
|
||||
// // 设置工作目录
|
||||
// if (StringUtils.hasText(localVariables.getWorkDir())) {
|
||||
// // Windows系统路径处理
|
||||
// String workDirValue = localVariables.getWorkDir();
|
||||
// if (os.contains("win")) {
|
||||
// // 确保使用Windows风格的路径分隔符
|
||||
// workDirValue = workDirValue.replace("/", "\\");
|
||||
// // 如果路径以\开头,去掉第一个\
|
||||
// if (workDirValue.startsWith("\\")) {
|
||||
// workDirValue = workDirValue.substring(1);
|
||||
// }
|
||||
// }
|
||||
// File workDirFile = new File(workDirValue);
|
||||
// if (!workDirFile.exists()) {
|
||||
// workDirFile.mkdirs();
|
||||
// }
|
||||
// processBuilder.directory(workDirFile);
|
||||
// }
|
||||
//
|
||||
// // 设置环境变量
|
||||
// if (localVariables.getEnv() != null) {
|
||||
// processBuilder.environment().putAll(localVariables.getEnv());
|
||||
// }
|
||||
//
|
||||
// // 执行命令
|
||||
// log.info("执行shell脚本: {}", panelVariables.getScript());
|
||||
// Process process = processBuilder.start();
|
||||
//
|
||||
// // 创建线程池处理输出
|
||||
// ExecutorService executorService = Executors.newFixedThreadPool(2);
|
||||
//
|
||||
// // 处理标准输出
|
||||
// Future<?> outputFuture = executorService.submit(() ->
|
||||
// processInputStream(process.getInputStream(), processInstanceId, NodeLogTypeEnums.STDOUT));
|
||||
//
|
||||
// // 处理错误输出
|
||||
// Future<?> errorFuture = executorService.submit(() ->
|
||||
// processInputStream(process.getErrorStream(), processInstanceId, NodeLogTypeEnums.STDERR));
|
||||
//
|
||||
// // 等待进程完成
|
||||
// int exitCode = process.waitFor();
|
||||
//
|
||||
// // 等待输出处理完成
|
||||
// outputFuture.get(5, TimeUnit.SECONDS);
|
||||
// errorFuture.get(5, TimeUnit.SECONDS);
|
||||
//
|
||||
// // 关闭线程池
|
||||
// executorService.shutdown();
|
||||
//
|
||||
// // 设置最终结果
|
||||
// StringBuilder finalOutput = outputMap.get(processInstanceId);
|
||||
// StringBuilder finalError = errorMap.get(processInstanceId);
|
||||
//
|
||||
// execution.setVariable("shellOutput", finalOutput.toString());
|
||||
// execution.setVariable("shellError", finalError.toString());
|
||||
// execution.setVariable("shellExitCode", exitCode);
|
||||
//
|
||||
// // 清理缓存
|
||||
// outputMap.remove(processInstanceId);
|
||||
// errorMap.remove(processInstanceId);
|
||||
//
|
||||
// if (exitCode != 0) {
|
||||
// log.error("Shell脚本执行失败,退出码: {}", exitCode);
|
||||
// execution.setVariable("errorDetail", "Shell脚本执行失败,退出码: " + exitCode);
|
||||
// throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Shell脚本执行失败,退出码: " + exitCode);
|
||||
// }
|
||||
// log.info("Shell脚本执行成功");
|
||||
// log.debug("脚本输出: {}", finalOutput);
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// log.error("Shell脚本执行失败", e);
|
||||
// throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, e.getMessage());
|
||||
// }
|
||||
}
|
||||
|
||||
private void processInputStream(InputStream inputStream, String processInstanceId, NodeLogTypeEnums logType) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// 发布日志事件
|
||||
eventPublisher.publishEvent(new ShellLogEvent(processInstanceId, line, logType));
|
||||
|
||||
// 同时保存到StringBuilder中
|
||||
if (logType == NodeLogTypeEnums.STDOUT) {
|
||||
StringBuilder output = outputMap.get(processInstanceId);
|
||||
synchronized (output) {
|
||||
output.append(line).append("\n");
|
||||
}
|
||||
// log.info("Shell output: {}", line);
|
||||
} else {
|
||||
StringBuilder error = errorMap.get(processInstanceId);
|
||||
synchronized (error) {
|
||||
error.append(line).append("\n");
|
||||
}
|
||||
// log.error("Shell error: {}", line);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error reading process output", e);
|
||||
protected ShellOutputs executeInternal(
|
||||
DelegateExecution execution,
|
||||
Map<String, Object> configs,
|
||||
ShellInputMapping input
|
||||
) {
|
||||
if (input.getScript() == null || input.getScript().isEmpty()) {
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR,
|
||||
"Script is required but not provided");
|
||||
}
|
||||
|
||||
log.info("Executing shell script: {}", input.getScript());
|
||||
|
||||
// TODO: 实现Shell脚本执行逻辑
|
||||
// 目前先返回模拟结果
|
||||
ShellOutputs outputs = new ShellOutputs();
|
||||
outputs.setExitCode(0);
|
||||
outputs.setStdout("Shell execution completed (mocked)");
|
||||
outputs.setStderr("");
|
||||
|
||||
return outputs;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.inputmapping;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 审批节点输入映射
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class ApprovalInputMapping {
|
||||
|
||||
/**
|
||||
* 审批人
|
||||
*/
|
||||
private String assignee;
|
||||
|
||||
/**
|
||||
* 候选组
|
||||
*/
|
||||
private String candidateGroup;
|
||||
|
||||
/**
|
||||
* 超时时间(小时)
|
||||
*/
|
||||
private Integer timeoutHours;
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.inputmapping;
|
||||
|
||||
import lombok.Data;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* Jenkins构建节点输入映射
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class JenkinsBuildInputMapping {
|
||||
|
||||
/**
|
||||
* Jenkins服务器ID
|
||||
*/
|
||||
@NotNull(message = "Jenkins服务器ID不能为空")
|
||||
private Integer jenkinsServerId;
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
@NotBlank(message = "项目名称不能为空")
|
||||
private String project;
|
||||
}
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.inputmapping;
|
||||
|
||||
import lombok.Data;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 通知节点输入映射
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class NotificationInputMapping {
|
||||
|
||||
/**
|
||||
* 通知渠道ID
|
||||
*/
|
||||
@NotNull(message = "通知渠道ID不能为空")
|
||||
private Integer notificationChannel;
|
||||
|
||||
/**
|
||||
* 通知标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 通知内容
|
||||
*/
|
||||
private String content;
|
||||
}
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.inputmapping;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Shell脚本节点输入映射
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class ShellInputMapping {
|
||||
|
||||
/**
|
||||
* Shell脚本内容
|
||||
*/
|
||||
private String script;
|
||||
|
||||
/**
|
||||
* 工作目录
|
||||
*/
|
||||
private String workDir;
|
||||
|
||||
/**
|
||||
* 环境变量
|
||||
*/
|
||||
private Map<String, String> env;
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.outputs;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 审批节点输出
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class ApprovalOutputs {
|
||||
|
||||
/**
|
||||
* 审批结果
|
||||
*/
|
||||
private String result;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
private String comment;
|
||||
|
||||
/**
|
||||
* 审批人
|
||||
*/
|
||||
private String approver;
|
||||
}
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.outputs;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Jenkins构建节点输出
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class JenkinsBuildOutputs {
|
||||
|
||||
/**
|
||||
* 构建编号
|
||||
*/
|
||||
private Integer buildNumber;
|
||||
|
||||
/**
|
||||
* 构建状态
|
||||
*/
|
||||
private String buildStatus;
|
||||
|
||||
/**
|
||||
* 构建URL
|
||||
*/
|
||||
private String buildUrl;
|
||||
|
||||
/**
|
||||
* 构建产物地址
|
||||
*/
|
||||
private String artifactUrl;
|
||||
|
||||
/**
|
||||
* Git提交ID
|
||||
*/
|
||||
private String gitCommitId;
|
||||
|
||||
/**
|
||||
* 构建时长(秒)
|
||||
*/
|
||||
private Integer buildDuration;
|
||||
}
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.outputs;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 通知节点输出
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class NotificationOutputs {
|
||||
|
||||
/**
|
||||
* 执行状态
|
||||
*/
|
||||
private String status;
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.outputs;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Shell脚本节点输出
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class ShellOutputs {
|
||||
|
||||
/**
|
||||
* 退出码
|
||||
*/
|
||||
private Integer exitCode;
|
||||
|
||||
/**
|
||||
* 标准输出
|
||||
*/
|
||||
private String stdout;
|
||||
|
||||
/**
|
||||
* 错误输出
|
||||
*/
|
||||
private String stderr;
|
||||
}
|
||||
|
||||
@ -325,7 +325,7 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
|
||||
WorkflowDefinition definition = workflowDefinitionRepository.findById(workflowDefinitionId)
|
||||
.orElseThrow(() -> new RuntimeException("Workflow definition not found: " + workflowDefinitionId));
|
||||
WorkflowDefinitionGraph graph = definition.getGraph();
|
||||
definition.setBpmnXml(bpmnConverter.convertToXml(graph, definition.getKey()));
|
||||
// definition.setBpmnXml(bpmnConverter.convertToXml(graph, definition.getKey()));
|
||||
Deployment deployment = this.deployWorkflow(definition);
|
||||
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
|
||||
definition.setStatus(WorkflowDefinitionStatusEnums.PUBLISHED);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user