增加构建通知
This commit is contained in:
parent
5205c6aaa1
commit
9d425aa1f7
@ -1,9 +1,9 @@
|
||||
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.WeworkSendNotificationRequest;
|
||||
import com.qqchen.deploy.backend.notification.dto.wework.WeworkWebhookRequest;
|
||||
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;
|
||||
@ -11,20 +11,15 @@ 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.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -40,7 +35,6 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
|
||||
private static final String BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook";
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public void send(WeworkNotificationConfig config, WeworkSendNotificationRequest request) throws Exception {
|
||||
@ -59,52 +53,17 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
|
||||
/**
|
||||
* 发送文本或Markdown消息
|
||||
*/
|
||||
private void sendTextOrMarkdownMessage(WeworkNotificationConfig config, WeworkSendNotificationRequest request) throws Exception {
|
||||
// 构建发送消息 URL
|
||||
private void sendTextOrMarkdownMessage(WeworkNotificationConfig config, WeworkSendNotificationRequest request) {
|
||||
String webhookUrl = BASE_URL + "/send?key=" + config.getKey();
|
||||
|
||||
// 构建企业微信消息体
|
||||
Map<String, Object> messageBody = new HashMap<>();
|
||||
String msgType = request.getMessageType().getApiType();
|
||||
messageBody.put("msgtype", msgType);
|
||||
// 转换为企微API格式
|
||||
var webhookRequest = request.toWebhookRequest();
|
||||
|
||||
if (request.getMessageType() == WeworkMessageTypeEnum.MARKDOWN) {
|
||||
// Markdown 格式消息
|
||||
Map<String, Object> markdownContent = new HashMap<>();
|
||||
markdownContent.put("content", request.getContent());
|
||||
messageBody.put("markdown", markdownContent);
|
||||
} else {
|
||||
// 纯文本消息
|
||||
Map<String, Object> textContent = new HashMap<>();
|
||||
textContent.put("content", request.getContent());
|
||||
log.info("发送企业微信通知 - URL: {}, 类型: {}, 消息: {}",
|
||||
webhookUrl, request.getMessageType().getApiType(), request.getContent());
|
||||
|
||||
// 添加@人列表
|
||||
if (!CollectionUtils.isEmpty(request.getMentionedUserList())) {
|
||||
textContent.put("mentioned_list", request.getMentionedUserList());
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(request.getMentionedMobileList())) {
|
||||
textContent.put("mentioned_mobile_list", request.getMentionedMobileList());
|
||||
}
|
||||
|
||||
messageBody.put("text", textContent);
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
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, request.getContent());
|
||||
|
||||
String response = restTemplate.exchange(
|
||||
webhookUrl,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
String.class
|
||||
).getBody();
|
||||
// RestTemplate 自动序列化对象为JSON
|
||||
String response = restTemplate.postForObject(webhookUrl, webhookRequest, String.class);
|
||||
|
||||
log.info("企业微信通知发送成功 - 响应: {}", response);
|
||||
}
|
||||
@ -112,7 +71,7 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
|
||||
/**
|
||||
* 发送文件消息
|
||||
*/
|
||||
private void sendFileMessage(WeworkNotificationConfig config, WeworkSendNotificationRequest request) throws Exception {
|
||||
private void sendFileMessage(WeworkNotificationConfig config, WeworkSendNotificationRequest request) {
|
||||
if (request.getFilePath() == null || request.getFilePath().isEmpty()) {
|
||||
throw new IllegalArgumentException("文件路径不能为空");
|
||||
}
|
||||
@ -125,24 +84,14 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
|
||||
throw new RuntimeException("文件上传失败,未获取到 media_id");
|
||||
}
|
||||
|
||||
// 2. 发送文件消息
|
||||
// 2. 转换为企微API格式(带media_id)
|
||||
String sendUrl = BASE_URL + "/send?key=" + config.getKey();
|
||||
Map<String, Object> messageBody = buildFileMessage(mediaId);
|
||||
var webhookRequest = request.toWebhookRequest(mediaId);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
log.info("发送企业微信文件消息 - 文件: {}, mediaId: {}", request.getFileName(), mediaId);
|
||||
|
||||
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();
|
||||
// RestTemplate 自动序列化对象为JSON
|
||||
String response = restTemplate.postForObject(sendUrl, webhookRequest, String.class);
|
||||
|
||||
log.info("企业微信文件消息发送成功 - 响应: {}", response);
|
||||
}
|
||||
@ -168,24 +117,17 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
|
||||
throw new RuntimeException("文件上传失败,未获取到 media_id");
|
||||
}
|
||||
|
||||
// 3. 发送文件消息
|
||||
// 3. 构建文件消息请求
|
||||
WeworkSendNotificationRequest request = new WeworkSendNotificationRequest();
|
||||
request.setMessageType(WeworkMessageTypeEnum.FILE);
|
||||
var webhookRequest = request.toWebhookRequest(mediaId);
|
||||
|
||||
// 4. 发送文件消息
|
||||
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("发送企业微信文件 - fileName: {}, mediaId: {}", fileName, mediaId);
|
||||
|
||||
String response = restTemplate.exchange(
|
||||
sendUrl,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
String.class
|
||||
).getBody();
|
||||
String response = restTemplate.postForObject(sendUrl, webhookRequest, String.class);
|
||||
|
||||
log.info("企业微信文件发送成功 - 响应: {}", response);
|
||||
|
||||
@ -213,33 +155,22 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
|
||||
// 2. 构建测试消息 URL
|
||||
String webhookUrl = BASE_URL + "/send?key=" + config.getKey();
|
||||
|
||||
// 2. 构建测试消息
|
||||
Map<String, Object> messageBody = new HashMap<>();
|
||||
messageBody.put("msgtype", "text");
|
||||
|
||||
Map<String, Object> textContent = new HashMap<>();
|
||||
textContent.put("content", "【测试消息】\n这是一条来自 Deploy Ease Platform 的测试消息,您的企业微信通知渠道配置正常!");
|
||||
messageBody.put("text", textContent);
|
||||
|
||||
// 3. 发送测试请求
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
String jsonBody = objectMapper.writeValueAsString(messageBody);
|
||||
HttpEntity<String> entity = new HttpEntity<>(jsonBody, headers);
|
||||
// 3. 构建测试消息
|
||||
WeworkWebhookRequest testRequest = WeworkWebhookRequest.builder()
|
||||
.msgtype("text")
|
||||
.text(WeworkWebhookRequest.TextContent.builder()
|
||||
.content("【测试消息】\n这是一条来自 Deploy Ease Platform 的测试消息,您的企业微信通知渠道配置正常!")
|
||||
.build())
|
||||
.build();
|
||||
|
||||
log.info("测试企业微信连接 - URL: {}", webhookUrl);
|
||||
|
||||
String response = restTemplate.exchange(
|
||||
webhookUrl,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
String.class
|
||||
).getBody();
|
||||
// 4. 发送测试请求(RestTemplate 自动序列化对象为JSON)
|
||||
String response = restTemplate.postForObject(webhookUrl, testRequest, String.class);
|
||||
|
||||
log.info("企业微信连接测试成功 - 响应: {}", response);
|
||||
|
||||
// 4. 检查响应
|
||||
// 5. 检查响应
|
||||
if (response != null && response.contains("\"errcode\":0")) {
|
||||
log.info("企业微信通知渠道测试成功");
|
||||
} else {
|
||||
@ -322,18 +253,6 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建文件消息体
|
||||
*/
|
||||
private Map<String, Object> buildFileMessage(String mediaId) {
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("msgtype", "file");
|
||||
|
||||
Map<String, String> file = new HashMap<>();
|
||||
file.put("media_id", mediaId);
|
||||
|
||||
message.put("file", file);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@ import com.qqchen.deploy.backend.notification.entity.config.EmailNotificationCon
|
||||
import com.qqchen.deploy.backend.notification.entity.config.WeworkNotificationConfig;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@ -22,24 +21,17 @@ import java.util.Map;
|
||||
@Mapper(config = BaseConverter.class)
|
||||
public abstract class NotificationChannelConverter implements BaseConverter<NotificationChannel, NotificationChannelDTO> {
|
||||
|
||||
protected NotificationConfigConverter notificationConfigConverter;
|
||||
|
||||
@Autowired
|
||||
public void setNotificationConfigConverter(NotificationConfigConverter notificationConfigConverter) {
|
||||
this.notificationConfigConverter = notificationConfigConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity -> DTO
|
||||
* config: Map -> BaseNotificationConfigDTO
|
||||
* config: Map -> BaseNotificationConfig
|
||||
*/
|
||||
@Override
|
||||
@Mapping(target = "config", expression = "java(mapToConfigDTO(entity))")
|
||||
@Mapping(target = "config", expression = "java(mapToConfig(entity))")
|
||||
public abstract NotificationChannelDTO toDto(NotificationChannel entity);
|
||||
|
||||
/**
|
||||
* DTO -> Entity
|
||||
* config: BaseNotificationConfigDTO -> Map
|
||||
* config: BaseNotificationConfig -> Map
|
||||
*/
|
||||
@Override
|
||||
@Mapping(target = "config", expression = "java(mapToConfigMap(dto))")
|
||||
@ -47,7 +39,7 @@ public abstract class NotificationChannelConverter implements BaseConverter<Noti
|
||||
|
||||
/**
|
||||
* 更新Entity(手动实现)
|
||||
* config: BaseNotificationConfigDTO -> Map
|
||||
* config: BaseNotificationConfig -> Map
|
||||
*/
|
||||
@Override
|
||||
public void updateEntity(NotificationChannel entity, NotificationChannelDTO dto) {
|
||||
@ -66,37 +58,31 @@ public abstract class NotificationChannelConverter implements BaseConverter<Noti
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Entity的Map配置转换为DTO
|
||||
* 将Entity的Map配置转换为Config对象
|
||||
*/
|
||||
protected BaseNotificationConfigDTO mapToConfigDTO(NotificationChannel entity) {
|
||||
protected BaseNotificationConfig mapToConfig(NotificationChannel entity) {
|
||||
if (entity == null || entity.getConfig() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. Map -> BaseNotificationConfig Entity
|
||||
BaseNotificationConfig configEntity = switch (entity.getChannelType()) {
|
||||
// Map -> BaseNotificationConfig(根据渠道类型自动识别)
|
||||
return switch (entity.getChannelType()) {
|
||||
case WEWORK -> JsonUtils.fromMap(entity.getConfig(), WeworkNotificationConfig.class);
|
||||
case EMAIL -> JsonUtils.fromMap(entity.getConfig(), EmailNotificationConfig.class);
|
||||
default -> throw new IllegalArgumentException("不支持的渠道类型: " + entity.getChannelType());
|
||||
};
|
||||
|
||||
// 2. BaseNotificationConfig Entity -> BaseNotificationConfigDTO
|
||||
return notificationConfigConverter.toDto(configEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将DTO配置转换为Entity的Map
|
||||
* 将Config对象转换为Entity的Map
|
||||
*/
|
||||
protected Map<String, Object> mapToConfigMap(NotificationChannelDTO dto) {
|
||||
if (dto == null || dto.getConfig() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. BaseNotificationConfigDTO -> BaseNotificationConfig Entity
|
||||
BaseNotificationConfig configEntity = notificationConfigConverter.toEntity(dto.getConfig());
|
||||
|
||||
// 2. BaseNotificationConfig Entity -> Map
|
||||
return JsonUtils.toMap(configEntity);
|
||||
// BaseNotificationConfig -> Map
|
||||
return JsonUtils.toMap(dto.getConfig());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
package com.qqchen.deploy.backend.notification.converter;
|
||||
|
||||
import com.qqchen.deploy.backend.notification.dto.*;
|
||||
import com.qqchen.deploy.backend.notification.entity.config.BaseNotificationConfig;
|
||||
import com.qqchen.deploy.backend.notification.entity.config.EmailNotificationConfig;
|
||||
import com.qqchen.deploy.backend.notification.entity.config.WeworkNotificationConfig;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
/**
|
||||
* 通知配置转换器
|
||||
* 负责Entity配置和DTO配置之间的转换
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-12
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface NotificationConfigConverter {
|
||||
|
||||
// Entity -> DTO
|
||||
WeworkNotificationConfigDTO toDto(WeworkNotificationConfig entity);
|
||||
EmailNotificationConfigDTO toDto(EmailNotificationConfig entity);
|
||||
|
||||
// DTO -> Entity
|
||||
WeworkNotificationConfig toEntity(WeworkNotificationConfigDTO dto);
|
||||
EmailNotificationConfig toEntity(EmailNotificationConfigDTO dto);
|
||||
|
||||
/**
|
||||
* 根据类型自动转换 Entity -> DTO
|
||||
*/
|
||||
default BaseNotificationConfigDTO toDto(BaseNotificationConfig entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entity instanceof WeworkNotificationConfig) {
|
||||
return toDto((WeworkNotificationConfig) entity);
|
||||
} else if (entity instanceof EmailNotificationConfig) {
|
||||
return toDto((EmailNotificationConfig) entity);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("不支持的配置类型: " + entity.getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型自动转换 DTO -> Entity
|
||||
*/
|
||||
default BaseNotificationConfig toEntity(BaseNotificationConfigDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dto instanceof WeworkNotificationConfigDTO) {
|
||||
return toEntity((WeworkNotificationConfigDTO) dto);
|
||||
} else if (dto instanceof EmailNotificationConfigDTO) {
|
||||
return toEntity((EmailNotificationConfigDTO) dto);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("不支持的配置类型: " + dto.getClass().getName());
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
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 lombok.Data;
|
||||
|
||||
/**
|
||||
* 通知配置DTO基类
|
||||
* 用于数据传输层,与Entity层的BaseNotificationConfig对应
|
||||
*
|
||||
* 使用 EXISTING_PROPERTY 方式,利用外层 NotificationChannelDTO 的 channelType 字段
|
||||
* 进行多态反序列化,无需在 config 内部添加额外的 type 字段
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-12
|
||||
*/
|
||||
@Data
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
property = "channelType"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = WeworkNotificationConfigDTO.class, name = "WEWORK"),
|
||||
@JsonSubTypes.Type(value = EmailNotificationConfigDTO.class, name = "EMAIL")
|
||||
})
|
||||
public abstract class BaseNotificationConfigDTO {
|
||||
|
||||
/**
|
||||
* 获取渠道类型(与外层 NotificationChannelDTO.channelType 保持一致)
|
||||
*/
|
||||
public abstract NotificationChannelTypeEnum getChannelType();
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
package com.qqchen.deploy.backend.notification.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 邮件通知配置DTO
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-12
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EmailNotificationConfigDTO extends BaseNotificationConfigDTO {
|
||||
|
||||
/**
|
||||
* 渠道类型(用于Jackson反序列化,与外层保持一致)
|
||||
*/
|
||||
private final NotificationChannelTypeEnum channelType = NotificationChannelTypeEnum.EMAIL;
|
||||
|
||||
/**
|
||||
* SMTP服务器地址(必填)
|
||||
*/
|
||||
private String smtpHost;
|
||||
|
||||
/**
|
||||
* SMTP服务器端口(必填)
|
||||
*/
|
||||
private Integer smtpPort;
|
||||
|
||||
/**
|
||||
* SMTP用户名(必填)
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* SMTP密码(必填)
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 发件人邮箱(必填)
|
||||
*/
|
||||
private String from;
|
||||
|
||||
/**
|
||||
* 发件人名称(可选)
|
||||
*/
|
||||
private String fromName;
|
||||
|
||||
/**
|
||||
* 是否使用SSL(可选,默认true)
|
||||
*/
|
||||
private Boolean useSsl = true;
|
||||
|
||||
@Override
|
||||
public NotificationChannelTypeEnum getChannelType() {
|
||||
return NotificationChannelTypeEnum.EMAIL;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package com.qqchen.deploy.backend.notification.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||
import com.qqchen.deploy.backend.notification.entity.config.BaseNotificationConfig;
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@ -29,7 +30,7 @@ public class NotificationChannelDTO extends BaseDTO {
|
||||
|
||||
@Schema(description = "渠道配置(根据渠道类型不同而不同)")
|
||||
@NotNull(message = "渠道配置不能为空")
|
||||
private BaseNotificationConfigDTO config;
|
||||
private BaseNotificationConfig config;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabled;
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
package com.qqchen.deploy.backend.notification.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 企业微信通知配置DTO
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-12
|
||||
*/
|
||||
@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;
|
||||
|
||||
@Override
|
||||
public NotificationChannelTypeEnum getChannelType() {
|
||||
return NotificationChannelTypeEnum.WEWORK;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package com.qqchen.deploy.backend.notification.dto;
|
||||
|
||||
import com.qqchen.deploy.backend.notification.dto.wework.WeworkWebhookRequest;
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum;
|
||||
import lombok.Data;
|
||||
@ -56,4 +57,71 @@ public class WeworkSendNotificationRequest extends BaseSendNotificationRequest {
|
||||
public NotificationChannelTypeEnum getChannelType() {
|
||||
return NotificationChannelTypeEnum.WEWORK;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为企微 Webhook API 请求格式
|
||||
*
|
||||
* @return 企微API标准格式的请求对象
|
||||
* @throws UnsupportedOperationException 当消息类型为FILE时抛出,需使用 toWebhookRequest(String mediaId)
|
||||
*/
|
||||
public WeworkWebhookRequest toWebhookRequest() {
|
||||
return switch (messageType) {
|
||||
case TEXT -> buildTextRequest();
|
||||
case MARKDOWN -> buildMarkdownRequest();
|
||||
case FILE -> throw new UnsupportedOperationException(
|
||||
"文件消息需要先上传获取media_id,请使用 toWebhookRequest(String mediaId)");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为文件消息格式(需要提供已上传的media_id)
|
||||
*
|
||||
* @param mediaId 企业微信文件上传后返回的media_id
|
||||
* @return 企微API标准格式的请求对象
|
||||
* @throws IllegalStateException 当消息类型不是FILE时抛出
|
||||
*/
|
||||
public WeworkWebhookRequest toWebhookRequest(String mediaId) {
|
||||
if (messageType != WeworkMessageTypeEnum.FILE) {
|
||||
throw new IllegalStateException("只有FILE类型消息才能使用此方法");
|
||||
}
|
||||
return buildFileRequest(mediaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建文本消息请求
|
||||
*/
|
||||
private WeworkWebhookRequest buildTextRequest() {
|
||||
return WeworkWebhookRequest.builder()
|
||||
.msgtype(WeworkMessageTypeEnum.TEXT.getApiType())
|
||||
.text(WeworkWebhookRequest.TextContent.builder()
|
||||
.content(getContent())
|
||||
.mentionedList(mentionedUserList)
|
||||
.mentionedMobileList(mentionedMobileList)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建Markdown消息请求
|
||||
*/
|
||||
private WeworkWebhookRequest buildMarkdownRequest() {
|
||||
return WeworkWebhookRequest.builder()
|
||||
.msgtype(WeworkMessageTypeEnum.MARKDOWN.getApiType())
|
||||
.markdown(WeworkWebhookRequest.MarkdownContent.builder()
|
||||
.content(getContent())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建文件消息请求
|
||||
*/
|
||||
private WeworkWebhookRequest buildFileRequest(String mediaId) {
|
||||
return WeworkWebhookRequest.builder()
|
||||
.msgtype(WeworkMessageTypeEnum.FILE.getApiType())
|
||||
.file(WeworkWebhookRequest.FileContent.builder()
|
||||
.mediaId(mediaId)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.qqchen.deploy.backend.notification.entity.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
@ -7,10 +9,22 @@ import lombok.Data;
|
||||
* 通知配置基类
|
||||
* 提取各渠道配置的共用字段
|
||||
*
|
||||
* 支持 Jackson 多态反序列化:
|
||||
* 利用外层 NotificationChannelDTO 的 channelType 字段进行类型识别
|
||||
*
|
||||
* @author qqchen
|
||||
* @since 2025-11-12
|
||||
*/
|
||||
@Data
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
property = "channelType"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = WeworkNotificationConfig.class, name = "WEWORK"),
|
||||
@JsonSubTypes.Type(value = EmailNotificationConfig.class, name = "EMAIL")
|
||||
})
|
||||
public abstract class BaseNotificationConfig {
|
||||
|
||||
/**
|
||||
|
||||
@ -110,53 +110,6 @@ public class NotificationChannelServiceImpl extends BaseServiceImpl<Notification
|
||||
log.info("禁用通知渠道: id={}, name={}", id, channel.getName());
|
||||
}
|
||||
|
||||
public void send(BaseSendNotificationRequest request) {
|
||||
// 1. 参数校验
|
||||
if (request == null || request.getChannelId() == null) {
|
||||
throw new BusinessException(ResponseCode.INVALID_PARAM);
|
||||
}
|
||||
|
||||
if (request.getContent() == null || request.getContent().isEmpty()) {
|
||||
throw new BusinessException(ResponseCode.INVALID_PARAM);
|
||||
}
|
||||
|
||||
// 2. 查询渠道配置
|
||||
NotificationChannel channel = notificationChannelRepository.findById(request.getChannelId())
|
||||
.orElseThrow(() -> new BusinessException(ResponseCode.NOTIFICATION_CHANNEL_NOT_FOUND));
|
||||
|
||||
// 3. 校验渠道状态
|
||||
if (!channel.getEnabled()) {
|
||||
throw new BusinessException(ResponseCode.NOTIFICATION_CHANNEL_DISABLED);
|
||||
}
|
||||
|
||||
// 4. 获取对应的适配器
|
||||
INotificationChannelAdapter adapter;
|
||||
try {
|
||||
adapter = adapterFactory.getAdapter(channel.getChannelType());
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("获取通知渠道适配器失败: {}", e.getMessage());
|
||||
throw new BusinessException(ResponseCode.ERROR);
|
||||
}
|
||||
|
||||
// 5. 转换配置
|
||||
BaseNotificationConfig config = convertConfig(channel);
|
||||
|
||||
// 6. 发送通知
|
||||
try {
|
||||
log.info("发送通知 - 渠道ID: {}, 渠道类型: {}, 内容: {}", channel.getId(), channel.getChannelType(), request.getContent());
|
||||
|
||||
adapter.send(config, request);
|
||||
|
||||
log.info("通知发送成功 - 渠道ID: {}", channel.getId());
|
||||
} catch (IllegalArgumentException e) {
|
||||
// 配置错误(如 Webhook Key 未配置)
|
||||
log.error("通知渠道配置错误 - 渠道ID: {}, 错误: {}", channel.getId(), e.getMessage());
|
||||
throw new BusinessException(ResponseCode.NOTIFICATION_CHANNEL_CONFIG_ERROR, new Object[] {e.getMessage()});
|
||||
} catch (Exception e) {
|
||||
log.error("通知发送失败 - 渠道ID: {}, 错误: {}", channel.getId(), e.getMessage(), e);
|
||||
throw new BusinessException(ResponseCode.NOTIFICATION_SEND_FAILED, new Object[] {e.getMessage()});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map配置转换为具体的配置类型
|
||||
|
||||
Loading…
Reference in New Issue
Block a user