From 49c65b68863ec12466f7d01739a757c779f9295f Mon Sep 17 00:00:00 2001 From: dengqichen Date: Thu, 13 Nov 2025 12:39:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9E=84=E5=BB=BA=E9=80=9A?= =?UTF-8?q?=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationTemplateConverter.java | 24 ++--- .../dto/NotificationTemplateDTO.java | 4 +- .../dto/template/BaseTemplateConfigDTO.java | 34 ------ .../dto/template/EmailTemplateConfigDTO.java | 42 -------- .../dto/template/WeworkTemplateConfigDTO.java | 35 ------ .../dto/wework/WeworkWebhookRequest.java | 102 ++++++++++++++++++ .../entity/config/BaseNotificationConfig.java | 32 +++++- .../entity/config/BaseTemplateConfig.java | 22 +++- 8 files changed, 162 insertions(+), 133 deletions(-) delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/BaseTemplateConfigDTO.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/EmailTemplateConfigDTO.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/WeworkTemplateConfigDTO.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/notification/dto/wework/WeworkWebhookRequest.java diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/converter/NotificationTemplateConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/converter/NotificationTemplateConverter.java index bcf026b5..2b1f31e5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/converter/NotificationTemplateConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/converter/NotificationTemplateConverter.java @@ -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.utils.JsonUtils; 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.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 org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -23,7 +23,7 @@ import java.util.Map; public interface NotificationTemplateConverter extends BaseConverter { @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) NotificationTemplateDTO toDto(NotificationTemplate entity); @@ -43,27 +43,27 @@ public interface NotificationTemplateConverter extends BaseConverter configMap, NotificationChannelTypeEnum channelType) { + default BaseTemplateConfig mapToTemplateConfig(Map configMap, NotificationChannelTypeEnum channelType) { if (configMap == null || channelType == null) { return null; } return switch (channelType) { - case WEWORK -> JsonUtils.fromMap(configMap, WeworkTemplateConfigDTO.class); - case EMAIL -> JsonUtils.fromMap(configMap, EmailTemplateConfigDTO.class); + case WEWORK -> JsonUtils.fromMap(configMap, WeworkTemplateConfig.class); + case EMAIL -> JsonUtils.fromMap(configMap, EmailTemplateConfig.class); default -> null; }; } /** - * 将TemplateConfigDTO转换为Map + * 将BaseTemplateConfig转换为Map */ - default Map mapToTemplateConfigMap(BaseTemplateConfigDTO configDTO) { - if (configDTO == null) { + default Map mapToTemplateConfigMap(BaseTemplateConfig config) { + if (config == null) { return null; } - return JsonUtils.toMap(configDTO); + return JsonUtils.toMap(config); } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationTemplateDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationTemplateDTO.java index 3cf0c4f2..b3796e64 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationTemplateDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/NotificationTemplateDTO.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.notification.dto; 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 io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -47,5 +47,5 @@ public class NotificationTemplateDTO extends BaseDTO { private Boolean enabled = true; @Schema(description = "模板配置(根据渠道类型不同而不同)") - private BaseTemplateConfigDTO templateConfig; + private BaseTemplateConfig templateConfig; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/BaseTemplateConfigDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/BaseTemplateConfigDTO.java deleted file mode 100644 index 8cf20166..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/BaseTemplateConfigDTO.java +++ /dev/null @@ -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(); -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/EmailTemplateConfigDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/EmailTemplateConfigDTO.java deleted file mode 100644 index 1fe441e6..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/EmailTemplateConfigDTO.java +++ /dev/null @@ -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; - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/WeworkTemplateConfigDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/WeworkTemplateConfigDTO.java deleted file mode 100644 index 0cf9fc55..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/template/WeworkTemplateConfigDTO.java +++ /dev/null @@ -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; - } -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/wework/WeworkWebhookRequest.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/wework/WeworkWebhookRequest.java new file mode 100644 index 00000000..dfca85fc --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/dto/wework/WeworkWebhookRequest.java @@ -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 { + + /** + * 消息类型:text、markdown、file + */ + @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 mentionedList; + + /** + * 手机号列表,提醒手机号对应的群成员(@某个成员),@all表示提醒所有人 + */ + @JsonProperty("mentioned_mobile_list") + private List 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; + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/config/BaseNotificationConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/config/BaseNotificationConfig.java index 515692bc..ca6bb94c 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/config/BaseNotificationConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/config/BaseNotificationConfig.java @@ -6,12 +6,34 @@ import com.qqchen.deploy.backend.notification.enums.NotificationChannelTypeEnum; import lombok.Data; /** - * 通知配置基类 - * 提取各渠道配置的共用字段 + * 通知渠道配置基类 + * + *

DDD 定位:Value Object(值对象)

+ * + *

设计说明:

+ *
    + *
  • 作为值对象,可跨层使用(Domain Layer ↔ API Layer)
  • + *
  • 通过 Jackson 注解支持多态反序列化(基于 channelType 字段)
  • + *
  • 以 JSON 格式存储在 NotificationChannel.config 字段中
  • + *
  • 不同渠道有不同的配置子类(EmailNotificationConfig、WeworkNotificationConfig)
  • + *
+ * + *

为何不需要单独的 DTO(务实的 DDD):

+ *
    + *
  1. 字段验证规则相同(创建和查询)
  2. + *
  3. 无复杂的转换逻辑,仅简单字段映射
  4. + *
  5. 相对稳定,不频繁变化
  6. + *
  7. 避免重复定义字段,减少维护成本
  8. + *
+ * + *

未来如需引入 DTO 的场景:

+ *
    + *
  • 不同场景需要不同的验证规则(如创建时必填,更新时可选)
  • + *
  • 需要敏感字段脱敏处理(如密码字段返回时隐藏)
  • + *
  • 需要复杂的字段转换逻辑
  • + *
  • Config 频繁变化,影响 API 稳定性
  • + *
* - * 支持 Jackson 多态反序列化: - * 利用外层 NotificationChannelDTO 的 channelType 字段进行类型识别 - * * @author qqchen * @since 2025-11-12 */ diff --git a/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/config/BaseTemplateConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/config/BaseTemplateConfig.java index fce5a8f9..d3057142 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/config/BaseTemplateConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/notification/entity/config/BaseTemplateConfig.java @@ -7,10 +7,26 @@ import lombok.Data; /** * 模板配置基类 - * 用于实体层,与DTO层的BaseTemplateConfigDTO对应 * - * 使用 PROPERTY 方式,通过 channelType 字段进行多态反序列化 - * + *

DDD 定位:Value Object(值对象)

+ * + *

设计说明:

+ *
    + *
  • 作为值对象,可跨层使用(Domain Layer ↔ API Layer)
  • + *
  • 通过 Jackson 注解支持多态反序列化(基于 channelType 字段)
  • + *
  • 以 JSON 格式存储在 NotificationTemplate.templateConfig 字段中
  • + *
  • 不同渠道有不同的配置子类(EmailTemplateConfig、WeworkTemplateConfig)
  • + *
+ * + *

为何不需要单独的 DTO(务实的 DDD):

+ *
    + *
  1. 无敏感字段,无需脱敏处理
  2. + *
  3. 字段验证规则相同(创建和查询)
  4. + *
  5. 无复杂的转换逻辑,仅简单字段映射
  6. + *
  7. 相对稳定,不频繁变化
  8. + *
  9. 避免重复定义字段,减少维护成本
  10. + *
+ * * @author qqchen * @since 2025-11-12 */