增加生成后端服务代码。

This commit is contained in:
asp_ly 2024-12-28 23:37:04 +08:00
parent 1a6deb77d4
commit 9a01ae8d30
5 changed files with 106 additions and 116 deletions

View File

@ -26,6 +26,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import org.apache.commons.lang3.StringUtils;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -206,7 +207,7 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration {
} }
public JenkinsCrumbIssuerResponse convertResponse(ResponseEntity<String> response) { public JenkinsCrumbIssuerResponse convertResponse(ResponseEntity<String> response) {
// 1. <EFBFBD><EFBFBD><EFBFBD>应体中解析JSON // 1. 应体中解析JSON
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode; JsonNode jsonNode;
try { try {
@ -230,17 +231,18 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration {
* 通用的Jenkins API调用方法 * 通用的Jenkins API调用方法
* *
* @param externalSystem Jenkins系统配置 * @param externalSystem Jenkins系统配置
* @param path API路径 * @param path API路径
* @param treeQuery tree查询参数 * @param treeQuery tree查询参数
* @param responseClass 响应类型 * @param jsonArrayKey JSON数组的key
* @param <T> 响应类型泛型 * @param responseType 响应类型的Class对象用于类型安全
* @param <T> 响应类型泛型
* @return API响应结果 * @return API响应结果
*/ */
private <T> List<T> callJenkinsApi(ExternalSystem externalSystem, private <T> List<T> callJenkinsApi(ExternalSystem externalSystem,
String path, String path,
String treeQuery, String treeQuery,
String jsonArrayKey, String jsonArrayKey,
Class<T> responseClass) { Class<T> responseType) {
try { try {
String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl()) String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl())
.path(path) .path(path)
@ -259,14 +261,15 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration {
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(response.getBody()); JsonNode root = mapper.readTree(response.getBody());
// 使用responseType进行类型安全的转换
return mapper.convertValue(root.get(jsonArrayKey), return mapper.convertValue(root.get(jsonArrayKey),
new TypeReference<List<T>>() { mapper.getTypeFactory().constructCollectionType(List.class, responseType));
});
} }
return Collections.emptyList(); return Collections.emptyList();
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to call Jenkins API: path={}, error={}", path, e.getMessage(), e); log.error("Failed to call Jenkins API: path={}, error={}, responseType={}",
throw new RuntimeException("调用Jenkins API失败: " + path, e); path, e.getMessage(), responseType.getSimpleName(), e);
throw new RuntimeException("调用Jenkins API<50><49>败: " + path, e);
} }
} }
@ -277,38 +280,73 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration {
* @return 视图列表 * @return 视图列表
*/ */
public List<JenkinsViewResponse> listViews(ExternalSystem externalSystem) { public List<JenkinsViewResponse> listViews(ExternalSystem externalSystem) {
String treeQuery = "views[name,url,description,_class,primaryView," + String treeQuery = "views[name,url,description,_class]";
"properties[owner,configurationStatus,filter]," + List<JenkinsViewResponse> views = callJenkinsApi(
"jobs[*,lastBuild[*],lastCompletedBuild[*]]]";
return callJenkinsApi(
externalSystem, externalSystem,
"/api/json", "/api/json",
treeQuery, treeQuery,
"views", "views",
JenkinsViewResponse.class 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 externalSystem Jenkins系统配置
* @param viewName 视图名称 * @param viewName 视图名称
* @return 任务列表 * @return 任务列表
*/ */
public List<JenkinsJobResponse> listJobs(ExternalSystem externalSystem, String viewName) { public List<JenkinsJobResponse> listJobs(ExternalSystem externalSystem, String viewName) {
String treeQuery = "jobs[name,url,description,_class,buildable,inQueue," + // 只查询必要的字段
"keepDependencies,nextBuildNumber,concurrentBuild,disabled," + String treeQuery = "jobs[name,url,description,buildable,nextBuildNumber," +
"displayName,fullName,color,lastBuild[*],lastCompletedBuild[*]," + "lastBuild[number],color]";
"lastFailedBuild[*],lastSuccessfulBuild[*],lastUnstableBuild[*]," +
"lastUnsuccessfulBuild[*],property[parameterDefinitions[*]]]"; List<JenkinsJobResponse> jobs = callJenkinsApi(
return callJenkinsApi(
externalSystem, externalSystem,
"/view/" + viewName + "/api/json", "/view/" + viewName + "/api/json",
treeQuery, treeQuery,
"jobs", "jobs",
JenkinsJobResponse.class 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();
} }
/** /**

View File

@ -1,14 +1,15 @@
package com.qqchen.deploy.backend.deploy.integration.response; package com.qqchen.deploy.backend.deploy.integration.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Jenkins任务响应对象 * Jenkins任务响应对象
*/ */
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class JenkinsJobResponse { public class JenkinsJobResponse {
/** /**
* 任务名称 * 任务名称
@ -25,12 +26,6 @@ public class JenkinsJobResponse {
*/ */
private String description; private String description;
/**
* 任务类型
*/
@JsonProperty("_class")
private String jobClass;
/** /**
* 是否可构建 * 是否可构建
*/ */
@ -41,26 +36,11 @@ public class JenkinsJobResponse {
*/ */
private Boolean inQueue; private Boolean inQueue;
/**
* 是否保持构建
*/
private Boolean keepDependencies;
/** /**
* 下一个构建号 * 下一个构建号
*/ */
private Integer nextBuildNumber; private Integer nextBuildNumber;
/**
* 并发构建
*/
private Boolean concurrentBuild;
/**
* 禁用原因
*/
private String disabledReason;
/** /**
* 显示名称 * 显示名称
*/ */
@ -76,71 +56,44 @@ public class JenkinsJobResponse {
*/ */
private String color; private String color;
/**
* Jenkins类型
*/
private String _class;
/** /**
* 最后一次构建 * 最后一次构建
*/ */
@JsonProperty("lastBuild")
private BuildInfo lastBuild; private BuildInfo lastBuild;
/** /**
* 最后一次完成的构建 * 最后一次完成的构建
*/ */
@JsonProperty("lastCompletedBuild")
private BuildInfo lastCompletedBuild; private BuildInfo lastCompletedBuild;
/** /**
* 最后一次失败的构建 * 最后一次失败的构建
*/ */
@JsonProperty("lastFailedBuild")
private BuildInfo lastFailedBuild; private BuildInfo lastFailedBuild;
/** /**
* 最后一次成功的构建 * 最后一次成功的构建
*/ */
@JsonProperty("lastSuccessfulBuild")
private BuildInfo lastSuccessfulBuild; private BuildInfo lastSuccessfulBuild;
/**
* 最后一次不稳定的构建
*/
@JsonProperty("lastUnstableBuild")
private BuildInfo lastUnstableBuild;
/**
* 最后一次不成功的构建
*/
@JsonProperty("lastUnsuccessfulBuild")
private BuildInfo lastUnsuccessfulBuild;
/**
* 构建参数定义
*/
private List<ParameterDefinition> parameterDefinitions;
/** /**
* 健康报告列表 * 健康报告列表
*/ */
private List<JenkinsHealthReportResponse> healthReports; private List<JenkinsHealthReportResponse> healthReports;
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class BuildInfo { public static class BuildInfo {
private Integer number; private Integer number;
private String url; private String url;
} private String _class;
private Long timestamp;
@Data private Long duration;
public static class ParameterDefinition { private String result;
@JsonProperty("_class")
private String type;
private String name;
private String description;
private String defaultValue;
private List<Choice> choices;
}
@Data
public static class Choice {
private String value;
private String name;
} }
} }

View File

@ -1,5 +1,7 @@
package com.qqchen.deploy.backend.deploy.service; 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.framework.service.IBaseService;
import com.qqchen.deploy.backend.deploy.entity.JenkinsJob; import com.qqchen.deploy.backend.deploy.entity.JenkinsJob;
import com.qqchen.deploy.backend.deploy.dto.JenkinsJobDTO; import com.qqchen.deploy.backend.deploy.dto.JenkinsJobDTO;
@ -13,11 +15,11 @@ public interface IJenkinsJobService extends IBaseService<JenkinsJob, JenkinsJobD
/** /**
* 同步指定视图下的Jenkins任务 * 同步指定视图下的Jenkins任务
* *
* @param externalSystemId 外部系统ID * @param externalSystem 外部系统
* @param viewId 视图ID * @param view 视图
* @return 同步的任务数量 * @return 同步的任务数量
*/ */
Integer syncJobsByView(Long externalSystemId, Long viewId); Integer syncJobsByView(ExternalSystem externalSystem, JenkinsView view);
/** /**
* 同步外部系统下所有视图的Jenkins任务 * 同步外部系统下所有视图的Jenkins任务

View File

@ -69,7 +69,7 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl<JenkinsJob, JenkinsJo
int totalSyncedJobs = 0; int totalSyncedJobs = 0;
for (JenkinsView view : views) { for (JenkinsView view : views) {
try { try {
Integer syncedJobs = syncJobsByView(externalSystemId, view.getId()); Integer syncedJobs = syncJobsByView(externalSystem, view);
totalSyncedJobs += syncedJobs; totalSyncedJobs += syncedJobs;
log.info("Successfully synchronized {} jobs for view: {}", syncedJobs, view.getViewName()); log.info("Successfully synchronized {} jobs for view: {}", syncedJobs, view.getViewName());
} catch (Exception e) { } catch (Exception e) {
@ -78,32 +78,24 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl<JenkinsJob, JenkinsJo
} }
} }
log.info("Successfully synchronized total {} jobs for external system: {}", log.info("Successfully synchronized total {} jobs for external system: {}", totalSyncedJobs, externalSystemId);
totalSyncedJobs, externalSystemId);
return totalSyncedJobs; return totalSyncedJobs;
} }
/** /**
* 同步指定视图下的Jenkins任务 * 同步指定视图下的Jenkins任务
* *
* @param externalSystemId 外部系统ID * @param externalSystem 外部系统
* @param viewId 视图ID * @param view 视图
* @return 同步的任务数量 * @return 同步的任务数量
*/ */
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Integer syncJobsByView(Long externalSystemId, Long viewId) { public Integer syncJobsByView(ExternalSystem externalSystem, JenkinsView view) {
// 1. 查询外部系统和视图
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
.orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));
JenkinsView jenkinsView = jenkinsViewRepository.findById(viewId)
.orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
// 2. 调用Jenkins API获取任务列表 List<JenkinsJobResponse> jobResponses = jenkinsServiceIntegration.listJobs(externalSystem, view.getViewName());
List<JenkinsJobResponse> jobResponses = jenkinsServiceIntegration.listJobs(externalSystem, jenkinsView.getViewName());
if (jobResponses.isEmpty()) { 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; return 0;
} }
@ -111,8 +103,7 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl<JenkinsJob, JenkinsJo
List<JenkinsJob> jenkinsJobs = new ArrayList<>(); List<JenkinsJob> jenkinsJobs = new ArrayList<>();
for (JenkinsJobResponse jobResponse : jobResponses) { for (JenkinsJobResponse jobResponse : jobResponses) {
// 查找是否存在相同的任务 // 查找是否存在相同的任务
Optional<JenkinsJob> existingJob = jenkinsJobRepository Optional<JenkinsJob> existingJob = jenkinsJobRepository.findByExternalSystemIdAndViewIdAndJobName(externalSystem.getId(), view.getId(), jobResponse.getName());
.findByExternalSystemIdAndViewIdAndJobName(externalSystemId, viewId, jobResponse.getName());
JenkinsJob jenkinsJob; JenkinsJob jenkinsJob;
if (existingJob.isPresent()) { if (existingJob.isPresent()) {
@ -123,8 +114,8 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl<JenkinsJob, JenkinsJo
} else { } else {
// 创建新的任务 // 创建新的任务
jenkinsJob = new JenkinsJob(); jenkinsJob = new JenkinsJob();
jenkinsJob.setExternalSystemId(externalSystemId); jenkinsJob.setExternalSystemId(externalSystem.getId());
jenkinsJob.setViewId(viewId); jenkinsJob.setViewId(view.getId());
jenkinsJob.setJobName(jobResponse.getName()); jenkinsJob.setJobName(jobResponse.getName());
updateJobFromResponse(jenkinsJob, jobResponse); updateJobFromResponse(jenkinsJob, jobResponse);
log.debug("Creating new Jenkins job: {}", jenkinsJob.getJobName()); log.debug("Creating new Jenkins job: {}", jenkinsJob.getJobName());
@ -135,7 +126,7 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl<JenkinsJob, JenkinsJo
// 4. 批量保存或更新 // 4. 批量保存或更新
jenkinsJobRepository.saveAll(jenkinsJobs); jenkinsJobRepository.saveAll(jenkinsJobs);
log.info("Successfully synchronized {} Jenkins jobs for view: {}", jenkinsJobs.size(), jenkinsView.getViewName()); log.info("Successfully synchronized {} Jenkins jobs for view: {}", jenkinsJobs.size(), view.getViewName());
return jenkinsJobs.size(); return jenkinsJobs.size();
} }

View File

@ -69,16 +69,14 @@ public class JenkinsViewServiceImpl extends BaseServiceImpl<JenkinsView, Jenkins
if (existingView.isPresent()) { if (existingView.isPresent()) {
// 更新已存在的视图 // 更新已存在的视图
jenkinsView = existingView.get(); jenkinsView = existingView.get();
jenkinsView.setViewUrl(viewResponse.getUrl()); updateViewFromResponse(jenkinsView, viewResponse);
jenkinsView.setDescription(viewResponse.getDescription());
log.debug("Updating existing Jenkins view: {}", jenkinsView.getViewName()); log.debug("Updating existing Jenkins view: {}", jenkinsView.getViewName());
} else { } else {
// 创建新的视图 // 创建新的视图
jenkinsView = new JenkinsView(); jenkinsView = new JenkinsView();
jenkinsView.setViewName(viewResponse.getName());
jenkinsView.setViewUrl(viewResponse.getUrl());
jenkinsView.setDescription(viewResponse.getDescription());
jenkinsView.setExternalSystemId(externalSystemId); jenkinsView.setExternalSystemId(externalSystemId);
jenkinsView.setViewName(viewResponse.getName());
updateViewFromResponse(jenkinsView, viewResponse);
log.debug("Creating new Jenkins view: {}", jenkinsView.getViewName()); log.debug("Creating new Jenkins view: {}", jenkinsView.getViewName());
} }
jenkinsViews.add(jenkinsView); jenkinsViews.add(jenkinsView);
@ -86,10 +84,18 @@ public class JenkinsViewServiceImpl extends BaseServiceImpl<JenkinsView, Jenkins
// 4. 批量保存或更新 // 4. 批量保存或更新
jenkinsViewRepository.saveAll(jenkinsViews); jenkinsViewRepository.saveAll(jenkinsViews);
log.info("Successfully synchronized {} Jenkins views for external system: {}", log.info("Successfully synchronized {} Jenkins views for external system: {}",
jenkinsViews.size(), externalSystemId); jenkinsViews.size(), externalSystemId);
return jenkinsViews.size(); return jenkinsViews.size();
} }
}
/**
* 从API响应更新视图信息
*/
private void updateViewFromResponse(JenkinsView jenkinsView, JenkinsViewResponse response) {
jenkinsView.setDescription(response.getDescription());
jenkinsView.setViewUrl(response.getUrl());
}
}