增加构建通知

This commit is contained in:
dengqichen 2025-11-13 12:39:42 +08:00
parent 9d425aa1f7
commit 49c65b6886
8 changed files with 162 additions and 133 deletions

View File

@ -3,10 +3,10 @@ package com.qqchen.deploy.backend.notification.converter;
import com.qqchen.deploy.backend.framework.converter.BaseConverter; import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.framework.utils.JsonUtils; import com.qqchen.deploy.backend.framework.utils.JsonUtils;
import com.qqchen.deploy.backend.notification.dto.NotificationTemplateDTO; import com.qqchen.deploy.backend.notification.dto.NotificationTemplateDTO;
import com.qqchen.deploy.backend.notification.dto.template.BaseTemplateConfigDTO;
import com.qqchen.deploy.backend.notification.dto.template.EmailTemplateConfigDTO;
import com.qqchen.deploy.backend.notification.dto.template.WeworkTemplateConfigDTO;
import com.qqchen.deploy.backend.notification.entity.NotificationTemplate; import com.qqchen.deploy.backend.notification.entity.NotificationTemplate;
import com.qqchen.deploy.backend.notification.entity.config.BaseTemplateConfig;
import com.qqchen.deploy.backend.notification.entity.config.EmailTemplateConfig;
import com.qqchen.deploy.backend.notification.entity.config.WeworkTemplateConfig;
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
@ -23,7 +23,7 @@ import java.util.Map;
public interface NotificationTemplateConverter extends BaseConverter<NotificationTemplate, NotificationTemplateDTO> { public interface NotificationTemplateConverter extends BaseConverter<NotificationTemplate, NotificationTemplateDTO> {
@Override @Override
@Mapping(target = "templateConfig", expression = "java(mapToTemplateConfigDTO(entity.getTemplateConfig(), entity.getChannelType()))") @Mapping(target = "templateConfig", expression = "java(mapToTemplateConfig(entity.getTemplateConfig(), entity.getChannelType()))")
@Mapping(target = "extraData", ignore = true) @Mapping(target = "extraData", ignore = true)
NotificationTemplateDTO toDto(NotificationTemplate entity); NotificationTemplateDTO toDto(NotificationTemplate entity);
@ -43,27 +43,27 @@ public interface NotificationTemplateConverter extends BaseConverter<Notificatio
void updateEntity(@org.mapstruct.MappingTarget NotificationTemplate entity, NotificationTemplateDTO dto); void updateEntity(@org.mapstruct.MappingTarget NotificationTemplate entity, NotificationTemplateDTO dto);
/** /**
* 将Map转换为TemplateConfigDTO * 将Map转换为BaseTemplateConfig根据渠道类型自动识别
*/ */
default BaseTemplateConfigDTO mapToTemplateConfigDTO(Map<String, Object> configMap, NotificationChannelTypeEnum channelType) { default BaseTemplateConfig mapToTemplateConfig(Map<String, Object> configMap, NotificationChannelTypeEnum channelType) {
if (configMap == null || channelType == null) { if (configMap == null || channelType == null) {
return null; return null;
} }
return switch (channelType) { return switch (channelType) {
case WEWORK -> JsonUtils.fromMap(configMap, WeworkTemplateConfigDTO.class); case WEWORK -> JsonUtils.fromMap(configMap, WeworkTemplateConfig.class);
case EMAIL -> JsonUtils.fromMap(configMap, EmailTemplateConfigDTO.class); case EMAIL -> JsonUtils.fromMap(configMap, EmailTemplateConfig.class);
default -> null; default -> null;
}; };
} }
/** /**
* TemplateConfigDTO转换为Map * BaseTemplateConfig转换为Map
*/ */
default Map<String, Object> mapToTemplateConfigMap(BaseTemplateConfigDTO configDTO) { default Map<String, Object> mapToTemplateConfigMap(BaseTemplateConfig config) {
if (configDTO == null) { if (config == null) {
return null; return null;
} }
return JsonUtils.toMap(configDTO); return JsonUtils.toMap(config);
} }
} }

View File

@ -1,7 +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.dto.template.BaseTemplateConfigDTO; import com.qqchen.deploy.backend.notification.entity.config.BaseTemplateConfig;
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;
@ -47,5 +47,5 @@ public class NotificationTemplateDTO extends BaseDTO {
private Boolean enabled = true; private Boolean enabled = true;
@Schema(description = "模板配置(根据渠道类型不同而不同)") @Schema(description = "模板配置(根据渠道类型不同而不同)")
private BaseTemplateConfigDTO templateConfig; private BaseTemplateConfig templateConfig;
} }

View File

@ -1,34 +0,0 @@
package com.qqchen.deploy.backend.notification.dto.template;
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层的BaseTemplateConfig对应
*
* 使用 PROPERTY 方式通过 templateConfig 内部的 channelType 字段
* 进行多态反序列化子类通过 final 字段自动提供该值
*
* @author qqchen
* @since 2025-11-12
*/
@Data
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "channelType"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = WeworkTemplateConfigDTO.class, name = "WEWORK"),
@JsonSubTypes.Type(value = EmailTemplateConfigDTO.class, name = "EMAIL")
})
public abstract class BaseTemplateConfigDTO {
/**
* 获取渠道类型与外层 NotificationTemplateDTO.channelType 保持一致
*/
public abstract NotificationChannelTypeEnum getChannelType();
}

View File

@ -1,42 +0,0 @@
package com.qqchen.deploy.backend.notification.dto.template;
import com.qqchen.deploy.backend.notification.enums.EmailContentTypeEnum;
import com.qqchen.deploy.backend.notification.enums.EmailPriorityEnum;
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 邮件模板配置DTO
*
* @author qqchen
* @since 2025-11-12
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "邮件模板配置")
public class EmailTemplateConfigDTO extends BaseTemplateConfigDTO {
/**
* 渠道类型用于Jackson反序列化与外层保持一致
*/
private final NotificationChannelTypeEnum channelType = NotificationChannelTypeEnum.EMAIL;
/**
* 内容类型
*/
@Schema(description = "内容类型", example = "TEXT", allowableValues = {"TEXT", "HTML"})
private EmailContentTypeEnum contentType = EmailContentTypeEnum.TEXT;
/**
* 邮件优先级
*/
@Schema(description = "邮件优先级", example = "NORMAL", allowableValues = {"LOW", "NORMAL", "HIGH"})
private EmailPriorityEnum priority = EmailPriorityEnum.NORMAL;
@Override
public NotificationChannelTypeEnum getChannelType() {
return NotificationChannelTypeEnum.EMAIL;
}
}

View File

@ -1,35 +0,0 @@
package com.qqchen.deploy.backend.notification.dto.template;
import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
import com.qqchen.deploy.backend.notification.enums.WeworkMessageTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 企业微信模板配置DTO
*
* @author qqchen
* @since 2025-11-12
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "企业微信模板配置")
public class WeworkTemplateConfigDTO extends BaseTemplateConfigDTO {
/**
* 渠道类型用于Jackson反序列化与外层保持一致
*/
private final NotificationChannelTypeEnum channelType = NotificationChannelTypeEnum.WEWORK;
/**
* 消息类型
*/
@Schema(description = "消息类型", example = "TEXT", allowableValues = {"TEXT", "MARKDOWN", "FILE"})
private WeworkMessageTypeEnum messageType = WeworkMessageTypeEnum.TEXT;
@Override
public NotificationChannelTypeEnum getChannelType() {
return NotificationChannelTypeEnum.WEWORK;
}
}

View File

@ -0,0 +1,102 @@
package com.qqchen.deploy.backend.notification.dto.wework;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 企业微信 Webhook 消息请求体
* 对应企微官方API格式https://developer.work.weixin.qq.com/document/path/91770
*
* @author qqchen
* @since 2025-11-13
*/
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class WeworkWebhookRequest {
/**
* 消息类型textmarkdownfile
*/
@JsonProperty("msgtype")
private String msgtype;
/**
* 文本消息内容
*/
@JsonProperty("text")
private TextContent text;
/**
* Markdown消息内容
*/
@JsonProperty("markdown")
private MarkdownContent markdown;
/**
* 文件消息内容
*/
@JsonProperty("file")
private FileContent file;
/**
* 文本消息内容
*/
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class TextContent {
/**
* 文本内容最长不超过2048个字节
*/
@JsonProperty("content")
private String content;
/**
* userid的列表提醒群中的指定成员(@某个成员)@all表示提醒所有人
*/
@JsonProperty("mentioned_list")
private List<String> mentionedList;
/**
* 手机号列表提醒手机号对应的群成员(@某个成员)@all表示提醒所有人
*/
@JsonProperty("mentioned_mobile_list")
private List<String> mentionedMobileList;
}
/**
* Markdown消息内容
*/
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class MarkdownContent {
/**
* Markdown内容最长不超过4096个字节
*/
@JsonProperty("content")
private String content;
}
/**
* 文件消息内容
*/
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class FileContent {
/**
* 文件id通过文件上传接口获取
*/
@JsonProperty("media_id")
private String mediaId;
}
}

View File

@ -6,11 +6,33 @@ import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum;
import lombok.Data; import lombok.Data;
/** /**
* 通知配置基类 * 通知渠道配置基类
* 提取各渠道配置的共用字段
* *
* 支持 Jackson 多态反序列化 * <p><b>DDD 定位Value Object值对象</b></p>
* 利用外层 NotificationChannelDTO channelType 字段进行类型识别 *
* <h3>设计说明</h3>
* <ul>
* <li>作为值对象可跨层使用Domain Layer API Layer</li>
* <li>通过 Jackson 注解支持多态反序列化基于 channelType 字段</li>
* <li> JSON 格式存储在 NotificationChannel.config 字段中</li>
* <li>不同渠道有不同的配置子类EmailNotificationConfigWeworkNotificationConfig</li>
* </ul>
*
* <h3>为何不需要单独的 DTO务实的 DDD</h3>
* <ol>
* <li>字段验证规则相同创建和查询</li>
* <li>无复杂的转换逻辑仅简单字段映射</li>
* <li>相对稳定不频繁变化</li>
* <li>避免重复定义字段减少维护成本</li>
* </ol>
*
* <h3>未来如需引入 DTO 的场景</h3>
* <ul>
* <li>不同场景需要不同的验证规则如创建时必填更新时可选</li>
* <li>需要敏感字段脱敏处理如密码字段返回时隐藏</li>
* <li>需要复杂的字段转换逻辑</li>
* <li>Config 频繁变化影响 API 稳定性</li>
* </ul>
* *
* @author qqchen * @author qqchen
* @since 2025-11-12 * @since 2025-11-12

View File

@ -7,9 +7,25 @@ import lombok.Data;
/** /**
* 模板配置基类 * 模板配置基类
* 用于实体层与DTO层的BaseTemplateConfigDTO对应
* *
* 使用 PROPERTY 方式通过 channelType 字段进行多态反序列化 * <p><b>DDD 定位Value Object值对象</b></p>
*
* <h3>设计说明</h3>
* <ul>
* <li>作为值对象可跨层使用Domain Layer API Layer</li>
* <li>通过 Jackson 注解支持多态反序列化基于 channelType 字段</li>
* <li> JSON 格式存储在 NotificationTemplate.templateConfig 字段中</li>
* <li>不同渠道有不同的配置子类EmailTemplateConfigWeworkTemplateConfig</li>
* </ul>
*
* <h3>为何不需要单独的 DTO务实的 DDD</h3>
* <ol>
* <li>无敏感字段无需脱敏处理</li>
* <li>字段验证规则相同创建和查询</li>
* <li>无复杂的转换逻辑仅简单字段映射</li>
* <li>相对稳定不频繁变化</li>
* <li>避免重复定义字段减少维护成本</li>
* </ol>
* *
* @author qqchen * @author qqchen
* @since 2025-11-12 * @since 2025-11-12