From 7bc5f66967157497f8b182a6edb07cfd09556136 Mon Sep 17 00:00:00 2001 From: asp_ly Date: Sat, 28 Dec 2024 22:13:37 +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 | 76 +++++++++- .../impl/JenkinsServiceIntegration.java | 114 +++++++++++++- .../response/JenkinsBuildResponse.java | 138 +++++++++++++++++ .../response/JenkinsJobResponse.java | 141 ++++++++++++++++++ .../response/JenkinsViewResponse.java | 65 ++++++++ 5 files changed, 530 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsBuildResponse.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsJobResponse.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsViewResponse.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 b93ee81e..73763e93 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 @@ -1,19 +1,89 @@ package com.qqchen.deploy.backend.deploy.integration; +import com.qqchen.deploy.backend.deploy.entity.ExternalSystem; import com.qqchen.deploy.backend.deploy.enums.JenkinsBuildStatus; -import com.qqchen.deploy.backend.deploy.integration.response.JenkinsBuildInfoResponse; -import com.qqchen.deploy.backend.deploy.integration.response.JenkinsQueueBuildInfoResponse; -import com.qqchen.deploy.backend.deploy.integration.response.JenkinsCrumbIssuerResponse; +import com.qqchen.deploy.backend.deploy.integration.response.*; +import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum; +import java.util.List; + +/** + * Jenkins集成服务接口 + */ public interface IJenkinsServiceIntegration extends IExternalSystemIntegration { + /** + * 测试Jenkins连接 + * + * @param system Jenkins系统配置 + * @return 连接是否成功 + */ + boolean testConnection(ExternalSystem system); + /** + * 获取Jenkins Crumb(用于CSRF保护) + * + * @return Crumb响应信息 + */ JenkinsCrumbIssuerResponse getJenkinsCrumbIssue(); + /** + * 使用参数触发构建 + * + * @return 构建队列ID + */ String buildWithParameters(); + /** + * 获取队列中的构建信息 + * + * @param queueId 队列ID + * @return 队列构建信息 + */ JenkinsQueueBuildInfoResponse getQueuedBuildInfo(String queueId); + /** + * 获取构建状态 + * + * @param jobName 任务名称 + * @param buildNumber 构建编号 + * @return 构建状态 + */ JenkinsBuildStatus getBuildStatus(String jobName, Integer buildNumber); + /** + * 查询所有视图 + * + * @param externalSystem Jenkins系统配置 + * @return 视图列表 + */ + List listViews(ExternalSystem externalSystem); + + /** + * 查询视图下的所有任务 + * + * @param externalSystem Jenkins系统配置 + * @param viewName 视图名称 + * @return 任务列表 + */ + List listJobs(ExternalSystem externalSystem, String viewName); + + /** + * 查询任务的构建信息 + * + * @param externalSystem Jenkins系统配置 + * @param jobName 任务名称 + * @return 构建信息列表 + */ + List listBuilds(ExternalSystem externalSystem, String jobName); + + /** + * 获取系统类型 + * + * @return Jenkins系统类型 + */ + @Override + default ExternalSystemTypeEnum getSystemType() { + return ExternalSystemTypeEnum.JENKINS; + } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegration.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegration.java index 721b1815..20687481 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegration.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegration.java @@ -1,14 +1,18 @@ package com.qqchen.deploy.backend.deploy.integration.impl; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.deploy.entity.ExternalSystem; import com.qqchen.deploy.backend.deploy.enums.JenkinsBuildStatus; import com.qqchen.deploy.backend.deploy.integration.IJenkinsServiceIntegration; import com.qqchen.deploy.backend.deploy.integration.response.JenkinsBuildInfoResponse; +import com.qqchen.deploy.backend.deploy.integration.response.JenkinsBuildResponse; +import com.qqchen.deploy.backend.deploy.integration.response.JenkinsJobResponse; import com.qqchen.deploy.backend.deploy.integration.response.JenkinsQueueBuildInfoResponse; import com.qqchen.deploy.backend.deploy.integration.response.JenkinsCrumbIssuerResponse; +import com.qqchen.deploy.backend.deploy.integration.response.JenkinsViewResponse; import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository; import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum; import jakarta.annotation.Resource; @@ -21,11 +25,14 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Base64; +import java.util.Collections; +import java.util.List; import java.util.Map; @Slf4j @@ -199,7 +206,7 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration { } public JenkinsCrumbIssuerResponse convertResponse(ResponseEntity response) { - // 1. 从响应体中解析JSON + // 1. 从���应体中解析JSON ObjectMapper objectMapper = new ObjectMapper(); JsonNode jsonNode; try { @@ -218,4 +225,109 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration { return result; } + + /** + * 通用的Jenkins API调用方法 + * + * @param externalSystem Jenkins系统配置 + * @param path API路径 + * @param treeQuery tree查询参数 + * @param responseClass 响应类型 + * @param 响应类型泛型 + * @return API响应结果 + */ + private List callJenkinsApi(ExternalSystem externalSystem, + String path, + String treeQuery, + String jsonArrayKey, + Class responseClass) { + try { + String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl()) + .path(path) + .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) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(response.getBody()); + return mapper.convertValue(root.get(jsonArrayKey), + new TypeReference>() { + }); + } + return Collections.emptyList(); + } catch (Exception e) { + log.error("Failed to call Jenkins API: path={}, error={}", path, e.getMessage(), e); + throw new RuntimeException("调用Jenkins API失败: " + path, e); + } + } + + /** + * 查询所有视图 + * + * @param externalSystem Jenkins系统配置 + * @return 视图列表 + */ + public List listViews(ExternalSystem externalSystem) { + String treeQuery = "views[name,url,description,_class,primaryView," + + "properties[owner,configurationStatus,filter]," + + "jobs[*,lastBuild[*],lastCompletedBuild[*]]]"; + return callJenkinsApi( + externalSystem, + "/api/json", + treeQuery, + "views", + JenkinsViewResponse.class + ); + } + + /** + * 查询视图下的所有任务 + * + * @param externalSystem Jenkins系统配置 + * @param viewName 视图名称 + * @return 任务列表 + */ + public List listJobs(ExternalSystem externalSystem, String viewName) { + String treeQuery = "jobs[name,url,description,_class,buildable,inQueue," + + "keepDependencies,nextBuildNumber,concurrentBuild,disabled," + + "displayName,fullName,color,lastBuild[*],lastCompletedBuild[*]," + + "lastFailedBuild[*],lastSuccessfulBuild[*],lastUnstableBuild[*]," + + "lastUnsuccessfulBuild[*],property[parameterDefinitions[*]]]"; + return callJenkinsApi( + externalSystem, + "/view/" + viewName + "/api/json", + treeQuery, + "jobs", + JenkinsJobResponse.class + ); + } + + /** + * 查询任务的构建信息 + * + * @param externalSystem Jenkins系统配置 + * @param jobName 任务名称 + * @return 构建信息列表 + */ + public List listBuilds(ExternalSystem externalSystem, String jobName) { + String treeQuery = "builds[number,url,result,timestamp,duration,building," + + "description,displayName,fullDisplayName,id,keepLog,queueId," + + "actions[*],changeSets[*],artifacts[*]]"; + return callJenkinsApi( + externalSystem, + "/job/" + jobName + "/api/json", + treeQuery, + "builds", + JenkinsBuildResponse.class + ); + } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsBuildResponse.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsBuildResponse.java new file mode 100644 index 00000000..e1beda17 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsBuildResponse.java @@ -0,0 +1,138 @@ +package com.qqchen.deploy.backend.deploy.integration.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import java.util.List; +import java.util.Map; + +/** + * Jenkins构建响应对象 + */ +@Data +public class JenkinsBuildResponse { + /** + * 构建编号 + */ + private Integer number; + + /** + * 构建URL + */ + private String url; + + /** + * 构建类型 + */ + @JsonProperty("_class") + private String buildClass; + + /** + * 构建结果 + */ + private String result; + + /** + * 构建时间戳 + */ + private Long timestamp; + + /** + * 构建开始时间 + */ + private Long startTimeMillis; + + /** + * 构建持续时间(毫秒) + */ + private Long duration; + + /** + * 预计持续时间 + */ + private Long estimatedDuration; + + /** + * 是否正在构建 + */ + private Boolean building; + + /** + * 构建描述 + */ + private String description; + + /** + * 显示名称 + */ + private String displayName; + + /** + * 完整显示名称 + */ + private String fullDisplayName; + + /** + * 构建ID + */ + private String id; + + /** + * 保持永久 + */ + private Boolean keepLog; + + /** + * 构建队列ID + */ + private Long queueId; + + /** + * 构建参数 + */ + private List actions; + + /** + * 构建变更集 + */ + private List changeSets; + + /** + * 构建制品 + */ + private List artifacts; + + /** + * 构建控制台输出 + */ + private String consoleLog; + + @Data + public static class BuildParameter { + @JsonProperty("_class") + private String type; + private Map parameters; + private Map causes; + } + + @Data + public static class ChangeSet { + private String kind; + private List items; + } + + @Data + public static class ChangeSetItem { + private String commitId; + private String author; + private String message; + private Long timestamp; + private List affectedPaths; + } + + @Data + public static class Artifact { + private String displayPath; + private String fileName; + private String relativePath; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsJobResponse.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsJobResponse.java new file mode 100644 index 00000000..70a987e9 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsJobResponse.java @@ -0,0 +1,141 @@ +package com.qqchen.deploy.backend.deploy.integration.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import java.util.List; +import java.util.Map; + +/** + * Jenkins任务响应对象 + */ +@Data +public class JenkinsJobResponse { + /** + * 任务名称 + */ + private String name; + + /** + * 任务URL + */ + private String url; + + /** + * 任务描述 + */ + private String description; + + /** + * 任务类型 + */ + @JsonProperty("_class") + private String jobClass; + + /** + * 是否可构建 + */ + private Boolean buildable; + + /** + * 是否在队列中 + */ + private Boolean inQueue; + + /** + * 是否保持构建 + */ + private Boolean keepDependencies; + + /** + * 下一个构建号 + */ + private Integer nextBuildNumber; + + /** + * 并发构建 + */ + private Boolean concurrentBuild; + + /** + * 禁用原因 + */ + private String disabledReason; + + /** + * 显示名称 + */ + private String displayName; + + /** + * 全名 + */ + private String fullName; + + /** + * 颜色(状态) + */ + private String color; + + /** + * 最后一次构建 + */ + @JsonProperty("lastBuild") + private BuildInfo lastBuild; + + /** + * 最后一次完成的构建 + */ + @JsonProperty("lastCompletedBuild") + private BuildInfo lastCompletedBuild; + + /** + * 最后一次失败的构建 + */ + @JsonProperty("lastFailedBuild") + private BuildInfo lastFailedBuild; + + /** + * 最后一次成功的构建 + */ + @JsonProperty("lastSuccessfulBuild") + private BuildInfo lastSuccessfulBuild; + + /** + * 最后一次不稳定的构建 + */ + @JsonProperty("lastUnstableBuild") + private BuildInfo lastUnstableBuild; + + /** + * 最后一次不成功的构建 + */ + @JsonProperty("lastUnsuccessfulBuild") + private BuildInfo lastUnsuccessfulBuild; + + /** + * 构建参数定义 + */ + private List parameterDefinitions; + + @Data + public static class BuildInfo { + private Integer number; + private String url; + } + + @Data + public static class ParameterDefinition { + @JsonProperty("_class") + private String type; + private String name; + private String description; + private String defaultValue; + private List choices; + } + + @Data + public static class Choice { + private String value; + private String name; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsViewResponse.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsViewResponse.java new file mode 100644 index 00000000..f7b79213 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsViewResponse.java @@ -0,0 +1,65 @@ +package com.qqchen.deploy.backend.deploy.integration.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import java.util.List; + +/** + * Jenkins视图响应对象 + */ +@Data +public class JenkinsViewResponse { + /** + * 视图名称 + */ + private String name; + + /** + * 视图URL + */ + private String url; + + /** + * 视图描述 + */ + private String description; + + /** + * 视图类型 + */ + @JsonProperty("_class") + private String viewClass; + + /** + * 是否为主视图 + */ + private Boolean isPrimary; + + /** + * 视图属性 + */ + private Properties properties; + + /** + * 视图下的任务 + */ + private List jobs; + + @Data + public static class Properties { + /** + * 视图所有者 + */ + private String owner; + + /** + * 视图配置状态 + */ + private String configurationStatus; + + /** + * 视图过滤器 + */ + private String filter; + } +} \ No newline at end of file