增加同步锁
This commit is contained in:
parent
2a6785d97d
commit
a5276ca524
@ -554,39 +554,6 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration
|
||||
}
|
||||
}
|
||||
|
||||
// 提取 gitCommitId:优先 changeSet/changeSets,兜底 actions.BuildData.lastBuiltRevision.SHA1
|
||||
String commitId = null;
|
||||
try {
|
||||
// 1. 先尝试从 changeSet(单数,FreeStyle 任务)获取
|
||||
if (buildResponse.getChangeSet() != null &&
|
||||
buildResponse.getChangeSet().getItems() != null &&
|
||||
!buildResponse.getChangeSet().getItems().isEmpty()) {
|
||||
// 取最后一个 commit(最新的)
|
||||
var items = buildResponse.getChangeSet().getItems();
|
||||
commitId = items.get(items.size() - 1).getCommitId();
|
||||
}
|
||||
// 2. 兜底:解析原始 JSON 的 actions 查找 BuildData
|
||||
if (commitId == null) {
|
||||
com.fasterxml.jackson.databind.JsonNode root = mapper.readTree(response.getBody());
|
||||
com.fasterxml.jackson.databind.JsonNode actions = root.get("actions");
|
||||
if (actions != null && actions.isArray()) {
|
||||
for (com.fasterxml.jackson.databind.JsonNode act : actions) {
|
||||
com.fasterxml.jackson.databind.JsonNode clazz = act.get("_class");
|
||||
if (clazz != null && "hudson.plugins.git.util.BuildData".equals(clazz.asText())) {
|
||||
com.fasterxml.jackson.databind.JsonNode lbr = act.get("lastBuiltRevision");
|
||||
if (lbr != null && lbr.get("SHA1") != null) {
|
||||
commitId = lbr.get("SHA1").asText();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.warn("Failed to extract git commit id from build details: {}", ex.getMessage());
|
||||
}
|
||||
buildResponse.setGitCommitId(commitId);
|
||||
|
||||
// 拼接制品完整URL串(逗号分隔)
|
||||
try {
|
||||
if (buildResponse.getArtifacts() != null && !buildResponse.getArtifacts().isEmpty()) {
|
||||
|
||||
@ -3,6 +3,7 @@ package com.qqchen.deploy.backend.deploy.integration.response;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -22,12 +23,6 @@ public class JenkinsBuildResponse {
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 构建类型
|
||||
*/
|
||||
@JsonProperty("_class")
|
||||
private String buildClass;
|
||||
|
||||
/**
|
||||
* 构建结果
|
||||
*/
|
||||
@ -88,11 +83,6 @@ public class JenkinsBuildResponse {
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 保持永久
|
||||
*/
|
||||
private Boolean keepLog;
|
||||
|
||||
/**
|
||||
* 构建队列ID
|
||||
*/
|
||||
@ -108,6 +98,11 @@ public class JenkinsBuildResponse {
|
||||
*/
|
||||
private ChangeSet changeSet;
|
||||
|
||||
/**
|
||||
* 构建变更集(Jenkins Pipeline 任务返回 changeSets 复数形式)
|
||||
*/
|
||||
private List<ChangeSet> changeSets;
|
||||
|
||||
/**
|
||||
* 构建制品
|
||||
*/
|
||||
@ -116,16 +111,35 @@ public class JenkinsBuildResponse {
|
||||
* 构建制品URL(逗号分隔的完整URL串),由服务层拼接
|
||||
*/
|
||||
private String artifactUrl;
|
||||
|
||||
/**
|
||||
* 构建控制台输出
|
||||
*/
|
||||
private String consoleLog;
|
||||
|
||||
/**
|
||||
* 提取的Git提交ID(兜底自 actions.BuildData.lastBuiltRevision.SHA1 或 changeSets)
|
||||
* 获取所有变更集(统一处理 FreeStyle 的 changeSet 和 Pipeline 的 changeSets)
|
||||
* 调用方无需关心底层差异,直接使用此方法获取所有变更集
|
||||
*/
|
||||
private String gitCommitId;
|
||||
public List<ChangeSet> getAllChangeSets() {
|
||||
List<ChangeSet> result = new ArrayList<>();
|
||||
if (changeSet != null) {
|
||||
result.add(changeSet);
|
||||
}
|
||||
if (changeSets != null) {
|
||||
result.addAll(changeSets);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有变更集中的提交记录(统一处理 FreeStyle 和 Pipeline)
|
||||
* 调用方无需关心底层差异,直接使用此方法获取所有 commit 信息
|
||||
*/
|
||||
public List<ChangeSetItem> getAllChangeSetItems() {
|
||||
List<ChangeSetItem> items = new ArrayList<>();
|
||||
for (ChangeSet cs : getAllChangeSets()) {
|
||||
if (cs.getItems() != null) {
|
||||
items.addAll(cs.getItems());
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
||||
@ -75,4 +75,27 @@ public interface IJenkinsBuildRepository extends IBaseRepository<JenkinsBuild, L
|
||||
*/
|
||||
List<JenkinsBuild> findByExternalSystemIdAndStarttimeAfter(
|
||||
Long externalSystemId, LocalDateTime starttime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询多个任务的最新构建记录(用于解决N+1查询问题)
|
||||
*
|
||||
* @param externalSystemId 外部系统ID
|
||||
* @param jobIds 任务ID集合
|
||||
* @return 最新构建记录列表
|
||||
*/
|
||||
@Query("""
|
||||
SELECT b FROM JenkinsBuild b
|
||||
WHERE b.externalSystemId = :externalSystemId
|
||||
AND b.jobId IN :jobIds
|
||||
AND (b.jobId, b.buildNumber) IN (
|
||||
SELECT b2.jobId, MAX(b2.buildNumber)
|
||||
FROM JenkinsBuild b2
|
||||
WHERE b2.externalSystemId = :externalSystemId
|
||||
AND b2.jobId IN :jobIds
|
||||
GROUP BY b2.jobId
|
||||
)
|
||||
""")
|
||||
List<JenkinsBuild> findLatestBuildsByJobIds(
|
||||
@Param("externalSystemId") Long externalSystemId,
|
||||
@Param("jobIds") Collection<Long> jobIds);
|
||||
}
|
||||
@ -40,6 +40,7 @@ import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -55,6 +56,7 @@ import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -183,7 +185,6 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
|
||||
// 4. 更新同步历史
|
||||
updateSyncHistorySuccess(context.getSyncHistory());
|
||||
|
||||
log.info("Successfully synchronized total {} builds for external system: {}",
|
||||
totalSyncedBuilds, context.getExternalSystem().getId());
|
||||
return totalSyncedBuilds;
|
||||
@ -207,15 +208,23 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 3. 创建任务名称到响应的映射
|
||||
// 3. 批量查询所有任务的最新构建(解决N+1查询问题)
|
||||
List<Long> jobIds = jobs.stream().map(JenkinsJob::getId).toList();
|
||||
List<JenkinsBuild> latestBuilds = jenkinsBuildRepository.findLatestBuildsByJobIds(
|
||||
externalSystem.getId(), jobIds);
|
||||
Map<Long, JenkinsBuild> latestBuildMap = latestBuilds.stream()
|
||||
.collect(Collectors.toMap(JenkinsBuild::getJobId, build -> build));
|
||||
|
||||
// 4. 创建任务名称到响应的映射
|
||||
Map<String, JenkinsJobResponse> jobResponseMap = jobResponses.stream()
|
||||
.collect(Collectors.toMap(JenkinsJobResponse::getName, job -> job));
|
||||
|
||||
// 4. 同步每个任务的构建信息
|
||||
// 5. 同步每个任务的构建信息
|
||||
return jobs.stream()
|
||||
.map(job -> {
|
||||
JenkinsJobResponse jobResponse = jobResponseMap.get(job.getJobName());
|
||||
return syncJob(externalSystem, job, jobResponse);
|
||||
JenkinsBuild latestBuild = latestBuildMap.get(job.getId());
|
||||
return syncJob(externalSystem, job, jobResponse, latestBuild);
|
||||
})
|
||||
.mapToInt(Integer::intValue)
|
||||
.sum();
|
||||
@ -227,6 +236,11 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
|
||||
@Transactional
|
||||
protected Integer syncJob(ExternalSystem externalSystem, JenkinsJob job, JenkinsJobResponse jobResponse) {
|
||||
return syncJob(externalSystem, job, jobResponse, null);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
protected Integer syncJob(ExternalSystem externalSystem, JenkinsJob job, JenkinsJobResponse jobResponse, JenkinsBuild latestBuild) {
|
||||
try {
|
||||
if (jobResponse == null || jobResponse.getLastBuild() == null) {
|
||||
log.info("No build information available for job: {}", job.getJobName());
|
||||
@ -234,21 +248,30 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
}
|
||||
|
||||
Integer latestBuildNumber = jobResponse.getLastBuild().getNumber();
|
||||
Integer lastSyncedBuildNumber = job.getLastBuildNumber();
|
||||
|
||||
// 1. 使用预查询的最新构建或查询Build表中该Job的实际最大构建号
|
||||
Integer lastSyncedBuildNumber;
|
||||
if (latestBuild != null) {
|
||||
lastSyncedBuildNumber = latestBuild.getBuildNumber();
|
||||
} else {
|
||||
Optional<JenkinsBuild> build = jenkinsBuildRepository
|
||||
.findTopByExternalSystemIdAndJobIdOrderByBuildNumberDesc(externalSystem.getId(), job.getId());
|
||||
lastSyncedBuildNumber = build.map(JenkinsBuild::getBuildNumber).orElse(null);
|
||||
}
|
||||
|
||||
// 1. 判断是否有新构建(对比本地缓存的 lastBuildNumber 和 Jenkins 最新的)
|
||||
// 2. 判断是否有新构建(基于Build表实际数据判断)
|
||||
if (lastSyncedBuildNumber != null && latestBuildNumber <= lastSyncedBuildNumber) {
|
||||
log.info("No new builds to sync for job: {} (last synced: {}, latest: {})", job.getJobName(), lastSyncedBuildNumber, latestBuildNumber);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 2. 确定同步范围
|
||||
// 3. 确定同步范围
|
||||
Integer fromBuildNumber = (lastSyncedBuildNumber != null) ? lastSyncedBuildNumber + 1 : 1;
|
||||
|
||||
// 3. 获取构建信息
|
||||
// 4. 获取构建信息
|
||||
List<JenkinsBuildResponse> builds = jenkinsServiceIntegration.listBuilds(externalSystem, job.getJobName());
|
||||
|
||||
// 4. 过滤出需要同步的构建
|
||||
// 5. 过滤出需要同步的构建
|
||||
List<JenkinsBuildResponse> newBuilds = builds.stream()
|
||||
.filter(build -> build.getNumber() >= fromBuildNumber && build.getNumber() <= latestBuildNumber)
|
||||
.collect(Collectors.toList());
|
||||
@ -258,10 +281,10 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 5. 保存新的构建信息
|
||||
// 6. 保存新的构建信息
|
||||
saveNewBuilds(externalSystem, job, newBuilds);
|
||||
|
||||
// 6. 更新任务的最新构建信息
|
||||
// 7. 更新任务的最新构建信息
|
||||
updateJobLastBuild(job, jobResponse);
|
||||
|
||||
log.info("Successfully synchronized {} builds for job: {} (from: {}, to: {})",
|
||||
@ -277,16 +300,29 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
ExternalSystem externalSystem,
|
||||
JenkinsJob job,
|
||||
List<JenkinsBuildResponse> builds) {
|
||||
List<JenkinsBuild> jenkinsBuilds = builds.stream()
|
||||
.map(buildResponse -> {
|
||||
JenkinsBuild build = new JenkinsBuild();
|
||||
build.setExternalSystemId(externalSystem.getId());
|
||||
build.setJobId(job.getId());
|
||||
build.setBuildNumber(buildResponse.getNumber());
|
||||
List<JenkinsBuild> jenkinsBuilds = new ArrayList<>();
|
||||
|
||||
for (JenkinsBuildResponse buildResponse : builds) {
|
||||
JenkinsBuild build = new JenkinsBuild();
|
||||
build.setExternalSystemId(externalSystem.getId());
|
||||
build.setJobId(job.getId());
|
||||
build.setBuildNumber(buildResponse.getNumber());
|
||||
|
||||
try {
|
||||
// 调用 getBuildDetails 获取完整信息(包含 changeSets 等)
|
||||
JenkinsBuildResponse detailedBuild = jenkinsServiceIntegration.getBuildDetails(externalSystem, job.getJobName(), buildResponse.getNumber());
|
||||
if (detailedBuild != null) {
|
||||
updateBuildFromResponse(build, detailedBuild);
|
||||
} else {
|
||||
updateBuildFromResponse(build, buildResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("获取构建详情失败,使用基础信息: job={}, build={}", job.getJobName(), buildResponse.getNumber());
|
||||
updateBuildFromResponse(build, buildResponse);
|
||||
return build;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
jenkinsBuilds.add(build);
|
||||
}
|
||||
|
||||
log.info("Saving {} builds for job: {}", jenkinsBuilds.size(), job.getJobName());
|
||||
jenkinsBuildRepository.saveAll(jenkinsBuilds);
|
||||
@ -304,8 +340,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
}
|
||||
|
||||
private void handleSyncException(JenkinsSyncContext context, Exception e) {
|
||||
log.error("Failed to sync Jenkins builds for external system: {}",
|
||||
context.getExternalSystem().getId(), 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());
|
||||
@ -319,7 +354,6 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
|
||||
private void updateBuildFromResponse(JenkinsBuild jenkinsBuild, JenkinsBuildResponse response) {
|
||||
jenkinsBuild.setBuildUrl(response.getUrl());
|
||||
// Jenkins API 返回的 result 在构建进行中时为 null,设置默认值 "BUILDING"
|
||||
jenkinsBuild.setBuildStatus(response.getResult() != null ? response.getResult() : "BUILDING");
|
||||
jenkinsBuild.setDuration(response.getDuration());
|
||||
|
||||
@ -332,16 +366,11 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
}
|
||||
|
||||
try {
|
||||
// 保存 actions 和 changeSet 到 JSON
|
||||
Map<String, Object> buildData = new HashMap<>();
|
||||
buildData.put("actions", response.getActions());
|
||||
buildData.put("changeSet", response.getChangeSet());
|
||||
buildData.put("gitCommitId", response.getGitCommitId());
|
||||
String actionsJson = objectMapper.writeValueAsString(buildData);
|
||||
jenkinsBuild.setActions(actionsJson);
|
||||
String changeSetsJson = objectMapper.writeValueAsString(response.getAllChangeSets());
|
||||
jenkinsBuild.setActions(changeSetsJson);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("Failed to serialize build actions", e);
|
||||
jenkinsBuild.setActions("{}");
|
||||
log.error("Failed to serialize changeSets", e);
|
||||
jenkinsBuild.setActions("[]");
|
||||
}
|
||||
}
|
||||
|
||||
@ -543,25 +572,26 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
JenkinsBuild latestBuild = latestBuildByJobId.get(job.getId());
|
||||
if (latestBuild == null) continue;
|
||||
|
||||
// 从 Jenkins API 实时获取最新状态(每个Job只调用一次)
|
||||
JenkinsBuildResponse latestBuildInfo = null;
|
||||
String latestStatus = "BUILDING";
|
||||
try {
|
||||
latestBuildInfo = jenkinsServiceIntegration.getBuildDetails(externalSystem, job.getJobName(), latestBuild.getBuildNumber());
|
||||
if (latestBuildInfo != null && latestBuildInfo.getResult() != null) {
|
||||
latestStatus = latestBuildInfo.getResult();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("从Jenkins获取构建信息失败,跳过本次处理,等待下次重试: job={}, buildNumber={}, error={}",
|
||||
job.getJobName(), latestBuild.getBuildNumber(), e.getMessage());
|
||||
continue; // 跳过这个 Job,下次定时任务重试
|
||||
}
|
||||
// 获取构建状态:只有 BUILDING 状态才需要调用 API 刷新
|
||||
String latestStatus = latestBuild.getBuildStatus();
|
||||
|
||||
// 更新数据库中的构建状态(如果有变化)
|
||||
if (latestBuildInfo != null && !latestStatus.equals(latestBuild.getBuildStatus())) {
|
||||
updateBuildFromResponse(latestBuild, latestBuildInfo);
|
||||
jenkinsBuildRepository.save(latestBuild);
|
||||
if ("BUILDING".equals(latestStatus)) {
|
||||
// 构建进行中,需要从 Jenkins API 获取最新状态
|
||||
try {
|
||||
JenkinsBuildResponse latestBuildInfo = jenkinsServiceIntegration.getBuildDetails(externalSystem, job.getJobName(), latestBuild.getBuildNumber());
|
||||
if (latestBuildInfo != null && latestBuildInfo.getResult() != null) {
|
||||
latestStatus = latestBuildInfo.getResult();
|
||||
// 状态有变化,更新数据库
|
||||
updateBuildFromResponse(latestBuild, latestBuildInfo);
|
||||
jenkinsBuildRepository.save(latestBuild);
|
||||
log.info("构建状态已更新: job={}, build={}, status={}", job.getJobName(), latestBuild.getBuildNumber(), latestStatus);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("从Jenkins获取构建信息失败,跳过本次处理,等待下次重试: job={}, buildNumber={}, error={}", job.getJobName(), latestBuild.getBuildNumber(), e.getMessage());
|
||||
continue; // 跳过这个 Job,下次定时任务重试
|
||||
}
|
||||
}
|
||||
// 已完成的构建(SUCCESS/FAILURE/ABORTED)直接使用数据库中的状态,不调用 API
|
||||
|
||||
// 处理每个关联的 TeamApplication
|
||||
for (TeamApplication teamApp : relatedTeamApps) {
|
||||
@ -599,20 +629,11 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
* <li>有记录但buildEndNotice=false:检查是否完成,发送结束通知</li>
|
||||
* </ul>
|
||||
*/
|
||||
private void processBuildNotification(
|
||||
TeamEnvironmentNotificationConfig config,
|
||||
NotificationChannel channel,
|
||||
JenkinsJob job,
|
||||
JenkinsBuild build,
|
||||
String latestStatus,
|
||||
ExternalSystem externalSystem,
|
||||
Application application,
|
||||
Environment environment,
|
||||
JenkinsBuildNotification existingRecord) {
|
||||
private void processBuildNotification(TeamEnvironmentNotificationConfig config, NotificationChannel channel, JenkinsJob job, JenkinsBuild build, String latestStatus, ExternalSystem externalSystem, Application application, Environment environment, JenkinsBuildNotification existingRecord) {
|
||||
|
||||
try {
|
||||
// 计算构建开始到现在的分钟数
|
||||
long minutesAgo = java.time.temporal.ChronoUnit.MINUTES.between(build.getStarttime(), LocalDateTime.now());
|
||||
long minutesAgo = ChronoUnit.MINUTES.between(build.getStarttime(), LocalDateTime.now());
|
||||
|
||||
// 1. 新构建(通知记录不存在)
|
||||
if (existingRecord == null) {
|
||||
@ -625,8 +646,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
record.setBuildStartNotice(true);
|
||||
record.setBuildEndNotice(true); // 直接标记结束
|
||||
jenkinsBuildNotificationRepository.save(record);
|
||||
log.info("构建超时,直接标记完成: job={}, build={}, minutesAgo={}",
|
||||
job.getJobName(), build.getBuildNumber(), minutesAgo);
|
||||
log.info("构建超时,直接标记完成: job={}, build={}, minutesAgo={}", job.getJobName(), build.getBuildNumber(), minutesAgo);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -650,26 +670,22 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
sendNotification(config, channel, job, build, latestStatus, externalSystem, application, environment);
|
||||
existingRecord.setBuildEndNotice(true);
|
||||
jenkinsBuildNotificationRepository.save(existingRecord);
|
||||
log.info("发送构建结束通知: job={}, build={}, status={}",
|
||||
job.getJobName(), build.getBuildNumber(), latestStatus);
|
||||
log.info("发送构建结束通知: job={}, build={}, status={}", job.getJobName(), build.getBuildNumber(), latestStatus);
|
||||
} else {
|
||||
// 兜底:超过2小时仍未完成,强制标记结束(不发通知)
|
||||
long hoursAgo = java.time.temporal.ChronoUnit.HOURS.between(build.getStarttime(), LocalDateTime.now());
|
||||
long hoursAgo = ChronoUnit.HOURS.between(build.getStarttime(), LocalDateTime.now());
|
||||
if (hoursAgo >= 2) {
|
||||
log.warn("构建超时未完成,强制标记结束: job={}, build={}, startTime={}",
|
||||
job.getJobName(), build.getBuildNumber(), build.getStarttime());
|
||||
log.warn("构建超时未完成,强制标记结束: job={}, build={}, startTime={}", job.getJobName(), build.getBuildNumber(), build.getStarttime());
|
||||
existingRecord.setBuildEndNotice(true);
|
||||
jenkinsBuildNotificationRepository.save(existingRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (org.springframework.orm.ObjectOptimisticLockingFailureException e) {
|
||||
log.warn("构建通知记录乐观锁冲突,跳过: teamId={}, envId={}, buildId={}",
|
||||
config.getTeamId(), config.getEnvironmentId(), build.getId());
|
||||
} catch (ObjectOptimisticLockingFailureException e) {
|
||||
log.warn("构建通知记录乐观锁冲突,跳过: teamId={}, envId={}, buildId={}", config.getTeamId(), config.getEnvironmentId(), build.getId());
|
||||
} catch (Exception e) {
|
||||
log.error("处理构建通知失败: teamId={}, envId={}, buildId={}",
|
||||
config.getTeamId(), config.getEnvironmentId(), build.getId(), e);
|
||||
log.error("处理构建通知失败: teamId={}, envId={}, buildId={}", config.getTeamId(), config.getEnvironmentId(), build.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -680,14 +696,6 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
return "SUCCESS".equals(status) || "FAILURE".equals(status) || "ABORTED".equals(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断构建是否已结束
|
||||
*/
|
||||
private boolean isBuildFinished(JenkinsBuild build) {
|
||||
String status = build.getBuildStatus();
|
||||
return "SUCCESS".equals(status) || "FAILURE".equals(status) || "ABORTED".equals(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送通知(使用模板)
|
||||
*/
|
||||
@ -696,8 +704,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
try {
|
||||
// 1. 检查是否配置了构建通知模板
|
||||
if (config.getBuildNotificationTemplateId() == null) {
|
||||
log.warn("未配置构建通知模板,跳过通知: teamId={}, envId={}",
|
||||
config.getTeamId(), config.getEnvironmentId());
|
||||
log.warn("未配置构建通知模板,跳过通知: teamId={}, envId={}", config.getTeamId(), config.getEnvironmentId());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -823,83 +830,43 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 actions JSON 中解析 commit 信息
|
||||
* 从 changeSets JSON 中解析 commit 信息
|
||||
*/
|
||||
private void parseCommitInfoFromActions(String actionsJson, Map<String, Object> templateParams) {
|
||||
if (actionsJson == null || actionsJson.isEmpty()) {
|
||||
log.debug("actionsJson 为空,跳过解析");
|
||||
private void parseCommitInfoFromActions(String changeSetsJson, Map<String, Object> templateParams) {
|
||||
if (changeSetsJson == null || changeSetsJson.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log.debug("开始解析 actionsJson: {}", actionsJson);
|
||||
JsonNode root = objectMapper.readTree(actionsJson);
|
||||
// 直接反序列化为 List<ChangeSet>
|
||||
List<JenkinsBuildResponse.ChangeSet> changeSets = objectMapper.readValue(
|
||||
changeSetsJson,
|
||||
new com.fasterxml.jackson.core.type.TypeReference<List<JenkinsBuildResponse.ChangeSet>>() {
|
||||
}
|
||||
);
|
||||
|
||||
// 解析 gitCommitId
|
||||
if (root.has("gitCommitId") && !root.get("gitCommitId").isNull()) {
|
||||
templateParams.put("gitCommitId", root.get("gitCommitId").asText());
|
||||
if (changeSets == null || changeSets.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析 changeSets 中的 commit 信息
|
||||
JsonNode changeSets = root.get("changeSets");
|
||||
log.debug("changeSets 节点: {}", changeSets);
|
||||
StringBuilder commitMessages = new StringBuilder();
|
||||
for (JenkinsBuildResponse.ChangeSet changeSet : changeSets) {
|
||||
if (changeSet.getItems() == null) continue;
|
||||
for (JenkinsBuildResponse.ChangeSetItem item : changeSet.getItems()) {
|
||||
if (item.getMsg() == null || item.getMsg().isEmpty()) continue;
|
||||
|
||||
if (changeSets != null && changeSets.isArray() && changeSets.size() > 0) {
|
||||
log.debug("changeSets 数组大小: {}", changeSets.size());
|
||||
StringBuilder commitMessages = new StringBuilder();
|
||||
for (JsonNode changeSet : changeSets) {
|
||||
JsonNode items = changeSet.get("items");
|
||||
log.debug("items 节点: {}", items);
|
||||
|
||||
if (items != null && items.isArray()) {
|
||||
log.debug("items 数组大小: {}", items.size());
|
||||
for (JsonNode item : items) {
|
||||
log.debug("处理 item: {}", item);
|
||||
|
||||
// Jenkins API 返回的字段名是 msg
|
||||
String msg = item.has("msg") ? item.get("msg").asText() : "";
|
||||
|
||||
// author 是一个对象,需要提取 fullName 或其他字段
|
||||
String author = "";
|
||||
if (item.has("author")) {
|
||||
JsonNode authorNode = item.get("author");
|
||||
log.debug("author 节点: {}", authorNode);
|
||||
|
||||
if (authorNode.isTextual()) {
|
||||
// 如果是字符串,直接使用
|
||||
author = authorNode.asText();
|
||||
} else if (authorNode.isObject()) {
|
||||
// 如果是对象,尝试提取 fullName
|
||||
if (authorNode.has("fullName")) {
|
||||
author = authorNode.get("fullName").asText();
|
||||
} else if (authorNode.has("name")) {
|
||||
author = authorNode.get("name").asText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String commitId = item.has("commitId") ? item.get("commitId").asText() : "";
|
||||
|
||||
log.debug("解析结果 - msg: {}, author: {}, commitId: {}", msg, author, commitId);
|
||||
|
||||
if (!msg.isEmpty()) {
|
||||
if (commitMessages.length() > 0) {
|
||||
commitMessages.append("\n");
|
||||
}
|
||||
// 截取 commitId 前8位
|
||||
String shortCommitId = commitId.length() > 8 ? commitId.substring(0, 8) : commitId;
|
||||
commitMessages.append(String.format("[%s] %s - %s", shortCommitId, msg.trim(), author));
|
||||
}
|
||||
}
|
||||
if (commitMessages.length() > 0) {
|
||||
commitMessages.append("\n");
|
||||
}
|
||||
String commitId = item.getCommitId() != null ? item.getCommitId() : "";
|
||||
String shortCommitId = commitId.length() > 8 ? commitId.substring(0, 8) : commitId;
|
||||
String author = item.getAuthor() != null && item.getAuthor().getFullName() != null
|
||||
? item.getAuthor().getFullName() : "";
|
||||
commitMessages.append(String.format("[%s] %s - %s", shortCommitId, item.getMsg().trim(), author));
|
||||
}
|
||||
if (commitMessages.length() > 0) {
|
||||
log.info("成功解析 commitMessage: {}", commitMessages.toString());
|
||||
templateParams.put("commitMessage", commitMessages.toString());
|
||||
} else {
|
||||
log.warn("commitMessages 为空");
|
||||
}
|
||||
} else {
|
||||
log.warn("changeSets 为空或不是数组");
|
||||
}
|
||||
|
||||
if (commitMessages.length() > 0) {
|
||||
templateParams.put("commitMessage", commitMessages.toString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析 commit 信息失败", e);
|
||||
@ -1073,7 +1040,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
*/
|
||||
private void cleanupTempFile(String filePath) {
|
||||
try {
|
||||
java.nio.file.Files.deleteIfExists(java.nio.file.Paths.get(filePath));
|
||||
Files.deleteIfExists(Paths.get(filePath));
|
||||
log.debug("临时文件已清理: {}", filePath);
|
||||
} catch (Exception e) {
|
||||
log.warn("清理临时文件失败: {}", filePath, e);
|
||||
|
||||
@ -276,8 +276,14 @@ public class JenkinsBuildDelegate extends BaseNodeDelegate<JenkinsBuildInputMapp
|
||||
output.setBuildEndTimeMillis(buildDetails.getEndTimeMillis());
|
||||
output.setBuildEndTime(buildDetails.getEndTime());
|
||||
|
||||
// Git 提交ID(服务层已兜底)
|
||||
output.setGitCommitId(buildDetails.getGitCommitId() != null ? buildDetails.getGitCommitId() : "");
|
||||
// Git 提交ID(从 changeSets 中获取最后一个 commit)
|
||||
String gitCommitId = "";
|
||||
var changeSetItems = buildDetails.getAllChangeSetItems();
|
||||
if (changeSetItems != null && !changeSetItems.isEmpty()) {
|
||||
var lastItem = changeSetItems.get(changeSetItems.size() - 1);
|
||||
gitCommitId = lastItem.getCommitId() != null ? lastItem.getCommitId() : "";
|
||||
}
|
||||
output.setGitCommitId(gitCommitId);
|
||||
|
||||
// 制品URL(服务层已拼接)
|
||||
output.setArtifactUrl(buildDetails.getArtifactUrl() != null ? buildDetails.getArtifactUrl() : "");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user