增加构建通知
This commit is contained in:
parent
433887c3cf
commit
35fb294879
@ -29,6 +29,7 @@ import com.qqchen.deploy.backend.notification.dto.WeworkSendNotificationRequest;
|
|||||||
import com.qqchen.deploy.backend.notification.dto.EmailSendNotificationRequest;
|
import com.qqchen.deploy.backend.notification.dto.EmailSendNotificationRequest;
|
||||||
import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum;
|
import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum;
|
||||||
import com.qqchen.deploy.backend.deploy.dto.sync.JenkinsSyncContext;
|
import com.qqchen.deploy.backend.deploy.dto.sync.JenkinsSyncContext;
|
||||||
|
import com.qqchen.deploy.backend.deploy.lock.SyncLockManager;
|
||||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
@ -108,18 +109,30 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
@Resource
|
@Resource
|
||||||
private INotificationService notificationService;
|
private INotificationService notificationService;
|
||||||
|
|
||||||
|
|
||||||
@Resource(name = "jenkinsTaskExecutor")
|
@Resource(name = "jenkinsTaskExecutor")
|
||||||
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SyncLockManager syncLockManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Async
|
@Async
|
||||||
@Transactional
|
@Transactional
|
||||||
public void syncBuilds(Long externalSystemId) {
|
public void syncBuilds(Long externalSystemId) {
|
||||||
doSyncBuilds(externalSystemId, null, null);
|
// 尝试获取锁(自动识别调用者:JenkinsBuildServiceImpl.syncBuilds)
|
||||||
|
if (!syncLockManager.tryLock(externalSystemId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 同步完成后检查并发送构建通知
|
try {
|
||||||
checkBuildNotifications(externalSystemId);
|
doSyncBuilds(externalSystemId, null, null);
|
||||||
|
|
||||||
|
// 同步完成后检查并发送构建通知
|
||||||
|
checkBuildNotifications(externalSystemId);
|
||||||
|
} finally {
|
||||||
|
// 释放锁
|
||||||
|
syncLockManager.unlock(externalSystemId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private JenkinsSyncContext createSyncContext(Long externalSystemId) {
|
private JenkinsSyncContext createSyncContext(Long externalSystemId) {
|
||||||
@ -220,24 +233,39 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 获取数据库中最后一次构建记录
|
Integer latestBuildNumber = jobResponse.getLastBuild().getNumber();
|
||||||
Optional<JenkinsBuild> lastBuild = jenkinsBuildRepository.findTopByExternalSystemIdAndJobIdOrderByBuildNumberDesc(
|
Integer lastSyncedBuildNumber = job.getLastBuildNumber();
|
||||||
externalSystem.getId(), job.getId());
|
|
||||||
|
|
||||||
// 2. 获取需要同步的构建信息
|
// 1. 判断是否有新构建(对比本地缓存的 lastBuildNumber 和 Jenkins 最新的)
|
||||||
List<JenkinsBuildResponse> newBuilds = getNewBuilds(externalSystem, job, jobResponse, lastBuild);
|
if (lastSyncedBuildNumber != null && latestBuildNumber <= lastSyncedBuildNumber) {
|
||||||
if (newBuilds.isEmpty()) {
|
log.info("No new builds to sync for job: {} (last synced: {}, latest: {})", job.getJobName(), lastSyncedBuildNumber, latestBuildNumber);
|
||||||
log.info("No new builds to sync for job: {}", job.getJobName());
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 保存新的构建信息
|
// 2. 确定同步范围
|
||||||
|
Integer fromBuildNumber = (lastSyncedBuildNumber != null) ? lastSyncedBuildNumber + 1 : 1;
|
||||||
|
|
||||||
|
// 3. 获取构建信息
|
||||||
|
List<JenkinsBuildResponse> builds = jenkinsServiceIntegration.listBuilds(externalSystem, job.getJobName());
|
||||||
|
|
||||||
|
// 4. 过滤出需要同步的构建
|
||||||
|
List<JenkinsBuildResponse> newBuilds = builds.stream()
|
||||||
|
.filter(build -> build.getNumber() >= fromBuildNumber && build.getNumber() <= latestBuildNumber)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (newBuilds.isEmpty()) {
|
||||||
|
log.info("No new builds found for job: {} (from: {}, to: {})", job.getJobName(), fromBuildNumber, latestBuildNumber);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 保存新的构建信息
|
||||||
saveNewBuilds(externalSystem, job, newBuilds);
|
saveNewBuilds(externalSystem, job, newBuilds);
|
||||||
|
|
||||||
// 4. 更新任务的最新构建信息
|
// 6. 更新任务的最新构建信息
|
||||||
updateJobLastBuild(job, jobResponse);
|
updateJobLastBuild(job, jobResponse);
|
||||||
|
|
||||||
log.info("Successfully synchronized {} builds for job: {}", newBuilds.size(), job.getJobName());
|
log.info("Successfully synchronized {} builds for job: {} (from: {}, to: {})",
|
||||||
|
newBuilds.size(), job.getJobName(), fromBuildNumber, latestBuildNumber);
|
||||||
return newBuilds.size();
|
return newBuilds.size();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to sync job: {}", job.getJobName(), e);
|
log.error("Failed to sync job: {}", job.getJobName(), e);
|
||||||
@ -245,50 +273,6 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<JenkinsBuildResponse> getNewBuilds(
|
|
||||||
ExternalSystem externalSystem,
|
|
||||||
JenkinsJob job,
|
|
||||||
JenkinsJobResponse jobResponse,
|
|
||||||
Optional<JenkinsBuild> lastBuild) {
|
|
||||||
|
|
||||||
// 1. 获取最新构建号(从jobResponse中获取,避免额外的API调用)
|
|
||||||
if (jobResponse.getLastBuild() == null) {
|
|
||||||
log.info("No builds found for job: {}", job.getJobName());
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
Integer latestBuildNumber = jobResponse.getLastBuild().getNumber();
|
|
||||||
|
|
||||||
// 2. 确定同步范围
|
|
||||||
Integer fromBuildNumber;
|
|
||||||
if (lastBuild.isEmpty()) {
|
|
||||||
// 首次同步,从第一个构建开始
|
|
||||||
log.info("First time sync for job: {}, will sync all builds", job.getJobName());
|
|
||||||
fromBuildNumber = 1;
|
|
||||||
} else {
|
|
||||||
// 增量同步,从上次同步的下一个构建开始
|
|
||||||
fromBuildNumber = lastBuild.get().getBuildNumber() + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 如果没有新的构建,直接返回
|
|
||||||
if (fromBuildNumber > latestBuildNumber) {
|
|
||||||
log.info("No new builds to sync for job: {} (last build: {}, latest build: {})",
|
|
||||||
job.getJobName(), lastBuild.map(JenkinsBuild::getBuildNumber).orElse(null), latestBuildNumber);
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 获取构建信息
|
|
||||||
List<JenkinsBuildResponse> builds = jenkinsServiceIntegration.listBuilds(externalSystem, job.getJobName());
|
|
||||||
|
|
||||||
// 5. 过滤出需要的构建
|
|
||||||
List<JenkinsBuildResponse> newBuilds = builds.stream()
|
|
||||||
.filter(build -> build.getNumber() >= fromBuildNumber && build.getNumber() <= latestBuildNumber)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
log.info("Found {} new builds for job: {} (from build: {}, to build: {})",
|
|
||||||
newBuilds.size(), job.getJobName(), fromBuildNumber, latestBuildNumber);
|
|
||||||
return newBuilds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveNewBuilds(
|
private void saveNewBuilds(
|
||||||
ExternalSystem externalSystem,
|
ExternalSystem externalSystem,
|
||||||
JenkinsJob job,
|
JenkinsJob job,
|
||||||
@ -437,7 +421,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查并发送构建通知(参考longi-deployment逻辑重构)
|
* 检查并发送构建通知(参考longi-deployment逻辑重构)
|
||||||
*
|
*
|
||||||
* <p>核心流程:
|
* <p>核心流程:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>获取所有绑定了该Jenkins系统的TeamApplication</li>
|
* <li>获取所有绑定了该Jenkins系统的TeamApplication</li>
|
||||||
@ -477,7 +461,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
// 4. 查询启用了构建通知的团队环境配置
|
// 4. 查询启用了构建通知的团队环境配置
|
||||||
Set<Long> teamIds = teamApps.stream().map(TeamApplication::getTeamId).collect(Collectors.toSet());
|
Set<Long> teamIds = teamApps.stream().map(TeamApplication::getTeamId).collect(Collectors.toSet());
|
||||||
Set<Long> envIds = teamApps.stream().map(TeamApplication::getEnvironmentId).collect(Collectors.toSet());
|
Set<Long> envIds = teamApps.stream().map(TeamApplication::getEnvironmentId).collect(Collectors.toSet());
|
||||||
|
|
||||||
List<TeamEnvironmentNotificationConfig> configs = teamEnvironmentNotificationConfigRepository
|
List<TeamEnvironmentNotificationConfig> configs = teamEnvironmentNotificationConfigRepository
|
||||||
.findByTeamIdInAndEnvironmentIdInAndBuildNotificationEnabledTrue(teamIds, envIds);
|
.findByTeamIdInAndEnvironmentIdInAndBuildNotificationEnabledTrue(teamIds, envIds);
|
||||||
if (configs.isEmpty()) {
|
if (configs.isEmpty()) {
|
||||||
@ -568,7 +552,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
latestStatus = latestBuildInfo.getResult();
|
latestStatus = latestBuildInfo.getResult();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("从Jenkins获取构建信息失败,跳过本次处理,等待下次重试: job={}, buildNumber={}, error={}",
|
log.warn("从Jenkins获取构建信息失败,跳过本次处理,等待下次重试: job={}, buildNumber={}, error={}",
|
||||||
job.getJobName(), latestBuild.getBuildNumber(), e.getMessage());
|
job.getJobName(), latestBuild.getBuildNumber(), e.getMessage());
|
||||||
continue; // 跳过这个 Job,下次定时任务重试
|
continue; // 跳过这个 Job,下次定时任务重试
|
||||||
}
|
}
|
||||||
@ -596,7 +580,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
JenkinsBuildNotification existingRecord = notificationMap.get(notificationKey);
|
JenkinsBuildNotification existingRecord = notificationMap.get(notificationKey);
|
||||||
|
|
||||||
// 处理该构建的通知
|
// 处理该构建的通知
|
||||||
processBuildNotification(config, channel, job, latestBuild, latestStatus,
|
processBuildNotification(config, channel, job, latestBuild, latestStatus,
|
||||||
externalSystem, application, environment, existingRecord);
|
externalSystem, application, environment, existingRecord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -608,7 +592,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理单个构建的通知(参考longi-deployment逻辑)
|
* 处理单个构建的通知(参考longi-deployment逻辑)
|
||||||
*
|
*
|
||||||
* <p>核心逻辑:
|
* <p>核心逻辑:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>无记录 = 新构建:超时(>20分钟)直接标记完成,未超时发送"构建中"通知</li>
|
* <li>无记录 = 新构建:超时(>20分钟)直接标记完成,未超时发送"构建中"通知</li>
|
||||||
@ -641,7 +625,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
record.setBuildStartNotice(true);
|
record.setBuildStartNotice(true);
|
||||||
record.setBuildEndNotice(true); // 直接标记结束
|
record.setBuildEndNotice(true); // 直接标记结束
|
||||||
jenkinsBuildNotificationRepository.save(record);
|
jenkinsBuildNotificationRepository.save(record);
|
||||||
log.info("构建超时,直接标记完成: job={}, build={}, minutesAgo={}",
|
log.info("构建超时,直接标记完成: job={}, build={}, minutesAgo={}",
|
||||||
job.getJobName(), build.getBuildNumber(), minutesAgo);
|
job.getJobName(), build.getBuildNumber(), minutesAgo);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -666,7 +650,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
sendNotification(config, channel, job, build, latestStatus, externalSystem, application, environment);
|
sendNotification(config, channel, job, build, latestStatus, externalSystem, application, environment);
|
||||||
existingRecord.setBuildEndNotice(true);
|
existingRecord.setBuildEndNotice(true);
|
||||||
jenkinsBuildNotificationRepository.save(existingRecord);
|
jenkinsBuildNotificationRepository.save(existingRecord);
|
||||||
log.info("发送构建结束通知: job={}, build={}, status={}",
|
log.info("发送构建结束通知: job={}, build={}, status={}",
|
||||||
job.getJobName(), build.getBuildNumber(), latestStatus);
|
job.getJobName(), build.getBuildNumber(), latestStatus);
|
||||||
} else {
|
} else {
|
||||||
// 兜底:超过2小时仍未完成,强制标记结束(不发通知)
|
// 兜底:超过2小时仍未完成,强制标记结束(不发通知)
|
||||||
@ -681,10 +665,10 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (org.springframework.orm.ObjectOptimisticLockingFailureException e) {
|
} catch (org.springframework.orm.ObjectOptimisticLockingFailureException e) {
|
||||||
log.warn("构建通知记录乐观锁冲突,跳过: teamId={}, envId={}, buildId={}",
|
log.warn("构建通知记录乐观锁冲突,跳过: teamId={}, envId={}, buildId={}",
|
||||||
config.getTeamId(), config.getEnvironmentId(), build.getId());
|
config.getTeamId(), config.getEnvironmentId(), build.getId());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("处理构建通知失败: teamId={}, envId={}, buildId={}",
|
log.error("处理构建通知失败: teamId={}, envId={}, buildId={}",
|
||||||
config.getTeamId(), config.getEnvironmentId(), build.getId(), e);
|
config.getTeamId(), config.getEnvironmentId(), build.getId(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -858,28 +842,28 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
// 解析 changeSets 中的 commit 信息
|
// 解析 changeSets 中的 commit 信息
|
||||||
JsonNode changeSets = root.get("changeSets");
|
JsonNode changeSets = root.get("changeSets");
|
||||||
log.debug("changeSets 节点: {}", changeSets);
|
log.debug("changeSets 节点: {}", changeSets);
|
||||||
|
|
||||||
if (changeSets != null && changeSets.isArray() && changeSets.size() > 0) {
|
if (changeSets != null && changeSets.isArray() && changeSets.size() > 0) {
|
||||||
log.debug("changeSets 数组大小: {}", changeSets.size());
|
log.debug("changeSets 数组大小: {}", changeSets.size());
|
||||||
StringBuilder commitMessages = new StringBuilder();
|
StringBuilder commitMessages = new StringBuilder();
|
||||||
for (JsonNode changeSet : changeSets) {
|
for (JsonNode changeSet : changeSets) {
|
||||||
JsonNode items = changeSet.get("items");
|
JsonNode items = changeSet.get("items");
|
||||||
log.debug("items 节点: {}", items);
|
log.debug("items 节点: {}", items);
|
||||||
|
|
||||||
if (items != null && items.isArray()) {
|
if (items != null && items.isArray()) {
|
||||||
log.debug("items 数组大小: {}", items.size());
|
log.debug("items 数组大小: {}", items.size());
|
||||||
for (JsonNode item : items) {
|
for (JsonNode item : items) {
|
||||||
log.debug("处理 item: {}", item);
|
log.debug("处理 item: {}", item);
|
||||||
|
|
||||||
// Jenkins API 返回的字段名是 msg
|
// Jenkins API 返回的字段名是 msg
|
||||||
String msg = item.has("msg") ? item.get("msg").asText() : "";
|
String msg = item.has("msg") ? item.get("msg").asText() : "";
|
||||||
|
|
||||||
// author 是一个对象,需要提取 fullName 或其他字段
|
// author 是一个对象,需要提取 fullName 或其他字段
|
||||||
String author = "";
|
String author = "";
|
||||||
if (item.has("author")) {
|
if (item.has("author")) {
|
||||||
JsonNode authorNode = item.get("author");
|
JsonNode authorNode = item.get("author");
|
||||||
log.debug("author 节点: {}", authorNode);
|
log.debug("author 节点: {}", authorNode);
|
||||||
|
|
||||||
if (authorNode.isTextual()) {
|
if (authorNode.isTextual()) {
|
||||||
// 如果是字符串,直接使用
|
// 如果是字符串,直接使用
|
||||||
author = authorNode.asText();
|
author = authorNode.asText();
|
||||||
@ -892,7 +876,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String commitId = item.has("commitId") ? item.get("commitId").asText() : "";
|
String commitId = item.has("commitId") ? item.get("commitId").asText() : "";
|
||||||
|
|
||||||
log.debug("解析结果 - msg: {}, author: {}, commitId: {}", msg, author, commitId);
|
log.debug("解析结果 - msg: {}, author: {}, commitId: {}", msg, author, commitId);
|
||||||
|
|||||||
@ -53,8 +53,8 @@ public class JenkinsSyncHistoryServiceImpl extends BaseServiceImpl<JenkinsSyncHi
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成同步历史编号
|
* 生成同步历史编号
|
||||||
* 格式:{同步类型}_{年月日时分秒}_{系统ID}
|
* 格式:{同步类型}_{年月日时分秒毫秒}_{系统ID}
|
||||||
* 例如:VIEW_20231220123456_1
|
* 例如:VIEW_20231220123456789_1
|
||||||
*
|
*
|
||||||
* @param syncType 同步类型
|
* @param syncType 同步类型
|
||||||
* @param externalSystemId 外部系统ID
|
* @param externalSystemId 外部系统ID
|
||||||
@ -63,7 +63,7 @@ public class JenkinsSyncHistoryServiceImpl extends BaseServiceImpl<JenkinsSyncHi
|
|||||||
private String generateSyncHistoryNumber(JenkinsSyncType syncType, Long externalSystemId) {
|
private String generateSyncHistoryNumber(JenkinsSyncType syncType, Long externalSystemId) {
|
||||||
return String.format("%s_%s_%d",
|
return String.format("%s_%s_%d",
|
||||||
syncType.name(),
|
syncType.name(),
|
||||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")),
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")),
|
||||||
externalSystemId);
|
externalSystemId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user