diff --git a/backend/.cursorrules b/backend/.cursorrules index 26e4e8cc..47483722 100644 --- a/backend/.cursorrules +++ b/backend/.cursorrules @@ -30,7 +30,7 @@ @Slf4j @Service @ServiceType(DATABASE) -public class ExternalSystemServiceImpl extends BaseServiceImpl +public class ExternalSystemServiceImpl extends BaseServiceImpl implements IExternalSystemService { @Override diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/JenkinsManagerApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/JenkinsManagerApiController.java index 945451ba..fdf19cb2 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/JenkinsManagerApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/JenkinsManagerApiController.java @@ -2,31 +2,58 @@ package com.qqchen.deploy.backend.deploy.api; import com.qqchen.deploy.backend.deploy.service.IJenkinsManagerService; import com.qqchen.deploy.backend.framework.api.Response; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; +import org.springframework.web.bind.annotation.*; /** - * Jenkins构建信息 Controller + * Jenkins管理API控制器 */ @Slf4j @RestController @RequestMapping("/api/v1/jenkins-manager") -@Tag(name = "Jenkins三方系统管理", description = "Jenkins三方系统管理") +@Tag(name = "Jenkins管理", description = "Jenkins管理相关接口") public class JenkinsManagerApiController { - @Resource private IJenkinsManagerService jenkinsManagerService; - @GetMapping("/views/page") - public Response page() { - return Response.success(jenkinsManagerService.viewsPage()); + @Operation(summary = "同步所有Jenkins数据") + @PostMapping("/{externalSystemId}/sync-all") + public Response syncAll( + @Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId + ) { + jenkinsManagerService.syncAll(externalSystemId); + return Response.success(); } + @Operation(summary = "同步Jenkins视图") + @PostMapping("/{externalSystemId}/sync-views") + public Response syncViews( + @Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId + ) { + jenkinsManagerService.syncViews(externalSystemId); + return Response.success(); + } + + @Operation(summary = "同步Jenkins任务") + @PostMapping("/{externalSystemId}/sync-jobs") + public Response syncJobs( + @Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId + ) { + jenkinsManagerService.syncJobs(externalSystemId); + return Response.success(); + } + + @Operation(summary = "同步Jenkins构建信息") + @PostMapping("/{externalSystemId}/sync-builds") + public Response syncBuilds( + @Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId + ) { + jenkinsManagerService.syncBuilds(externalSystemId); + return Response.success(); + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsHealthReportResponse.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsHealthReportResponse.java new file mode 100644 index 00000000..46b08b88 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/JenkinsHealthReportResponse.java @@ -0,0 +1,41 @@ +package com.qqchen.deploy.backend.deploy.integration.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * Jenkins健康报告响应对象 + * 用于表示Jenkins任务的健康状态,包括构建稳定性、测试结果、代码覆盖率等 + */ +@Data +public class JenkinsHealthReportResponse { + + /** + * 健康检查描述 + * 例如: "Build stability: No recent builds failed" + */ + private String description; + + /** + * 健康分数(0-100) + */ + private Integer score; + + /** + * 健康检查类型 + * 例如: "Build stability", "Test result", "Code coverage" + */ + @JsonProperty("_class") + private String type; + + /** + * 图标名称 + * 例如: "health-80plus" 表示健康度大于80% + */ + private String iconName; + + /** + * 图标URL + */ + private String iconUrl; +} \ 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 index 70a987e9..e96015d7 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 @@ -117,6 +117,11 @@ public class JenkinsJobResponse { */ private List parameterDefinitions; + /** + * 健康报告列表 + */ + private List healthReports; + @Data public static class BuildInfo { private Integer number; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsBuildRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsBuildRepository.java index dfd7fb4e..2eed6f4c 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsBuildRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsBuildRepository.java @@ -1,12 +1,35 @@ package com.qqchen.deploy.backend.deploy.repository; -import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.deploy.entity.JenkinsBuild; +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; +import java.util.List; + /** - * Jenkins构建信息 Repository + * Jenkins构建信息仓库接口 */ @Repository public interface IJenkinsBuildRepository extends IBaseRepository { + + /** + * 根据外部系统ID、任务ID和构建号查询构建信息 + * + * @param externalSystemId 外部系统ID + * @param jobId 任务ID + * @param buildNumber 构建号 + * @return 构建信息 + */ + Optional findByExternalSystemIdAndJobIdAndBuildNumber( + Long externalSystemId, Long jobId, Integer buildNumber); + + /** + * 根据外部系统ID和任务ID查询所有构建信息 + * + * @param externalSystemId 外部系统ID + * @param jobId 任务ID + * @return 构建信息列表 + */ + List findByExternalSystemIdAndJobId(Long externalSystemId, Long jobId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsJobRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsJobRepository.java index c5305397..885aa963 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsJobRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsJobRepository.java @@ -1,12 +1,34 @@ package com.qqchen.deploy.backend.deploy.repository; -import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.deploy.entity.JenkinsJob; +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; +import java.util.List; + /** - * Jenkins工作 Repository + * Jenkins任务仓库接口 */ @Repository public interface IJenkinsJobRepository extends IBaseRepository { + + /** + * 根据外部系统ID、视图ID和任务名称查询任务 + * + * @param externalSystemId 外部系统ID + * @param viewId 视图ID + * @param jobName 任务名称 + * @return 任务对象 + */ + Optional findByExternalSystemIdAndViewIdAndJobName(Long externalSystemId, Long viewId, String jobName); + + /** + * 根据外部系统ID和视图ID查询所有任务 + * + * @param externalSystemId 外部系统ID + * @param viewId 视图ID + * @return 任务列表 + */ + List findByExternalSystemIdAndViewId(Long externalSystemId, Long viewId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsViewRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsViewRepository.java index be2c1e7d..c462eb91 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsViewRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsViewRepository.java @@ -1,12 +1,32 @@ package com.qqchen.deploy.backend.deploy.repository; -import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.deploy.entity.JenkinsView; +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; +import java.util.List; + /** - * Jenkins视图 Repository + * Jenkins视图仓库接口 */ @Repository public interface IJenkinsViewRepository extends IBaseRepository { + + /** + * 根据外部系统ID和视图名称查询视图 + * + * @param externalSystemId 外部系统ID + * @param viewName 视图名称 + * @return 视图对象 + */ + Optional findByExternalSystemIdAndViewName(Long externalSystemId, String viewName); + + /** + * 根据外部系统ID查询所有视图 + * + * @param externalSystemId 外部系统ID + * @return 视图列表 + */ + List findByExternalSystemId(Long externalSystemId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsBuildService.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsBuildService.java index fb63bf7e..28ac8656 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsBuildService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsBuildService.java @@ -9,4 +9,30 @@ import com.qqchen.deploy.backend.deploy.query.JenkinsBuildQuery; * Jenkins构建信息 Service接口 */ public interface IJenkinsBuildService extends IBaseService { + + /** + * 同步指定任务的构建信息 + * + * @param externalSystemId 外部系统ID + * @param jobId 任务ID + * @return 同步的构建数量 + */ + Integer syncBuilds(Long externalSystemId, Long jobId); + + /** + * 同步指定视图下所有任务的构建信息 + * + * @param externalSystemId 外部系统ID + * @param viewId 视图ID + * @return 同步的构建总数 + */ + Integer syncBuildsByView(Long externalSystemId, Long viewId); + + /** + * 同步外部系统下所有构建信息 + * + * @param externalSystemId 外部系统ID + * @return 同步的构建总数 + */ + Integer syncAllBuilds(Long externalSystemId); } \ 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 68de9144..9bd5155f 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 @@ -6,7 +6,24 @@ import com.qqchen.deploy.backend.deploy.dto.JenkinsJobDTO; import com.qqchen.deploy.backend.deploy.query.JenkinsJobQuery; /** - * Jenkins工作 Service接口 + * Jenkins任务 Service接口 */ public interface IJenkinsJobService extends IBaseService { + + /** + * 同步指定视图下的Jenkins任务 + * + * @param externalSystemId 外部系统ID + * @param viewId 视图ID + * @return 同步的任务数量 + */ + Integer syncJobsByView(Long externalSystemId, Long viewId); + + /** + * 同步外部系统下所有视图的Jenkins任务 + * + * @param externalSystemId 外部系统ID + * @return 同步的任务总数 + */ + Integer syncJobs(Long externalSystemId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsManagerService.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsManagerService.java index ef95e188..48e2e0ec 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsManagerService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsManagerService.java @@ -1,7 +1,35 @@ package com.qqchen.deploy.backend.deploy.service; - +/** + * Jenkins管理服务接口 + */ public interface IJenkinsManagerService { - Void viewsPage(); + /** + * 同步Jenkins所有数据(视图、任务、构建信息) + * + * @param externalSystemId 外部系统ID + */ + void syncAll(Long externalSystemId); + + /** + * 同步Jenkins视图 + * + * @param externalSystemId 外部系统ID + */ + void syncViews(Long externalSystemId); + + /** + * 同步Jenkins任务 + * + * @param externalSystemId 外部系统ID + */ + void syncJobs(Long externalSystemId); + + /** + * 同步Jenkins构建信息 + * + * @param externalSystemId 外部系统ID + */ + void syncBuilds(Long externalSystemId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsViewService.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsViewService.java index fdf6386a..61848d85 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsViewService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/IJenkinsViewService.java @@ -1,12 +1,20 @@ package com.qqchen.deploy.backend.deploy.service; -import com.qqchen.deploy.backend.framework.service.IBaseService; import com.qqchen.deploy.backend.deploy.entity.JenkinsView; import com.qqchen.deploy.backend.deploy.dto.JenkinsViewDTO; import com.qqchen.deploy.backend.deploy.query.JenkinsViewQuery; +import com.qqchen.deploy.backend.framework.service.IBaseService; /** * Jenkins视图 Service接口 */ public interface IJenkinsViewService extends IBaseService { + + /** + * 同步Jenkins视图 + * + * @param externalSystemId 外部系统ID + * @return 同步的视图数量 + */ + Integer syncViews(Long externalSystemId); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsBuildServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsBuildServiceImpl.java index 72f67799..8cc58b1d 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsBuildServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsBuildServiceImpl.java @@ -1,12 +1,34 @@ package com.qqchen.deploy.backend.deploy.service.impl; -import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.deploy.entity.ExternalSystem; import com.qqchen.deploy.backend.deploy.entity.JenkinsBuild; +import com.qqchen.deploy.backend.deploy.entity.JenkinsJob; +import com.qqchen.deploy.backend.deploy.entity.JenkinsView; import com.qqchen.deploy.backend.deploy.dto.JenkinsBuildDTO; import com.qqchen.deploy.backend.deploy.query.JenkinsBuildQuery; +import com.qqchen.deploy.backend.deploy.integration.IJenkinsServiceIntegration; +import com.qqchen.deploy.backend.deploy.integration.response.JenkinsBuildResponse; +import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository; +import com.qqchen.deploy.backend.deploy.repository.IJenkinsBuildRepository; +import com.qqchen.deploy.backend.deploy.repository.IJenkinsJobRepository; +import com.qqchen.deploy.backend.deploy.repository.IJenkinsViewRepository; import com.qqchen.deploy.backend.deploy.service.IJenkinsBuildService; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; /** * Jenkins构建信息 Service实现 @@ -15,4 +37,158 @@ import org.springframework.stereotype.Service; @Service public class JenkinsBuildServiceImpl extends BaseServiceImpl implements IJenkinsBuildService { + + @Resource + private IExternalSystemRepository externalSystemRepository; + + @Resource + private IJenkinsViewRepository jenkinsViewRepository; + + @Resource + private IJenkinsJobRepository jenkinsJobRepository; + + @Resource + private IJenkinsBuildRepository jenkinsBuildRepository; + + @Resource + private IJenkinsServiceIntegration jenkinsServiceIntegration; + + @Resource + private ObjectMapper objectMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer syncAllBuilds(Long externalSystemId) { + // 1. 查询外部系统 + ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId) + .orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND)); + + // 2. 查询所有视图 + List views = jenkinsViewRepository.findByExternalSystemId(externalSystemId); + if (views.isEmpty()) { + log.info("No views found for external system: {}", externalSystemId); + return 0; + } + + // 3. 同步每个视图下的构建信息 + int totalSyncedBuilds = 0; + for (JenkinsView view : views) { + try { + Integer syncedBuilds = syncBuildsByView(externalSystemId, view.getId()); + totalSyncedBuilds += syncedBuilds; + log.info("Successfully synchronized {} builds for view: {}", syncedBuilds, view.getViewName()); + } catch (Exception e) { + log.error("Failed to sync builds for view: {}", view.getViewName(), e); + } + } + + log.info("Successfully synchronized total {} builds for external system: {}", + totalSyncedBuilds, externalSystemId); + return totalSyncedBuilds; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer syncBuildsByView(Long externalSystemId, Long viewId) { + // 1. 查询视图下的所有任务 + List jobs = jenkinsJobRepository.findByExternalSystemIdAndViewId(externalSystemId, viewId); + if (jobs.isEmpty()) { + log.info("No jobs found for view: {}", viewId); + return 0; + } + + // 2. 同步每个任务的构建信息 + int totalSyncedBuilds = 0; + for (JenkinsJob job : jobs) { + try { + Integer syncedBuilds = syncBuilds(externalSystemId, job.getId()); + totalSyncedBuilds += syncedBuilds; + log.info("Successfully synchronized {} builds for job: {}", syncedBuilds, job.getJobName()); + } catch (Exception e) { + log.error("Failed to sync builds for job: {}", job.getJobName(), e); + } + } + + return totalSyncedBuilds; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer syncBuilds(Long externalSystemId, Long jobId) { + // 1. 查询外部系统和任务 + ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId) + .orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND)); + + JenkinsJob jenkinsJob = jenkinsJobRepository.findById(jobId) + .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND)); + + // 2. 调用Jenkins API获取构建列表 + List buildResponses = jenkinsServiceIntegration.listBuilds(externalSystem, jenkinsJob.getJobName()); + if (buildResponses.isEmpty()) { + log.info("No builds found for job: {}", jenkinsJob.getJobName()); + return 0; + } + + // 3. 转换并保存/更新构建数据 + List jenkinsBuilds = new ArrayList<>(); + for (JenkinsBuildResponse buildResponse : buildResponses) { + // 查找是否存在相同的构建 + Optional existingBuild = jenkinsBuildRepository + .findByExternalSystemIdAndJobIdAndBuildNumber(externalSystemId, jobId, buildResponse.getNumber()); + + JenkinsBuild jenkinsBuild; + if (existingBuild.isPresent()) { + // 更新已存在的构建 + jenkinsBuild = existingBuild.get(); + updateBuildFromResponse(jenkinsBuild, buildResponse); + log.debug("Updating existing Jenkins build: {} for job: {}", + jenkinsBuild.getBuildNumber(), jenkinsJob.getJobName()); + } else { + // 创建新的构建 + jenkinsBuild = new JenkinsBuild(); + jenkinsBuild.setExternalSystemId(externalSystemId); + jenkinsBuild.setJobId(jobId); + jenkinsBuild.setBuildNumber(buildResponse.getNumber()); + updateBuildFromResponse(jenkinsBuild, buildResponse); + log.debug("Creating new Jenkins build: {} for job: {}", + jenkinsBuild.getBuildNumber(), jenkinsJob.getJobName()); + } + jenkinsBuilds.add(jenkinsBuild); + } + + // 4. 批量保存或更新 + jenkinsBuildRepository.saveAll(jenkinsBuilds); + + log.info("Successfully synchronized {} Jenkins builds for job: {}", + jenkinsBuilds.size(), jenkinsJob.getJobName()); + + return jenkinsBuilds.size(); + } + + /** + * 从API响应更新构建信息 + */ + private void updateBuildFromResponse(JenkinsBuild jenkinsBuild, JenkinsBuildResponse response) { + jenkinsBuild.setBuildUrl(response.getUrl()); + jenkinsBuild.setBuildStatus(response.getResult()); + jenkinsBuild.setDuration(response.getDuration()); + + // 转换时间戳为LocalDateTime + if (response.getTimestamp() != null) { + LocalDateTime startTime = LocalDateTime.ofInstant( + Instant.ofEpochMilli(response.getTimestamp()), + ZoneId.systemDefault() + ); + jenkinsBuild.setStarttime(startTime); + } + + // 将构建参数转换为JSON字符串 + try { + String actionsJson = objectMapper.writeValueAsString(response.getActions()); + jenkinsBuild.setActions(actionsJson); + } catch (JsonProcessingException e) { + log.error("Failed to serialize build actions", e); + jenkinsBuild.setActions("{}"); + } + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsJobServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsJobServiceImpl.java index d594d7e4..1f579d8a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsJobServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsJobServiceImpl.java @@ -1,18 +1,231 @@ package com.qqchen.deploy.backend.deploy.service.impl; -import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.deploy.entity.ExternalSystem; import com.qqchen.deploy.backend.deploy.entity.JenkinsJob; +import com.qqchen.deploy.backend.deploy.entity.JenkinsView; import com.qqchen.deploy.backend.deploy.dto.JenkinsJobDTO; +import com.qqchen.deploy.backend.deploy.integration.response.JenkinsHealthReportResponse; import com.qqchen.deploy.backend.deploy.query.JenkinsJobQuery; +import com.qqchen.deploy.backend.deploy.integration.IJenkinsServiceIntegration; +import com.qqchen.deploy.backend.deploy.integration.response.JenkinsJobResponse; +import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository; +import com.qqchen.deploy.backend.deploy.repository.IJenkinsJobRepository; +import com.qqchen.deploy.backend.deploy.repository.IJenkinsViewRepository; import com.qqchen.deploy.backend.deploy.service.IJenkinsJobService; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalDouble; /** - * Jenkins工作 Service实现 + * Jenkins任务 Service实现 */ @Slf4j @Service public class JenkinsJobServiceImpl extends BaseServiceImpl implements IJenkinsJobService { + + @Resource + private IExternalSystemRepository externalSystemRepository; + + @Resource + private IJenkinsViewRepository jenkinsViewRepository; + + @Resource + private IJenkinsJobRepository jenkinsJobRepository; + + @Resource + private IJenkinsServiceIntegration jenkinsServiceIntegration; + + /** + * 同步外部系统下所有视图的Jenkins任务 + * + * @param externalSystemId 外部系统ID + * @return 同步的任务总数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Integer syncJobs(Long externalSystemId) { + // 1. 查询外部系统 + ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId) + .orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND)); + + // 2. 查询该外部系统下的所有视图 + List views = jenkinsViewRepository.findByExternalSystemId(externalSystemId); + if (views.isEmpty()) { + log.info("No views found for external system: {}", externalSystemId); + return 0; + } + + // 3. 遍历所有视图,同步每个视图下的任务 + int totalSyncedJobs = 0; + for (JenkinsView view : views) { + try { + Integer syncedJobs = syncJobsByView(externalSystemId, view.getId()); + totalSyncedJobs += syncedJobs; + log.info("Successfully synchronized {} jobs for view: {}", syncedJobs, view.getViewName()); + } catch (Exception e) { + // 记录错误但继续同步其他视图 + log.error("Failed to sync jobs for view: {}", view.getViewName(), e); + } + } + + log.info("Successfully synchronized total {} jobs for external system: {}", + totalSyncedJobs, externalSystemId); + return totalSyncedJobs; + } + + /** + * 同步指定视图下的Jenkins任务 + * + * @param externalSystemId 外部系统ID + * @param viewId 视图ID + * @return 同步的任务数量 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Integer syncJobsByView(Long externalSystemId, Long viewId) { + // 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 jobResponses = jenkinsServiceIntegration.listJobs(externalSystem, jenkinsView.getViewName()); + if (jobResponses.isEmpty()) { + log.info("No jobs found in Jenkins view: {}", jenkinsView.getViewName()); + return 0; + } + + // 3. 转换并保存/更新任务数据 + List jenkinsJobs = new ArrayList<>(); + for (JenkinsJobResponse jobResponse : jobResponses) { + // 查找是否存在相同的任务 + Optional existingJob = jenkinsJobRepository + .findByExternalSystemIdAndViewIdAndJobName(externalSystemId, viewId, jobResponse.getName()); + + JenkinsJob jenkinsJob; + if (existingJob.isPresent()) { + // 更新已存在的任务 + jenkinsJob = existingJob.get(); + updateJobFromResponse(jenkinsJob, jobResponse); + log.debug("Updating existing Jenkins job: {}", jenkinsJob.getJobName()); + } else { + // 创建新的任务 + jenkinsJob = new JenkinsJob(); + jenkinsJob.setExternalSystemId(externalSystemId); + jenkinsJob.setViewId(viewId); + jenkinsJob.setJobName(jobResponse.getName()); + updateJobFromResponse(jenkinsJob, jobResponse); + log.debug("Creating new Jenkins job: {}", jenkinsJob.getJobName()); + } + jenkinsJobs.add(jenkinsJob); + } + + // 4. 批量保存或更新 + jenkinsJobRepository.saveAll(jenkinsJobs); + + log.info("Successfully synchronized {} Jenkins jobs for view: {}", jenkinsJobs.size(), jenkinsView.getViewName()); + + return jenkinsJobs.size(); + } + + /** + * 从API响应更新任务信息 + */ + private void updateJobFromResponse(JenkinsJob jenkinsJob, JenkinsJobResponse response) { + jenkinsJob.setBuildable(response.getBuildable()); + jenkinsJob.setDescription(response.getDescription()); + jenkinsJob.setJobUrl(response.getUrl()); + jenkinsJob.setNextBuildNumber(response.getNextBuildNumber()); + + // 设置最后构建信息 + if (response.getLastBuild() != null) { + jenkinsJob.setLastBuildNumber(response.getLastBuild().getNumber()); + // 根据颜色判断构建状态 + jenkinsJob.setLastBuildStatus(convertColorToStatus(response.getColor())); + } + + // 设置健康分数 + jenkinsJob.setHealthReportScore(calculateHealthScore(response)); + } + + /** + * 将Jenkins颜色转换为构建状态 + */ + private String convertColorToStatus(String color) { + if (color == null) return "UNKNOWN"; + return switch (color) { + case "blue" -> "SUCCESS"; + case "red" -> "FAILURE"; + case "yellow" -> "UNSTABLE"; + case "grey", "disabled" -> "DISABLED"; + case "notbuilt" -> "NOT_BUILT"; + case "aborted" -> "ABORTED"; + default -> "UNKNOWN"; + }; + } + + /** + * 计算任务健康分数 + * Jenkins健康分数基于以下几个方面: + * 1. 构建稳定性(最近构建的成功率) + * 2. 测试结果 + * 3. 代码覆盖率 + * 4. 静态代码分析结果等 + * + * @param response Jenkins任务响应 + * @return 健康分数(0-100) + */ + private Integer calculateHealthScore(JenkinsJobResponse response) { + // 如果任务被禁用,返回0分 + if (!response.getBuildable()) { + return 0; + } + + // 获取任务的健康报告 + List healthReports = response.getHealthReports(); + if (healthReports == null || healthReports.isEmpty()) { + // 如果没有健康报告,根据最近构建状态判断 + return calculateScoreByBuildStatus(response); + } + + // 如果有多个健康报告,取平均分 + OptionalDouble avgScore = healthReports.stream() + .mapToInt(JenkinsHealthReportResponse::getScore) + .average(); + + return avgScore.isPresent() ? (int) avgScore.getAsDouble() : 0; + } + + /** + * 根据构建状态计算分数 + */ + private Integer calculateScoreByBuildStatus(JenkinsJobResponse response) { + String color = response.getColor(); + if (color == null) { + return 0; + } + + return switch (color) { + case "blue" -> 100; // 成功 + case "yellow" -> 70; // 不稳定 + case "red" -> 40; // 失败 + case "grey" -> 0; // 禁用 + case "disabled" -> 0; // 禁用 + case "notbuilt" -> 50; // 未构建 + case "aborted" -> 60; // 中断 + default -> 0; + }; + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsManagerServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsManagerServiceImpl.java index 7dce8b7f..45fde9ea 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsManagerServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsManagerServiceImpl.java @@ -1,16 +1,70 @@ package com.qqchen.deploy.backend.deploy.service.impl; import com.qqchen.deploy.backend.deploy.service.IJenkinsManagerService; +import com.qqchen.deploy.backend.deploy.service.IJenkinsViewService; +import com.qqchen.deploy.backend.deploy.service.IJenkinsJobService; +import com.qqchen.deploy.backend.deploy.service.IJenkinsBuildService; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +/** + * Jenkins管理服务实现 + */ @Slf4j @Service public class JenkinsManagerServiceImpl implements IJenkinsManagerService { + @Resource + private IJenkinsViewService jenkinsViewService; + + @Resource + private IJenkinsJobService jenkinsJobService; + + @Resource + private IJenkinsBuildService jenkinsBuildService; @Override - public Void viewsPage() { - return null; + @Transactional(rollbackFor = Exception.class) + public void syncAll(Long externalSystemId) { + // 1. 同步视图 + Integer viewCount = jenkinsViewService.syncViews(externalSystemId); + log.info("Synchronized {} views", viewCount); + + // 2. 同步任务 + Integer jobCount = jenkinsJobService.syncJobs(externalSystemId); + log.info("Synchronized {} jobs", jobCount); + + // 3. 同步构建信息 + Integer buildCount = jenkinsBuildService.syncAllBuilds(externalSystemId); + log.info("Synchronized {} builds", buildCount); + + log.info("Successfully synchronized {} views, {} jobs, and {} builds", + viewCount, jobCount, buildCount); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncViews(Long externalSystemId) { + // 同步视图 + Integer viewCount = jenkinsViewService.syncViews(externalSystemId); + log.info("Successfully synchronized {} views", viewCount); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncJobs(Long externalSystemId) { + // 同步任务 + Integer jobCount = jenkinsJobService.syncJobs(externalSystemId); + log.info("Successfully synchronized {} jobs", jobCount); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncBuilds(Long externalSystemId) { + // 同步构建信息 + Integer buildCount = jenkinsBuildService.syncAllBuilds(externalSystemId); + log.info("Successfully synchronized {} builds", buildCount); } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsViewServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsViewServiceImpl.java index 22b70de7..bdad73dd 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsViewServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/JenkinsViewServiceImpl.java @@ -1,12 +1,25 @@ package com.qqchen.deploy.backend.deploy.service.impl; -import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.deploy.entity.ExternalSystem; import com.qqchen.deploy.backend.deploy.entity.JenkinsView; import com.qqchen.deploy.backend.deploy.dto.JenkinsViewDTO; import com.qqchen.deploy.backend.deploy.query.JenkinsViewQuery; +import com.qqchen.deploy.backend.deploy.integration.IJenkinsServiceIntegration; +import com.qqchen.deploy.backend.deploy.integration.response.JenkinsViewResponse; +import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository; +import com.qqchen.deploy.backend.deploy.repository.IJenkinsViewRepository; import com.qqchen.deploy.backend.deploy.service.IJenkinsViewService; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; /** * Jenkins视图 Service实现 @@ -15,4 +28,68 @@ import org.springframework.stereotype.Service; @Service public class JenkinsViewServiceImpl extends BaseServiceImpl implements IJenkinsViewService { + + @Resource + private IExternalSystemRepository externalSystemRepository; + + @Resource + private IJenkinsViewRepository jenkinsViewRepository; + + @Resource + private IJenkinsServiceIntegration jenkinsServiceIntegration; + + /** + * 同步Jenkins视图 + * + * @param externalSystemId 外部系统ID + * @return 同步的视图数量 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Integer syncViews(Long externalSystemId) { + // 1. 查询外部系统 + ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId) + .orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND)); + + // 2. 调用Jenkins API获取视图列表 + List viewResponses = jenkinsServiceIntegration.listViews(externalSystem); + if (viewResponses.isEmpty()) { + log.info("No views found in Jenkins system: {}", externalSystemId); + return 0; + } + + // 3. 转换并保存/更新视图数据 + List jenkinsViews = new ArrayList<>(); + for (JenkinsViewResponse viewResponse : viewResponses) { + // 查找是否存在相同的视图 + Optional existingView = jenkinsViewRepository + .findByExternalSystemIdAndViewName(externalSystemId, viewResponse.getName()); + + JenkinsView jenkinsView; + if (existingView.isPresent()) { + // 更新已存在的视图 + jenkinsView = existingView.get(); + jenkinsView.setViewUrl(viewResponse.getUrl()); + jenkinsView.setDescription(viewResponse.getDescription()); + log.debug("Updating existing Jenkins view: {}", jenkinsView.getViewName()); + } else { + // 创建新的视图 + jenkinsView = new JenkinsView(); + jenkinsView.setViewName(viewResponse.getName()); + jenkinsView.setViewUrl(viewResponse.getUrl()); + jenkinsView.setDescription(viewResponse.getDescription()); + jenkinsView.setExternalSystemId(externalSystemId); + log.debug("Creating new Jenkins view: {}", jenkinsView.getViewName()); + } + jenkinsViews.add(jenkinsView); + } + + // 4. 批量保存或更新 + jenkinsViewRepository.saveAll(jenkinsViews); + + log.info("Successfully synchronized {} Jenkins views for external system: {}", + jenkinsViews.size(), externalSystemId); + + return jenkinsViews.size(); + } } \ No newline at end of file