增加构建通知

This commit is contained in:
dengqichen 2025-11-13 12:14:01 +08:00
parent 5205c6aaa1
commit 9d425aa1f7
10 changed files with 127 additions and 377 deletions

View File

@ -1,9 +1,9 @@
package com.qqchen.deploy.backend.notification.adapter.impl; 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.framework.utils.JsonUtils;
import com.qqchen.deploy.backend.notification.adapter.INotificationChannelAdapter; import com.qqchen.deploy.backend.notification.adapter.INotificationChannelAdapter;
import com.qqchen.deploy.backend.notification.dto.WeworkSendNotificationRequest; 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.entity.config.WeworkNotificationConfig;
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum; 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.core.io.FileSystemResource;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; 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 static final String BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook";
private final RestTemplate restTemplate = new RestTemplate(); private final RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();
@Override @Override
public void send(WeworkNotificationConfig config, WeworkSendNotificationRequest request) throws Exception { public void send(WeworkNotificationConfig config, WeworkSendNotificationRequest request) throws Exception {
@ -59,52 +53,17 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
/** /**
* 发送文本或Markdown消息 * 发送文本或Markdown消息
*/ */
private void sendTextOrMarkdownMessage(WeworkNotificationConfig config, WeworkSendNotificationRequest request) throws Exception { private void sendTextOrMarkdownMessage(WeworkNotificationConfig config, WeworkSendNotificationRequest request) {
// 构建发送消息 URL
String webhookUrl = BASE_URL + "/send?key=" + config.getKey(); String webhookUrl = BASE_URL + "/send?key=" + config.getKey();
// 构建企业微信消息体 // 转换为企微API格式
Map<String, Object> messageBody = new HashMap<>(); var webhookRequest = request.toWebhookRequest();
String msgType = request.getMessageType().getApiType();
messageBody.put("msgtype", msgType);
if (request.getMessageType() == WeworkMessageTypeEnum.MARKDOWN) { log.info("发送企业微信通知 - URL: {}, 类型: {}, 消息: {}",
// Markdown 格式消息 webhookUrl, request.getMessageType().getApiType(), request.getContent());
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());
// 添加@人列表
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);
}
// 发送请求 // RestTemplate 自动序列化对象为JSON
HttpHeaders headers = new HttpHeaders(); String response = restTemplate.postForObject(webhookUrl, webhookRequest, String.class);
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();
log.info("企业微信通知发送成功 - 响应: {}", response); 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()) { if (request.getFilePath() == null || request.getFilePath().isEmpty()) {
throw new IllegalArgumentException("文件路径不能为空"); throw new IllegalArgumentException("文件路径不能为空");
} }
@ -125,24 +84,14 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
throw new RuntimeException("文件上传失败,未获取到 media_id"); throw new RuntimeException("文件上传失败,未获取到 media_id");
} }
// 2. 发送文件消息 // 2. 转换为企微API格式带media_id
String sendUrl = BASE_URL + "/send?key=" + config.getKey(); String sendUrl = BASE_URL + "/send?key=" + config.getKey();
Map<String, Object> messageBody = buildFileMessage(mediaId); var webhookRequest = request.toWebhookRequest(mediaId);
HttpHeaders headers = new HttpHeaders(); log.info("发送企业微信文件消息 - 文件: {}, mediaId: {}", request.getFileName(), mediaId);
headers.setContentType(MediaType.APPLICATION_JSON);
String jsonBody = objectMapper.writeValueAsString(messageBody); // RestTemplate 自动序列化对象为JSON
HttpEntity<String> entity = new HttpEntity<>(jsonBody, headers); String response = restTemplate.postForObject(sendUrl, webhookRequest, String.class);
log.info("发送企业微信文件消息 - 文件: {}", request.getFileName());
String response = restTemplate.exchange(
sendUrl,
HttpMethod.POST,
entity,
String.class
).getBody();
log.info("企业微信文件消息发送成功 - 响应: {}", response); log.info("企业微信文件消息发送成功 - 响应: {}", response);
} }
@ -168,24 +117,17 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
throw new RuntimeException("文件上传失败,未获取到 media_id"); 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(); 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); log.info("发送企业微信文件 - fileName: {}, mediaId: {}", fileName, mediaId);
String response = restTemplate.exchange( String response = restTemplate.postForObject(sendUrl, webhookRequest, String.class);
sendUrl,
HttpMethod.POST,
entity,
String.class
).getBody();
log.info("企业微信文件发送成功 - 响应: {}", response); log.info("企业微信文件发送成功 - 响应: {}", response);
@ -213,33 +155,22 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter<WeworkN
// 2. 构建测试消息 URL // 2. 构建测试消息 URL
String webhookUrl = BASE_URL + "/send?key=" + config.getKey(); String webhookUrl = BASE_URL + "/send?key=" + config.getKey();
// 2. 构建测试消息 // 3. 构建测试消息
Map<String, Object> messageBody = new HashMap<>(); WeworkWebhookRequest testRequest = WeworkWebhookRequest.builder()
messageBody.put("msgtype", "text"); .msgtype("text")
.text(WeworkWebhookRequest.TextContent.builder()
Map<String, Object> textContent = new HashMap<>(); .content("【测试消息】\n这是一条来自 Deploy Ease Platform 的测试消息,您的企业微信通知渠道配置正常!")
textContent.put("content", "【测试消息】\n这是一条来自 Deploy Ease Platform 的测试消息,您的企业微信通知渠道配置正常!"); .build())
messageBody.put("text", textContent); .build();
// 3. 发送测试请求
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); log.info("测试企业微信连接 - URL: {}", webhookUrl);
String response = restTemplate.exchange( // 4. 发送测试请求RestTemplate 自动序列化对象为JSON
webhookUrl, String response = restTemplate.postForObject(webhookUrl, testRequest, String.class);
HttpMethod.POST,
entity,
String.class
).getBody();
log.info("企业微信连接测试成功 - 响应: {}", response); log.info("企业微信连接测试成功 - 响应: {}", response);
// 4. 检查响应 // 5. 检查响应
if (response != null && response.contains("\"errcode\":0")) { if (response != null && response.contains("\"errcode\":0")) {
log.info("企业微信通知渠道测试成功"); log.info("企业微信通知渠道测试成功");
} else { } 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;
}
} }

View File

@ -9,7 +9,6 @@ import com.qqchen.deploy.backend.notification.entity.config.EmailNotificationCon
import com.qqchen.deploy.backend.notification.entity.config.WeworkNotificationConfig; import com.qqchen.deploy.backend.notification.entity.config.WeworkNotificationConfig;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Map; import java.util.Map;
@ -22,24 +21,17 @@ import java.util.Map;
@Mapper(config = BaseConverter.class) @Mapper(config = BaseConverter.class)
public abstract class NotificationChannelConverter implements BaseConverter<NotificationChannel, NotificationChannelDTO> { public abstract class NotificationChannelConverter implements BaseConverter<NotificationChannel, NotificationChannelDTO> {
protected NotificationConfigConverter notificationConfigConverter;
@Autowired
public void setNotificationConfigConverter(NotificationConfigConverter notificationConfigConverter) {
this.notificationConfigConverter = notificationConfigConverter;
}
/** /**
* Entity -> DTO * Entity -> DTO
* config: Map -> BaseNotificationConfigDTO * config: Map -> BaseNotificationConfig
*/ */
@Override @Override
@Mapping(target = "config", expression = "java(mapToConfigDTO(entity))") @Mapping(target = "config", expression = "java(mapToConfig(entity))")
public abstract NotificationChannelDTO toDto(NotificationChannel entity); public abstract NotificationChannelDTO toDto(NotificationChannel entity);
/** /**
* DTO -> Entity * DTO -> Entity
* config: BaseNotificationConfigDTO -> Map * config: BaseNotificationConfig -> Map
*/ */
@Override @Override
@Mapping(target = "config", expression = "java(mapToConfigMap(dto))") @Mapping(target = "config", expression = "java(mapToConfigMap(dto))")
@ -47,7 +39,7 @@ public abstract class NotificationChannelConverter implements BaseConverter<Noti
/** /**
* 更新Entity手动实现 * 更新Entity手动实现
* config: BaseNotificationConfigDTO -> Map * config: BaseNotificationConfig -> Map
*/ */
@Override @Override
public void updateEntity(NotificationChannel entity, NotificationChannelDTO dto) { 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) { if (entity == null || entity.getConfig() == null) {
return null; return null;
} }
// 1. Map -> BaseNotificationConfig Entity // Map -> BaseNotificationConfig根据渠道类型自动识别
BaseNotificationConfig configEntity = switch (entity.getChannelType()) { return switch (entity.getChannelType()) {
case WEWORK -> JsonUtils.fromMap(entity.getConfig(), WeworkNotificationConfig.class); case WEWORK -> JsonUtils.fromMap(entity.getConfig(), WeworkNotificationConfig.class);
case EMAIL -> JsonUtils.fromMap(entity.getConfig(), EmailNotificationConfig.class); case EMAIL -> JsonUtils.fromMap(entity.getConfig(), EmailNotificationConfig.class);
default -> throw new IllegalArgumentException("不支持的渠道类型: " + entity.getChannelType()); 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) { protected Map<String, Object> mapToConfigMap(NotificationChannelDTO dto) {
if (dto == null || dto.getConfig() == null) { if (dto == null || dto.getConfig() == null) {
return null; return null;
} }
// 1. BaseNotificationConfigDTO -> BaseNotificationConfig Entity // BaseNotificationConfig -> Map
BaseNotificationConfig configEntity = notificationConfigConverter.toEntity(dto.getConfig()); return JsonUtils.toMap(dto.getConfig());
// 2. BaseNotificationConfig Entity -> Map
return JsonUtils.toMap(configEntity);
} }
} }

View File

@ -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());
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -1,6 +1,7 @@
package com.qqchen.deploy.backend.notification.dto; package com.qqchen.deploy.backend.notification.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO; 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 com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@ -29,7 +30,7 @@ public class NotificationChannelDTO extends BaseDTO {
@Schema(description = "渠道配置(根据渠道类型不同而不同)") @Schema(description = "渠道配置(根据渠道类型不同而不同)")
@NotNull(message = "渠道配置不能为空") @NotNull(message = "渠道配置不能为空")
private BaseNotificationConfigDTO config; private BaseNotificationConfig config;
@Schema(description = "是否启用", example = "true") @Schema(description = "是否启用", example = "true")
private Boolean enabled; private Boolean enabled;

View File

@ -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;
}
}

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.notification.dto; 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.NotificationChannelTypeEnum;
import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum; import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum;
import lombok.Data; import lombok.Data;
@ -56,4 +57,71 @@ public class WeworkSendNotificationRequest extends BaseSendNotificationRequest {
public NotificationChannelTypeEnum getChannelType() { public NotificationChannelTypeEnum getChannelType() {
return NotificationChannelTypeEnum.WEWORK; 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();
}
} }

View File

@ -1,16 +1,30 @@
package com.qqchen.deploy.backend.notification.entity.config; 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 com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
import lombok.Data; import lombok.Data;
/** /**
* 通知配置基类 * 通知配置基类
* 提取各渠道配置的共用字段 * 提取各渠道配置的共用字段
*
* 支持 Jackson 多态反序列化
* 利用外层 NotificationChannelDTO channelType 字段进行类型识别
* *
* @author qqchen * @author qqchen
* @since 2025-11-12 * @since 2025-11-12
*/ */
@Data @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 { public abstract class BaseNotificationConfig {
/** /**

View File

@ -110,53 +110,6 @@ public class NotificationChannelServiceImpl extends BaseServiceImpl<Notification
log.info("禁用通知渠道: id={}, name={}", id, channel.getName()); 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配置转换为具体的配置类型 * 将Map配置转换为具体的配置类型