增加构建通知
This commit is contained in:
parent
8e860443d2
commit
0de08cb09b
@ -18,7 +18,9 @@ import com.qqchen.deploy.backend.deploy.service.IJenkinsSyncHistoryService;
|
||||
import com.qqchen.deploy.backend.notification.entity.NotificationChannel;
|
||||
import com.qqchen.deploy.backend.notification.repository.INotificationChannelRepository;
|
||||
import com.qqchen.deploy.backend.notification.service.INotificationSendService;
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.dto.WeworkSendNotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.dto.EmailSendNotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum;
|
||||
import com.qqchen.deploy.backend.deploy.dto.sync.JenkinsSyncContext;
|
||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
@ -640,27 +642,15 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
// 构建失败时,发送日志文件(如果开启)
|
||||
if ("FAILURE".equals(status) && config.getBuildFailureFileEnabled() != null && config.getBuildFailureFileEnabled()) {
|
||||
// 先发送文本通知
|
||||
NotificationRequest textRequest = NotificationRequest.builder()
|
||||
.channelId(channel.getId())
|
||||
.title("Jenkins构建通知")
|
||||
.content(content.toString())
|
||||
.build();
|
||||
notificationSendService.send(textRequest);
|
||||
sendNotificationByChannelType(channel, "Jenkins构建通知", content.toString());
|
||||
|
||||
// 然后发送日志文件
|
||||
sendBuildFailureLogFile(externalSystem, channel, job.getJobName(), build.getBuildNumber());
|
||||
return; // 已发送,直接返回
|
||||
}
|
||||
|
||||
// 构建通知请求
|
||||
NotificationRequest request = NotificationRequest.builder()
|
||||
.channelId(channel.getId())
|
||||
.title("Jenkins构建通知")
|
||||
.content(content.toString())
|
||||
.build();
|
||||
|
||||
// 发送通知
|
||||
notificationSendService.send(request);
|
||||
sendNotificationByChannelType(channel, "Jenkins构建通知", content.toString());
|
||||
|
||||
log.info("已发送构建通知: job={}, build={}, status={}",
|
||||
job.getJobName(), build.getBuildNumber(), status);
|
||||
@ -670,6 +660,37 @@ public class JenkinsBuildServiceImpl extends BaseServiceImpl<JenkinsBuild, Jenki
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据渠道类型发送通知
|
||||
*/
|
||||
private void sendNotificationByChannelType(NotificationChannel channel, String title, String content) {
|
||||
try {
|
||||
switch (channel.getChannelType()) {
|
||||
case WEWORK -> {
|
||||
WeworkSendNotificationRequest weworkRequest = new WeworkSendNotificationRequest();
|
||||
weworkRequest.setChannelId(channel.getId());
|
||||
weworkRequest.setContent(title + "\n\n" + content);
|
||||
weworkRequest.setMessageType(WeworkMessageTypeEnum.MARKDOWN); // 使用Markdown格式
|
||||
notificationSendService.send(weworkRequest);
|
||||
}
|
||||
case EMAIL -> {
|
||||
EmailSendNotificationRequest emailRequest = new EmailSendNotificationRequest();
|
||||
emailRequest.setChannelId(channel.getId());
|
||||
emailRequest.setSubject(title);
|
||||
emailRequest.setContent(content);
|
||||
// 这里需要设置收件人,但Jenkins构建通知中没有提供
|
||||
// 实际应该从团队环境配置或其他地方获取
|
||||
emailRequest.setToReceivers(java.util.Arrays.asList("admin@company.com"));
|
||||
notificationSendService.send(emailRequest);
|
||||
}
|
||||
default -> throw new RuntimeException("不支持的渠道类型: " + channel.getChannelType());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("发送通知失败: channelId={}, type={}", channel.getId(), channel.getChannelType(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送构建失败日志文件到企业微信
|
||||
*/
|
||||
|
||||
@ -1,27 +1,28 @@
|
||||
package com.qqchen.deploy.backend.notification.adapter;
|
||||
|
||||
import com.qqchen.deploy.backend.notification.entity.config.BaseNotificationConfig;
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.dto.BaseSendNotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
|
||||
/**
|
||||
* 通知渠道适配器接口
|
||||
* 使用泛型约束配置类型,避免类型转换
|
||||
* 使用泛型约束配置类型和请求类型,避免类型转换
|
||||
*
|
||||
* @param <T> 配置类型,必须继承 BaseNotificationConfig
|
||||
* @param <C> 配置类型,必须继承 BaseNotificationConfig
|
||||
* @param <R> 请求类型,必须继承 BaseSendNotificationRequest
|
||||
* @author qqchen
|
||||
* @since 2025-11-03
|
||||
*/
|
||||
public interface INotificationChannelAdapter<T extends BaseNotificationConfig> {
|
||||
public interface INotificationChannelAdapter<C extends BaseNotificationConfig, R extends BaseSendNotificationRequest> {
|
||||
|
||||
/**
|
||||
* 发送通知
|
||||
*
|
||||
* @param config 渠道配置
|
||||
* @param request 通知请求
|
||||
* @param request 发送通知请求
|
||||
* @throws Exception 发送失败时抛出异常
|
||||
*/
|
||||
void send(T config, NotificationRequest request) throws Exception;
|
||||
void send(C config, R request) throws Exception;
|
||||
|
||||
/**
|
||||
* 发送文件(可选,不是所有渠道都支持)
|
||||
@ -31,7 +32,7 @@ public interface INotificationChannelAdapter<T extends BaseNotificationConfig> {
|
||||
* @param fileName 文件名称
|
||||
* @throws Exception 发送失败时抛出异常
|
||||
*/
|
||||
default void sendFile(T config, String filePath, String fileName) throws Exception {
|
||||
default void sendFile(C config, String filePath, String fileName) throws Exception {
|
||||
throw new UnsupportedOperationException("该渠道不支持发送文件");
|
||||
}
|
||||
|
||||
@ -48,7 +49,7 @@ public interface INotificationChannelAdapter<T extends BaseNotificationConfig> {
|
||||
* @param config 渠道配置
|
||||
* @return 校验结果消息
|
||||
*/
|
||||
default String validateConfig(T config) {
|
||||
default String validateConfig(C config) {
|
||||
return "配置有效";
|
||||
}
|
||||
|
||||
@ -59,6 +60,6 @@ public interface INotificationChannelAdapter<T extends BaseNotificationConfig> {
|
||||
* @param config 渠道配置
|
||||
* @throws Exception 测试失败时抛出异常
|
||||
*/
|
||||
void testConnection(T config) throws Exception;
|
||||
void testConnection(C config) throws Exception;
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.notification.adapter.impl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.notification.adapter.INotificationChannelAdapter;
|
||||
import com.qqchen.deploy.backend.notification.entity.config.EmailNotificationConfig;
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.dto.EmailSendNotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
@ -24,23 +24,21 @@ import java.util.Properties;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class EmailChannelAdapter implements INotificationChannelAdapter<EmailNotificationConfig> {
|
||||
public class EmailChannelAdapter implements INotificationChannelAdapter<EmailNotificationConfig, EmailSendNotificationRequest> {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public void send(EmailNotificationConfig emailConfig, NotificationRequest request) throws Exception {
|
||||
public void send(EmailNotificationConfig emailConfig, EmailSendNotificationRequest request) throws Exception {
|
||||
// 1. 校验配置
|
||||
validateEmailConfig(emailConfig);
|
||||
|
||||
// 2. 创建JavaMailSender
|
||||
JavaMailSenderImpl mailSender = createMailSender(emailConfig);
|
||||
|
||||
// 3. 确定收件人
|
||||
List<String> receivers = determineReceivers(emailConfig, request);
|
||||
|
||||
if (CollectionUtils.isEmpty(receivers)) {
|
||||
throw new IllegalArgumentException("收件人列表为空,且未配置默认收件人");
|
||||
// 3. 校验收件人
|
||||
if (CollectionUtils.isEmpty(request.getToReceivers())) {
|
||||
throw new IllegalArgumentException("收件人列表不能为空");
|
||||
}
|
||||
|
||||
// 4. 构建邮件
|
||||
@ -55,24 +53,26 @@ public class EmailChannelAdapter implements INotificationChannelAdapter<EmailNot
|
||||
}
|
||||
|
||||
// 设置收件人
|
||||
helper.setTo(receivers.toArray(new String[0]));
|
||||
helper.setTo(request.getToReceivers().toArray(new String[0]));
|
||||
|
||||
// 设置抄送
|
||||
if (!CollectionUtils.isEmpty(request.getMentions())) {
|
||||
helper.setCc(request.getMentions().toArray(new String[0]));
|
||||
if (!CollectionUtils.isEmpty(request.getCcReceivers())) {
|
||||
helper.setCc(request.getCcReceivers().toArray(new String[0]));
|
||||
}
|
||||
|
||||
// 设置密送
|
||||
if (!CollectionUtils.isEmpty(request.getBccReceivers())) {
|
||||
helper.setBcc(request.getBccReceivers().toArray(new String[0]));
|
||||
}
|
||||
|
||||
// 设置主题
|
||||
String subject = request.getTitle() != null && !request.getTitle().isEmpty()
|
||||
? request.getTitle()
|
||||
: "系统通知";
|
||||
helper.setSubject(subject);
|
||||
helper.setSubject(request.getSubject());
|
||||
|
||||
// 设置内容(支持HTML)
|
||||
helper.setText(request.getContent(), false);
|
||||
helper.setText(request.getContent(), request.getIsHtml() != null ? request.getIsHtml() : false);
|
||||
|
||||
// 5. 发送邮件
|
||||
log.info("发送邮件通知 - 收件人: {}, 主题: {}", receivers, subject);
|
||||
log.info("发送邮件通知 - 收件人: {}, 主题: {}", request.getToReceivers(), request.getSubject());
|
||||
mailSender.send(mimeMessage);
|
||||
log.info("邮件通知发送成功");
|
||||
}
|
||||
@ -112,40 +112,8 @@ public class EmailChannelAdapter implements INotificationChannelAdapter<EmailNot
|
||||
throw new Exception("SMTP连接失败: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 5. 发送测试邮件(可选:如果配置了默认收件人)
|
||||
if (!CollectionUtils.isEmpty(emailConfig.getDefaultReceivers())) {
|
||||
try {
|
||||
MimeMessage mimeMessage = mailSender.createMimeMessage();
|
||||
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
|
||||
|
||||
// 设置发件人
|
||||
if (emailConfig.getFromName() != null && !emailConfig.getFromName().isEmpty()) {
|
||||
helper.setFrom(new InternetAddress(emailConfig.getFrom(), emailConfig.getFromName(), "UTF-8"));
|
||||
} else {
|
||||
helper.setFrom(emailConfig.getFrom());
|
||||
}
|
||||
|
||||
// 设置收件人(使用配置的默认收件人)
|
||||
helper.setTo(emailConfig.getDefaultReceivers().toArray(new String[0]));
|
||||
|
||||
// 设置主题
|
||||
helper.setSubject("【测试邮件】Deploy Ease Platform 邮件通知测试");
|
||||
|
||||
// 设置内容
|
||||
String content = "这是一条来自 Deploy Ease Platform 的测试邮件。\n\n" +
|
||||
"如果您收到此邮件,说明邮件通知渠道配置正常!\n\n" +
|
||||
"发送时间:" + java.time.LocalDateTime.now();
|
||||
helper.setText(content, false);
|
||||
|
||||
// 发送邮件
|
||||
log.info("发送测试邮件到: {}", emailConfig.getDefaultReceivers());
|
||||
mailSender.send(mimeMessage);
|
||||
log.info("测试邮件发送成功");
|
||||
} catch (Exception e) {
|
||||
log.error("测试邮件发送失败: {}", e.getMessage());
|
||||
throw new Exception("测试邮件发送失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
// 5. 测试连接成功(不发送测试邮件,因为没有默认收件人)
|
||||
log.info("邮件通知渠道配置测试成功 - SMTP连接正常");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,17 +167,5 @@ public class EmailChannelAdapter implements INotificationChannelAdapter<EmailNot
|
||||
return mailSender;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定收件人列表
|
||||
*/
|
||||
private List<String> determineReceivers(EmailNotificationConfig config, NotificationRequest request) {
|
||||
// 优先使用请求中的收件人
|
||||
if (!CollectionUtils.isEmpty(request.getReceivers())) {
|
||||
return request.getReceivers();
|
||||
}
|
||||
|
||||
// 使用配置中的默认收件人
|
||||
return config.getDefaultReceivers();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,9 +3,10 @@ package com.qqchen.deploy.backend.notification.adapter.impl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.framework.utils.JsonUtils;
|
||||
import com.qqchen.deploy.backend.notification.adapter.INotificationChannelAdapter;
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.dto.WeworkSendNotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.entity.config.WeworkNotificationConfig;
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.HttpEntity;
|
||||
@ -34,7 +35,7 @@ import java.util.Map;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkNotificationConfig> {
|
||||
public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkNotificationConfig, WeworkSendNotificationRequest> {
|
||||
|
||||
private static final String BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook";
|
||||
|
||||
@ -42,59 +43,61 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public void send(WeworkNotificationConfig config, NotificationRequest request) throws Exception {
|
||||
public void send(WeworkNotificationConfig config, WeworkSendNotificationRequest request) throws Exception {
|
||||
// 1. 校验配置
|
||||
if (config.getKey() == null || config.getKey().isEmpty()) {
|
||||
throw new IllegalArgumentException("企业微信 Webhook Key 未配置");
|
||||
}
|
||||
|
||||
// 2. 构建发送消息 URL
|
||||
// 2. 根据消息类型处理
|
||||
switch (request.getMessageType()) {
|
||||
case FILE -> sendFileMessage(config, request);
|
||||
case TEXT, MARKDOWN -> sendTextOrMarkdownMessage(config, request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送文本或Markdown消息
|
||||
*/
|
||||
private void sendTextOrMarkdownMessage(WeworkNotificationConfig config, WeworkSendNotificationRequest request) throws Exception {
|
||||
// 构建发送消息 URL
|
||||
String webhookUrl = BASE_URL + "/send?key=" + config.getKey();
|
||||
|
||||
// 2. 构建消息内容
|
||||
String message = buildMessage(request);
|
||||
|
||||
// 3. 构建@人列表
|
||||
List<String> mentionedList = buildMentionedList(config, request);
|
||||
List<String> mentionedMobileList = buildMentionedMobileList(config, request);
|
||||
|
||||
// 4. 构建企业微信消息体
|
||||
// 构建企业微信消息体
|
||||
Map<String, Object> messageBody = new HashMap<>();
|
||||
|
||||
// 智能判断消息类型:如果包含 Markdown 标签,使用 markdown 类型
|
||||
boolean isMarkdown = isMarkdownMessage(message);
|
||||
String msgType = isMarkdown ? "markdown" : "text";
|
||||
String msgType = request.getMessageType().getApiType();
|
||||
messageBody.put("msgtype", msgType);
|
||||
|
||||
if (isMarkdown) {
|
||||
if (request.getMessageType() == WeworkMessageTypeEnum.MARKDOWN) {
|
||||
// Markdown 格式消息
|
||||
Map<String, Object> markdownContent = new HashMap<>();
|
||||
markdownContent.put("content", message);
|
||||
markdownContent.put("content", request.getContent());
|
||||
messageBody.put("markdown", markdownContent);
|
||||
} else {
|
||||
// 纯文本消息
|
||||
Map<String, Object> textContent = new HashMap<>();
|
||||
textContent.put("content", message);
|
||||
textContent.put("content", request.getContent());
|
||||
|
||||
if (!CollectionUtils.isEmpty(mentionedList)) {
|
||||
textContent.put("mentioned_list", mentionedList);
|
||||
// 添加@人列表
|
||||
if (!CollectionUtils.isEmpty(request.getMentionedUserList())) {
|
||||
textContent.put("mentioned_list", request.getMentionedUserList());
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(mentionedMobileList)) {
|
||||
textContent.put("mentioned_mobile_list", mentionedMobileList);
|
||||
if (!CollectionUtils.isEmpty(request.getMentionedMobileList())) {
|
||||
textContent.put("mentioned_mobile_list", request.getMentionedMobileList());
|
||||
}
|
||||
|
||||
messageBody.put("text", textContent);
|
||||
}
|
||||
|
||||
// 5. 发送请求
|
||||
// 发送请求
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
String jsonBody = objectMapper.writeValueAsString(messageBody);
|
||||
HttpEntity<String> entity = new HttpEntity<>(jsonBody, headers);
|
||||
|
||||
log.info("发送企业微信通知 - URL: {}, 类型: {}, 消息: {}", webhookUrl, msgType, message);
|
||||
log.info("发送企业微信通知 - URL: {}, 类型: {}, 消息: {}", webhookUrl, msgType, request.getContent());
|
||||
|
||||
String response = restTemplate.exchange(
|
||||
webhookUrl,
|
||||
@ -106,6 +109,44 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
|
||||
log.info("企业微信通知发送成功 - 响应: {}", response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送文件消息
|
||||
*/
|
||||
private void sendFileMessage(WeworkNotificationConfig config, WeworkSendNotificationRequest request) throws Exception {
|
||||
if (request.getFilePath() == null || request.getFilePath().isEmpty()) {
|
||||
throw new IllegalArgumentException("文件路径不能为空");
|
||||
}
|
||||
|
||||
// 1. 上传文件获取 media_id
|
||||
String uploadUrl = BASE_URL + "/upload_media?key=" + config.getKey() + "&type=file";
|
||||
String mediaId = uploadFile(uploadUrl, request.getFilePath());
|
||||
|
||||
if (mediaId == null) {
|
||||
throw new RuntimeException("文件上传失败,未获取到 media_id");
|
||||
}
|
||||
|
||||
// 2. 发送文件消息
|
||||
String sendUrl = BASE_URL + "/send?key=" + config.getKey();
|
||||
Map<String, Object> messageBody = buildFileMessage(mediaId);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
String jsonBody = objectMapper.writeValueAsString(messageBody);
|
||||
HttpEntity<String> entity = new HttpEntity<>(jsonBody, headers);
|
||||
|
||||
log.info("发送企业微信文件消息 - 文件: {}", request.getFileName());
|
||||
|
||||
String response = restTemplate.exchange(
|
||||
sendUrl,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
String.class
|
||||
).getBody();
|
||||
|
||||
log.info("企业微信文件消息发送成功 - 响应: {}", response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotificationChannelTypeEnum supportedType() {
|
||||
return NotificationChannelTypeEnum.WEWORK;
|
||||
@ -206,46 +247,6 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建消息内容
|
||||
*/
|
||||
private String buildMessage(NotificationRequest request) {
|
||||
if (request.getTitle() != null && !request.getTitle().isEmpty()) {
|
||||
return request.getTitle() + "\n" + request.getContent();
|
||||
}
|
||||
return request.getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建@人列表(userid)
|
||||
*/
|
||||
private List<String> buildMentionedList(WeworkNotificationConfig config, NotificationRequest request) {
|
||||
List<String> mentionedList = new ArrayList<>();
|
||||
|
||||
// 优先使用请求中的mentions
|
||||
if (!CollectionUtils.isEmpty(request.getMentions())) {
|
||||
mentionedList.addAll(request.getMentions());
|
||||
} else if (!CollectionUtils.isEmpty(config.getMentionedList())) {
|
||||
// 使用配置中的默认值
|
||||
mentionedList.addAll(config.getMentionedList());
|
||||
}
|
||||
|
||||
return mentionedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建@人列表(手机号)
|
||||
*/
|
||||
private List<String> buildMentionedMobileList(WeworkNotificationConfig config, NotificationRequest request) {
|
||||
List<String> mentionedMobileList = new ArrayList<>();
|
||||
|
||||
// 使用配置中的默认手机号
|
||||
if (!CollectionUtils.isEmpty(config.getMentionedMobileList())) {
|
||||
mentionedMobileList.addAll(config.getMentionedMobileList());
|
||||
}
|
||||
|
||||
return mentionedMobileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是 Markdown 格式消息
|
||||
|
||||
@ -4,7 +4,7 @@ import com.qqchen.deploy.backend.framework.api.Response;
|
||||
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationChannelDTO;
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationChannelQuery;
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.dto.BaseSendNotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.entity.NotificationChannel;
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import com.qqchen.deploy.backend.notification.service.INotificationChannelService;
|
||||
@ -41,7 +41,7 @@ public class NotificationChannelApiController extends BaseController<Notificatio
|
||||
|
||||
@Resource
|
||||
private INotificationChannelService notificationChannelService;
|
||||
|
||||
|
||||
@Resource
|
||||
private INotificationSendService notificationSendService;
|
||||
|
||||
@ -51,7 +51,7 @@ public class NotificationChannelApiController extends BaseController<Notificatio
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response<NotificationChannelDTO> update(@PathVariable Long id,@Validated @RequestBody NotificationChannelDTO dto) {
|
||||
public Response<NotificationChannelDTO> update(@PathVariable Long id, @Validated @RequestBody NotificationChannelDTO dto) {
|
||||
return super.update(id, dto);
|
||||
}
|
||||
|
||||
@ -127,11 +127,11 @@ public class NotificationChannelApiController extends BaseController<Notificatio
|
||||
notificationChannelService.disable(id);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "发送通知消息")
|
||||
@PostMapping("/send")
|
||||
public Response<Void> send(
|
||||
@Parameter(description = "通知请求", required = true) @RequestBody NotificationRequest request
|
||||
@Parameter(description = "通知请求", required = true) @RequestBody BaseSendNotificationRequest request
|
||||
) {
|
||||
notificationSendService.send(request);
|
||||
return Response.success();
|
||||
|
||||
@ -8,6 +8,9 @@ import lombok.Data;
|
||||
/**
|
||||
* 通知配置DTO基类
|
||||
* 用于数据传输层,与Entity层的BaseNotificationConfig对应
|
||||
*
|
||||
* 使用 EXISTING_PROPERTY 方式,利用外层 NotificationChannelDTO 的 channelType 字段
|
||||
* 进行多态反序列化,无需在 config 内部添加额外的 type 字段
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-12
|
||||
@ -15,7 +18,7 @@ import lombok.Data;
|
||||
@Data
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
property = "channelType"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@ -25,7 +28,7 @@ import lombok.Data;
|
||||
public abstract class BaseNotificationConfigDTO {
|
||||
|
||||
/**
|
||||
* 获取渠道类型
|
||||
* 获取渠道类型(与外层 NotificationChannelDTO.channelType 保持一致)
|
||||
*/
|
||||
public abstract NotificationChannelTypeEnum getChannelType();
|
||||
}
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
package com.qqchen.deploy.backend.notification.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 发送通知请求基类
|
||||
* 不同渠道有不同的发送参数,使用多态设计
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-12
|
||||
*/
|
||||
@Data
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
property = "channelType"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = WeworkSendNotificationRequest.class, name = "WEWORK"),
|
||||
@JsonSubTypes.Type(value = EmailSendNotificationRequest.class, name = "EMAIL")
|
||||
})
|
||||
public abstract class BaseSendNotificationRequest {
|
||||
|
||||
/**
|
||||
* 通知渠道ID(必填)
|
||||
*/
|
||||
@NotNull(message = "渠道ID不能为空")
|
||||
private Long channelId;
|
||||
|
||||
/**
|
||||
* 消息内容(必填)
|
||||
*/
|
||||
@NotBlank(message = "消息内容不能为空")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 获取渠道类型
|
||||
*/
|
||||
public abstract NotificationChannelTypeEnum getChannelType();
|
||||
}
|
||||
@ -16,6 +16,11 @@ import java.util.List;
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EmailNotificationConfigDTO extends BaseNotificationConfigDTO {
|
||||
|
||||
/**
|
||||
* 渠道类型(用于Jackson反序列化,与外层保持一致)
|
||||
*/
|
||||
private final NotificationChannelTypeEnum channelType = NotificationChannelTypeEnum.EMAIL;
|
||||
|
||||
/**
|
||||
* SMTP服务器地址(必填)
|
||||
*/
|
||||
@ -46,11 +51,6 @@ public class EmailNotificationConfigDTO extends BaseNotificationConfigDTO {
|
||||
*/
|
||||
private String fromName;
|
||||
|
||||
/**
|
||||
* 默认收件人列表(可选)
|
||||
*/
|
||||
private List<String> defaultReceivers;
|
||||
|
||||
/**
|
||||
* 是否使用SSL(可选,默认true)
|
||||
*/
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
package com.qqchen.deploy.backend.notification.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 邮件发送通知请求
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-12
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EmailSendNotificationRequest extends BaseSendNotificationRequest {
|
||||
|
||||
/**
|
||||
* 渠道类型(用于Jackson反序列化)
|
||||
*/
|
||||
private final NotificationChannelTypeEnum channelType = NotificationChannelTypeEnum.EMAIL;
|
||||
|
||||
/**
|
||||
* 邮件主题(必填)
|
||||
*/
|
||||
@NotEmpty(message = "邮件主题不能为空")
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 收件人列表(必填)
|
||||
*/
|
||||
@NotEmpty(message = "收件人不能为空")
|
||||
private List<String> toReceivers;
|
||||
|
||||
/**
|
||||
* 抄送列表(可选)
|
||||
*/
|
||||
private List<String> ccReceivers;
|
||||
|
||||
/**
|
||||
* 密送列表(可选)
|
||||
*/
|
||||
private List<String> bccReceivers;
|
||||
|
||||
/**
|
||||
* 是否HTML格式(可选,默认false)
|
||||
*/
|
||||
private Boolean isHtml = false;
|
||||
|
||||
@Override
|
||||
public NotificationChannelTypeEnum getChannelType() {
|
||||
return NotificationChannelTypeEnum.EMAIL;
|
||||
}
|
||||
}
|
||||
@ -15,24 +15,18 @@ import java.util.List;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WeworkNotificationConfigDTO extends BaseNotificationConfigDTO {
|
||||
|
||||
|
||||
/**
|
||||
* 渠道类型(用于Jackson反序列化,与外层保持一致)
|
||||
*/
|
||||
private final NotificationChannelTypeEnum channelType = NotificationChannelTypeEnum.WEWORK;
|
||||
|
||||
/**
|
||||
* 企业微信机器人 Webhook Key(必填)
|
||||
* 完整URL格式: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={key}
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 默认@的手机号列表(可选)
|
||||
*/
|
||||
private List<String> mentionedMobileList;
|
||||
|
||||
/**
|
||||
* 默认@的用户列表(可选)
|
||||
* 例如:["@all"] 表示@所有人
|
||||
*/
|
||||
private List<String> mentionedList;
|
||||
|
||||
|
||||
@Override
|
||||
public NotificationChannelTypeEnum getChannelType() {
|
||||
return NotificationChannelTypeEnum.WEWORK;
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
package com.qqchen.deploy.backend.notification.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 企业微信发送通知请求
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-12
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WeworkSendNotificationRequest extends BaseSendNotificationRequest {
|
||||
|
||||
/**
|
||||
* 渠道类型(用于Jackson反序列化)
|
||||
*/
|
||||
private final NotificationChannelTypeEnum channelType = NotificationChannelTypeEnum.WEWORK;
|
||||
|
||||
/**
|
||||
* 消息类型(必填)
|
||||
* - TEXT: 普通文本消息
|
||||
* - MARKDOWN: Markdown格式消息
|
||||
* - FILE: 文件消息
|
||||
*/
|
||||
private WeworkMessageTypeEnum messageType = WeworkMessageTypeEnum.TEXT;
|
||||
|
||||
/**
|
||||
* @的手机号列表(可选) 例如:["13800138000", "13900139000"]
|
||||
*/
|
||||
private List<String> mentionedMobileList;
|
||||
|
||||
/**
|
||||
* @的用户ID列表(可选) 例如:["@all"] 表示@所有人,或具体的userid
|
||||
*/
|
||||
private List<String> mentionedUserList;
|
||||
|
||||
/**
|
||||
* 文件路径(仅当messageType为FILE时使用)
|
||||
* 服务器本地文件路径,用于文件上传
|
||||
*/
|
||||
private String filePath;
|
||||
|
||||
/**
|
||||
* 文件名称(仅当messageType为FILE时使用)
|
||||
* 显示给用户的文件名
|
||||
*/
|
||||
private String fileName;
|
||||
|
||||
@Override
|
||||
public NotificationChannelTypeEnum getChannelType() {
|
||||
return NotificationChannelTypeEnum.WEWORK;
|
||||
}
|
||||
}
|
||||
@ -46,11 +46,6 @@ public class EmailNotificationConfig extends BaseNotificationConfig {
|
||||
*/
|
||||
private String fromName;
|
||||
|
||||
/**
|
||||
* 默认收件人列表(可选)
|
||||
*/
|
||||
private List<String> defaultReceivers;
|
||||
|
||||
/**
|
||||
* 是否使用SSL(可选,默认true)
|
||||
*/
|
||||
|
||||
@ -22,17 +22,6 @@ public class WeworkNotificationConfig extends BaseNotificationConfig {
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 默认@的手机号列表(可选)
|
||||
*/
|
||||
private List<String> mentionedMobileList;
|
||||
|
||||
/**
|
||||
* 默认@的用户列表(可选)
|
||||
* 例如:["@all"] 表示@所有人
|
||||
*/
|
||||
private List<String> mentionedList;
|
||||
|
||||
@Override
|
||||
public NotificationChannelTypeEnum getChannelType() {
|
||||
return NotificationChannelTypeEnum.WEWORK;
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
package com.qqchen.deploy.backend.notification.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 企业微信消息类型枚举
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-12
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum WeworkMessageTypeEnum {
|
||||
|
||||
/**
|
||||
* 文本消息
|
||||
*/
|
||||
TEXT("text", "文本消息"),
|
||||
|
||||
/**
|
||||
* Markdown消息
|
||||
*/
|
||||
MARKDOWN("markdown", "Markdown消息"),
|
||||
|
||||
/**
|
||||
* 文件消息
|
||||
*/
|
||||
FILE("file", "文件消息");
|
||||
|
||||
/**
|
||||
* 企业微信API中的消息类型标识
|
||||
*/
|
||||
private final String apiType;
|
||||
|
||||
/**
|
||||
* 消息类型描述
|
||||
*/
|
||||
private final String description;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qqchen.deploy.backend.notification.service;
|
||||
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.dto.BaseSendNotificationRequest;
|
||||
|
||||
/**
|
||||
* 通知发送服务接口
|
||||
@ -16,36 +16,9 @@ public interface INotificationSendService {
|
||||
* @param request 通知请求
|
||||
* @throws com.qqchen.deploy.backend.framework.exception.BusinessException 渠道不存在、渠道已禁用、发送失败
|
||||
*/
|
||||
void send(NotificationRequest request);
|
||||
void send(BaseSendNotificationRequest request);
|
||||
|
||||
/**
|
||||
* 便捷方法:发送简单文本通知
|
||||
*
|
||||
* @param channelId 渠道ID
|
||||
* @param content 消息内容
|
||||
*/
|
||||
default void sendSimple(Long channelId, String content) {
|
||||
NotificationRequest request = NotificationRequest.builder()
|
||||
.channelId(channelId)
|
||||
.content(content)
|
||||
.build();
|
||||
send(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷方法:发送带标题的通知
|
||||
*
|
||||
* @param channelId 渠道ID
|
||||
* @param title 标题
|
||||
* @param content 内容
|
||||
*/
|
||||
default void send(Long channelId, String title, String content) {
|
||||
NotificationRequest request = NotificationRequest.builder()
|
||||
.channelId(channelId)
|
||||
.title(title)
|
||||
.content(content)
|
||||
.build();
|
||||
send(request);
|
||||
}
|
||||
// TODO: 便捷方法需要重新设计,因为现在需要指定具体的请求类型
|
||||
// 暂时注释掉,后续根据需要添加
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ import com.qqchen.deploy.backend.notification.entity.config.BaseNotificationConf
|
||||
import com.qqchen.deploy.backend.notification.entity.config.EmailNotificationConfig;
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationChannelDTO;
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationChannelQuery;
|
||||
import com.qqchen.deploy.backend.notification.dto.NotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.dto.BaseSendNotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.entity.config.WeworkNotificationConfig;
|
||||
import com.qqchen.deploy.backend.notification.entity.NotificationChannel;
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelStatusEnum;
|
||||
@ -115,8 +115,7 @@ public class NotificationChannelServiceImpl
|
||||
log.info("禁用通知渠道: id={}, name={}", id, channel.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(NotificationRequest request) {
|
||||
public void send(BaseSendNotificationRequest request) {
|
||||
// 1. 参数校验
|
||||
if (request == null || request.getChannelId() == null) {
|
||||
throw new BusinessException(ResponseCode.INVALID_PARAM);
|
||||
@ -149,8 +148,8 @@ public class NotificationChannelServiceImpl
|
||||
|
||||
// 6. 发送通知
|
||||
try {
|
||||
log.info("发送通知 - 渠道ID: {}, 渠道类型: {}, 标题: {}",
|
||||
channel.getId(), channel.getChannelType(), request.getTitle());
|
||||
log.info("发送通知 - 渠道ID: {}, 渠道类型: {}, 内容: {}",
|
||||
channel.getId(), channel.getChannelType(), request.getContent());
|
||||
|
||||
adapter.send(config, request);
|
||||
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
package com.qqchen.deploy.backend.workflow.delegate;
|
||||
|
||||
import com.qqchen.deploy.backend.notification.dto.EmailSendNotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.dto.WeworkSendNotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.entity.NotificationChannel;
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum;
|
||||
import com.qqchen.deploy.backend.notification.repository.INotificationChannelRepository;
|
||||
import com.qqchen.deploy.backend.notification.service.INotificationSendService;
|
||||
import com.qqchen.deploy.backend.workflow.dto.inputmapping.NotificationInputMapping;
|
||||
import com.qqchen.deploy.backend.workflow.dto.outputs.NotificationOutputs;
|
||||
@ -9,6 +15,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -23,6 +30,9 @@ public class NotificationNodeDelegate extends BaseNodeDelegate<NotificationInput
|
||||
|
||||
@Resource
|
||||
private INotificationSendService notificationSendService;
|
||||
|
||||
@Resource
|
||||
private INotificationChannelRepository notificationChannelRepository;
|
||||
|
||||
@Override
|
||||
protected void executeInternal(DelegateExecution execution, Map<String, Object> configs, NotificationInputMapping input) {
|
||||
@ -30,6 +40,47 @@ public class NotificationNodeDelegate extends BaseNodeDelegate<NotificationInput
|
||||
logError(String.format("Notification delegate parameter verification failed %s %s %s", input.getChannelId(), input.getTitle(), input.getContent()));
|
||||
return;
|
||||
}
|
||||
notificationSendService.send(input.getChannelId(), input.getTitle(), input.getContent());
|
||||
try {
|
||||
// 1. 查询渠道信息
|
||||
NotificationChannel channel = notificationChannelRepository.findById(input.getChannelId())
|
||||
.orElseThrow(() -> new RuntimeException("通知渠道不存在: " + input.getChannelId()));
|
||||
|
||||
// 2. 根据渠道类型构建对应的请求对象
|
||||
switch (channel.getChannelType()) {
|
||||
case WEWORK -> {
|
||||
WeworkSendNotificationRequest weworkRequest = new WeworkSendNotificationRequest();
|
||||
weworkRequest.setChannelId(input.getChannelId());
|
||||
weworkRequest.setContent(buildWeworkContent(input.getTitle(), input.getContent()));
|
||||
weworkRequest.setMessageType(WeworkMessageTypeEnum.TEXT);
|
||||
notificationSendService.send(weworkRequest);
|
||||
}
|
||||
case EMAIL -> {
|
||||
EmailSendNotificationRequest emailRequest = new EmailSendNotificationRequest();
|
||||
emailRequest.setChannelId(input.getChannelId());
|
||||
emailRequest.setSubject(input.getTitle());
|
||||
emailRequest.setContent(input.getContent());
|
||||
// 这里需要设置收件人,但工作流中没有提供,需要从其他地方获取
|
||||
// 暂时使用一个默认值,实际应该从工作流变量或配置中获取
|
||||
emailRequest.setToReceivers(Arrays.asList("admin@company.com"));
|
||||
notificationSendService.send(emailRequest);
|
||||
}
|
||||
default -> throw new RuntimeException("不支持的渠道类型: " + channel.getChannelType());
|
||||
}
|
||||
|
||||
log.info("工作流通知发送成功 - 渠道ID: {}, 类型: {}", input.getChannelId(), channel.getChannelType());
|
||||
} catch (Exception e) {
|
||||
logError("工作流通知发送失败: " + e.getMessage());
|
||||
throw new RuntimeException("通知发送失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建企业微信消息内容(标题+内容)
|
||||
*/
|
||||
private String buildWeworkContent(String title, String content) {
|
||||
if (StringUtils.isNotEmpty(title)) {
|
||||
return title + "\n\n" + content;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user