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 2eed6f4c..9b4ead38 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 @@ -32,4 +32,14 @@ public interface IJenkinsBuildRepository extends IBaseRepository findByExternalSystemIdAndJobId(Long externalSystemId, Long jobId); + + /** + * 查询指定任务的最新构建记录 + * + * @param externalSystemId 外部系统ID + * @param jobId 任务ID + * @return 最新的构建记录 + */ + Optional findTopByExternalSystemIdAndJobIdOrderByBuildNumberDesc( + Long externalSystemId, Long jobId); } \ 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 1ff00c89..9751b629 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 @@ -13,24 +13,6 @@ import com.qqchen.deploy.backend.deploy.query.JenkinsBuildQuery; */ public interface IJenkinsBuildService extends IBaseService { - /** - * 同步指定任务的构建信息 - * - * @param externalSystem 外部系统 - * @param job 任务 - * @return 同步的构建数量 - */ - Integer syncBuilds(ExternalSystem externalSystem, JenkinsJob job); - - /** - * 同步指定视图下所有任务的构建信息 - * - * @param externalSystem 外部系统 - * @param view 视图 - * @return 同步的构建总数 - */ - Integer syncBuildsByView(ExternalSystem externalSystem, JenkinsView view); - /** * 同步外部系统下所有构建信息 * 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 f05de9fb..3a8de7d7 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 @@ -21,6 +21,7 @@ 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.deploy.service.IJenkinsSyncHistoryService; +import com.qqchen.deploy.backend.deploy.service.sync.JenkinsSyncContext; 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; @@ -78,84 +79,50 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND)); + + // 2. 创建同步历史 JenkinsSyncHistoryDTO syncHistory = new JenkinsSyncHistoryDTO(); syncHistory.setExternalSystemId(externalSystemId); syncHistory.setSyncType(JenkinsSyncType.BUILD); syncHistory.setStatus(ExternalSystemSyncStatus.RUNNING); jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory); - try { - // 2. 查询外部系统 - ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId).orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND)); - // 3. 查询所有视图 - List views = jenkinsViewRepository.findByExternalSystemId(externalSystemId); - if (views.isEmpty()) { - log.info("No views found for external system: {}", externalSystemId); - // 更新同步历史为成功 - syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS); - jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory); - return 0; - } - // 4. 同步每个视图下的构建信息 - int totalSyncedBuilds = 0; - StringBuilder errorMessages = new StringBuilder(); - for (JenkinsView view : views) { - try { - Integer syncedBuilds = syncBuildsByView(externalSystem, view); - totalSyncedBuilds += syncedBuilds; - log.info("Successfully synchronized {} builds for view: {}", syncedBuilds, view.getViewName()); - } catch (Exception e) { - String errorMessage = String.format("Failed to sync builds for view %s: %s", view.getViewName(), e.getMessage()); - log.error(errorMessage, e); - errorMessages.append(errorMessage).append("\n"); - } - } - - // 5. 更新同步历史状态 - if (errorMessages.length() > 0) { - syncHistory.setStatus(ExternalSystemSyncStatus.FAILED); - syncHistory.setErrorMessage(errorMessages.toString()); - } else { - syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS); - } - jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory); - - log.info("Successfully synchronized total {} builds for external system: {}", - totalSyncedBuilds, externalSystemId); - return totalSyncedBuilds; - } catch (Exception e) { - log.error("Failed to sync Jenkins builds for external system: {}", externalSystemId, e); - // 更新同步历史为失败 - syncHistory.setStatus(ExternalSystemSyncStatus.FAILED); - syncHistory.setErrorMessage(e.getMessage()); - jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory); - throw e; - } + // 3. 返回同步上下文 + return new JenkinsSyncContext(externalSystem, syncHistory); } - @Override - @Transactional(rollbackFor = Exception.class) - public Integer syncBuildsByView(ExternalSystem externalSystem, JenkinsView view) { - // 1. 查询视图下的所有任务 - List jobs = jenkinsJobRepository.findByExternalSystemIdAndViewId(externalSystem.getId(), view.getId()); - if (jobs.isEmpty()) { - log.info("No jobs found for view: {}", view.getId()); + private Integer doSync(JenkinsSyncContext context) { + // 1. 获取所有视图 + List views = jenkinsViewRepository.findByExternalSystemId(context.getExternalSystem().getId()); + if (views.isEmpty()) { + log.info("No views found for external system: {}", context.getExternalSystem().getId()); + updateSyncHistorySuccess(context.getSyncHistory()); return 0; } - // 2. 使用线程池并发同步每个任务的构建信息 - List> futures = jobs.stream() - .map(job -> CompletableFuture.supplyAsync(() -> { - try { - Integer syncedBuilds = syncBuilds(externalSystem, job); - log.info("Successfully synchronized {} builds for job: {}", syncedBuilds, job.getJobName()); - return syncedBuilds; - } catch (Exception e) { - log.error("Failed to sync builds for job: {}", job.getJobName(), e); - return 0; - } - }, threadPoolTaskExecutor)) + // 2. 并发同步每个视图 + List> futures = views.stream() + .map(view -> CompletableFuture.supplyAsync( + () -> syncView(context.getExternalSystem(), view), + threadPoolTaskExecutor + )) .collect(Collectors.toList()); // 3. 等待所有任务完成并汇总结果 @@ -171,67 +138,143 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl buildResponses = jenkinsServiceIntegration.listBuilds(externalSystem, job.getJobName()); - if (buildResponses.isEmpty()) { - log.info("No builds found for job: {}", job.getJobName()); - return 0; - } - - // Convert and save/update build data - List jenkinsBuilds = new ArrayList<>(); - for (JenkinsBuildResponse buildResponse : buildResponses) { - // Skip existing builds - if (job.getLastBuildNumber() != null && buildResponse.getNumber() <= job.getLastBuildNumber()) { - continue; + protected Integer syncView(ExternalSystem externalSystem, JenkinsView view) { + try { + // 1. 获取视图下的所有任务 + List jobs = jenkinsJobRepository.findByExternalSystemIdAndViewId( + externalSystem.getId(), view.getId()); + + if (jobs.isEmpty()) { + return 0; } - // Create new build - JenkinsBuild jenkinsBuild = new JenkinsBuild(); - jenkinsBuild.setExternalSystemId(externalSystem.getId()); - jenkinsBuild.setJobId(job.getId()); - jenkinsBuild.setBuildNumber(buildResponse.getNumber()); - updateBuildFromResponse(jenkinsBuild, buildResponse); - jenkinsBuilds.add(jenkinsBuild); - log.debug("Creating new Jenkins build: {} for job: {}", jenkinsBuild.getBuildNumber(), job.getJobName()); + // 2. 同步每个任务 + return jobs.stream() + .map(job -> syncJob(externalSystem, job)) + .mapToInt(Integer::intValue) + .sum(); + } catch (Exception e) { + log.error("Failed to sync view: {}", view.getViewName(), e); + return 0; } + } - // Batch save - if (!jenkinsBuilds.isEmpty()) { - jenkinsBuildRepository.saveAll(jenkinsBuilds); - - // Update job's last build info - job.setLastBuildNumber(queryJob.getLastBuild().getNumber()); - if (queryJob.getLastBuild().getTimestamp() != null) { - job.setLastBuildTime(LocalDateTime.ofInstant( - Instant.ofEpochMilli(queryJob.getLastBuild().getTimestamp()), - ZoneId.systemDefault() - )); + @Transactional + protected Integer syncJob(ExternalSystem externalSystem, JenkinsJob job) { + try { + // 1. 获取任务最新状态 + JenkinsJobResponse jobResponse = jenkinsServiceIntegration.job(externalSystem, job.getJobName()); + if (jobResponse == null || jobResponse.getLastBuild() == null) { + log.info("No build information available for job: {}", job.getJobName()); + return 0; } - jenkinsJobRepository.save(job); - log.info("Successfully synchronized {} builds for job: {}", jenkinsBuilds.size(), job.getJobName()); + // 2. 获取数据库中最后一次构建记录 + Optional lastBuild = jenkinsBuildRepository.findTopByExternalSystemIdAndJobIdOrderByBuildNumberDesc( + externalSystem.getId(), job.getId()); + + // 3. 获取需要同步的构建信息 + List newBuilds = getNewBuilds(externalSystem, job, jobResponse, lastBuild); + if (newBuilds.isEmpty()) { + log.info("No new builds to sync for job: {}", job.getJobName()); + return 0; + } + + // 4. 保存新的构建信息 + saveNewBuilds(externalSystem, job, newBuilds); + + // 5. 更新任务的最新构建信息 + updateJobLastBuild(job, jobResponse); + + log.info("Successfully synchronized {} builds for job: {}", newBuilds.size(), job.getJobName()); + return newBuilds.size(); + } catch (Exception e) { + log.error("Failed to sync job: {}", job.getJobName(), e); + return 0; + } + } + + private List getNewBuilds( + ExternalSystem externalSystem, + JenkinsJob job, + JenkinsJobResponse jobResponse, + Optional lastBuild) { + // 获取所有构建 + List allBuilds = jenkinsServiceIntegration.listBuilds( + externalSystem, job.getJobName()); + + if (allBuilds.isEmpty()) { + return allBuilds; } - return jenkinsBuilds.size(); + // 如果是首次同步,获取所有构建 + if (lastBuild.isEmpty()) { + log.info("First time sync for job: {}, will sync all builds", job.getJobName()); + return allBuilds; + } + + // 获取新的构建 + Integer lastBuildNumber = lastBuild.get().getBuildNumber(); + List newBuilds = allBuilds.stream() + .filter(build -> build.getNumber() > lastBuildNumber) + .collect(Collectors.toList()); + + log.info("Found {} new builds for job: {} (last build number: {})", + newBuilds.size(), job.getJobName(), lastBuildNumber); + return newBuilds; + } + + private void saveNewBuilds( + ExternalSystem externalSystem, + JenkinsJob job, + List builds) { + List jenkinsBuilds = builds.stream() + .map(buildResponse -> { + JenkinsBuild build = new JenkinsBuild(); + build.setExternalSystemId(externalSystem.getId()); + build.setJobId(job.getId()); + build.setBuildNumber(buildResponse.getNumber()); + updateBuildFromResponse(build, buildResponse); + return build; + }) + .collect(Collectors.toList()); + + log.info("Saving {} builds for job: {}", jenkinsBuilds.size(), job.getJobName()); + jenkinsBuildRepository.saveAll(jenkinsBuilds); + } + + private void updateJobLastBuild(JenkinsJob job, JenkinsJobResponse jobResponse) { + job.setLastBuildNumber(jobResponse.getLastBuild().getNumber()); + if (jobResponse.getLastBuild().getTimestamp() != null) { + job.setLastBuildTime(LocalDateTime.ofInstant( + Instant.ofEpochMilli(jobResponse.getLastBuild().getTimestamp()), + ZoneId.systemDefault() + )); + } + jenkinsJobRepository.save(job); + } + + private void handleSyncException(JenkinsSyncContext context, Exception e) { + log.error("Failed to sync Jenkins builds for external system: {}", + context.getExternalSystem().getId(), e); + + context.getSyncHistory().setStatus(ExternalSystemSyncStatus.FAILED); + context.getSyncHistory().setErrorMessage(e.getMessage()); + jenkinsSyncHistoryService.saveOrUpdateHistory(context.getSyncHistory()); + } + + private void updateSyncHistorySuccess(JenkinsSyncHistoryDTO syncHistory) { + syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS); + jenkinsSyncHistoryService.saveOrUpdateHistory(syncHistory); } /** @@ -242,7 +285,6 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl