From 332f27fac1a8ff821d6fb9cae9f0c5896b1faee5 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Wed, 22 Oct 2025 22:44:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=9F=E6=88=90=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E6=9C=8D=E5=8A=A1=E4=BB=A3=E7=A0=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IJenkinsServiceIntegration.java | 10 ++ .../impl/JenkinsServiceIntegrationImpl.java | 50 ++++++++ .../delegate/JenkinsBuildDelegate.java | 110 +++++++++++++----- .../delegate/NotificationNodeDelegate.java | 6 +- .../workflow/dto/outputs/ApprovalOutputs.java | 4 +- .../workflow/dto/outputs/BaseNodeOutputs.java | 22 ++++ .../dto/outputs/JenkinsBuildOutputs.java | 4 +- .../dto/outputs/NotificationOutputs.java | 8 +- .../workflow/dto/outputs/ShellOutputs.java | 4 +- .../enums/NodeExecutionStatusEnum.java | 51 ++++++++ 10 files changed, 234 insertions(+), 35 deletions(-) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/outputs/BaseNodeOutputs.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeExecutionStatusEnum.java diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/IJenkinsServiceIntegration.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/IJenkinsServiceIntegration.java index 67fc9ef8..c859762f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/IJenkinsServiceIntegration.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/IJenkinsServiceIntegration.java @@ -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); + /** * 查询所有视图 * diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java index 7645aa04..b5107db4 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java @@ -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 entity = new HttpEntity<>(createHeaders(externalSystem)); + ResponseEntity 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"); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsBuildDelegate.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsBuildDelegate.java index ff0182fa..0a669db9 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsBuildDelegate.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsBuildDelegate.java @@ -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 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 - Map parameters = new HashMap<>(); - // parameters.put("PROJECT", input.getProject()); + String jobName = input.getProject(); - // String queueId = jenkinsServiceIntegration.buildWithParameters( - // externalSystem, input.getProject(), parameters); - // JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(queueId); - // JenkinsBuildStatus status = pollBuildStatus(externalSystem, input.getProject(), buildInfo.getBuildNumber()); + // 2. 触发构建 + Map parameters = new HashMap<>(); + // 可以根据需要添加构建参数 + // parameters.put("BRANCH", "main"); + + String queueId = jenkinsServiceIntegration.buildWithParameters( + externalSystem, jobName, parameters); + + // 3. 等待构建从队列中开始 + JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(queueId); + + // 4. 轮询构建状态直到完成 + // 注意:如果构建失败或被取消,pollBuildStatus 会抛出 BpmnError,触发错误边界事件 + // 只有成功时才会返回到这里 + JenkinsBuildStatus buildStatus = pollBuildStatus(externalSystem, jobName, buildInfo.getBuildNumber()); - // 3. 构造输出结果 + // 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