Răsfoiți Sursa

邮件模块 添加邮件发送api

wangjingyi 3 ani în urmă
părinte
comite
d7305739d3
44 a modificat fișierele cu 1085 adăugiri și 59 ștergeri
  1. 1 0
      yudao-framework/pom.xml
  2. 65 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/pom.xml
  3. 21 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/config/YudaoMailAutoConfiguration.java
  4. 23 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/MailClient.java
  5. 5 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/MailClientFactory.java
  6. 17 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/MailCodeMapping.java
  7. 68 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/MailCommonResult.java
  8. 48 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/dto/MailReceiveRespDTO.java
  9. 18 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/dto/MailSendRespDTO.java
  10. 31 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/dto/MailTemplateRespDTO.java
  11. 14 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/impl/AbstractMailClient.java
  12. 39 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/impl/MailClientFactoryImpl.java
  13. 29 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/impl/hutool/HutoolMailClient.java
  14. 20 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/impl/hutool/HutoolMailCodeMapping.java
  15. 32 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/enums/MailChannelEnum.java
  16. 47 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/enums/MailFrameworkErrorCodeConstants.java
  17. 21 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/enums/MailTemplateAuditStatusEnum.java
  18. 52 0
      yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/property/MailChannelProperties.java
  19. 4 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApi.java
  20. 38 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendReqDTO.java
  21. 1 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
  22. 24 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/mail/MailSendStatusEnum.java
  23. 5 0
      yudao-module-system/yudao-module-system-impl/pom.xml
  24. 14 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java
  25. 14 8
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountBaseVO.java
  26. 3 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogExcelVO.java
  27. 1 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java
  28. 8 6
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/send/MailReqVO.java
  29. 10 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateBaseVO.java
  30. 1 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplatePageReqVO.java
  31. 14 8
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java
  32. 5 4
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailTemplateDO.java
  33. 3 4
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailAccountMapper.java
  34. 13 2
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailTemplateMapper.java
  35. 7 1
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailSendConsumer.java
  36. 16 10
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java
  37. 65 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
  38. 16 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java
  39. 41 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java
  40. 17 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateService.java
  41. 14 6
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailAccountServiceImpl.java
  42. 42 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailLogServiceImpl.java
  43. 107 0
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailSendServiceImpl.java
  44. 51 4
      yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailTemplateServiceImpl.java

+ 1 - 0
yudao-framework/pom.xml

@@ -30,6 +30,7 @@
         <module>yudao-spring-boot-starter-biz-operatelog</module>
         <module>yudao-spring-boot-starter-biz-dict</module>
         <module>yudao-spring-boot-starter-biz-sms</module>
+        <module>yudao-spring-boot-starter-biz-mail</module>
         <module>yudao-spring-boot-starter-activiti</module>
         <module>yudao-spring-boot-starter-biz-pay</module>
         <module>yudao-spring-boot-starter-biz-weixin</module>

+ 65 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/pom.xml

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>yudao-framework</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>yudao-spring-boot-starter-biz-mail</artifactId>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-common</artifactId>
+        </dependency>
+
+        <!-- Spring 核心 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 监控相关 -->
+        <dependency>
+            <groupId>io.opentracing</groupId>
+            <artifactId>opentracing-util</artifactId> <!-- aliyun 短信需要,进行链路追踪 -->
+        </dependency>
+
+        <!-- Test 测试相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <optional>true</optional> <!-- 设置为可选,因为使用到 @VisibleForTesting 用于单元测试 -->
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>jakarta.validation</groupId>
+            <artifactId>jakarta.validation-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 21 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/config/YudaoMailAutoConfiguration.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.framework.mail.config;
+
+import cn.iocoder.yudao.framework.mail.core.client.MailClientFactory;
+import cn.iocoder.yudao.framework.mail.core.client.impl.MailClientFactoryImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 邮箱配置类
+ *
+ * @author 芋道源码
+ */
+@Configuration
+public class YudaoMailAutoConfiguration {
+
+    @Bean
+    public MailClientFactory mailClientFactory() {
+        return new MailClientFactoryImpl();
+    }
+
+}

+ 23 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/MailClient.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.framework.mail.core.client;
+
+import java.util.List;
+
+/**
+ * 邮件客户端,用于对接各邮箱平台的 SDK,实现邮件发送等功能
+ *
+ * @author wangjingyi
+ * @date 2021/4/19 19:21
+ */
+public interface MailClient {
+
+    /**
+     * 发送邮件
+     *
+     * @param from 邮箱账号
+     * @param content 内容
+     * @param title 标题
+     * @param tos 收件人
+     * @return 邮件发送结果
+     */
+    String sendMail(String from, String content, String title, List<String> tos);
+}

+ 5 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/MailClientFactory.java

@@ -0,0 +1,5 @@
+package cn.iocoder.yudao.framework.mail.core.client;
+
+public interface MailClientFactory {
+    MailClient getMailClient();
+}

+ 17 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/MailCodeMapping.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.framework.mail.core.client;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.mail.core.enums.MailFrameworkErrorCodeConstants;
+
+import java.util.function.Function;
+
+/**
+ * 将 API 的错误码,转换为通用的错误码
+ *
+ * @see MailCommonResult
+ * @see MailFrameworkErrorCodeConstants
+ *
+ * @author 芋道源码
+ */
+public interface MailCodeMapping extends Function<String, ErrorCode> {
+}

+ 68 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/MailCommonResult.java

@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.framework.mail.core.client;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import cn.iocoder.yudao.framework.mail.core.enums.MailFrameworkErrorCodeConstants;
+
+/**
+ * 短信的 CommonResult 拓展类
+ *
+ * 考虑到不同的平台,返回的 code 和 msg 是不同的,所以统一额外返回 {@link #apiCode} 和 {@link #apiMsg} 字段
+ *
+ * 另外,一些短信平台(例如说阿里云、腾讯云)会返回一个请求编号,用于排查请求失败的问题,我们设置到 {@link #apiRequestId} 字段
+ *
+ * @author 芋道源码
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailCommonResult<T> extends CommonResult<T> {
+
+    /**
+     * API 返回错误码
+     *
+     * 由于第三方的错误码可能是字符串,所以使用 String 类型
+     */
+    private String apiCode;
+    /**
+     * API 返回提示
+     */
+    private String apiMsg;
+
+    /**
+     * API 请求编号
+     */
+    private String apiRequestId;
+
+    private MailCommonResult() {
+    }
+
+    public static <T> MailCommonResult<T> build(String apiCode, String apiMsg, String apiRequestId,
+                                                T data, MailCodeMapping codeMapping) {
+        Assert.notNull(codeMapping, "参数 codeMapping 不能为空");
+        MailCommonResult<T> result = new MailCommonResult<T>().setApiCode(apiCode).setApiMsg(apiMsg).setApiRequestId(apiRequestId);
+        result.setData(data);
+        // 翻译错误码
+        if (codeMapping != null) {
+            ErrorCode errorCode = codeMapping.apply(apiCode);
+            if (errorCode == null) {
+                errorCode = MailFrameworkErrorCodeConstants.MAIL_UNKNOWN;
+            }
+            result.setCode(errorCode.getCode()).setMsg(errorCode.getMsg());
+        }
+        return result;
+    }
+
+    public static <T> MailCommonResult<T> error(Throwable ex) {
+        MailCommonResult<T> result = new MailCommonResult<>();
+        result.setCode(MailFrameworkErrorCodeConstants.EXCEPTION.getCode());
+        result.setMsg(ExceptionUtil.getRootCauseMessage(ex));
+        return result;
+    }
+
+}

+ 48 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/dto/MailReceiveRespDTO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.framework.mail.core.client.dto;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 消息接收 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class MailReceiveRespDTO {
+
+    /**
+     * 是否接收成功
+     */
+    private Boolean success;
+    /**
+     * API 接收结果的编码
+     */
+    private String errorCode;
+    /**
+     * API 接收结果的说明
+     */
+    private String errorMsg;
+
+    /**
+     * 手机号
+     */
+    private String mobile;
+    /**
+     * 用户接收时间
+     */
+    private Date receiveTime;
+
+    /**
+     * 短信 API 发送返回的序号
+     */
+    private String serialNo;
+    /**
+     * 短信日志编号
+     *
+     * 对应 SysSmsLogDO 的编号
+     */
+    private Long logId;
+
+}

+ 18 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/dto/MailSendRespDTO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.framework.mail.core.client.dto;
+
+import lombok.Data;
+
+/**
+ * 短信发送 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class MailSendRespDTO {
+
+    /**
+     * 短信 API 发送返回的序号
+     */
+    private String serialNo;
+
+}

+ 31 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/dto/MailTemplateRespDTO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.framework.mail.core.client.dto;
+
+import lombok.Data;
+
+/**
+ * 短信模板 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class MailTemplateRespDTO {
+
+    /**
+     * 模板编号
+     */
+    private String id;
+    /**
+     * 短信内容
+     */
+    private String content;
+    /**
+     * 审核状态
+     *
+     */
+    private Integer auditStatus;
+    /**
+     * 审核未通过的理由
+     */
+    private String auditReason;
+
+}

+ 14 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/impl/AbstractMailClient.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.framework.mail.core.client.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import cn.iocoder.yudao.framework.mail.core.client.MailClient;
+
+/**
+ * 短信客户端的抽象类,提供模板方法,减少子类的冗余代码
+ *
+ * @author zzf
+ * @date 2021/2/1 9:28
+ */
+@Slf4j
+public abstract class AbstractMailClient implements MailClient {
+}

+ 39 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/impl/MailClientFactoryImpl.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.framework.mail.core.client.impl;
+
+import cn.iocoder.yudao.framework.mail.core.client.MailClient;
+import cn.iocoder.yudao.framework.mail.core.client.impl.hutool.HutoolMailClient;
+import lombok.extern.slf4j.Slf4j;
+import cn.iocoder.yudao.framework.mail.core.client.MailClientFactory;
+import cn.iocoder.yudao.framework.mail.core.enums.MailChannelEnum;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+@Validated
+@Slf4j
+public class MailClientFactoryImpl implements MailClientFactory {
+
+    private final ConcurrentMap<String, AbstractMailClient> channelCodeClients = new ConcurrentHashMap<>();
+
+    public MailClientFactoryImpl (){
+        Arrays.stream(MailChannelEnum.values()).forEach(mailChannelEnum -> {
+            AbstractMailClient abstractMailClient = createMailClient(mailChannelEnum);
+            channelCodeClients.put(mailChannelEnum.getCode() , abstractMailClient);
+        });
+    }
+
+    private AbstractMailClient createMailClient(MailChannelEnum mailChannelEnum) {
+        switch (mailChannelEnum){
+            case HUTOOL: return new HutoolMailClient();
+        }
+        // 创建失败,错误日志 + 抛出异常
+        log.error("[createMailClient][配置({}) 找不到合适的客户端实现]" , mailChannelEnum);
+        throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", mailChannelEnum));
+    }
+
+    @Override
+    public MailClient getMailClient() {
+        return channelCodeClients.get("HUTOOL");
+    }
+}

+ 29 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/impl/hutool/HutoolMailClient.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.framework.mail.core.client.impl.hutool;
+
+import cn.hutool.extra.mail.MailUtil;
+import cn.iocoder.yudao.framework.mail.core.client.impl.AbstractMailClient;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+
+/**
+ * 邮件客户端实现
+ *
+ * @author wangjingyi
+ * @date 2021/4/25 14:25
+ */
+@Slf4j
+public class HutoolMailClient extends AbstractMailClient {
+
+    @Override
+    public String sendMail(String from, String content, String title, List<String> tos) {
+        try{
+            return MailUtil.send(from , title , content , false , null);
+        }catch (Exception e){
+            log.error(e.getMessage());
+        }
+        return "";
+    }
+
+}

+ 20 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/client/impl/hutool/HutoolMailCodeMapping.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.framework.mail.core.client.impl.hutool;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.mail.core.client.MailCodeMapping;
+
+/**
+ * 阿里云的 SmsCodeMapping 实现类
+ *
+ * 参见 https://help.aliyun.com/document_detail/101346.htm 文档
+ *
+ * @author 芋道源码
+ */
+public class HutoolMailCodeMapping implements MailCodeMapping {
+
+    @Override
+    public ErrorCode apply(String apiCode) {
+        return null;
+    }
+
+}

+ 32 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/enums/MailChannelEnum.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.framework.mail.core.enums;
+
+import cn.hutool.core.util.ArrayUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 短信渠道枚举
+ *
+ * @author zzf
+ * @date 2021/1/25 10:56
+ */
+@Getter
+@AllArgsConstructor
+public enum MailChannelEnum {
+    HUTOOL("HUTOOL" , "HUTOOL"),
+    ;
+
+    /**
+     * 编码
+     */
+    private final String code;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    public static MailChannelEnum getByCode(String code) {
+        return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values());
+    }
+
+}

+ 47 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/enums/MailFrameworkErrorCodeConstants.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.framework.mail.core.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * 短信框架的错误码枚举
+ *
+ * 短信框架,使用 2-001-000-000 段
+ *
+ * @author 芋道源码
+ */
+public interface MailFrameworkErrorCodeConstants {
+
+    ErrorCode MAIL_UNKNOWN = new ErrorCode(2001000000, "未知错误,需要解析");
+
+    // ========== 权限 / 限流等相关 2001000100 ==========
+
+    ErrorCode SMS_PERMISSION_DENY = new ErrorCode(2001000100, "没有发送短信的权限");
+    // 云片:可以配置 IP 白名单,只有在白名单中才可以发送短信
+    ErrorCode SMS_IP_DENY = new ErrorCode(2001000100, "IP 不允许发送短信");
+
+    // 阿里云:将短信发送频率限制在正常的业务限流范围内。默认短信验证码:使用同一签名,对同一个手机号验证码,支持 1 条 / 分钟,5 条 / 小时,累计 10 条 / 天。
+    ErrorCode SMS_SEND_BUSINESS_LIMIT_CONTROL = new ErrorCode(2001000102, "指定手机的发送限流");
+    // 阿里云:已经达到您在控制台设置的短信日发送量限额值。在国内消息设置 > 安全设置,修改发送总量阈值。
+    ErrorCode SMS_SEND_DAY_LIMIT_CONTROL = new ErrorCode(2001000103, "每天的发送限流");
+
+    ErrorCode SMS_SEND_CONTENT_INVALID = new ErrorCode(2001000104, "短信内容有敏感词");
+
+    // ========== 模板相关 2001000200 ==========
+    ErrorCode SMS_TEMPLATE_INVALID = new ErrorCode(2001000200, "短信模板不合法"); // 包括短信模板不存在
+    ErrorCode SMS_TEMPLATE_PARAM_ERROR = new ErrorCode(2001000201, "模板参数不正确");
+
+    // ========== 签名相关 2001000300 ==========
+    ErrorCode SMS_SIGN_INVALID = new ErrorCode(2001000300, "短信签名不可用");
+
+    // ========== 账户相关 2001000400 ==========
+    ErrorCode SMS_ACCOUNT_MONEY_NOT_ENOUGH = new ErrorCode(2001000400, "账户余额不足");
+    ErrorCode SMS_ACCOUNT_INVALID = new ErrorCode(2001000401, "apiKey 不存在");
+
+    // ========== 其它相关 2001000900 开头 ==========
+    ErrorCode SMS_API_PARAM_ERROR = new ErrorCode(2001000900, "请求参数缺失");
+    ErrorCode SMS_MOBILE_INVALID = new ErrorCode(2001000901, "手机格式不正确");
+    ErrorCode SMS_MOBILE_BLACK = new ErrorCode(2001000902, "手机号在黑名单中");
+
+    ErrorCode EXCEPTION = new ErrorCode(2001000999, "调用异常");
+
+}

+ 21 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/enums/MailTemplateAuditStatusEnum.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.framework.mail.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 短信模板的审核状态枚举
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Getter
+public enum MailTemplateAuditStatusEnum {
+
+    CHECKING(1),
+    SUCCESS(2),
+    FAIL(3);
+
+    private final Integer status;
+
+}

+ 52 - 0
yudao-framework/yudao-spring-boot-starter-biz-mail/src/main/java/cn/iocoder/yudao/framework/mail/core/property/MailChannelProperties.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.framework.mail.core.property;
+
+import lombok.Data;
+import cn.iocoder.yudao.framework.mail.core.enums.MailChannelEnum;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 短信渠道配置类
+ *
+ * @author zzf
+ * @date 2021/1/25 17:01
+ */
+@Data
+@Validated
+public class MailChannelProperties {
+
+    /**
+     * 渠道编号
+     */
+    @NotNull(message = "短信渠道 ID 不能为空")
+    private Long id;
+    /**
+     * 短信签名
+     */
+    @NotEmpty(message = "短信签名不能为空")
+    private String signature;
+    /**
+     * 渠道编码
+     *
+     * 枚举 {@link MailChannelEnum}
+     */
+    @NotEmpty(message = "渠道编码不能为空")
+    private String code;
+    /**
+     * 短信 API 的账号
+     */
+    @NotEmpty(message = "短信 API 的账号不能为空")
+    private String apiKey;
+    /**
+     * 短信 API 的秘钥
+     */
+    @NotEmpty(message = "短信 API 的秘钥不能为空")
+    private String apiSecret;
+    /**
+     * 短信发送回调 URL
+     */
+    private String callbackUrl;
+
+}

+ 4 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApi.java

@@ -0,0 +1,4 @@
+package cn.iocoder.yudao.module.system.api.mail;
+
+public interface MailSendApi {
+}

+ 38 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendReqDTO.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.system.api.mail.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@ApiModel("管理后台 - 邮件发送 Req VO")
+@Data
+public class MailSendReqDTO { // TODO @wangjingqi:1), 不用空格;2)应该只要传递 templateCode、参数就好,title、from、content、附件应该都是参数里的
+
+    @ApiModelProperty(value = "邮箱",required = true,example = "yudaoyuanma@123.com")
+    @NotNull(message = "邮箱账号不能为空")
+    @Email(message = "邮箱账号格式错误")
+    private String from;
+
+    @ApiModelProperty(value = "标题",example = "标题")
+    private String title;
+
+    @ApiModelProperty(value = "内容",example = "内容")
+    private String content;
+
+    @ApiModelProperty(value = "邮箱模版id",example = "1024")
+    @NotNull(message = "邮箱模版id不能为空")
+    private Integer templateId;
+
+    @ApiModelProperty(value = "收件人",required = true,example = "yudaoyuanma@123.com")
+    @NotNull(message = "收件人不能为空")
+    private List<String> tos;
+
+    @ApiModelProperty(value = "附件",example = "附件编码")
+    private List<String> fileIds;
+
+
+}

+ 1 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java

@@ -126,5 +126,6 @@ public interface ErrorCodeConstants {
     // ========== 邮箱模版 1002020000 ==========
     ErrorCode MAIL_TEMPLATE_NOT_EXISTS = new ErrorCode(1002020000 , "邮箱模版不存在");
     ErrorCode MAIL_TEMPLATE_EXISTS = new ErrorCode(1002020001, "邮箱模版存在");
+    ErrorCode MAIL_RELATE_TEMPLATE_EXISTS = new ErrorCode(1002020002, "存在关联邮箱模版");
 
 }

+ 24 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/mail/MailSendStatusEnum.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.system.enums.mail;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 邮件的发送状态枚举
+ *
+ * @author wangjingyi
+ * @date 2022/4/10 13:39
+ */
+@Getter
+@AllArgsConstructor
+public enum MailSendStatusEnum {
+
+    INIT(0), // 初始化
+    SUCCESS(10), // 发送成功
+    FAILURE(20), // 发送失败
+    IGNORE(30), // 忽略,即不发送
+    ;
+
+    private final int status;
+
+}

+ 5 - 0
yudao-module-system/yudao-module-system-impl/pom.xml

@@ -47,6 +47,10 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-mail</artifactId>
+        </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
@@ -102,6 +106,7 @@
             <artifactId>yudao-spring-boot-starter-excel</artifactId>
         </dependency>
 
+
     </dependencies>
 
 </project>

+ 14 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.system.api.mail;
+
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * 邮件发送 API 接口
+ *
+ * @author wangjingyi
+ */
+@Service
+@Validated
+public class MailSendApiImpl implements MailSendApi{
+}

+ 14 - 8
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountBaseVO.java

@@ -4,28 +4,34 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import javax.validation.constraints.Email;
 import javax.validation.constraints.NotNull;
 
 @ApiModel("管理后台 - 邮箱账号基类 Base VO")
 @Data
-public class MailAccountBaseVO { // TODO @wangjingqi:1), 不用空格;2)from、username、password、host、sslEnable 都要参数校验,非空;3)username 要 Email 格式;port Integer;
+public class MailAccountBaseVO {
 
-    @ApiModelProperty(value = "邮箱" , required = true , example = "yudaoyuanma@123.com")
+    @ApiModelProperty(value = "邮箱",required = true,example = "yudaoyuanma@123.com")
+    @NotNull(message = "邮箱必填")
     private String from;
 
-    @ApiModelProperty(value = "用户名" , required = true , example = "yudao")
+    @ApiModelProperty(value = "用户名",required = true,example = "yudao")
     @NotNull(message = "用户名必填")
+    @Email(message = "必须是Email格式")
     private String username;
 
-    @ApiModelProperty(value = "密码" , required = true , example = "123456")
+    @ApiModelProperty(value = "密码",required = true,example = "123456")
+    @NotNull(message = "密码必填")
     private String password;
 
-    @ApiModelProperty(value = "网站" , required = true , example = "www.iocoder.cn")
+    @ApiModelProperty(value = "网站",required = true,example = "www.iocoder.cn")
+    @NotNull(message = "网站必填")
     private String host;
 
-    @ApiModelProperty(value = "端口" , required = true , example = "80")
-    private String port;
+    @ApiModelProperty(value = "端口",required = true,example = "80")
+    private Integer port;
 
-    @ApiModelProperty(value = "是否开启ssl" , required = true , example = "2")
+    @ApiModelProperty(value = "是否开启ssl",required = true,example = "2")
+    @NotNull(message = "是否开启ssl必填")
     private Boolean sslEnable;
 }

+ 3 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogExcelVO.java

@@ -6,6 +6,7 @@ import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
 
 import java.sql.Timestamp;
+import java.util.Date;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
@@ -30,10 +31,10 @@ public class MailLogExcelVO {
 
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     @ExcelProperty(value = "发送时间" )
-    private Timestamp sendTime;
+    private Date sendTime;
 
     @ExcelProperty(value = "发送状态")
-    private Boolean sendStatus;
+    private Integer sendStatus;
 
     @ExcelProperty(value = "发送结果")
     private String sendResult;

+ 1 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java

@@ -36,7 +36,7 @@ public class MailLogRespVO {
     private Timestamp sendTime;
 
     @ApiModelProperty(value = "发送状态" , required = false , example = "1")
-    private Boolean sendStatus;
+    private Integer sendStatus;
 
     @ApiModelProperty(value = "发送结果" , required = false , example = "yudaoyuanma@123.com")
     private String sendResult;

+ 8 - 6
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/send/MailReqVO.java

@@ -4,6 +4,7 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import javax.validation.constraints.Email;
 import javax.validation.constraints.NotNull;
 import java.util.List;
 
@@ -11,25 +12,26 @@ import java.util.List;
 @Data
 public class MailReqVO { // TODO @wangjingqi:1), 不用空格;2)应该只要传递 templateCode、参数就好,title、from、content、附件应该都是参数里的
 
-    @ApiModelProperty(value = "邮箱" , required = true , example = "yudaoyuanma@123.com")
+    @ApiModelProperty(value = "邮箱",required = true,example = "yudaoyuanma@123.com")
     @NotNull(message = "邮箱账号不能为空")
+    @Email(message = "邮箱账号格式错误")
     private String from;
 
-    @ApiModelProperty(value = "标题"  , example = "标题")
+    @ApiModelProperty(value = "标题",example = "标题")
     private String title;
 
-    @ApiModelProperty(value = "内容"  , example = "内容")
+    @ApiModelProperty(value = "内容",example = "内容")
     private String content;
 
-    @ApiModelProperty(value = "邮箱模版id" , example = "1024")
+    @ApiModelProperty(value = "邮箱模版id",example = "1024")
     @NotNull(message = "邮箱模版id不能为空")
     private Integer templateId;
 
-    @ApiModelProperty(value = "收件人" , required = true , example = "yudaoyuanma@123.com")
+    @ApiModelProperty(value = "收件人",required = true,example = "yudaoyuanma@123.com")
     @NotNull(message = "收件人不能为空")
     private List<String> tos;
 
-    @ApiModelProperty(value = "附件"  , example = "附件编码")
+    @ApiModelProperty(value = "附件",example = "附件编码")
     private List<String> fileIds;
 
 

+ 10 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateBaseVO.java

@@ -4,15 +4,18 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import javax.validation.constraints.Email;
 import javax.validation.constraints.NotNull;
 
 @ApiModel("管理后台 - 邮箱模版基类 Base VO")
 @Data
-public class MailTemplateBaseVO {  // TODO @wangjingqi:1)swagger 注解不完善;2)id、name、code、username、title、content、status 是不是要参数校验呀
+public class MailTemplateBaseVO {
     @ApiModelProperty("主键")
+    @NotNull(message = "主键不能为空")
     private Long id;
 
     @ApiModelProperty("名称")
+    @NotNull(message = "名称不能为空")
     private String name;
 
     @ApiModelProperty("标识")
@@ -20,16 +23,21 @@ public class MailTemplateBaseVO {  // TODO @wangjingqi:1)swagger 注解不
     private String code;
 
     @ApiModelProperty("发件人")
+    @NotNull(message = "发件人不能为空")
+    @Email(message = "发件人格式有误")
     private String username;
 
     @ApiModelProperty("标题")
+    @NotNull(message = "标题不能为空")
     private String title;
 
     @ApiModelProperty("内容")
+    @NotNull(message = "内容不能为空")
     private String content;
 
     @ApiModelProperty("状态")
-    private String status;
+    @NotNull(message = "状态不能为空")
+    private Integer status;
 
     @ApiModelProperty("备注")
     private String remark;

+ 1 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplatePageReqVO.java

@@ -31,7 +31,7 @@ public class MailTemplatePageReqVO extends PageParam {
     private String content;
 
     @ApiModelProperty("状态")
-    private String status;
+    private Integer status;
 
     @ApiModelProperty("备注")
     private String remark;

+ 14 - 8
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java

@@ -1,12 +1,12 @@
 package cn.iocoder.yudao.module.system.dal.dataobject.mail;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum;
 import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
+import lombok.*;
 
 import java.io.Serializable;
-import java.sql.Timestamp;
+import java.util.Date;
 
 /**
  * 邮箱日志
@@ -18,6 +18,10 @@ import java.sql.Timestamp;
 @TableName(value = "system_mail_log", autoResultMap = true)
 @Data
 @EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
 public class MailLogDO extends BaseDO implements Serializable {
 
     /**
@@ -29,7 +33,7 @@ public class MailLogDO extends BaseDO implements Serializable {
     /**
      * 邮箱账号编号
      */
-    private String accountCode;
+    private Long accountId;
 
     // TODO @wangjingyi:如果是冗余字段,记得 @ 下;
     /**
@@ -40,7 +44,7 @@ public class MailLogDO extends BaseDO implements Serializable {
     /**
      * 模版主键
      */
-    private String templateId;
+    private Long templateId;
 
     /**
      * 模版编号
@@ -65,16 +69,18 @@ public class MailLogDO extends BaseDO implements Serializable {
     /**
      * 发送时间
      */
-    private Timestamp sendTime;
+    private Date sendTime;
 
     /**
      * 发送状态
+     *
+     * 枚举 {@link MailSendStatusEnum}
      */
-    // TODO @wangjingyi:四个状态,参考短信模块
-    private Boolean sendStatus;
+    private Integer sendStatus;
 
     /**
      * 发送结果
+     *
      */
     private String sendResult;
 

+ 5 - 4
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailTemplateDO.java

@@ -6,6 +6,8 @@ import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
+import java.util.Date;
+
 /**
  * 邮箱模版
  *
@@ -29,11 +31,10 @@ public class MailTemplateDO extends BaseDO {
      * 模版编号
      */
     private String code;
-    // TODO @wangjingyi:应该使用 accountId 呀
     /**
-     * 用户名
+     * 邮箱账号主键
      */
-    private String username;
+    private Long accountId;
     /**
      * 标题
      */
@@ -47,7 +48,7 @@ public class MailTemplateDO extends BaseDO {
      *
      * 枚举 {@link CommonStatusEnum}
      */
-    private String status; // TODO @wangjingyi:Integer
+    private Integer status;
     /**
      * 备注
      */

+ 3 - 4
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailAccountMapper.java

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
 import org.apache.ibatis.annotations.Mapper;
 
 @Mapper
@@ -21,9 +22,7 @@ public interface MailAccountMapper extends BaseMapperX<MailAccountDO> {
     }
 
     default MailAccountDO selectByUserName(String userName){
-        // TODO @wangjingyi:selectOne 有封装的方法;然后,编码一定要学会使用泛型呀。例如说 QueryWrapperX<MailAccountDO> queryWrapperX = new QueryWrapperX<>();
-        QueryWrapperX<MailAccountDO> queryWrapperX = new QueryWrapperX<>();
-        queryWrapperX.eqIfPresent("username", userName);
-        return this.selectOne(queryWrapperX);
+        return selectOne(new QueryWrapperX<MailAccountDO>()
+                .eqIfPresent("username" , userName));
     };
 }

+ 13 - 2
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailTemplateMapper.java

@@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.Date;
 
 
 @Mapper
@@ -23,7 +26,15 @@ public interface MailTemplateMapper extends BaseMapperX<MailTemplateDO> {
     }
 
     default MailTemplateDO selectOneByCode(String code){
-        // TODO @wangjingyi:优先使用 lambada 查询
-        return selectOne("code" , code);
+        return selectOne(new QueryWrapperX<MailTemplateDO>()
+                .eqIfPresent("code" , code));
+    };
+
+    @Select("SELECT id FROM system_mail_template WHERE update_time > #{maxUpdateTime} LIMIT 1")
+    Long selectByMaxUpdateTime(Date maxUpdateTime);
+
+    default MailTemplateDO selectOneByAccountId(Long accountId){
+        return selectOne(new QueryWrapperX<MailTemplateDO>()
+                .eqIfPresent("account_id" , accountId));
     };
 }

+ 7 - 1
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailSendConsumer.java

@@ -2,17 +2,23 @@ package cn.iocoder.yudao.module.system.mq.consumer.mail;
 
 import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
 import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
+import cn.iocoder.yudao.module.system.service.mail.MailSendService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-// TODO 芋艿:这个暂未实现
+import javax.annotation.Resource;
+
+
 @Component
 @Slf4j
 public class MailSendConsumer extends AbstractStreamMessageListener<MailSendMessage> {
 
+    @Resource
+    private MailSendService mailSendService;
     @Override
     public void onMessage(MailSendMessage message) {
         log.info("[onMessage][消息内容({})]", message);
+        mailSendService.doSendMail(message);
     }
 
 }

+ 16 - 10
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java

@@ -5,6 +5,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import javax.validation.constraints.NotNull;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -16,29 +17,34 @@ import java.util.Map;
 @EqualsAndHashCode(callSuper = true)
 public class MailSendMessage extends AbstractStreamMessage {
 
+    /**
+     * 日志id
+     */
+    @NotNull(message = "邮箱日志id不能为空")
+    private Long logId;
     /**
      * 邮箱地址
      */
     @NotNull(message = "邮箱地址不能为空")
-    private String address;
+    private String from;
     /**
-     * 短信模板编号
+     * 邮箱模板编号
      */
-    @NotNull(message = "短信模板编号不能为空")
+    @NotNull(message = "邮箱模板编号不能为空")
     private String templateCode;
     /**
-     * 短信模板参数
+     * 收件人
      */
-    private Map<String, Object> templateParams;
-
+    @NotNull(message = "收件人不能为空")
+    private List<String> tos;
     /**
-     * 用户编号,允许空
+     * 标题
      */
-    private Integer userId;
+    private String title;
     /**
-     * 用户类型,允许空
+     * 内容
      */
-    private Integer userType;
+    private String content;
 
     @Override
     public String getStreamKey() {

+ 65 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java

@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.system.mq.producer.mail;
+
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
+import cn.iocoder.yudao.module.system.mq.message.sms.SmsChannelRefreshMessage;
+import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
+import cn.iocoder.yudao.module.system.mq.message.sms.SmsTemplateRefreshMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * Mail 邮件相关消息的 Producer
+ *
+ * @author wangjingyi
+ * @date 2021/4/19 13:33
+ */
+@Slf4j
+@Component
+public class MailProducer {
+
+    @Resource
+    private RedisMQTemplate redisMQTemplate;
+
+    /**
+     * 发送 {@link SmsChannelRefreshMessage} 消息
+     */
+    public void sendMailChannelRefreshMessage() {
+        SmsChannelRefreshMessage message = new SmsChannelRefreshMessage();
+        redisMQTemplate.send(message);
+    }
+
+    /**
+     * 发送 {@link SmsTemplateRefreshMessage} 消息
+     */
+    public void sendMailTemplateRefreshMessage() {
+        SmsTemplateRefreshMessage message = new SmsTemplateRefreshMessage();
+        redisMQTemplate.send(message);
+    }
+
+    /**
+     * 发送 {@link MailSendMessage} 消息
+     *
+     * @param mailAccountDO 邮箱账号信息
+     * @param mailTemplateDO 邮箱模版信息
+     * @param content 内容
+     * @param tos 收件人
+     * @param title 标题
+     */
+    public void sendMailSendMessage(MailAccountDO mailAccountDO, MailTemplateDO mailTemplateDO, String content, List<String> tos, String title , Long sendLogId) {
+        MailSendMessage message = new MailSendMessage();
+        message.setContent(content);
+        message.setFrom(mailAccountDO.getFrom());
+        message.setTemplateCode(mailTemplateDO.getCode());
+        message.setTitle(title);
+        message.setTos(tos);
+        message.setLogId(sendLogId);
+        redisMQTemplate.send(message);
+    }
+}

+ 16 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java

@@ -4,7 +4,9 @@ package cn.iocoder.yudao.module.system.service.mail;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
 
 import java.util.List;
 
@@ -28,4 +30,18 @@ public interface MailLogService {
      * @return
      */
     List<MailLogDO> getMailLogList(MailLogExportReqVO exportReqVO);
+
+    /**
+     * 创建邮箱日志
+     * @param mailAccountDO 邮箱账号信息
+     * @param mailTemplateDO 模版信息
+     * @param from 邮箱
+     * @param content 内容
+     * @param tos 收件人
+     * @param title 标题
+     * @param isSend 是否发送成功
+     */
+    Long createMailLog(MailAccountDO mailAccountDO, MailTemplateDO mailTemplateDO, String from, String content, List<String> tos, String title, Boolean isSend);
+
+    Long updateSmsSendResult(Long logId, String result);
 }

+ 41 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.send.MailReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *  邮箱模版服务类
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+public interface MailSendService {
+
+    /**
+     * 发送邮件
+     *
+     * @param templateCode 邮件模版编码
+     * @param from 邮箱
+     * @param content 内容
+     * @param tos 收件人
+     * @param title 标题
+     */
+    void sendMail(String templateCode, String from , String content , List<String> tos , String title);
+
+    /**
+     * 执行真正的邮件发送
+     * 注意,该方法仅仅提供给 MQ Consumer 使用
+     *
+     * @param message 邮件
+     */
+    void doSendMail(MailSendMessage message);
+}

+ 17 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateService.java

@@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
 
 import javax.validation.Valid;
 import java.util.List;
+import java.util.Map;
 
 /**
  *  邮箱模版服务类
@@ -18,6 +19,7 @@ import java.util.List;
  */
 public interface MailTemplateService {
 
+    void initLocalCache();
     /**
      * 邮箱模版创建
      *
@@ -62,6 +64,13 @@ public interface MailTemplateService {
      * @return 模版数组
      */
     List<MailTemplateDO> getMailTemplateList();
+    /**
+     *从缓存中获取邮箱模版
+     *
+     * @param code 模板编码
+     * @return 邮箱模板
+     */
+    MailTemplateDO getMailTemplateByCodeFromCache(String code);
 
     /**
      * 发送邮件
@@ -69,4 +78,12 @@ public interface MailTemplateService {
      * @param mailReqVO 邮件发送信息
      */
     void sendMail(MailReqVO mailReqVO);
+
+    /**
+     * 邮件模版内容合成
+     * @param content 邮箱模版
+     * @param params 合成参数
+     * @return
+     */
+    String formateMailTemplateContent(String content, Map<String, String> params);
 }

+ 14 - 6
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailAccountServiceImpl.java

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccou
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountUpdateReqVO;
 import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
 import cn.iocoder.yudao.module.system.service.mail.MailAccountService;
@@ -16,8 +17,7 @@ import javax.annotation.Resource;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_EXISTS;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 
 
 /**
@@ -47,8 +47,8 @@ public class MailAccountServiceImpl implements MailAccountService {
 
     @Override
     public void update(MailAccountUpdateReqVO updateReqVO) {
-        // username 要校验唯一 // TODO @wangjingyi:更新的就是自己,username 这样写,会重复呀。
-        this.validateMailAccountOnlyByUserName(updateReqVO.getUsername());
+        // username 要校验唯一
+        this.validateMailAccountExists(updateReqVO.getId());
         MailAccountDO mailAccountDO = MailAccountConvert.INSTANCE.convert(updateReqVO);
         // 校验是否存在
         this.validateMailAccountExists(mailAccountDO.getId());
@@ -57,9 +57,10 @@ public class MailAccountServiceImpl implements MailAccountService {
 
     @Override
     public void delete(Long id) {
-        // TODO @wangjingyi:删除时,要判断是否有使用的模板
-        // 校验是否存在
+        // 校验是否存在账号
         this.validateMailAccountExists(id);
+        // 校验是否存在关联模版
+        this.validateMailTemplateByAccountId(id);
         mailAccountMapper.deleteById(id);
     }
 
@@ -90,4 +91,11 @@ public class MailAccountServiceImpl implements MailAccountService {
             throw exception(MAIL_ACCOUNT_EXISTS);
         }
     }
+
+    private void validateMailTemplateByAccountId(Long accountId){
+        MailTemplateDO mailTemplateDO =  mailTemplateMapper.selectOneByAccountId(accountId);
+        if (mailTemplateDO != null) {
+            throw exception(MAIL_RELATE_TEMPLATE_EXISTS);
+        }
+    }
 }

+ 42 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailLogServiceImpl.java

@@ -4,14 +4,21 @@ package cn.iocoder.yudao.module.system.service.mail.impl;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailLogMapper;
+import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum;
+import cn.iocoder.yudao.module.system.enums.sms.SmsSendStatusEnum;
 import cn.iocoder.yudao.module.system.service.mail.MailLogService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.sql.Timestamp;
+import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 
 /**
  *  邮箱日志实现类
@@ -35,4 +42,39 @@ public class MailLogServiceImpl implements MailLogService {
     public List<MailLogDO> getMailLogList(MailLogExportReqVO exportReqVO) {
         return mailLogMapper.selectList(exportReqVO);
     }
+
+    @Override
+    public Long createMailLog(MailAccountDO mailAccountDO , MailTemplateDO mailTemplateDO , String from, String content, List<String> tos, String title, Boolean isSend) {
+        MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder();
+        logDOBuilder.from(mailAccountDO.getFrom());
+        logDOBuilder.accountId(mailAccountDO.getId());
+        logDOBuilder.content(content);
+        logDOBuilder.title(title);
+        logDOBuilder.templateCode(mailTemplateDO.getCode());
+        logDOBuilder.templateId(mailTemplateDO.getId());
+        logDOBuilder.to(tos.toString());
+        logDOBuilder.sendTime(new Date());
+        logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus()
+                : MailSendStatusEnum.IGNORE.getStatus());
+
+        MailLogDO mailLogDO = logDOBuilder.build();
+        mailLogMapper.insert(mailLogDO);
+        return mailLogDO.getId();
+    }
+
+    @Override
+    public Long updateSmsSendResult(Long logId, String result) {
+        MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder();
+        logDOBuilder.id(logId);
+        logDOBuilder.sendResult(result);
+        MailLogDO mailLogDO = logDOBuilder.build();
+        mailLogMapper.updateById(mailLogDO);
+        return logId;
+    }
+
+    public Long create(){
+        MailLogDO mailLogDO = new MailLogDO();
+        mailLogMapper.insert(mailLogDO);
+        return mailLogDO.getId();
+    }
 }

+ 107 - 0
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailSendServiceImpl.java

@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.module.system.service.mail.impl;
+
+
+import cn.hutool.extra.mail.MailAccount;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
+import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
+import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
+import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
+import cn.iocoder.yudao.module.system.service.mail.MailLogService;
+import cn.iocoder.yudao.module.system.service.mail.MailSendService;
+import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
+import lombok.extern.slf4j.Slf4j;
+import cn.iocoder.yudao.framework.mail.core.client.MailClientFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+import cn.iocoder.yudao.framework.mail.core.client.MailClient;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_EXISTS;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS;
+
+/**
+ * 邮箱模版 服务实现类
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+@Service
+@Validated
+@Slf4j
+public class MailSendServiceImpl implements MailSendService {
+
+    @Resource
+    private MailTemplateMapper mailTemplateMapper;
+    @Resource
+    private MailAccountMapper mailAccountMapper;
+    @Resource
+    private MailTemplateService mailTemplateService;
+    @Resource
+    private MailLogService mailLogService;
+    @Resource
+    private MailClientFactory mailClientFactory;
+    @Resource
+    private MailProducer mailProducer;
+
+
+    @Override
+    public void sendMail(String templateCode, String from , String content , List<String> tos , String title) {
+        // TODO @@wangjingyi:发送的时候,参考下短信;
+        MailTemplateDO mailTemplateDO =  this.checkMailTemplateValid(templateCode);
+        // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
+        Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(mailTemplateDO.getStatus());
+        //查询账号信息
+        MailAccountDO mailAccountDO = mailAccountMapper.selectOne(
+                "from", from
+        );
+        Map<String , String> params = MailAccountConvert.INSTANCE.convertToMap(mailAccountDO , content);
+        content = mailTemplateService.formateMailTemplateContent(mailTemplateDO.getContent(), params);
+        Long sendLogId = mailLogService.createMailLog(mailAccountDO , mailTemplateDO , from , content , tos , title , isSend);
+
+        // 后续功能 TODO :附件查询
+        //List<String> fileIds = mailSendVO.getFileIds();
+
+        //装载账号信息
+        MailAccount account  = MailAccountConvert.INSTANCE.convertAccount(mailAccountDO);
+
+        // 发送 MQ 消息,异步执行发送短信
+        if (isSend) {
+            mailProducer.sendMailSendMessage(mailAccountDO , mailTemplateDO ,content , tos , title , sendLogId);
+        }
+    }
+
+    @Override
+    public void doSendMail(MailSendMessage message) {
+        MailClient mailClient = mailClientFactory.getMailClient();
+        String result = mailClient.sendMail(message.getFrom() , message.getContent() , message.getTitle() , message.getTos());
+        mailLogService.updateSmsSendResult(message.getLogId() , result);
+    }
+
+    private MailTemplateDO checkMailTemplateValid(String templateCode) {
+        MailTemplateDO mailTemplateDO = mailTemplateService.getMailTemplateByCodeFromCache(templateCode);
+        if (mailTemplateDO == null){
+            throw exception(MAIL_TEMPLATE_NOT_EXISTS);
+        }
+        return mailTemplateDO;
+    }
+
+    private void validateMailTemplateExists(Long id) {
+        if (mailTemplateMapper.selectById(id) == null) {
+            throw exception(MAIL_TEMPLATE_NOT_EXISTS);
+        }
+    }
+
+    private void validateMailTemplateOnlyByCode(String code){
+        if (mailTemplateMapper.selectOneByCode(code) != null) {
+            throw exception(MAIL_TEMPLATE_EXISTS);
+        }
+    }
+}

+ 51 - 4
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailTemplateServiceImpl.java

@@ -1,10 +1,12 @@
 package cn.iocoder.yudao.module.system.service.mail.impl;
 
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.mail.MailAccount;
 import cn.hutool.extra.mail.MailUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.send.MailReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
@@ -13,14 +15,18 @@ import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
 import cn.iocoder.yudao.module.system.convert.mail.MailTemplateConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
 import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import javax.validation.Valid;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
@@ -36,6 +42,7 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPL
  */
 @Service
 @Validated
+@Slf4j
 public class MailTemplateServiceImpl implements MailTemplateService {
 
     @Resource
@@ -43,6 +50,38 @@ public class MailTemplateServiceImpl implements MailTemplateService {
     @Resource
     private MailAccountMapper mailAccountMapper;
 
+    private volatile List<MailTemplateDO> mailTemplateDOList;
+
+    /**
+     * 邮件模板缓存
+     * key:邮箱模板编码 {@link MailTemplateDO#getCode()}
+     *
+     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
+     */
+    private volatile Map<String, MailTemplateDO> mailTemplateCache;
+
+    private volatile Date maxUpdateTime;
+
+    @Override
+    @PostConstruct
+    public void initLocalCache() {
+        if(maxUpdateTime == null){
+            mailTemplateDOList = mailTemplateMapper.selectList();
+        }else{
+            if(mailTemplateMapper.selectByMaxUpdateTime(maxUpdateTime)<=0){
+                return;
+            }
+        }
+        if (CollUtil.isEmpty(mailTemplateDOList)) {
+            return;
+        }
+
+        // 写入缓存
+        mailTemplateCache = CollectionUtils.convertMap(mailTemplateDOList, MailTemplateDO::getCode);
+        maxUpdateTime = CollectionUtils.getMaxValue(mailTemplateDOList, MailTemplateDO::getUpdateTime);
+        log.info("[initLocalCache][初始化 mailTemplate 数量为 {}]", mailTemplateDOList.size());
+    }
+
     @Override
     public Long create(MailTemplateCreateReqVO createReqVO) {
         // code 要校验唯一
@@ -54,11 +93,9 @@ public class MailTemplateServiceImpl implements MailTemplateService {
 
     @Override
     public void update(@Valid MailTemplateUpdateReqVO updateReqVO) {
-        // code 要校验唯一
-        this.validateMailTemplateOnlyByCode(updateReqVO.getCode()); // TODO @wangjingyi:code 这样写,修改自己会有问题
-        MailTemplateDO mailTemplateDO = MailTemplateConvert.INSTANCE.convert(updateReqVO);
         // 校验是否存在
-        this.validateMailTemplateExists(mailTemplateDO.getId());
+        this.validateMailTemplateExists(updateReqVO.getId());
+        MailTemplateDO mailTemplateDO = MailTemplateConvert.INSTANCE.convert(updateReqVO);
         mailTemplateMapper.updateById(mailTemplateDO);
     }
 
@@ -80,6 +117,11 @@ public class MailTemplateServiceImpl implements MailTemplateService {
     @Override
     public List<MailTemplateDO> getMailTemplateList() {return mailTemplateMapper.selectList();}
 
+    @Override
+    public MailTemplateDO getMailTemplateByCodeFromCache(String code) {
+        return mailTemplateCache.get(code);
+    }
+
     @Override
     public void sendMail(MailReqVO mailReqVO) {
         // TODO @@wangjingyi:发送的时候,参考下短信;
@@ -102,6 +144,11 @@ public class MailTemplateServiceImpl implements MailTemplateService {
         MailUtil.send(account , mailReqVO.getTos() , mailReqVO.getTitle() , content , false);
     }
 
+    @Override
+    public String formateMailTemplateContent(String content, Map<String, String> params) {
+        return StrUtil.format(content, params);
+    }
+
     private void validateMailTemplateExists(Long id) {
         if (mailTemplateMapper.selectById(id) == null) {
             throw exception(MAIL_TEMPLATE_NOT_EXISTS);