增加生成后端服务代码。
This commit is contained in:
parent
197439a381
commit
332f27fac1
@ -52,6 +52,16 @@ public interface IJenkinsServiceIntegration extends IExternalSystemIntegration {
|
||||
*/
|
||||
JenkinsBuildStatus getBuildStatus(ExternalSystem externalSystem, String jobName, Integer buildNumber);
|
||||
|
||||
/**
|
||||
* 获取构建详细信息
|
||||
*
|
||||
* @param externalSystem Jenkins系统配置
|
||||
* @param jobName 任务名称
|
||||
* @param buildNumber 构建编号
|
||||
* @return 构建详细信息
|
||||
*/
|
||||
JenkinsBuildResponse getBuildDetails(ExternalSystem externalSystem, String jobName, Integer buildNumber);
|
||||
|
||||
/**
|
||||
* 查询所有视图
|
||||
*
|
||||
|
||||
@ -419,6 +419,56 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JenkinsBuildResponse getBuildDetails(ExternalSystem externalSystem, String jobName, Integer buildNumber) {
|
||||
try {
|
||||
// 构建 tree 参数,只获取我们需要的字段
|
||||
String treeQuery = "number,url,result,duration,timestamp,building," +
|
||||
"changeSets[items[commitId,author,message]]," +
|
||||
"artifacts[fileName,relativePath,displayPath]";
|
||||
|
||||
String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl())
|
||||
.path("/job/")
|
||||
.path(jobName)
|
||||
.path("/")
|
||||
.path(buildNumber.toString())
|
||||
.path("/api/json")
|
||||
.queryParam("tree", treeQuery)
|
||||
.build()
|
||||
.toUriString();
|
||||
|
||||
HttpEntity<String> entity = new HttpEntity<>(createHeaders(externalSystem));
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
entity,
|
||||
String.class
|
||||
);
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
|
||||
// 打印原始响应用于调试
|
||||
log.info("Jenkins build details raw response: {}", response.getBody());
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JenkinsBuildResponse buildResponse = mapper.readValue(response.getBody(), JenkinsBuildResponse.class);
|
||||
|
||||
// 清理URL,移除基础URL部分
|
||||
if (buildResponse.getUrl() != null) {
|
||||
String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/");
|
||||
buildResponse.setUrl(buildResponse.getUrl().replace(baseUrl, ""));
|
||||
}
|
||||
|
||||
return buildResponse;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Failed to get build details: empty response");
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get build details: job={}, buildNumber={}, error={}",
|
||||
jobName, buildNumber, e.getMessage(), e);
|
||||
throw new RuntimeException("获取Jenkins构建详情失败: " + jobName + "#" + buildNumber, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String extractQueueId(String location) {
|
||||
// location格式: http://jenkins-url/queue/item/12345/
|
||||
return location.replaceAll(".*/item/(\\d+)/.*", "$1");
|
||||
|
||||
@ -10,6 +10,7 @@ import com.qqchen.deploy.backend.deploy.repository.IJenkinsJobRepository;
|
||||
import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants;
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.JenkinsBuildInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.outputs.JenkinsBuildOutputs;
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.delegate.BpmnError;
|
||||
@ -49,31 +50,79 @@ public class JenkinsBuildDelegate extends BaseNodeDelegate<JenkinsBuildInputMapp
|
||||
Map<String, Object> configs,
|
||||
JenkinsBuildInputMapping input
|
||||
) {
|
||||
log.info("Jenkins Build - serverId: {}, project: {}",
|
||||
log.info("Jenkins Build - serverId: {}, jobName: {}",
|
||||
input.getJenkinsServerId(), input.getProject());
|
||||
|
||||
// 1. 获取外部系统和Jenkins Job
|
||||
// 1. 获取外部系统
|
||||
ExternalSystem externalSystem = externalSystemRepository.findById(input.getJenkinsServerId().longValue())
|
||||
.orElseThrow(() -> new RuntimeException("Jenkins服务器不存在: " + input.getJenkinsServerId()));
|
||||
|
||||
// 2. 触发构建(这里简化处理,实际需要根据project获取jenkinsJob)
|
||||
// TODO: 根据input.getProject()查找对应的JenkinsJob
|
||||
String jobName = input.getProject();
|
||||
|
||||
// 2. 触发构建
|
||||
Map<String, String> parameters = new HashMap<>();
|
||||
// parameters.put("PROJECT", input.getProject());
|
||||
// 可以根据需要添加构建参数
|
||||
// parameters.put("BRANCH", "main");
|
||||
|
||||
// String queueId = jenkinsServiceIntegration.buildWithParameters(
|
||||
// externalSystem, input.getProject(), parameters);
|
||||
// JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(queueId);
|
||||
// JenkinsBuildStatus status = pollBuildStatus(externalSystem, input.getProject(), buildInfo.getBuildNumber());
|
||||
String queueId = jenkinsServiceIntegration.buildWithParameters(
|
||||
externalSystem, jobName, parameters);
|
||||
|
||||
// 3. 构造输出结果
|
||||
// 3. 等待构建从队列中开始
|
||||
JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(queueId);
|
||||
|
||||
// 4. 轮询构建状态直到完成
|
||||
// 注意:如果构建失败或被取消,pollBuildStatus 会抛出 BpmnError,触发错误边界事件
|
||||
// 只有成功时才会返回到这里
|
||||
JenkinsBuildStatus buildStatus = pollBuildStatus(externalSystem, jobName, buildInfo.getBuildNumber());
|
||||
|
||||
// 5. 获取构建详细信息(包括 duration, changeSets, artifacts)
|
||||
com.qqchen.deploy.backend.deploy.integration.response.JenkinsBuildResponse buildDetails =
|
||||
jenkinsServiceIntegration.getBuildDetails(externalSystem, jobName, buildInfo.getBuildNumber());
|
||||
|
||||
// 打印调试信息
|
||||
log.info("Build details - changeSets: {}, artifacts: {}",
|
||||
buildDetails.getChangeSets(), buildDetails.getArtifacts());
|
||||
|
||||
// 6. 构造输出结果(执行到这里说明构建成功)
|
||||
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");
|
||||
|
||||
// 设置统一的执行状态为成功
|
||||
outputs.setStatus(NodeExecutionStatusEnum.SUCCESS);
|
||||
|
||||
// 设置 Jenkins 特有字段
|
||||
outputs.setBuildStatus(buildStatus.name());
|
||||
outputs.setBuildNumber(buildInfo.getBuildNumber());
|
||||
outputs.setBuildUrl(buildInfo.getBuildUrl());
|
||||
|
||||
// 从构建详情中提取信息
|
||||
outputs.setBuildDuration(buildDetails.getDuration() != null ? buildDetails.getDuration().intValue() : 0);
|
||||
|
||||
// 提取 Git Commit ID(从 changeSets 中获取第一个)
|
||||
if (buildDetails.getChangeSets() != null && !buildDetails.getChangeSets().isEmpty()) {
|
||||
log.info("Found {} changeSets", buildDetails.getChangeSets().size());
|
||||
var changeSet = buildDetails.getChangeSets().get(0);
|
||||
if (changeSet.getItems() != null && !changeSet.getItems().isEmpty()) {
|
||||
log.info("Found {} items in changeSet", changeSet.getItems().size());
|
||||
outputs.setGitCommitId(changeSet.getItems().get(0).getCommitId());
|
||||
}
|
||||
} else {
|
||||
log.warn("No changeSets found in build details");
|
||||
}
|
||||
if (outputs.getGitCommitId() == null) {
|
||||
outputs.setGitCommitId("");
|
||||
}
|
||||
|
||||
// 提取构建制品URL(如果有多个制品,拼接成逗号分隔的列表)
|
||||
if (buildDetails.getArtifacts() != null && !buildDetails.getArtifacts().isEmpty()) {
|
||||
log.info("Found {} artifacts", buildDetails.getArtifacts().size());
|
||||
String artifactUrls = buildDetails.getArtifacts().stream()
|
||||
.map(artifact -> buildInfo.getBuildUrl() + "artifact/" + artifact.getRelativePath())
|
||||
.collect(java.util.stream.Collectors.joining(","));
|
||||
outputs.setArtifactUrl(artifactUrls);
|
||||
} else {
|
||||
log.warn("No artifacts found in build details");
|
||||
outputs.setArtifactUrl("");
|
||||
}
|
||||
|
||||
return outputs;
|
||||
}
|
||||
@ -101,7 +150,7 @@ public class JenkinsBuildDelegate extends BaseNodeDelegate<JenkinsBuildInputMapp
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, String.format("Build did not start within %d seconds", MAX_QUEUE_POLLS * QUEUE_POLL_INTERVAL));
|
||||
}
|
||||
|
||||
private void pollBuildStatus(ExternalSystem externalSystem, String jobName, Integer buildNumber) {
|
||||
private JenkinsBuildStatus pollBuildStatus(ExternalSystem externalSystem, String jobName, Integer buildNumber) {
|
||||
int attempts = 0;
|
||||
while (attempts < MAX_BUILD_POLLS) {
|
||||
try {
|
||||
@ -114,27 +163,34 @@ public class JenkinsBuildDelegate extends BaseNodeDelegate<JenkinsBuildInputMapp
|
||||
|
||||
switch (status) {
|
||||
case SUCCESS:
|
||||
// 构建成功,返回继续流程
|
||||
return;
|
||||
// 构建成功,返回状态
|
||||
log.info("Jenkins build succeeded: job={}, buildNumber={}", jobName, buildNumber);
|
||||
return status;
|
||||
case FAILURE:
|
||||
// 构建失败,抛出错误
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, String.format("Jenkins build failed: job=%s, buildNumber=%d", jobName, buildNumber));
|
||||
// 构建失败,抛出错误(触发边界事件)
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR,
|
||||
String.format("Jenkins build failed: job=%s, buildNumber=%d", jobName, buildNumber));
|
||||
case ABORTED:
|
||||
// 构建被取消,抛出错误
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, String.format("Jenkins build was aborted: job=%s, buildNumber=%d", jobName, buildNumber));
|
||||
// 构建被取消,抛出错误(触发边界事件)
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR,
|
||||
String.format("Jenkins build was aborted: job=%s, buildNumber=%d", jobName, buildNumber));
|
||||
case IN_PROGRESS:
|
||||
// 继续轮询
|
||||
attempts++;
|
||||
break;
|
||||
case NOT_FOUND:
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, String.format("Jenkins build not found: job=%s, buildNumber=%d", jobName, buildNumber));
|
||||
// 构建记录丢失,抛出系统异常
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR,
|
||||
String.format("Jenkins build not found: job=%s, buildNumber=%d", jobName, buildNumber));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, "Build status polling was interrupted");
|
||||
}
|
||||
}
|
||||
// 超过最大轮询次数,视为超时
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR, String.format("Jenkins build timed out after %d minutes: job=%s, buildNumber=%d", MAX_BUILD_POLLS * BUILD_POLL_INTERVAL / 60, jobName, buildNumber));
|
||||
// 超过最大轮询次数,视为超时(系统异常)
|
||||
throw new BpmnError(WorkFlowConstants.WORKFLOW_EXEC_ERROR,
|
||||
String.format("Jenkins build timed out after %d minutes: job=%s, buildNumber=%d",
|
||||
MAX_BUILD_POLLS * BUILD_POLL_INTERVAL / 60, jobName, buildNumber));
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,13 +46,15 @@ public class NotificationNodeDelegate extends BaseNodeDelegate<NotificationInput
|
||||
|
||||
// 3. 返回成功结果
|
||||
NotificationOutputs outputs = new NotificationOutputs();
|
||||
outputs.setStatus("SUCCESS");
|
||||
outputs.setStatus(com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum.SUCCESS);
|
||||
outputs.setMessage("通知发送成功");
|
||||
return outputs;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send notification", e);
|
||||
NotificationOutputs outputs = new NotificationOutputs();
|
||||
outputs.setStatus("FAILURE");
|
||||
outputs.setStatus(com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum.FAILURE);
|
||||
outputs.setMessage("通知发送失败: " + e.getMessage());
|
||||
return outputs;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.outputs;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 审批节点输出
|
||||
@ -9,7 +10,8 @@ import lombok.Data;
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class ApprovalOutputs {
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApprovalOutputs extends BaseNodeOutputs {
|
||||
|
||||
/**
|
||||
* 审批结果
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.outputs;
|
||||
|
||||
import com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 节点输出基类
|
||||
* 所有节点输出类的公共父类,包含统一的执行状态字段
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class BaseNodeOutputs {
|
||||
|
||||
/**
|
||||
* 节点执行状态
|
||||
* 用于流程条件判断,所有节点统一使用此字段
|
||||
*/
|
||||
private NodeExecutionStatusEnum status;
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.outputs;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Jenkins构建节点输出
|
||||
@ -9,7 +10,8 @@ import lombok.Data;
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class JenkinsBuildOutputs {
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class JenkinsBuildOutputs extends BaseNodeOutputs {
|
||||
|
||||
/**
|
||||
* 构建编号
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.outputs;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 通知节点输出
|
||||
@ -9,11 +10,12 @@ import lombok.Data;
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class NotificationOutputs {
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class NotificationOutputs extends BaseNodeOutputs {
|
||||
|
||||
/**
|
||||
* 执行状态
|
||||
* 通知发送结果详细信息
|
||||
*/
|
||||
private String status;
|
||||
private String message;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto.outputs;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Shell脚本节点输出
|
||||
@ -9,7 +10,8 @@ import lombok.Data;
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Data
|
||||
public class ShellOutputs {
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ShellOutputs extends BaseNodeOutputs {
|
||||
|
||||
/**
|
||||
* 退出码
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
package com.qqchen.deploy.backend.workflow.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 节点执行状态枚举
|
||||
* 所有工作流节点的统一执行状态
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-10-22
|
||||
*/
|
||||
@Getter
|
||||
public enum NodeExecutionStatusEnum {
|
||||
|
||||
/**
|
||||
* 执行成功
|
||||
*/
|
||||
SUCCESS("SUCCESS", "执行成功"),
|
||||
|
||||
/**
|
||||
* 执行失败
|
||||
*/
|
||||
FAILURE("FAILURE", "执行失败");
|
||||
|
||||
@JsonValue
|
||||
private final String code;
|
||||
|
||||
private final String description;
|
||||
|
||||
NodeExecutionStatusEnum(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码获取枚举
|
||||
*
|
||||
* @param code 状态编码
|
||||
* @return 枚举实例
|
||||
*/
|
||||
public static NodeExecutionStatusEnum fromCode(String code) {
|
||||
for (NodeExecutionStatusEnum status : values()) {
|
||||
if (status.code.equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown NodeExecutionStatus code: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user