团队app配置增加绑定工作流

This commit is contained in:
dengqichen 2025-11-02 16:13:25 +08:00
parent d59d21a062
commit 87b8023e1c
10 changed files with 371 additions and 191 deletions

View File

@ -1,5 +1,7 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.DeployRequestDTO;
import com.qqchen.deploy.backend.deploy.dto.DeployResultDTO;
import com.qqchen.deploy.backend.deploy.dto.UserDeployableDTO;
import com.qqchen.deploy.backend.deploy.service.IDeployService;
import com.qqchen.deploy.backend.framework.api.Response;
@ -8,9 +10,9 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 部署管理API控制器
@ -36,5 +38,15 @@ public class DeployApiController {
public Response<UserDeployableDTO> getDeployableEnvironments() {
return Response.success(deployService.getDeployableEnvironments());
}
/**
* 执行部署
*/
@Operation(summary = "执行部署", description = "根据团队应用配置启动部署工作流")
@PostMapping("/execute")
@PreAuthorize("isAuthenticated()")
public Response<DeployResultDTO> executeDeploy(@Validated @RequestBody DeployRequestDTO request) {
return Response.success(deployService.executeDeploy(request));
}
}

View File

@ -0,0 +1,24 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 部署请求DTO
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "部署请求")
public class DeployRequestDTO {
@Schema(description = "团队应用关联ID", required = true)
@NotNull(message = "团队应用关联ID不能为空")
private Long teamApplicationId;
@Schema(description = "部署备注")
private String remark;
}

View File

@ -0,0 +1,31 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 部署结果DTO
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "部署结果")
public class DeployResultDTO {
@Schema(description = "工作流实例ID")
private Long workflowInstanceId;
@Schema(description = "业务标识")
private String businessKey;
@Schema(description = "流程实例ID")
private String processInstanceId;
@Schema(description = "部署状态")
private String status;
@Schema(description = "提示信息")
private String message;
}

View File

@ -1,5 +1,7 @@
package com.qqchen.deploy.backend.deploy.service;
import com.qqchen.deploy.backend.deploy.dto.DeployRequestDTO;
import com.qqchen.deploy.backend.deploy.dto.DeployResultDTO;
import com.qqchen.deploy.backend.deploy.dto.UserDeployableDTO;
/**
@ -16,5 +18,13 @@ public interface IDeployService {
* @return 用户可部署环境信息
*/
UserDeployableDTO getDeployableEnvironments();
/**
* 执行部署
*
* @param request 部署请求
* @return 部署结果
*/
DeployResultDTO executeDeploy(DeployRequestDTO request);
}

View File

@ -5,10 +5,17 @@ import com.qqchen.deploy.backend.deploy.entity.*;
import com.qqchen.deploy.backend.deploy.repository.*;
import com.qqchen.deploy.backend.deploy.service.IDeployService;
import com.qqchen.deploy.backend.framework.security.SecurityUtils;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.system.entity.User;
import com.qqchen.deploy.backend.system.repository.IUserRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest;
import com.qqchen.deploy.backend.workflow.dto.inputmapping.JenkinsBuildInputMapping;
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -56,6 +63,12 @@ public class DeployServiceImpl implements IDeployService {
@Resource
private IWorkflowDefinitionRepository workflowDefinitionRepository;
@Resource
private IWorkflowInstanceService workflowInstanceService;
@Resource
private ObjectMapper objectMapper;
@Override
@Transactional(readOnly = true)
public UserDeployableDTO getDeployableEnvironments() {
@ -388,5 +401,80 @@ public class DeployServiceImpl implements IDeployService {
return dto;
}
@Override
@Transactional
public DeployResultDTO executeDeploy(DeployRequestDTO request) {
// 1. 查询团队应用配置
TeamApplication teamApp = teamApplicationRepository.findById(request.getTeamApplicationId())
.orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND));
// 2. 查询工作流定义获取 processKey
WorkflowDefinition workflowDefinition = workflowDefinitionRepository.findById(teamApp.getWorkflowDefinitionId())
.orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[]{"工作流定义"}));
// 3. 查询应用信息
Application application = applicationRepository.findById(teamApp.getApplicationId())
.orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[]{"应用"}));
// 4. 查询环境信息
Environment environment = environmentRepository.findById(teamApp.getEnvironmentId())
.orElseThrow(() -> new BusinessException(ResponseCode.NOT_FOUND, new Object[]{"环境"}));
// 5. 生成业务标识UUID
String businessKey = UUID.randomUUID().toString();
// 6. 构造流程变量
Map<String, Object> variables = new HashMap<>();
// 部署上下文
Map<String, Object> deployContext = new HashMap<>();
deployContext.put("teamApplicationId", teamApp.getId());
deployContext.put("teamId", teamApp.getTeamId());
deployContext.put("applicationId", teamApp.getApplicationId());
deployContext.put("applicationCode", application.getAppCode());
deployContext.put("applicationName", application.getAppName());
deployContext.put("environmentId", teamApp.getEnvironmentId());
deployContext.put("environmentCode", environment.getEnvCode());
deployContext.put("environmentName", environment.getEnvName());
deployContext.put("by", SecurityUtils.getCurrentUsername());
deployContext.put("remark", request.getRemark());
variables.put("deploy", deployContext);
// Jenkins 配置使用强类型 JenkinsBuildInputMapping
if (teamApp.getDeploySystemId() != null && teamApp.getDeployJob() != null) {
JenkinsBuildInputMapping jenkinsInput = new JenkinsBuildInputMapping();
jenkinsInput.setServerId(teamApp.getDeploySystemId());
jenkinsInput.setJobName(teamApp.getDeployJob());
if (teamApp.getBranch() != null) {
jenkinsInput.setBranch(teamApp.getBranch());
}
// 转换为 MapFlowable 只支持基本类型
variables.put("jenkins", objectMapper.convertValue(jenkinsInput, Map.class));
}
// 7. 构造工作流启动请求
WorkflowInstanceStartRequest workflowRequest = new WorkflowInstanceStartRequest();
workflowRequest.setProcessKey(workflowDefinition.getKey());
workflowRequest.setBusinessKey(businessKey);
workflowRequest.setVariables(variables);
// 8. 启动工作流
WorkflowInstanceDTO workflowInstance = workflowInstanceService.startWorkflow(workflowRequest);
log.info("部署流程已启动: businessKey={}, workflowInstanceId={}, application={}, environment={}",
businessKey, workflowInstance.getId(), application.getAppCode(), environment.getEnvCode());
// 9. 返回结果
DeployResultDTO result = new DeployResultDTO();
result.setWorkflowInstanceId(workflowInstance.getId());
result.setBusinessKey(businessKey);
result.setProcessInstanceId(workflowInstance.getProcessInstanceId());
result.setStatus(workflowInstance.getStatus().name());
result.setMessage("部署流程已启动");
return result;
}
}

View File

@ -184,6 +184,8 @@ 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<>();
log.debug("开始解析 inputMap: {}", inputMap);
for (Map.Entry<String, Object> entry : inputMap.entrySet()) {
Object value = entry.getValue();
@ -193,9 +195,10 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
try {
// 使用简单的变量替换${sid_xxx.fieldName} -> 从execution中获取
String resolvedValue = resolveVariableExpression(strValue, execution);
log.debug("解析表达式: {} = {} -> {}", entry.getKey(), strValue, resolvedValue);
resolvedMap.put(entry.getKey(), resolvedValue);
} catch (Exception e) {
log.warn("Failed to resolve expression: {}, using original value", strValue);
log.warn("Failed to resolve expression: {}, using original value", strValue, e);
resolvedMap.put(entry.getKey(), value);
}
} else {
@ -206,6 +209,7 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
}
}
log.debug("解析后 resolvedMap: {}", resolvedMap);
return resolvedMap;
}
@ -240,19 +244,27 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
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}
return execution.getVariable(parts[0]);
Object result = execution.getVariable(parts[0]);
log.debug("直接变量 {} = {}", parts[0], result);
return result;
} else {
// 对象属性${sid_xxx.buildNumber}
// 对象属性${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;
return map.get(fieldName);
Object fieldValue = map.get(fieldName);
log.debug("从 Map 中获取字段 {} = {}", fieldName, fieldValue);
return fieldValue;
}
}

View File

@ -41,24 +41,22 @@ public class JenkinsBuildDelegate extends BaseNodeDelegate<JenkinsBuildInputMapp
private IJenkinsJobRepository jenkinsJobRepository;
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 JenkinsBuildOutputs executeInternal(
DelegateExecution execution,
Map<String, Object> configs,
JenkinsBuildInputMapping input
) {
log.info("Jenkins Build - serverId: {}, jobName: {}",
input.getJenkinsServerId(), input.getProject());
protected JenkinsBuildOutputs executeInternal(DelegateExecution execution, Map<String, Object> configs, JenkinsBuildInputMapping input) {
log.info("Jenkins Build - serverId: {}, jobName: {}", input.getServerId(), input.getJobName());
// 1. 获取外部系统
ExternalSystem externalSystem = externalSystemRepository.findById(input.getJenkinsServerId().longValue())
.orElseThrow(() -> new RuntimeException("Jenkins服务器不存在: " + input.getJenkinsServerId()));
ExternalSystem externalSystem = externalSystemRepository.findById(input.getServerId())
.orElseThrow(() -> new RuntimeException("Jenkins服务器不存在: " + input.getServerId()));
String jobName = input.getProject();
String jobName = input.getJobName();
// 2. 触发构建
Map<String, String> parameters = new HashMap<>();

View File

@ -19,12 +19,18 @@ public class JenkinsBuildInputMapping {
* Jenkins服务器ID
*/
@NotNull(message = "Jenkins服务器ID不能为空")
private Integer jenkinsServerId;
private Long serverId;
/**
* 项目名称
*/
@NotBlank(message = "项目名称不能为空")
private String project;
private String jobName;
/**
* 项目分支
*/
@NotBlank(message = "项目分支不能为空")
private String branch;
}

View File

@ -173,12 +173,11 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
.orElseThrow(() -> new RuntimeException("Workflow definition not found: " + request.getProcessKey()));
// 2. 获取流程变量如果没有则使用空 Map
Map<String, Object> variables = request.getVariables() != null ? request.getVariables() : new HashMap<>();
// 3. 启动 Flowable 流程
ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
.processDefinitionKey(request.getProcessKey())
.variables(variables)
.variables(request.getVariables())
.businessKey(request.getBusinessKey())
.startAsync(); // 异步启动会自动执行任务