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 20687481..372bfb8e 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 @@ -26,6 +26,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import org.apache.commons.lang3.StringUtils; import java.time.Instant; import java.time.LocalDateTime; @@ -206,7 +207,7 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration { } public JenkinsCrumbIssuerResponse convertResponse(ResponseEntity response) { - // 1. 从���应体中解析JSON + // 1. 从响应体中解析JSON ObjectMapper objectMapper = new ObjectMapper(); JsonNode jsonNode; try { @@ -230,17 +231,18 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration { * 通用的Jenkins API调用方法 * * @param externalSystem Jenkins系统配置 - * @param path API路径 - * @param treeQuery tree查询参数 - * @param responseClass 响应类型 - * @param 响应类型泛型 + * @param path API路径 + * @param treeQuery tree查询参数 + * @param jsonArrayKey JSON数组的key + * @param responseType 响应类型的Class对象,用于类型安全 + * @param 响应类型泛型 * @return API响应结果 */ private List callJenkinsApi(ExternalSystem externalSystem, - String path, - String treeQuery, - String jsonArrayKey, - Class responseClass) { + String path, + String treeQuery, + String jsonArrayKey, + Class responseType) { try { String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl()) .path(path) @@ -259,14 +261,15 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration { if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(response.getBody()); + // 使用responseType进行类型安全的转换 return mapper.convertValue(root.get(jsonArrayKey), - new TypeReference>() { - }); + mapper.getTypeFactory().constructCollectionType(List.class, responseType)); } 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); + log.error("Failed to call Jenkins API: path={}, error={}, responseType={}", + path, e.getMessage(), responseType.getSimpleName(), e); + throw new RuntimeException("调用Jenkins API��败: " + path, e); } } @@ -277,38 +280,73 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration { * @return 视图列表 */ public List listViews(ExternalSystem externalSystem) { - String treeQuery = "views[name,url,description,_class,primaryView," + - "properties[owner,configurationStatus,filter]," + - "jobs[*,lastBuild[*],lastCompletedBuild[*]]]"; - return callJenkinsApi( + String treeQuery = "views[name,url,description,_class]"; + List views = callJenkinsApi( externalSystem, "/api/json", treeQuery, "views", JenkinsViewResponse.class ); + + // 过滤和清洗数据 + return views.stream() + .filter(view -> !"all".equalsIgnoreCase(view.getName())) + .peek(view -> { + // 清理描述中的多余空格 + if (view.getDescription() != null) { + view.setDescription(view.getDescription().trim()); + } + // 转换URL为相对路径 + if (view.getUrl() != null) { + String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/"); + view.setUrl(view.getUrl().replace(baseUrl, "")); + } + }) + .toList(); } /** * 查询视图下的所有任务 * * @param externalSystem Jenkins系统配置 - * @param viewName 视图名称 + * @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( + // 只查询必要的字段 + String treeQuery = "jobs[name,url,description,buildable,nextBuildNumber," + + "lastBuild[number],color]"; + + List jobs = callJenkinsApi( externalSystem, "/view/" + viewName + "/api/json", treeQuery, "jobs", JenkinsJobResponse.class ); + + // 过滤和清洗数据 + String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/"); + return jobs.stream() + .peek(job -> { + // 清理描述中的多余空格 + if (job.getDescription() != null) { + job.setDescription(job.getDescription().trim()); + } + // 转换URL为相对路径 + if (job.getUrl() != null) { + job.setUrl(job.getUrl().replace(baseUrl, "")); + } + // 设置默认值 + if (job.getBuildable() == null) { + job.setBuildable(false); + } + if (job.getNextBuildNumber() == null) { + job.setNextBuildNumber(1); + } + }) + .toList(); } /** 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 index e96015d7..131662c7 100644 --- 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 @@ -1,14 +1,15 @@ package com.qqchen.deploy.backend.deploy.integration.response; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; -import java.util.Map; /** * Jenkins任务响应对象 */ @Data +@JsonIgnoreProperties(ignoreUnknown = true) public class JenkinsJobResponse { /** * 任务名称 @@ -25,12 +26,6 @@ public class JenkinsJobResponse { */ private String description; - /** - * 任务类型 - */ - @JsonProperty("_class") - private String jobClass; - /** * 是否可构建 */ @@ -41,26 +36,11 @@ public class JenkinsJobResponse { */ private Boolean inQueue; - /** - * 是否保持构建 - */ - private Boolean keepDependencies; - /** * 下一个构建号 */ private Integer nextBuildNumber; - /** - * 并发构建 - */ - private Boolean concurrentBuild; - - /** - * 禁用原因 - */ - private String disabledReason; - /** * 显示名称 */ @@ -76,71 +56,44 @@ public class JenkinsJobResponse { */ private String color; + /** + * Jenkins类型 + */ + private String _class; + /** * 最后一次构建 */ - @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; - /** * 健康报告列表 */ private List healthReports; - + @Data + @JsonIgnoreProperties(ignoreUnknown = true) 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; + private String _class; + private Long timestamp; + private Long duration; + private String result; } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsJobService.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsJobService.java index 9bd5155f..c5f7692c 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsJobService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsJobService.java @@ -1,5 +1,7 @@ package com.qqchen.deploy.backend.deploy.service; +import com.qqchen.deploy.backend.deploy.entity.ExternalSystem; +import com.qqchen.deploy.backend.deploy.entity.JenkinsView; import com.qqchen.deploy.backend.framework.service.IBaseService; import com.qqchen.deploy.backend.deploy.entity.JenkinsJob; import com.qqchen.deploy.backend.deploy.dto.JenkinsJobDTO; @@ -13,11 +15,11 @@ public interface IJenkinsJobService extends IBaseService new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND)); - - JenkinsView jenkinsView = jenkinsViewRepository.findById(viewId) - .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND)); + public Integer syncJobsByView(ExternalSystem externalSystem, JenkinsView view) { - // 2. 调用Jenkins API获取任务列表 - List jobResponses = jenkinsServiceIntegration.listJobs(externalSystem, jenkinsView.getViewName()); + List jobResponses = jenkinsServiceIntegration.listJobs(externalSystem, view.getViewName()); if (jobResponses.isEmpty()) { - log.info("No jobs found in Jenkins view: {}", jenkinsView.getViewName()); + log.info("No jobs found in Jenkins view: {}", view.getViewName()); return 0; } @@ -111,8 +103,7 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl jenkinsJobs = new ArrayList<>(); for (JenkinsJobResponse jobResponse : jobResponses) { // 查找是否存在相同的任务 - Optional existingJob = jenkinsJobRepository - .findByExternalSystemIdAndViewIdAndJobName(externalSystemId, viewId, jobResponse.getName()); + Optional existingJob = jenkinsJobRepository.findByExternalSystemIdAndViewIdAndJobName(externalSystem.getId(), view.getId(), jobResponse.getName()); JenkinsJob jenkinsJob; if (existingJob.isPresent()) { @@ -123,8 +114,8 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl