增加同步锁

This commit is contained in:
dengqichen 2025-12-02 17:45:40 +08:00
parent 2a6785d97d
commit a5276ca524
5 changed files with 181 additions and 204 deletions

View File

@ -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()) {

View File

@ -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)

View File

@ -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);
}

View File

@ -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);

View File

@ -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() : "");