增加构建通知

This commit is contained in:
dengqichen 2025-11-27 17:02:28 +08:00
parent f6569fe020
commit 3fc6ddc8fc
2 changed files with 141 additions and 33 deletions

View File

@ -67,12 +67,12 @@ public interface IJenkinsBuildRepository extends IBaseRepository<JenkinsBuild, L
List<Object[]> countByJobIds(@Param("jobIds") Collection<Long> jobIds);
/**
* 查询指定时间之后创建的构建记录用于构建通知
* 查询指定时间之后开始的构建记录用于构建通知
*
* @param externalSystemId 外部系统ID
* @param createTime 创建时间
* @param starttime 构建开始时间
* @return 构建记录列表
*/
List<JenkinsBuild> findByExternalSystemIdAndCreateTimeAfter(
Long externalSystemId, LocalDateTime createTime);
List<JenkinsBuild> findByExternalSystemIdAndStarttimeAfter(
Long externalSystemId, LocalDateTime starttime);
}

View File

@ -74,6 +74,12 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
@Resource
private ITeamApplicationRepository teamApplicationRepository;
@Resource
private IApplicationRepository applicationRepository;
@Resource
private IEnvironmentRepository environmentRepository;
@Resource
private ITeamEnvironmentNotificationConfigRepository teamEnvironmentNotificationConfigRepository;
@ -330,7 +336,12 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
}
try {
String actionsJson = objectMapper.writeValueAsString(response.getActions());
// 保存 actions changeSets JSON
Map<String, Object> buildData = new HashMap<>();
buildData.put("actions", response.getActions());
buildData.put("changeSets", response.getChangeSets());
buildData.put("gitCommitId", response.getGitCommitId());
String actionsJson = objectMapper.writeValueAsString(buildData);
jenkinsBuild.setActions(actionsJson);
} catch (JsonProcessingException e) {
log.error("Failed to serialize build actions", e);
@ -427,10 +438,10 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
return;
}
// 1. 查询最近同步的构建6分钟内比同步间隔5分钟多1分钟
// 1. 查询最近开始的构建6分钟内比同步间隔5分钟多1分钟
LocalDateTime since = LocalDateTime.now().minusMinutes(6);
List<JenkinsBuild> recentBuilds = jenkinsBuildRepository
.findByExternalSystemIdAndCreateTimeAfter(externalSystemId, since);
.findByExternalSystemIdAndStarttimeAfter(externalSystemId, since);
if (recentBuilds.isEmpty()) {
log.info("没有新的构建记录");
@ -500,7 +511,18 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
build -> jobMap.get(build.getJobId()).getJobName()
));
// 9. 处理每个 Job 的构建通知
// 9. 批量查询应用信息
Set<Long> applicationIds = teamApps.stream()
.map(TeamApplication::getApplicationId)
.collect(Collectors.toSet());
Map<Long, Application> applicationMap = applicationRepository.findAllById(applicationIds).stream()
.collect(Collectors.toMap(Application::getId, a -> a));
// 10. 批量查询环境信息
Map<Long, Environment> environmentMap = environmentRepository.findAllById(envIds).stream()
.collect(Collectors.toMap(Environment::getId, e -> e));
// 11. 处理每个 Job 的构建通知
buildsByJobName.forEach((jobName, builds) -> {
List<TeamApplication> relatedTeamApps = teamAppsByJob.get(jobName);
if (relatedTeamApps == null || relatedTeamApps.isEmpty()) {
@ -517,10 +539,12 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
if (channel == null) continue;
JenkinsJob job = jobMap.get(builds.get(0).getJobId());
Application application = applicationMap.get(teamApp.getApplicationId());
Environment environment = environmentMap.get(teamApp.getEnvironmentId());
// 处理该团队环境的所有构建通知
for (JenkinsBuild build : builds) {
processBuildNotification(config, channel, job, build, externalSystem);
processBuildNotification(config, channel, job, build, externalSystem, application, environment);
}
}
});
@ -531,14 +555,16 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
}
/**
* 处理单个构建的通知照搬longi逻辑
* 处理单个构建的通知
*/
private void processBuildNotification(
TeamEnvironmentNotificationConfig config,
NotificationChannel channel,
JenkinsJob job,
JenkinsBuild build,
ExternalSystem externalSystem) {
ExternalSystem externalSystem,
Application application,
Environment environment) {
try {
// 1. 查询通知记录
@ -583,7 +609,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
// 只通知成功和失败
if ("SUCCESS".equals(status) || "FAILURE".equals(status)) {
sendNotification(config, channel, job, build, status, externalSystem);
sendNotification(config, channel, job, build, status, externalSystem, application, environment);
}
record.setBuildEndNotice(true);
@ -615,7 +641,9 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
JenkinsJob job,
JenkinsBuild build,
String status,
ExternalSystem externalSystem) {
ExternalSystem externalSystem,
Application application,
Environment environment) {
try {
// 1. 检查是否配置了构建通知模板
@ -636,37 +664,70 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
// 3. 构建模板参数
Map<String, Object> templateParams = new HashMap<>();
// 应用信息
if (application != null) {
templateParams.put("applicationId", application.getId());
templateParams.put("applicationCode", application.getAppCode());
templateParams.put("applicationName", application.getAppName());
}
// 环境信息
if (environment != null) {
templateParams.put("environmentId", environment.getId());
templateParams.put("environmentCode", environment.getEnvCode());
templateParams.put("environmentName", environment.getEnvName());
}
// Jenkins 构建信息
templateParams.put("jobName", job.getJobName());
templateParams.put("buildNumber", build.getBuildNumber());
templateParams.put("buildUrl", build.getBuildUrl());
templateParams.put("status", status);
templateParams.put("startTime", build.getStarttime());
templateParams.put("buildStatus", status);
// 状态显示
String statusDisplay = switch (status) {
case "SUCCESS" -> "✅ 成功";
case "FAILURE" -> "❌ 失败";
case "BUILDING" -> "🔄 构建中";
default -> status;
};
templateParams.put("statusDisplay", statusDisplay);
// 耗时
if (build.getDuration() != null) {
long seconds = build.getDuration() / 1000;
long minutes = seconds / 60;
long secs = seconds % 60;
templateParams.put("duration", String.format("%d分%d秒", minutes, secs));
templateParams.put("durationMs", build.getDuration());
// 时间格式化
java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
if (build.getStarttime() != null) {
templateParams.put("buildStartTime", build.getStarttime().format(formatter));
}
// 4. 构建 SendNotificationRequest
// 构建结束时间如果有
if (build.getStarttime() != null && build.getDuration() != null) {
LocalDateTime endTime = build.getStarttime().plusNanos(build.getDuration() * 1_000_000);
templateParams.put("buildEndTime", endTime.format(formatter));
}
// 耗时格式化
if (build.getDuration() != null) {
long totalSeconds = build.getDuration() / 1000;
long minutes = totalSeconds / 60;
long seconds = totalSeconds % 60;
templateParams.put("buildDurationFormatted", String.format("%d分%d秒", minutes, seconds));
templateParams.put("buildDurationMs", build.getDuration());
}
// actions JSON 中解析 commit 信息
parseCommitInfoFromActions(build.getActions(), templateParams);
// 4. 校验模板和渠道类型是否匹配
if (!template.getChannelType().equals(channel.getChannelType())) {
log.warn("模板渠道类型({})与通知渠道类型({})不匹配,跳过通知: templateId={}, channelId={}",
template.getChannelType(), channel.getChannelType(),
config.getBuildNotificationTemplateId(), channel.getId());
return;
}
// 5. 构建 SendNotificationRequest
SendNotificationRequest request = new SendNotificationRequest();
request.setNotificationTemplateId(config.getBuildNotificationTemplateId());
request.setTemplateParams(templateParams);
request.setSendRequest(createSendRequestByChannel(channel, template));
// 5. 发送通知
log.debug("准备发送构建通知: job={}, build={}, templateId={}, channelId={}, channelType={}",
job.getJobName(), build.getBuildNumber(), config.getBuildNotificationTemplateId(),
channel.getId(), channel.getChannelType());
// 6. 发送通知
notificationService.send(request);
log.info("已发送构建通知: job={}, build={}, status={}, templateId={}",
@ -721,6 +782,53 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
return WeworkMessageTypeEnum.TEXT;
}
/**
* actions JSON 中解析 commit 信息
*/
private void parseCommitInfoFromActions(String actionsJson, Map<String, Object> templateParams) {
if (actionsJson == null || actionsJson.isEmpty()) {
return;
}
try {
JsonNode root = objectMapper.readTree(actionsJson);
// 解析 gitCommitId
if (root.has("gitCommitId") && !root.get("gitCommitId").isNull()) {
templateParams.put("gitCommitId", root.get("gitCommitId").asText());
}
// 解析 changeSets 中的 commit 信息
JsonNode changeSets = root.get("changeSets");
if (changeSets != null && changeSets.isArray() && changeSets.size() > 0) {
StringBuilder commitMessages = new StringBuilder();
for (JsonNode changeSet : changeSets) {
JsonNode items = changeSet.get("items");
if (items != null && items.isArray()) {
for (JsonNode item : items) {
String message = item.has("message") ? item.get("message").asText() : "";
String author = item.has("author") ? item.get("author").asText() : "";
String commitId = item.has("commitId") ? item.get("commitId").asText() : "";
if (!message.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, message.trim(), author));
}
}
}
}
if (commitMessages.length() > 0) {
templateParams.put("commitMessage", commitMessages.toString());
}
}
} catch (Exception e) {
log.warn("解析 commit 信息失败: {}", e.getMessage());
}
}
/**
* 发送构建失败日志文件到企业微信
*/