增加构建通知

This commit is contained in:
dengqichen 2025-11-28 09:36:54 +08:00
parent 730bb94926
commit e7211f8699
2 changed files with 176 additions and 210 deletions

View File

@ -16,6 +16,7 @@ import com.qqchen.deploy.backend.deploy.integration.IJenkinsServiceIntegration;
import com.qqchen.deploy.backend.deploy.repository.*; import com.qqchen.deploy.backend.deploy.repository.*;
import com.qqchen.deploy.backend.deploy.service.IJenkinsBuildService; import com.qqchen.deploy.backend.deploy.service.IJenkinsBuildService;
import com.qqchen.deploy.backend.deploy.service.IJenkinsSyncHistoryService; import com.qqchen.deploy.backend.deploy.service.IJenkinsSyncHistoryService;
import com.qqchen.deploy.backend.notification.dto.BaseSendNotificationRequest;
import com.qqchen.deploy.backend.notification.entity.NotificationChannel; import com.qqchen.deploy.backend.notification.entity.NotificationChannel;
import com.qqchen.deploy.backend.notification.entity.NotificationTemplate; import com.qqchen.deploy.backend.notification.entity.NotificationTemplate;
import com.qqchen.deploy.backend.notification.entity.config.WeworkTemplateConfig; import com.qqchen.deploy.backend.notification.entity.config.WeworkTemplateConfig;
@ -33,14 +34,26 @@ 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;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -48,8 +61,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, JenkinsBuildDTO, JenkinsBuildQuery, Long> public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, JenkinsBuildDTO, JenkinsBuildQuery, Long> implements IJenkinsBuildService {
implements IJenkinsBuildService {
@Resource @Resource
private IExternalSystemRepository externalSystemRepository; private IExternalSystemRepository externalSystemRepository;
@ -96,8 +108,6 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
@Resource @Resource
private INotificationService notificationService; private INotificationService notificationService;
@Resource
private INotificationSendService notificationSendService;
@Resource(name = "jenkinsTaskExecutor") @Resource(name = "jenkinsTaskExecutor")
private ThreadPoolTaskExecutor threadPoolTaskExecutor; private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@ -107,7 +117,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
@Transactional @Transactional
public void syncBuilds(Long externalSystemId) { public void syncBuilds(Long externalSystemId) {
doSyncBuilds(externalSystemId, null, null); doSyncBuilds(externalSystemId, null, null);
// 同步完成后检查并发送构建通知 // 同步完成后检查并发送构建通知
checkBuildNotifications(externalSystemId); checkBuildNotifications(externalSystemId);
} }
@ -143,7 +153,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
() -> syncView(context.getExternalSystem(), view), () -> syncView(context.getExternalSystem(), view),
threadPoolTaskExecutor threadPoolTaskExecutor
)) ))
.collect(Collectors.toList()); .toList();
// 3. 等待所有任务完成并汇总结果 // 3. 等待所有任务完成并汇总结果
int totalSyncedBuilds = futures.stream() int totalSyncedBuilds = futures.stream()
@ -160,7 +170,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
// 4. 更新同步历史 // 4. 更新同步历史
updateSyncHistorySuccess(context.getSyncHistory()); updateSyncHistorySuccess(context.getSyncHistory());
log.info("Successfully synchronized total {} builds for external system: {}", log.info("Successfully synchronized total {} builds for external system: {}",
totalSyncedBuilds, context.getExternalSystem().getId()); totalSyncedBuilds, context.getExternalSystem().getId());
return totalSyncedBuilds; return totalSyncedBuilds;
@ -172,7 +182,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
// 1. 获取视图下的所有任务 // 1. 获取视图下的所有任务
List<JenkinsJob> jobs = jenkinsJobRepository.findByExternalSystemIdAndViewId( List<JenkinsJob> jobs = jenkinsJobRepository.findByExternalSystemIdAndViewId(
externalSystem.getId(), view.getId()); externalSystem.getId(), view.getId());
if (jobs.isEmpty()) { if (jobs.isEmpty()) {
return 0; return 0;
} }
@ -223,7 +233,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
// 3. 保存新的构建信息 // 3. 保存新的构建信息
saveNewBuilds(externalSystem, job, newBuilds); saveNewBuilds(externalSystem, job, newBuilds);
// 4. 更新任务的最新构建信息 // 4. 更新任务的最新构建信息
updateJobLastBuild(job, jobResponse); updateJobLastBuild(job, jobResponse);
@ -236,11 +246,11 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
} }
private List<JenkinsBuildResponse> getNewBuilds( private List<JenkinsBuildResponse> getNewBuilds(
ExternalSystem externalSystem, ExternalSystem externalSystem,
JenkinsJob job, JenkinsJob job,
JenkinsJobResponse jobResponse, JenkinsJobResponse jobResponse,
Optional<JenkinsBuild> lastBuild) { Optional<JenkinsBuild> lastBuild) {
// 1. 获取最新构建号从jobResponse中获取避免额外的API调用 // 1. 获取最新构建号从jobResponse中获取避免额外的API调用
if (jobResponse.getLastBuild() == null) { if (jobResponse.getLastBuild() == null) {
log.info("No builds found for job: {}", job.getJobName()); log.info("No builds found for job: {}", job.getJobName());
@ -268,7 +278,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
// 4. 获取构建信息 // 4. 获取构建信息
List<JenkinsBuildResponse> builds = jenkinsServiceIntegration.listBuilds(externalSystem, job.getJobName()); List<JenkinsBuildResponse> builds = jenkinsServiceIntegration.listBuilds(externalSystem, job.getJobName());
// 5. 过滤出需要的构建 // 5. 过滤出需要的构建
List<JenkinsBuildResponse> newBuilds = builds.stream() List<JenkinsBuildResponse> newBuilds = builds.stream()
.filter(build -> build.getNumber() >= fromBuildNumber && build.getNumber() <= latestBuildNumber) .filter(build -> build.getNumber() >= fromBuildNumber && build.getNumber() <= latestBuildNumber)
@ -280,9 +290,9 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
} }
private void saveNewBuilds( private void saveNewBuilds(
ExternalSystem externalSystem, ExternalSystem externalSystem,
JenkinsJob job, JenkinsJob job,
List<JenkinsBuildResponse> builds) { List<JenkinsBuildResponse> builds) {
List<JenkinsBuild> jenkinsBuilds = builds.stream() List<JenkinsBuild> jenkinsBuilds = builds.stream()
.map(buildResponse -> { .map(buildResponse -> {
JenkinsBuild build = new JenkinsBuild(); JenkinsBuild build = new JenkinsBuild();
@ -310,9 +320,9 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
} }
private void handleSyncException(JenkinsSyncContext context, Exception e) { private void handleSyncException(JenkinsSyncContext context, Exception e) {
log.error("Failed to sync Jenkins builds for external system: {}", log.error("Failed to sync Jenkins builds for external system: {}",
context.getExternalSystem().getId(), e); context.getExternalSystem().getId(), e);
context.getSyncHistory().setStatus(ExternalSystemSyncStatus.FAILED); context.getSyncHistory().setStatus(ExternalSystemSyncStatus.FAILED);
context.getSyncHistory().setErrorMessage(e.getMessage()); context.getSyncHistory().setErrorMessage(e.getMessage());
jenkinsSyncHistoryService.saveOrUpdateHistory(context.getSyncHistory()); jenkinsSyncHistoryService.saveOrUpdateHistory(context.getSyncHistory());
@ -355,7 +365,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
* 同步指定视图下的构建信息异步执行 * 同步指定视图下的构建信息异步执行
* *
* @param externalSystemId 外部系统ID * @param externalSystemId 外部系统ID
* @param viewId 视图ID * @param viewId 视图ID
*/ */
@Override @Override
@Async @Async
@ -368,8 +378,8 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
* 同步指定任务的构建信息异步执行 * 同步指定任务的构建信息异步执行
* *
* @param externalSystemId 外部系统ID * @param externalSystemId 外部系统ID
* @param viewId 视图ID (未使用但为了保持API一致性) * @param viewId 视图ID (未使用但为了保持API一致性)
* @param jobId 任务ID * @param jobId 任务ID
*/ */
@Override @Override
@Async @Async
@ -382,8 +392,8 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
* 执行构建同步的核心方法 * 执行构建同步的核心方法
* *
* @param externalSystemId 外部系统ID * @param externalSystemId 外部系统ID
* @param viewId 视图ID可选 * @param viewId 视图ID可选
* @param jobId 任务ID可选 * @param jobId 任务ID可选
*/ */
private void doSyncBuilds(Long externalSystemId, Long viewId, Long jobId) { private void doSyncBuilds(Long externalSystemId, Long viewId, Long jobId) {
// 1. 创建同步上下文 // 1. 创建同步上下文
@ -430,7 +440,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
*/ */
private void checkBuildNotifications(Long externalSystemId) { private void checkBuildNotifications(Long externalSystemId) {
log.info("开始检查构建通知: externalSystemId={}", externalSystemId); log.info("开始检查构建通知: externalSystemId={}", externalSystemId);
try { try {
// 0. 获取外部系统信息用于后续获取日志 // 0. 获取外部系统信息用于后续获取日志
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId) ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
@ -439,99 +449,99 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
log.warn("外部系统不存在: externalSystemId={}", externalSystemId); log.warn("外部系统不存在: externalSystemId={}", externalSystemId);
return; return;
} }
// 1. 查询最近6分钟内开始的新构建用于发送开始通知 // 1. 查询最近6分钟内开始的新构建用于发送开始通知
LocalDateTime recentSince = LocalDateTime.now().minusMinutes(6); LocalDateTime recentSince = LocalDateTime.now().minusMinutes(6);
List<JenkinsBuild> recentBuilds = jenkinsBuildRepository List<JenkinsBuild> recentBuilds = jenkinsBuildRepository
.findByExternalSystemIdAndStarttimeAfter(externalSystemId, recentSince); .findByExternalSystemIdAndStarttimeAfter(externalSystemId, recentSince);
// 2. 查询未完成通知的构建ID用于发送结束通知和兜底处理 // 2. 查询未完成通知的构建ID用于发送结束通知和兜底处理
List<JenkinsBuildNotification> pendingNotifications = jenkinsBuildNotificationRepository List<JenkinsBuildNotification> pendingNotifications = jenkinsBuildNotificationRepository
.findByBuildEndNoticeFalseAndDeletedFalse(); .findByBuildEndNoticeFalseAndDeletedFalse();
Set<Long> pendingBuildIds = pendingNotifications.stream() Set<Long> pendingBuildIds = pendingNotifications.stream()
.map(JenkinsBuildNotification::getBuildId) .map(JenkinsBuildNotification::getBuildId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 3. 查询未完成通知对应的构建记录 // 3. 查询未完成通知对应的构建记录
List<JenkinsBuild> pendingBuilds = pendingBuildIds.isEmpty() List<JenkinsBuild> pendingBuilds = pendingBuildIds.isEmpty()
? Collections.emptyList() ? Collections.emptyList()
: jenkinsBuildRepository.findAllById(pendingBuildIds).stream() : jenkinsBuildRepository.findAllById(pendingBuildIds).stream()
.filter(b -> b.getExternalSystemId().equals(externalSystemId)) .filter(b -> b.getExternalSystemId().equals(externalSystemId))
.collect(Collectors.toList()); .toList();
// 4. 合并需要处理的构建去重 // 4. 合并需要处理的构建去重
Map<Long, JenkinsBuild> buildMap = new HashMap<>(); Map<Long, JenkinsBuild> buildMap = new HashMap<>();
recentBuilds.forEach(b -> buildMap.put(b.getId(), b)); recentBuilds.forEach(b -> buildMap.put(b.getId(), b));
pendingBuilds.forEach(b -> buildMap.put(b.getId(), b)); pendingBuilds.forEach(b -> buildMap.put(b.getId(), b));
List<JenkinsBuild> buildsToProcess = new ArrayList<>(buildMap.values()); List<JenkinsBuild> buildsToProcess = new ArrayList<>(buildMap.values());
if (buildsToProcess.isEmpty()) { if (buildsToProcess.isEmpty()) {
log.info("没有需要处理的构建记录"); log.info("没有需要处理的构建记录");
return; return;
} }
// 2. 反查团队绑定关系只查询构建类型为JENKINS的应用 // 2. 反查团队绑定关系只查询构建类型为JENKINS的应用
List<TeamApplication> teamApps = teamApplicationRepository.findByDeploySystemIdAndBuildType(externalSystemId, BuildTypeEnum.JENKINS); List<TeamApplication> teamApps = teamApplicationRepository.findByDeploySystemIdAndBuildType(externalSystemId, BuildTypeEnum.JENKINS);
if (teamApps.isEmpty()) { if (teamApps.isEmpty()) {
log.info("没有团队绑定该Jenkins系统: externalSystemId={}", externalSystemId); log.info("没有团队绑定该Jenkins系统: externalSystemId={}", externalSystemId);
return; return;
} }
// 3. deploy_job 分组 TeamApplication // 3. deploy_job 分组 TeamApplication
Map<String, List<TeamApplication>> teamAppsByJob = teamApps.stream() Map<String, List<TeamApplication>> teamAppsByJob = teamApps.stream()
.collect(Collectors.groupingBy(TeamApplication::getDeployJob)); .collect(Collectors.groupingBy(TeamApplication::getDeployJob));
// 4. 查询启用了构建通知的团队环境配置 // 4. 查询启用了构建通知的团队环境配置
Set<Long> teamIds = teamApps.stream() Set<Long> teamIds = teamApps.stream()
.map(TeamApplication::getTeamId) .map(TeamApplication::getTeamId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
Set<Long> envIds = teamApps.stream() Set<Long> envIds = teamApps.stream()
.map(TeamApplication::getEnvironmentId) .map(TeamApplication::getEnvironmentId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
List<TeamEnvironmentNotificationConfig> configs = List<TeamEnvironmentNotificationConfig> configs =
teamEnvironmentNotificationConfigRepository teamEnvironmentNotificationConfigRepository
.findByTeamIdInAndEnvironmentIdInAndBuildNotificationEnabledTrue(teamIds, envIds); .findByTeamIdInAndEnvironmentIdInAndBuildNotificationEnabledTrue(teamIds, envIds);
if (configs.isEmpty()) { if (configs.isEmpty()) {
log.info("没有团队启用构建通知"); log.info("没有团队启用构建通知");
return; return;
} }
// 5. team_id + environment_id 分组配置 // 5. team_id + environment_id 分组配置
Map<String, TeamEnvironmentNotificationConfig> configMap = configs.stream() Map<String, TeamEnvironmentNotificationConfig> configMap = configs.stream()
.collect(Collectors.toMap( .collect(Collectors.toMap(
cfg -> cfg.getTeamId() + "_" + cfg.getEnvironmentId(), cfg -> cfg.getTeamId() + "_" + cfg.getEnvironmentId(),
cfg -> cfg cfg -> cfg
)); ));
// 6. 批量查询通知渠道 // 6. 批量查询通知渠道
Set<Long> channelIds = configs.stream() Set<Long> channelIds = configs.stream()
.map(TeamEnvironmentNotificationConfig::getNotificationChannelId) .map(TeamEnvironmentNotificationConfig::getNotificationChannelId)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
Map<Long, NotificationChannel> channelMap = Map<Long, NotificationChannel> channelMap =
notificationChannelRepository.findAllById(channelIds).stream() notificationChannelRepository.findAllById(channelIds).stream()
.collect(Collectors.toMap(NotificationChannel::getId, c -> c)); .collect(Collectors.toMap(NotificationChannel::getId, c -> c));
// 7. 批量查询 Job // 7. 批量查询 Job
Set<Long> jobIds = buildsToProcess.stream() Set<Long> jobIds = buildsToProcess.stream()
.map(JenkinsBuild::getJobId) .map(JenkinsBuild::getJobId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
Map<Long, JenkinsJob> jobMap = jenkinsJobRepository Map<Long, JenkinsJob> jobMap = jenkinsJobRepository
.findAllById(jobIds).stream() .findAllById(jobIds).stream()
.collect(Collectors.toMap(JenkinsJob::getId, j -> j)); .collect(Collectors.toMap(JenkinsJob::getId, j -> j));
// 8. job_name 分组构建记录 // 8. job_name 分组构建记录
Map<String, List<JenkinsBuild>> buildsByJobName = buildsToProcess.stream() Map<String, List<JenkinsBuild>> buildsByJobName = buildsToProcess.stream()
.collect(Collectors.groupingBy( .collect(Collectors.groupingBy(
build -> jobMap.get(build.getJobId()).getJobName() build -> jobMap.get(build.getJobId()).getJobName()
)); ));
// 9. 批量查询应用信息 // 9. 批量查询应用信息
Set<Long> applicationIds = teamApps.stream() Set<Long> applicationIds = teamApps.stream()
.map(TeamApplication::getApplicationId) .map(TeamApplication::getApplicationId)
@ -549,66 +559,54 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
if (relatedTeamApps == null || relatedTeamApps.isEmpty()) { if (relatedTeamApps == null || relatedTeamApps.isEmpty()) {
return; return;
} }
for (TeamApplication teamApp : relatedTeamApps) { for (TeamApplication teamApp : relatedTeamApps) {
String key = teamApp.getTeamId() + "_" + teamApp.getEnvironmentId(); String key = teamApp.getTeamId() + "_" + teamApp.getEnvironmentId();
TeamEnvironmentNotificationConfig config = configMap.get(key); TeamEnvironmentNotificationConfig config = configMap.get(key);
if (config == null) continue; if (config == null) continue;
NotificationChannel channel = channelMap.get(config.getNotificationChannelId()); NotificationChannel channel = channelMap.get(config.getNotificationChannelId());
if (channel == null) continue; if (channel == null) continue;
JenkinsJob job = jobMap.get(builds.get(0).getJobId()); JenkinsJob job = jobMap.get(builds.get(0).getJobId());
Application application = applicationMap.get(teamApp.getApplicationId()); Application application = applicationMap.get(teamApp.getApplicationId());
Environment environment = environmentMap.get(teamApp.getEnvironmentId()); Environment environment = environmentMap.get(teamApp.getEnvironmentId());
// 处理该团队环境的所有构建通知 // 处理该团队环境的所有构建通知
for (JenkinsBuild build : builds) { for (JenkinsBuild build : builds) {
processBuildNotification(config, channel, job, build, externalSystem, application, environment); processBuildNotification(config, channel, job, build, externalSystem, application, environment);
} }
} }
}); });
} catch (Exception e) { } catch (Exception e) {
log.error("检查构建通知失败: externalSystemId={}", externalSystemId, e); log.error("检查构建通知失败: externalSystemId={}", externalSystemId, e);
} }
} }
/** /**
* 处理单个构建的通知 * 处理单个构建的通知
*/ */
private void processBuildNotification( private void processBuildNotification(TeamEnvironmentNotificationConfig config, NotificationChannel channel, JenkinsJob job, JenkinsBuild build, ExternalSystem externalSystem, Application application, Environment environment) {
TeamEnvironmentNotificationConfig config,
NotificationChannel channel,
JenkinsJob job,
JenkinsBuild build,
ExternalSystem externalSystem,
Application application,
Environment environment) {
try { try {
// 1. 查询通知记录 // 1. 查询通知记录
JenkinsBuildNotification record = jenkinsBuildNotificationRepository JenkinsBuildNotification record = jenkinsBuildNotificationRepository.findByBuildIdAndTeamIdAndEnvironmentId(build.getId(), config.getTeamId(), config.getEnvironmentId())
.findByBuildIdAndTeamIdAndEnvironmentId(
build.getId(),
config.getTeamId(),
config.getEnvironmentId()
)
.orElse(null); .orElse(null);
// 2. 新构建只处理6分钟内的新构建 // 2. 新构建只处理6分钟内的新构建
if (record == null) { if (record == null) {
long minutesAgo = java.time.temporal.ChronoUnit.MINUTES.between( long minutesAgo = java.time.temporal.ChronoUnit.MINUTES.between(
build.getStarttime(), build.getStarttime(),
LocalDateTime.now() LocalDateTime.now()
); );
// 超过6分钟的旧构建不创建通知记录直接跳过 // 超过6分钟的旧构建不创建通知记录直接跳过
if (minutesAgo > 6) { if (minutesAgo > 6) {
return; return;
} }
// 6分钟内的新构建发送"构建中"通知 // 6分钟内的新构建发送"构建中"通知
record = new JenkinsBuildNotification(); record = new JenkinsBuildNotification();
record.setBuildId(build.getId()); record.setBuildId(build.getId());
@ -619,120 +617,106 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
jenkinsBuildNotificationRepository.save(record); jenkinsBuildNotificationRepository.save(record);
return; return;
} }
// 3. 已有记录检查结束通知 // 3. 已有记录检查结束通知
if (!record.getBuildEndNotice()) { if (!record.getBuildEndNotice()) {
if (isBuildFinished(build)) { if (isBuildFinished(build)) {
// 构建已完成发送结束通知 // 构建已完成发送结束通知
String status = build.getBuildStatus(); String status = build.getBuildStatus();
// 通知成功失败取消 // 通知成功失败取消
if ("SUCCESS".equals(status) || "FAILURE".equals(status) || "ABORTED".equals(status)) { if ("SUCCESS".equals(status) || "FAILURE".equals(status) || "ABORTED".equals(status)) {
sendNotification(config, channel, job, build, status, externalSystem, application, environment); sendNotification(config, channel, job, build, status, externalSystem, application, environment);
} }
record.setBuildEndNotice(true); record.setBuildEndNotice(true);
jenkinsBuildNotificationRepository.save(record); jenkinsBuildNotificationRepository.save(record);
} else { } else {
// 兜底逻辑构建开始超过2小时仍未完成强制标记为已完成不发通知 // 兜底逻辑构建开始超过2小时仍未完成强制标记为已完成不发通知
long hoursAgo = java.time.temporal.ChronoUnit.HOURS.between( long hoursAgo = java.time.temporal.ChronoUnit.HOURS.between(
build.getStarttime(), build.getStarttime(),
LocalDateTime.now() LocalDateTime.now()
); );
if (hoursAgo >= 2) { if (hoursAgo >= 2) {
log.warn("构建超时未完成,强制标记结束通知: buildId={}, teamId={}, envId={}, startTime={}", log.warn("构建超时未完成,强制标记结束通知: buildId={}, teamId={}, envId={}, startTime={}",
build.getId(), config.getTeamId(), config.getEnvironmentId(), build.getStarttime()); build.getId(), config.getTeamId(), config.getEnvironmentId(), build.getStarttime());
record.setBuildEndNotice(true); record.setBuildEndNotice(true);
jenkinsBuildNotificationRepository.save(record); jenkinsBuildNotificationRepository.save(record);
} }
} }
} }
} 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);
} }
} }
/** /**
* 判断构建是否已结束 * 判断构建是否已结束
*/ */
private boolean isBuildFinished(JenkinsBuild build) { private boolean isBuildFinished(JenkinsBuild build) {
String status = build.getBuildStatus(); String status = build.getBuildStatus();
return "SUCCESS".equals(status) || return "SUCCESS".equals(status) || "FAILURE".equals(status) || "ABORTED".equals(status);
"FAILURE".equals(status) ||
"ABORTED".equals(status);
} }
/** /**
* 发送通知使用模板 * 发送通知使用模板
*/ */
private void sendNotification( private void sendNotification(TeamEnvironmentNotificationConfig config, NotificationChannel channel, JenkinsJob job, JenkinsBuild build, String status, ExternalSystem externalSystem, Application application, Environment environment) {
TeamEnvironmentNotificationConfig config,
NotificationChannel channel,
JenkinsJob job,
JenkinsBuild build,
String status,
ExternalSystem externalSystem,
Application application,
Environment environment) {
try { try {
// 1. 检查是否配置了构建通知模板 // 1. 检查是否配置了构建通知模板
if (config.getBuildNotificationTemplateId() == null) { if (config.getBuildNotificationTemplateId() == null) {
log.warn("未配置构建通知模板,跳过通知: teamId={}, envId={}", log.warn("未配置构建通知模板,跳过通知: teamId={}, envId={}",
config.getTeamId(), config.getEnvironmentId()); config.getTeamId(), config.getEnvironmentId());
return; return;
} }
// 2. 查询模板 // 2. 查询模板
NotificationTemplate template = notificationTemplateRepository NotificationTemplate template = notificationTemplateRepository.findById(config.getBuildNotificationTemplateId()).orElse(null);
.findById(config.getBuildNotificationTemplateId())
.orElse(null);
if (template == null) { if (template == null) {
log.warn("构建通知模板不存在: templateId={}", config.getBuildNotificationTemplateId()); log.warn("构建通知模板不存在: templateId={}", config.getBuildNotificationTemplateId());
return; return;
} }
// 3. 构建模板参数 // 3. 构建模板参数
Map<String, Object> templateParams = new HashMap<>(); Map<String, Object> templateParams = new HashMap<>();
// 应用信息 // 应用信息
if (application != null) { if (application != null) {
templateParams.put("applicationId", application.getId()); templateParams.put("applicationId", application.getId());
templateParams.put("applicationCode", application.getAppCode()); templateParams.put("applicationCode", application.getAppCode());
templateParams.put("applicationName", application.getAppName()); templateParams.put("applicationName", application.getAppName());
} }
// 环境信息 // 环境信息
if (environment != null) { if (environment != null) {
templateParams.put("environmentId", environment.getId()); templateParams.put("environmentId", environment.getId());
templateParams.put("environmentCode", environment.getEnvCode()); templateParams.put("environmentCode", environment.getEnvCode());
templateParams.put("environmentName", environment.getEnvName()); templateParams.put("environmentName", environment.getEnvName());
} }
// Jenkins 构建信息 // Jenkins 构建信息
templateParams.put("jobName", job.getJobName()); templateParams.put("jobName", job.getJobName());
templateParams.put("buildNumber", build.getBuildNumber()); templateParams.put("buildNumber", build.getBuildNumber());
templateParams.put("buildUrl", build.getBuildUrl()); templateParams.put("buildUrl", build.getBuildUrl());
templateParams.put("buildStatus", status); templateParams.put("buildStatus", status);
// 时间格式化 // 时间格式化
java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
if (build.getStarttime() != null) { if (build.getStarttime() != null) {
templateParams.put("buildStartTime", build.getStarttime().format(formatter)); templateParams.put("buildStartTime", build.getStarttime().format(formatter));
} }
// 构建结束时间如果有 // 构建结束时间如果有
if (build.getStarttime() != null && build.getDuration() != null) { if (build.getStarttime() != null && build.getDuration() != null) {
LocalDateTime endTime = build.getStarttime().plusNanos(build.getDuration() * 1_000_000); LocalDateTime endTime = build.getStarttime().plusNanos(build.getDuration() * 1_000_000);
templateParams.put("buildEndTime", endTime.format(formatter)); templateParams.put("buildEndTime", endTime.format(formatter));
} }
// 耗时格式化 // 耗时格式化
if (build.getDuration() != null) { if (build.getDuration() != null) {
long totalSeconds = build.getDuration() / 1000; long totalSeconds = build.getDuration() / 1000;
@ -741,49 +725,44 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
templateParams.put("buildDurationFormatted", String.format("%d分%d秒", minutes, seconds)); templateParams.put("buildDurationFormatted", String.format("%d分%d秒", minutes, seconds));
templateParams.put("buildDurationMs", build.getDuration()); templateParams.put("buildDurationMs", build.getDuration());
} }
// actions JSON 中解析 commit 信息 // actions JSON 中解析 commit 信息
parseCommitInfoFromActions(build.getActions(), templateParams); parseCommitInfoFromActions(build.getActions(), templateParams);
// 4. 校验模板和渠道类型是否匹配 // 4. 校验模板和渠道类型是否匹配
if (!template.getChannelType().equals(channel.getChannelType())) { if (!template.getChannelType().equals(channel.getChannelType())) {
log.warn("模板渠道类型({})与通知渠道类型({})不匹配,跳过通知: templateId={}, channelId={}", log.warn("模板渠道类型({})与通知渠道类型({})不匹配,跳过通知: templateId={}, channelId={}", template.getChannelType(), channel.getChannelType(), config.getBuildNotificationTemplateId(), channel.getId());
template.getChannelType(), channel.getChannelType(),
config.getBuildNotificationTemplateId(), channel.getId());
return; return;
} }
// 5. 构建 SendNotificationRequest // 5. 构建 SendNotificationRequest
SendNotificationRequest request = new SendNotificationRequest(); SendNotificationRequest request = new SendNotificationRequest();
request.setNotificationTemplateId(config.getBuildNotificationTemplateId()); request.setNotificationTemplateId(config.getBuildNotificationTemplateId());
request.setTemplateParams(templateParams); request.setTemplateParams(templateParams);
request.setSendRequest(createSendRequestByChannel(channel, template)); request.setSendRequest(createSendRequestByChannel(channel, template));
log.debug("准备发送构建通知: job={}, build={}, templateId={}, channelId={}, channelType={}", log.debug("准备发送构建通知: job={}, build={}, templateId={}, channelId={}, channelType={}", job.getJobName(), build.getBuildNumber(), config.getBuildNotificationTemplateId(), channel.getId(), channel.getChannelType());
job.getJobName(), build.getBuildNumber(), config.getBuildNotificationTemplateId(),
channel.getId(), channel.getChannelType());
// 6. 发送通知 // 6. 发送通知
notificationService.send(request); notificationService.send(request);
log.info("已发送构建通知: job={}, build={}, status={}, templateId={}", log.info("已发送构建通知: job={}, build={}, status={}, templateId={}", job.getJobName(), build.getBuildNumber(), status, config.getBuildNotificationTemplateId());
job.getJobName(), build.getBuildNumber(), status, config.getBuildNotificationTemplateId());
// 6. 构建失败时发送日志文件如果开启 // 6. 构建失败时发送日志文件如果开启
if ("FAILURE".equals(status) && Boolean.TRUE.equals(config.getBuildFailureFileEnabled())) { if ("FAILURE".equals(status) && Boolean.TRUE.equals(config.getBuildFailureFileEnabled())) {
sendBuildFailureLogFile(externalSystem, channel, job.getJobName(), build.getBuildNumber()); sendBuildFailureLogFile(externalSystem, channel, job.getJobName(), build.getBuildNumber());
} }
} catch (Exception e) { } catch (Exception e) {
log.error("发送通知失败: job={}, build={}", job.getJobName(), build.getBuildNumber(), e); log.error("发送通知失败: job={}, build={}", job.getJobName(), build.getBuildNumber(), e);
} }
} }
/** /**
* 根据渠道类型创建对应的发送请求 * 根据渠道类型创建对应的发送请求
*/ */
private com.qqchen.deploy.backend.notification.dto.BaseSendNotificationRequest createSendRequestByChannel( private BaseSendNotificationRequest createSendRequestByChannel(
NotificationChannel channel, NotificationTemplate template) { NotificationChannel channel, NotificationTemplate template) {
switch (channel.getChannelType()) { switch (channel.getChannelType()) {
case WEWORK: case WEWORK:
WeworkSendNotificationRequest weworkRequest = new WeworkSendNotificationRequest(); WeworkSendNotificationRequest weworkRequest = new WeworkSendNotificationRequest();
@ -799,15 +778,14 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
throw new RuntimeException("不支持的渠道类型: " + channel.getChannelType()); throw new RuntimeException("不支持的渠道类型: " + channel.getChannelType());
} }
} }
/** /**
* 从模板配置中获取企业微信消息类型 * 从模板配置中获取企业微信消息类型
*/ */
private WeworkMessageTypeEnum getWeworkMessageType(NotificationTemplate template) { private WeworkMessageTypeEnum getWeworkMessageType(NotificationTemplate template) {
try { try {
if (template.getTemplateConfig() != null) { if (template.getTemplateConfig() != null) {
WeworkTemplateConfig weworkConfig = JsonUtils.fromMap( WeworkTemplateConfig weworkConfig = JsonUtils.fromMap(template.getTemplateConfig(), WeworkTemplateConfig.class);
template.getTemplateConfig(), WeworkTemplateConfig.class);
if (weworkConfig != null && weworkConfig.getMessageType() != null) { if (weworkConfig != null && weworkConfig.getMessageType() != null) {
return weworkConfig.getMessageType(); return weworkConfig.getMessageType();
} }
@ -827,12 +805,12 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
} }
try { try {
JsonNode root = objectMapper.readTree(actionsJson); JsonNode root = objectMapper.readTree(actionsJson);
// 解析 gitCommitId // 解析 gitCommitId
if (root.has("gitCommitId") && !root.get("gitCommitId").isNull()) { if (root.has("gitCommitId") && !root.get("gitCommitId").isNull()) {
templateParams.put("gitCommitId", root.get("gitCommitId").asText()); templateParams.put("gitCommitId", root.get("gitCommitId").asText());
} }
// 解析 changeSets 中的 commit 信息 // 解析 changeSets 中的 commit 信息
JsonNode changeSets = root.get("changeSets"); JsonNode changeSets = root.get("changeSets");
if (changeSets != null && changeSets.isArray() && changeSets.size() > 0) { if (changeSets != null && changeSets.isArray() && changeSets.size() > 0) {
@ -844,7 +822,7 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
String message = item.has("message") ? item.get("message").asText() : ""; String message = item.has("message") ? item.get("message").asText() : "";
String author = item.has("author") ? item.get("author").asText() : ""; String author = item.has("author") ? item.get("author").asText() : "";
String commitId = item.has("commitId") ? item.get("commitId").asText() : ""; String commitId = item.has("commitId") ? item.get("commitId").asText() : "";
if (!message.isEmpty()) { if (!message.isEmpty()) {
if (commitMessages.length() > 0) { if (commitMessages.length() > 0) {
commitMessages.append("\n"); commitMessages.append("\n");
@ -864,78 +842,72 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
log.warn("解析 commit 信息失败: {}", e.getMessage()); log.warn("解析 commit 信息失败: {}", e.getMessage());
} }
} }
/** /**
* 发送构建失败日志文件到企业微信 * 发送构建失败日志文件到企业微信
*/ */
private void sendBuildFailureLogFile(ExternalSystem externalSystem, NotificationChannel channel, private void sendBuildFailureLogFile(ExternalSystem externalSystem, NotificationChannel channel,
String jobName, Integer buildNumber) { String jobName, Integer buildNumber) {
try { try {
log.info("开始下载构建失败日志: job={}, buildNumber={}", jobName, buildNumber); log.info("开始下载构建失败日志: job={}, buildNumber={}", jobName, buildNumber);
// 1. 下载日志文件到临时目录 // 1. 下载日志文件到临时目录
String logFilePath = downloadBuildLogFile(externalSystem, jobName, buildNumber); String logFilePath = downloadBuildLogFile(externalSystem, jobName, buildNumber);
if (logFilePath == null) { if (logFilePath == null) {
log.warn("日志文件下载失败,跳过文件发送"); log.warn("日志文件下载失败,跳过文件发送");
return; return;
} }
// 2. 上传文件到企业微信 // 2. 上传文件到企业微信
String mediaId = uploadFileToWechat(channel, logFilePath); String mediaId = uploadFileToWechat(channel, logFilePath);
if (mediaId == null) { if (mediaId == null) {
log.warn("文件上传到企业微信失败"); log.warn("文件上传到企业微信失败");
return; return;
} }
// 3. 发送文件消息 // 3. 发送文件消息
sendWechatFileMessage(channel, mediaId); sendWechatFileMessage(channel, mediaId);
// 4. 清理临时文件 // 4. 清理临时文件
cleanupTempFile(logFilePath); cleanupTempFile(logFilePath);
log.info("构建失败日志文件发送成功: job={}, buildNumber={}", jobName, buildNumber); log.info("构建失败日志文件发送成功: job={}, buildNumber={}", jobName, buildNumber);
} catch (Exception e) { } catch (Exception e) {
log.error("发送构建失败日志文件失败: job={}, buildNumber={}", log.error("发送构建失败日志文件失败: job={}, buildNumber={}", jobName, buildNumber, e);
jobName, buildNumber, e);
} }
} }
/** /**
* 下载构建日志文件 * 下载构建日志文件
*/ */
private String downloadBuildLogFile(ExternalSystem externalSystem, String jobName, Integer buildNumber) { private String downloadBuildLogFile(ExternalSystem externalSystem, String jobName, Integer buildNumber) {
try { try {
// 获取完整的控制台输出 // 获取完整的控制台输出
var consoleOutput = jenkinsServiceIntegration.getConsoleOutput( var consoleOutput = jenkinsServiceIntegration.getConsoleOutput(externalSystem, jobName, buildNumber, 0L);
externalSystem,
jobName,
buildNumber,
0L
);
if (consoleOutput == null || consoleOutput.getLines() == null || consoleOutput.getLines().isEmpty()) { if (consoleOutput == null || consoleOutput.getLines() == null || consoleOutput.getLines().isEmpty()) {
return null; return null;
} }
// 创建临时文件 // 创建临时文件
String tempDir = System.getProperty("java.io.tmpdir"); String tempDir = System.getProperty("java.io.tmpdir");
String fileName = String.format("jenkins-build-%s-%d.log", jobName, buildNumber); String fileName = String.format("jenkins-build-%s-%d.log", jobName, buildNumber);
java.nio.file.Path filePath = java.nio.file.Paths.get(tempDir, fileName); Path filePath = Paths.get(tempDir, fileName);
// 写入日志内容 // 写入日志内容
java.nio.file.Files.write(filePath, consoleOutput.getLines()); Files.write(filePath, consoleOutput.getLines());
log.info("日志文件已下载: {}", filePath.toString()); log.info("日志文件已下载: {}", filePath);
return filePath.toString(); return filePath.toString();
} catch (Exception e) { } catch (Exception e) {
log.error("下载构建日志文件失败: job={}, buildNumber={}", log.error("下载构建日志文件失败: job={}, buildNumber={}",
jobName, buildNumber, e); jobName, buildNumber, e);
return null; return null;
} }
} }
/** /**
* 上传文件到企业微信 * 上传文件到企业微信
*/ */
@ -951,28 +923,24 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
log.error("企业微信渠道 key 为空: channelId={}", channel.getId()); log.error("企业微信渠道 key 为空: channelId={}", channel.getId());
return null; return null;
} }
// 企业微信文件上传接口 // 企业微信文件上传接口
String uploadUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=" + key + "&type=file"; String uploadUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=" + key + "&type=file";
// 构建 multipart/form-data 请求 // 构建 multipart/form-data 请求
org.springframework.core.io.FileSystemResource fileResource = FileSystemResource fileResource = new FileSystemResource(filePath);
new org.springframework.core.io.FileSystemResource(filePath);
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
org.springframework.util.LinkedMultiValueMap<String, Object> map =
new org.springframework.util.LinkedMultiValueMap<>();
map.add("media", fileResource); map.add("media", fileResource);
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders(); HttpHeaders headers = new org.springframework.http.HttpHeaders();
headers.setContentType(org.springframework.http.MediaType.MULTIPART_FORM_DATA); headers.setContentType(org.springframework.http.MediaType.MULTIPART_FORM_DATA);
org.springframework.http.HttpEntity<org.springframework.util.MultiValueMap<String, Object>> requestEntity = HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);
new org.springframework.http.HttpEntity<>(map, headers);
RestTemplate restTemplate = new org.springframework.web.client.RestTemplate();
org.springframework.web.client.RestTemplate restTemplate = new org.springframework.web.client.RestTemplate(); ResponseEntity<String> response = restTemplate.postForEntity(uploadUrl, requestEntity, String.class);
org.springframework.http.ResponseEntity<String> response =
restTemplate.postForEntity(uploadUrl, requestEntity, String.class);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
// 解析响应获取 media_id // 解析响应获取 media_id
JsonNode jsonNode = JsonUtils.parseJson(response.getBody()); JsonNode jsonNode = JsonUtils.parseJson(response.getBody());
@ -980,27 +948,27 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
log.error("文件上传响应解析失败"); log.error("文件上传响应解析失败");
return null; return null;
} }
int errcode = jsonNode.get("errcode").asInt(); int errcode = jsonNode.get("errcode").asInt();
if (errcode == 0) { if (errcode == 0) {
String mediaId = jsonNode.get("media_id").asText(); String mediaId = jsonNode.get("media_id").asText();
log.info("文件上传成功media_id: {}", mediaId); log.info("文件上传成功media_id: {}", mediaId);
return mediaId; return mediaId;
} else { } else {
log.error("文件上传失败errcode: {}, errmsg: {}", log.error("文件上传失败errcode: {}, errmsg: {}",
errcode, jsonNode.get("errmsg").asText()); errcode, jsonNode.get("errmsg").asText());
return null; return null;
} }
} }
return null; return null;
} catch (Exception e) { } catch (Exception e) {
log.error("上传文件到企业微信失败: filePath={}", filePath, e); log.error("上传文件到企业微信失败: filePath={}", filePath, e);
return null; return null;
} }
} }
/** /**
* 发送企业微信文件消息 * 发送企业微信文件消息
*/ */
@ -1008,37 +976,35 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
try { try {
String key = (String) channel.getConfig().get("key"); String key = (String) channel.getConfig().get("key");
String webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=" + key; String webhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=" + key;
// 构建文件消息 // 构建文件消息
Map<String, Object> message = new HashMap<>(); Map<String, Object> message = new HashMap<>();
message.put("msgtype", "file"); message.put("msgtype", "file");
Map<String, String> file = new HashMap<>(); Map<String, String> file = new HashMap<>();
file.put("media_id", mediaId); file.put("media_id", mediaId);
message.put("file", file); message.put("file", file);
// 发送请求 // 发送请求
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON); headers.setContentType(MediaType.APPLICATION_JSON);
org.springframework.http.HttpEntity<Map<String, Object>> requestEntity = HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(message, headers);
new org.springframework.http.HttpEntity<>(message, headers);
RestTemplate restTemplate = new RestTemplate();
org.springframework.web.client.RestTemplate restTemplate = new org.springframework.web.client.RestTemplate(); ResponseEntity<String> response = restTemplate.postForEntity(webhookUrl, requestEntity, String.class);
org.springframework.http.ResponseEntity<String> response =
restTemplate.postForEntity(webhookUrl, requestEntity, String.class);
if (response.getStatusCode().is2xxSuccessful()) { if (response.getStatusCode().is2xxSuccessful()) {
log.info("企业微信文件消息发送成功"); log.info("企业微信文件消息发送成功");
} else { } else {
log.error("企业微信文件消息发送失败: {}", response.getBody()); log.error("企业微信文件消息发送失败: {}", response.getBody());
} }
} catch (Exception e) { } catch (Exception e) {
log.error("发送企业微信文件消息失败: mediaId={}", mediaId, e); log.error("发送企业微信文件消息失败: mediaId={}", mediaId, e);
} }
} }
/** /**
* 清理临时文件 * 清理临时文件
*/ */

View File

@ -52,15 +52,15 @@ VALUES
(99, '工作台', '/dashboard', 'Dashboard', 'DashboardOutlined', 'dashboard', 2, NULL, 0, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), (99, '工作台', '/dashboard', 'Dashboard', 'DashboardOutlined', 'dashboard', 2, NULL, 0, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
-- 工作流管理 -- 工作流管理
(100, '工作流管理', '/workflow', NULL, 'DeploymentUnitOutlined', NULL, 1, NULL, 1, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), (100, '管理', '/workflow', NULL, 'DeploymentUnitOutlined', NULL, 1, NULL, 1, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
-- 工作流设计 -- 设计
(101, '工作流设计', '/workflow/definitions', 'Workflow/Definition/List', 'EditOutlined', 'workflow:definition', 2, 100, 10, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), (101, '设计', '/workflow/definitions', 'Workflow/Definition/List', 'EditOutlined', 'workflow:definition', 2, 100, 10, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
-- 工作流设计器(隐藏路由,用于编辑工作流) -- 工作流设计器(隐藏路由,用于编辑工作流)
(1011, '工作流设计器', '/workflow/design/:id', 'Workflow/Design', 'EditOutlined', NULL, 2, 100, 11, TRUE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), (1011, '工作流设计器', '/workflow/design/:id', 'Workflow/Design', 'EditOutlined', NULL, 2, 100, 11, TRUE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
-- 工作流实例 -- 实例
(102, '工作流实例', '/workflow/instances', 'Workflow/Instance/List', 'BranchesOutlined', 'workflow:instance', 2, 100, 20, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), (102, '实例', '/workflow/instances', 'Workflow/Instance/List', 'BranchesOutlined', 'workflow:instance', 2, 100, 20, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
-- 动态表单菜单 -- 动态表单
(104, '动态表单菜单', '/workflow/form', 'Form/Definition/List', 'FormOutlined', 'workflow:form', 2, 100, 30, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), (104, '动态表单', '/workflow/form', 'Form/Definition/List', 'FormOutlined', 'workflow:form', 2, 100, 30, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
-- 表单设计器(隐藏路由,用于设计表单) -- 表单设计器(隐藏路由,用于设计表单)
(1041, '表单设计器', '/workflow/form/:id/design', 'Form/Definition/Designer', 'FormOutlined', NULL, 2, 100, 31, TRUE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), (1041, '表单设计器', '/workflow/form/:id/design', 'Form/Definition/Designer', 'FormOutlined', NULL, 2, 100, 31, TRUE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
-- 表单数据详情(隐藏路由,用于查看表单数据) -- 表单数据详情(隐藏路由,用于查看表单数据)