增加构建通知

This commit is contained in:
dengqichen 2025-11-13 17:58:25 +08:00
parent 694e6690ca
commit 8e6dbec07e
5 changed files with 121 additions and 129 deletions

View File

@ -1,9 +1,17 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.TeamEnvironmentNotificationConfigDTO;
import com.qqchen.deploy.backend.deploy.dto.UserTeamEnvironmentNotificationConfigDTO;
import com.qqchen.deploy.backend.deploy.entity.TeamEnvironmentNotificationConfig;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.notification.entity.NotificationChannel;
import com.qqchen.deploy.backend.notification.entity.NotificationTemplate;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.Map;
import java.util.Optional;
/**
* 团队环境通知配置Converter
@ -14,4 +22,41 @@ import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface TeamEnvironmentNotificationConfigConverter
extends BaseConverter<TeamEnvironmentNotificationConfig, TeamEnvironmentNotificationConfigDTO> {
/**
* 转换为用户环境通知配置DTO包含扩展字段
*/
@Mapping(target = "notificationChannelName",
expression = "java(getChannelName(config.getNotificationChannelId(), channelMap))")
@Mapping(target = "deployNotificationTemplateName",
expression = "java(getTemplateName(config.getDeployNotificationTemplateId(), templateMap))")
@Mapping(target = "buildNotificationTemplateName",
expression = "java(getTemplateName(config.getBuildNotificationTemplateId(), templateMap))")
@Mapping(target = "buildFailureNotificationTemplateName",
expression = "java(getTemplateName(config.getBuildFailureNotificationTemplateId(), templateMap))")
UserTeamEnvironmentNotificationConfigDTO toUserDTO(
TeamEnvironmentNotificationConfig config,
@Context Map<Long, NotificationChannel> channelMap,
@Context Map<Long, NotificationTemplate> templateMap
);
/**
* 获取渠道名称
*/
default String getChannelName(Long channelId, Map<Long, NotificationChannel> channelMap) {
return Optional.ofNullable(channelId)
.map(channelMap::get)
.map(NotificationChannel::getName)
.orElse(null);
}
/**
* 获取模板名称
*/
default String getTemplateName(Long templateId, Map<Long, NotificationTemplate> templateMap) {
return Optional.ofNullable(templateId)
.map(templateMap::get)
.map(NotificationTemplate::getName)
.orElse(null);
}
}

View File

@ -106,12 +106,27 @@ public class DeployExecuteRequest {
@Data
@Schema(description = "通知配置")
public static class NotificationConfig {
@Schema(description = "是否需要通知", required = true)
@NotNull(message = "是否需要通知不能为空")
private Boolean required;
@Schema(description = "通知渠道ID")
private Long channelId;
private Long notificationChannelId;
@Schema(description = "是否启用部署通知")
private Boolean deployNotificationEnabled;
@Schema(description = "部署通知模板ID")
private Long deployNotificationTemplateId;
@Schema(description = "是否启用构建通知")
private Boolean buildNotificationEnabled;
@Schema(description = "构建通知模板ID")
private Long buildNotificationTemplateId;
@Schema(description = "构建失败时是否发送日志文件到企业微信")
private Boolean buildFailureFileEnabled;
@Schema(description = "构建失败通知模板ID")
private Long buildFailureNotificationTemplateId;
}
}

View File

@ -4,7 +4,10 @@ import com.qqchen.deploy.backend.deploy.dto.*;
import com.qqchen.deploy.backend.deploy.entity.*;
import com.qqchen.deploy.backend.deploy.repository.*;
import com.qqchen.deploy.backend.notification.entity.NotificationChannel;
import com.qqchen.deploy.backend.notification.entity.NotificationTemplate;
import com.qqchen.deploy.backend.notification.repository.INotificationChannelRepository;
import com.qqchen.deploy.backend.notification.repository.INotificationTemplateRepository;
import com.qqchen.deploy.backend.deploy.converter.TeamEnvironmentNotificationConfigConverter;
import com.qqchen.deploy.backend.deploy.service.IDeployService;
import com.qqchen.deploy.backend.framework.security.SecurityUtils;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
@ -46,6 +49,7 @@ import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*;
@ -113,6 +117,12 @@ public class DeployServiceImpl implements IDeployService {
@Resource
private INotificationChannelRepository notificationChannelRepository;
@Resource
private INotificationTemplateRepository notificationTemplateRepository;
@Resource
private TeamEnvironmentNotificationConfigConverter notificationConfigConverter;
@Override
public List<UserTeamDeployableDTO> getDeployableEnvironments() {
@ -262,7 +272,24 @@ public class DeployServiceImpl implements IDeployService {
);
}
// 16. 批量查询审批人信息
// 16. 批量查询通知模板信息
Set<Long> templateIds = notificationConfigMap.values().stream()
.flatMap(config -> Stream.of(
config.getDeployNotificationTemplateId(),
config.getBuildNotificationTemplateId(),
config.getBuildFailureNotificationTemplateId()
))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Map<Long, NotificationTemplate> templateMap = new HashMap<>();
if (!templateIds.isEmpty()) {
notificationTemplateRepository.findAllById(templateIds).forEach(template ->
templateMap.put(template.getId(), template)
);
}
// 17. 批量查询审批人信息
Set<Long> approverUserIds = teamEnvConfigs.stream()
.filter(c -> c.getApproverUserIds() != null)
.flatMap(c -> c.getApproverUserIds().stream())
@ -283,7 +310,7 @@ public class DeployServiceImpl implements IDeployService {
currentUserId, teamId, teamMap, ownerMap, membersByTeam, memberUserMap,
teamAppsMap, envMap, appMap, systemMap, workflowMap,
teamEnvConfigMap, approverMap,
notificationConfigMap, channelMap,
notificationConfigMap, channelMap, templateMap,
statisticsMap, latestRecordMap, recentRecordsMap
);
if (teamDTO != null) {
@ -314,6 +341,7 @@ public class DeployServiceImpl implements IDeployService {
Map<Long, User> approverMap,
Map<String, TeamEnvironmentNotificationConfig> notificationConfigMap,
Map<Long, NotificationChannel> channelMap,
Map<Long, NotificationTemplate> templateMap,
Map<Long, DeployStatisticsDTO> statisticsMap,
Map<Long, DeployRecord> latestRecordMap,
Map<Long, List<DeployRecordSummaryDTO>> recentRecordsMap
@ -380,7 +408,7 @@ public class DeployServiceImpl implements IDeployService {
teamId, env, envApps,
appMap, systemMap, workflowMap,
teamEnvConfigMap, approverMap,
notificationConfigMap, channelMap,
notificationConfigMap, channelMap, templateMap,
statisticsMap, latestRecordMap, recentRecordsMap
);
environments.add(envDTO);
@ -409,6 +437,7 @@ public class DeployServiceImpl implements IDeployService {
Map<Long, User> approverMap,
Map<String, TeamEnvironmentNotificationConfig> notificationConfigMap,
Map<Long, NotificationChannel> channelMap,
Map<Long, NotificationTemplate> templateMap,
Map<Long, DeployStatisticsDTO> statisticsMap,
Map<Long, DeployRecord> latestRecordMap,
Map<Long, List<DeployRecordSummaryDTO>> recentRecordsMap
@ -451,24 +480,10 @@ public class DeployServiceImpl implements IDeployService {
dto.setApprovers(Collections.emptyList());
}
// 构建通知配置
UserTeamEnvironmentNotificationConfigDTO notificationConfigDTO = null;
// 构建通知配置 - 使用MapStruct转换器
TeamEnvironmentNotificationConfig notificationConfig = notificationConfigMap.get(configKey);
if (notificationConfig != null) {
notificationConfigDTO = new UserTeamEnvironmentNotificationConfigDTO();
notificationConfigDTO.setNotificationChannelId(notificationConfig.getNotificationChannelId());
notificationConfigDTO.setDeployNotificationEnabled(notificationConfig.getDeployNotificationEnabled());
notificationConfigDTO.setBuildNotificationEnabled(notificationConfig.getBuildNotificationEnabled());
notificationConfigDTO.setBuildFailureFileEnabled(notificationConfig.getBuildFailureFileEnabled());
// 设置通知渠道名称
if (notificationConfig.getNotificationChannelId() != null) {
NotificationChannel channel = channelMap.get(notificationConfig.getNotificationChannelId());
if (channel != null) {
notificationConfigDTO.setNotificationChannelName(channel.getName());
}
}
}
UserTeamEnvironmentNotificationConfigDTO notificationConfigDTO =
notificationConfigConverter.toUserDTO(notificationConfig, channelMap, templateMap);
dto.setNotificationConfig(notificationConfigDTO);
} else {
dto.setRequiresApproval(false);

View File

@ -1,6 +1,7 @@
package com.qqchen.deploy.backend.notification.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@ -16,14 +17,16 @@ import java.util.Map;
@Schema(description = "发送通知请求")
public class SendNotificationRequest {
@Schema(description = "渠道ID", required = true, example = "1")
@NotNull(message = "渠道ID不能为空")
private Long channelId;
@Schema(description = "通知模板ID", required = true, example = "1")
@NotNull(message = "通知模板ID不能为空")
private Long notificationTemplateId;
@Schema(description = "模板参数", example = "{\"projectName\":\"测试项目\",\"buildNumber\":\"123\"}")
private Map<String, Object> params;
@Schema(description = "模板渲染参数(仅用于模板变量替换)",
example = "{\"projectName\":\"测试项目\",\"buildNumber\":\"123\"}")
private Map<String, Object> templateParams;
@Schema(description = "具体的发送请求配置", required = true)
@NotNull(message = "发送请求配置不能为空")
@Valid
private BaseSendNotificationRequest sendRequest;
}

View File

@ -54,7 +54,7 @@ public class NotificationServiceImpl implements INotificationService {
.orElseThrow(() -> new BusinessException(ResponseCode.NOTIFICATION_TEMPLATE_NOT_FOUND));
// 2. 获取通知渠道
NotificationChannel channel = notificationChannelRepository.findById(request.getChannelId())
NotificationChannel channel = notificationChannelRepository.findById(request.getSendRequest().getChannelId())
.orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
// 3. 验证模板和渠道类型是否匹配
@ -62,7 +62,12 @@ public class NotificationServiceImpl implements INotificationService {
throw new BusinessException(ResponseCode.INVALID_PARAM);
}
// 4. 验证模板和渠道是否启用
// 4. 验证SendRequest渠道类型和模板渠道类型是否匹配
if (!request.getSendRequest().getChannelType().equals(template.getChannelType())) {
throw new BusinessException(ResponseCode.INVALID_PARAM);
}
// 5. 验证模板和渠道是否启用
if (!template.getEnabled()) {
throw new BusinessException(ResponseCode.NOTIFICATION_TEMPLATE_DISABLED);
}
@ -70,103 +75,12 @@ public class NotificationServiceImpl implements INotificationService {
throw new BusinessException(ResponseCode.DATA_NOT_FOUND);
}
// 5. 渲染模板内容
String content = notificationTemplateService.renderTemplate(template.getContentTemplate(), request.getParams());
// 6. 渲染模板内容
String content = notificationTemplateService.renderTemplate(template.getContentTemplate(), request.getTemplateParams());
// 6. 根据渠道类型发送通知
switch (template.getChannelType()) {
case WEWORK -> sendWeworkNotification(request.getChannelId(), content, template, request.getParams());
case EMAIL -> sendEmailNotification(request.getChannelId(), content, template, request.getParams());
default -> throw new BusinessException(ResponseCode.INVALID_PARAM);
}
// 7. 设置渲染后的内容并发送
request.getSendRequest().setContent(content);
notificationSendService.send(request.getSendRequest());
}
/**
* 发送企业微信通知
*/
private void sendWeworkNotification(Long channelId, String content, NotificationTemplate template, Map<String, Object> params) {
WeworkSendNotificationRequest request = new WeworkSendNotificationRequest();
request.setChannelId(channelId);
request.setContent(content);
// 从模板配置获取消息类型
WeworkMessageTypeEnum messageType = WeworkMessageTypeEnum.TEXT; // 默认值
try {
BaseTemplateConfig baseTemplateConfig = JsonUtils.fromMap(template.getTemplateConfig(), BaseTemplateConfig.class);
if (baseTemplateConfig instanceof WeworkTemplateConfig weworkTemplateConfig) {
messageType = weworkTemplateConfig.getMessageType();
}
} catch (Exception e) {
log.warn("获取模板配置失败,使用默认消息类型: {}", e.getMessage());
}
// params 仍可以覆盖模板配置
if (params != null && params.containsKey("messageType")) {
messageType = WeworkMessageTypeEnum.valueOf(params.get("messageType").toString());
}
request.setMessageType(messageType);
// 从参数中获取@人员信息
if (params != null) {
if (params.containsKey("mentionedMobileList")) {
request.setMentionedMobileList((List<String>) params.get("mentionedMobileList"));
}
if (params.containsKey("mentionedUserList")) {
request.setMentionedUserList((List<String>) params.get("mentionedUserList"));
}
}
notificationSendService.send(request);
}
/**
* 发送邮件通知
*/
private void sendEmailNotification(Long channelId, String content, NotificationTemplate template, Map<String, Object> params) {
EmailSendNotificationRequest request = new EmailSendNotificationRequest();
request.setChannelId(channelId);
request.setContent(content);
// 邮件标题从参数中获取或使用默认值
String subject = "系统通知";
if (params != null && params.containsKey("subject")) {
subject = params.get("subject").toString();
}
request.setSubject(subject);
// 从模板配置获取内容类型
Boolean isHtml = false; // 默认值
try {
BaseTemplateConfig baseTemplateConfig = JsonUtils.fromMap(template.getTemplateConfig(), BaseTemplateConfig.class);
if (baseTemplateConfig instanceof EmailTemplateConfig emailTemplateConfig) {
isHtml = emailTemplateConfig.getContentType() == EmailContentTypeEnum.HTML;
}
} catch (Exception e) {
log.warn("获取模板配置失败,使用默认内容类型: {}", e.getMessage());
}
// params 仍可以覆盖模板配置
if (params != null && params.containsKey("isHtml")) {
isHtml = (Boolean) params.get("isHtml");
}
request.setIsHtml(isHtml);
// 收件人信息必须
if (params == null || !params.containsKey("toReceivers")) {
throw new BusinessException(ResponseCode.INVALID_PARAM);
}
request.setToReceivers((List<String>) params.get("toReceivers"));
// 可选参数
if (params != null) {
if (params.containsKey("ccReceivers")) {
request.setCcReceivers((List<String>) params.get("ccReceivers"));
}
if (params.containsKey("bccReceivers")) {
request.setBccReceivers((List<String>) params.get("bccReceivers"));
}
}
notificationSendService.send(request);
}
}