你有沒有覺得郵件傳送人固定配置在yml檔案中是不妥當的呢?SpringBoot 動態設定郵件傳送人

寧在春發表於2021-11-27

明月當天,不知道你有沒有思念的人

前言

之前其實已經寫過SpringBoot非同步傳送郵件,但是今天在一個小專案中要用到傳送郵件時,我突然覺得郵件傳送人只有一個,並且固定寫在yml檔案中,就是非常的不妥當,就想著怎麼整成一個動態的。

在寫之前已經翻過很多部落格了,該踩的坑都踩的差不多了,我是實現之後寫的文章,有問題大家可以一起交流。

小聲bb(對於CSDN我真的逐漸變得麻木了,簡稱CV大法現場,雖然我本人也是CSDN的一名小小博主,也是資深使用者,對於文章的這塊很多時候真的沒法說,除了能說加油也沒有了吧)。

於是就有了下面這篇文章啦....


一、需求分析

預設大家都已經會 SpringBoot 整合 郵件傳送啦哈,不行的,點一下上文的連結啦。

我先說說我想要達到什麼樣的效果:

  1. 郵件傳送人可以是多個,yml檔案中是兜底配置(即資料庫中沒有一個可用時,使用yml檔案中配置的郵件傳送人)
  2. 專案啟動後,我也可以臨時增加郵件傳送人,或者禁用掉某個郵件傳送人(操作完也無需重啟專案即可生效)
  3. 傳送郵件內容為html;另外非同步傳送郵件(可有可無,大家都會)

思路其實蠻簡單的,就只要做到每次我們新新增或者修改郵件傳送人配置的時候,對JavaSendMailImpl這個類重新初始化即可。這個地方沒啥可講的,就是不讓框架給我們自動配置,我們手動來即可。


二、詳細步驟

2.1、編碼

1)yml配置檔案

spring:  
  mail:
    host: smtp.163.com
    username: nxxxxxx@163.com
    password: IXXXXXXXXXN(開啟允許第三方登入後的授權碼)
    default-encoding: utf-8
    protocol: smtps
    properties:
      mail:
        smtp:
          port: 465
          auth: true
          starttls:
            enable: true
            required: true

注意:關於郵件的協議protocol:smtps的配置,我最開始也是配置的smtp,我當時報的錯誤是一個no provider for smtp錯誤,我之前也寫過一直用的是這個smtp協議,但是報了這個錯誤,我就去搜尋,然後找到有篇部落格說,

SMTPS協議

SMTPS (SMTP-over-SSL)是SMTP協議基於SSL安全協議之上的一種變種協議,它繼承了SSL安全協議的非對稱加密的高度安全可靠性,可防止郵件洩露。SMTPSSMTP協議一樣,也是用來傳送郵件的,只是更安全些,防止郵件被黑客擷取洩密,還可實現郵件傳送者抗抵賴功能。防止傳送者傳送之後刪除已發郵件,拒不承認傳送過這樣一份郵件。埠465和587便是基於SMTPS協議開放的。

465埠(SMTPS)︰它是SMTPS協議服務所使用的其中一個埠,它在郵件的傳輸過程中是加密傳輸(SSL/TLS)的,相比於SMTP協議攻擊者無法獲得郵件內容,郵件在一開始就被保護了起來。

所以實際上我們使用的配置應該是stmps


另外建個properties資源類 與 配置檔案一一對應

/**
 * @author crush
 */
@Data
@Component
@ConfigurationProperties(prefix = "spring.mail")
public class MailProperties {
        /**  * 使用者名稱 */
        private String username;
        /** * 授權碼 */
        private String password;
        /** * host */
        private String host;
        /** * 埠 */
        private Integer port;
        /*** 協議 */
        private String protocol;
        /** * 預設編碼*/
        private String defaultEncoding;
}

2.2、建表

根據yml檔案,我們大致知道了要建立張什麼樣的資料表了哈。

image-20211127003820993

這些大家都可以自定義哈,根據自己需求來建哈。

根據資料表建一個pojo類。

/**
 * @Author: crush
 * @Date: 2021-11-26 18:28
 * version 1.0
 */
@Data
@Accessors(chain = true)
@TableName("tb_email")
public class MailPO {

    private String emailHost;
    private String emailUsername;
    private String emailPassword;
    private Integer emailPort=465;
    /** * 協議 */
    private String protocol="smtps";

    /** * 預設編碼 */
    private String defaultEncoding="utf-8";
    /**
     * 使用狀態,1:正在使用,2:禁用,3:停用
     * TODO 後期應該更改為 列舉類來進行實現
     */
    private Integer state=1;
    /** * 建立時間 */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /*** 修改時間 */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime  updateTime;
}

如果不是用mybatis-plus 可以把建立時間和修改時間去掉@TableField(fill = FieldFill.INSERT)是Mybatis-plus中的註解。另外我主鍵是設定了自增,所以就空了。至於返回的類我用的vo包下的。

2.3、mapper、service層

@Repository
public interface MailMapper extends BaseMapper<MailPO> {
}

service

/**
 * @Author: crush
 * @Date: 2021-11-26 15:55
 * version 1.0
 */
public interface MailService {

    void send(MailDTO mailDTO);

    boolean addMailPerson(MailPO mailPO);
}

impl

import cn.hutool.core.util.IdUtil;
/**
 * @author crush
 * 郵箱傳送實現類
 */
@Service
public class MailServiceImpl implements MailService {

    @Autowired
    MailSenderConfig senderConfig;

    @Autowired
    MailProperties mailProperties;

    @Autowired
    MailMapper mailMapper;

    // 這裡之前配置了一個執行緒池,上文的連結中有,就不說了哈
    // @Async("taskExecutor")
    @Override
    public void send(MailDTO mailDTO) {
        String context = "<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\" />\n" +
                "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n" +
                "    <title>xxxx郵件</title>\n" +
                "    <style>\n" +
                "        body {\n" +
                "            margin: 0;\n" +
                "            padding: 0;\n" +
                "        }\n" +
                "        \n" +
                "        .email {\n" +
                "            position: relative;\n" +
                "            width: 100%;\n" +
                "            /* background-color: rgba(0, 0, 0, 1); */\n" +
                "        }\n" +
                "        \n" +
                "        .main {\n" +
                "            left: 0;\n" +
                "            right: 0;\n" +
                "            margin: auto;\n" +
                "            width: 80%;\n" +
                "            max-width: 800px;\n" +
                "            box-sizing: content-box;\n" +
                "        }\n" +
                "        \n" +
                "        .main .title {\n" +
                "            /* color: white; */\n" +
                "            display: inline-flex;\n" +
                "            align-items: center;\n" +
                "        }\n" +
                "        \n" +
                "        .main .title span {\n" +
                "            margin: 0 10px;\n" +
                "        }\n" +
                "        \n" +
                "        .main table {\n" +
                "            width: 100%;\n" +
                "        }\n" +
                "        \n" +
                "        .main table tbody td {\n" +
                "            /* background-color: white; */\n" +
                "            padding: 20px;\n" +
                "            text-align: left;\n" +
                "            border-bottom: 1px solid rgb(161, 161, 161);\n" +
                "        }\n" +
                "        \n" +
                "        tfoot td p {\n" +
                "            color: rgb(161, 161, 161);\n" +
                "            font-size: 13px;\n" +
                "        }\n" +
                "        \n" +
                "        a {\n" +
                "            color: rgb(161, 161, 161);\n" +
                "            text-decoration: none;\n" +
                "        }\n" +
                "        \n" +
                "        a:hover {\n" +
                "            border-bottom: 1px solid rgb(161, 161, 161);\n" +
                "        }\n" +
                "    </style>\n" +
                "</head>\n" +
                "\n" +
                "<body>\n" +
                "    <div class=\"email\">\n" +
                "        <div class=\"main\">\n" +
                "            <table>\n" +
                "                <thead>\n" +
                "                    <tr>\n" +
                "                        <td>\n" +
                "                            <h1 class=\"title\">\n" +
                "                                <img width=\"60\" src=\"xxxxx\" alt=\"\" />\n" +
                "                                <span>" + mailDTO.getTitle() + "</span>\n" +
                "                            </h1>\n" +
                "                        </td>\n" +
                "                    </tr>\n" +
                "                </thead>\n" +
                "                <tbody>\n" +
                "                    <tr>\n" +
                "                        <td>\n" +
                "                            " + mailDTO.getContent() + "\n" +
                "                        </td>\n" +
                "                    </tr>\n" +
                "                </tbody>\n" +
                "                <tfoot>\n" +
                "                    <tr>\n" +
                "                        <td>\n" +
                "                            <p>郵件由系統自動傳送,請勿直接回復。</p>\n" +
                "                            <p>官方網站:\n" +
                "                                <a href=\"https://blog.csdn.net/weixin_45821811?spm=1000.2115.3001.5343\">寧在春部落格</a>\n" +
                "                            </p>\n" +
                "                        </td>\n" +
                "                    </tr>\n" +
                "                </tfoot>\n" +
                "            </table>\n" +
                "        </div>\n" +
                "    </div>\n" +
                "</body>\n" +
                "\n" +
                "</html>";

        JavaMailSenderImpl mailSender = senderConfig.getSender();
        //建立一個SimpleMailMessage物件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //需要建立一個MimeMessageHelper物件,相關引數和簡單郵件類似
        try {
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
                //發件人
                helper.setFrom(mailSender.getUsername());
                //收件人 這個收件人可以是陣列的,只是我這隻需要單個 就沒多做了。
                helper.setTo(mailDTO.getMail());
                helper.setSubject("驗證碼");
                //將郵件內容設定為html格式
                // 傳送
                helper.setText( context, true);
                mailSender.send(mimeMessage);
            } catch (MessagingException e) {
                e.printStackTrace();
        }

    }

    // 新增就清空初始化的資訊,重新初始化一遍即可。
    @Override
    public boolean addMailPerson(MailPO mailPO) {
        if(mailMapper.insert(mailPO)>0){
            senderConfig.clear();
            senderConfig.buildMailSender();
            return true;
        }
        return false;
    }
}

用到的MailDto

/**
 * @author crush
 * 郵箱傳送-前端傳輸引數
 */
@Data
public class MailDTO implements Serializable {

    /*** 接受郵箱賬戶*/
    private String mail;
    /*** 郵箱標題*/
    private String title;
    /** * 要傳送的內容*/
    private String content;
}

2.4、MailSenderConfig 配置類

/**
 * @author crush
 */
@Slf4j
@Component
@AllArgsConstructor
public class MailSenderConfig {

    private final List<JavaMailSenderImpl> senderList;

    private final MailProperties mailProperties;

    private final MailMapper mailMapper;

    /**
     * 初始化 sender
     * PostConstruct註解用於需要在依賴注入完成後執行任何初始化的方法。 必須在類投入使用之前呼叫此方法
     * 因為剛開始我覺得這種方式(@PostConstruct) 不合適,就是沒能做到修改了馬上就能用的那種感覺。
 	 * 但是後來寫完才發現,其實只要每次新增新的郵件傳送人時,都重新初始化一次就可以了。
 	 * 後來我又用啟動事件監聽器。@PostConstruct 後來就沒去測試了。
 	 * 理論新增、修改完 呼叫這個初始化方法就可以了。
     */
//    @PostConstruct
    public void buildMailSender() {
        log.info("初始化mailSender");
        List<MailPO> mails = mailMapper.selectList(new QueryWrapper<MailPO>().eq("state", 1));
        /**
         * 需求:原本就是打算做成一個動態的郵件傳送人,因為如果總是用一個郵件傳送驗證碼或者是那種打擾簡訊,速度一旦太過於頻繁,就會造成郵件傳送錯誤。
         * 思路:從資料庫中拿到所有可用的郵件傳送人,然後封裝起來,之後傳送郵件時,再進行隨機的選擇即可。
         * 另外一種方式就是這是動態的。
         * 最後就是加個兜底的,如果資料庫中查詢不到郵件傳送人,我們使用配置檔案中的傳送郵件的配置。
         */
        if(mails!=null&&!mails.isEmpty()){
            mails.forEach(mail -> {
                JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
                javaMailSender.setDefaultEncoding(mail.getDefaultEncoding());
                javaMailSender.setHost(mail.getEmailHost());
                javaMailSender.setPort(mail.getEmailPort());
                javaMailSender.setProtocol(mail.getProtocol());
                javaMailSender.setUsername(mail.getEmailUsername());
                javaMailSender.setPassword(mail.getEmailPassword());
                // 新增資料
                senderList.add(javaMailSender);
            });
        }
        else{
            JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
            javaMailSender.setDefaultEncoding(mailProperties.getDefaultEncoding());
            javaMailSender.setHost(mailProperties.getHost());
            javaMailSender.setPort(mailProperties.getPort());
            javaMailSender.setProtocol(mailProperties.getProtocol());
            javaMailSender.setUsername(mailProperties.getUsername());
            javaMailSender.setPassword(mailProperties.getPassword());
            // 新增資料
            senderList.add(javaMailSender);
        }

    }

    /**
     * 獲取MailSender
     *
     * @return CustomMailSender
     */
    public JavaMailSenderImpl getSender() {
        if (senderList.isEmpty()) {
            buildMailSender();
        }
        // 隨機返回一個JavaMailSender
        return senderList.get(new Random().nextInt(senderList.size()));
    }

    /**
     * 清理 sender
     */
    public void clear() {
        senderList.clear();
    }
}

2.5、監聽器

一兩句沒啥說的,可以直接通過idea進去看原始碼上的doc註解。下次再一起研究。

/**
 * 初始化操作
 * 目前只定義了動態設定郵件傳送人的操作
 * @Author: crush
 * @Date: 2021-11-26 19:51
 * version 1.0
 */
@Slf4j
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class StartListener implements ApplicationListener<ApplicationStartedEvent> {
    
    MailSenderConfig mailSenderConfig;

    public StartListener(MailSenderConfig mailSenderConfig) {
        this.mailSenderConfig = mailSenderConfig;
    }

    @SneakyThrows
    @Override
    public void onApplicationEvent(@NotNull ApplicationStartedEvent event) {
        this.mailSenderConfig.buildMailSender();
    }
}

2.6、controller

/**
 * @Author: crush
 * @Date: 2021-11-26 16:10
 * version 1.0
 */
@RestController
@RequestMapping("/email")
public class MailController {

    @Autowired
    private MailService mailService;

    @PostMapping("/send")
    public String send(@RequestBody MailDTO mailDTO){
        mailService.send(mailDTO);
        return "傳送成功!!!可能會稍有延遲,請檢視郵箱資訊!!";
    }

    @PostMapping("/addConfig")
    public String addMailPerson(@RequestBody MailPO mailPO){
        String message=mailService.addMailPerson(mailPO)?"新增成功!!!不過,請注意:可能會有延遲":"新增失敗,請稍後重試!!";
        return message;
    }

}

三、測試

image-20211127011047427

模板大致就是如下狀態吧。

image-20211127011111724

是新增進去的

image-20211127011530374

多點了一次哈。

image-20211127011522418

我再點選傳送郵件,因為是隨機數的方式,我們多測試幾次,總會用到這個錯誤的郵件傳送人的,用到了就表示我們已經成功啦哈。

因為新增的隨便輸入的,肯定是失敗的哈。但是可以確定我們用到了我們專案啟動後加入的郵件傳送人啦。 你們可以填入爭取的試一試。

image-20211127011707568

結束了結束啦。

沒寫小demo,沒啥原始碼。


後語

大家一起加油!!!如若文章中有不足之處,請大家及時指出,在此鄭重感謝。

紙上得來終覺淺,絕知此事要躬行。

大家好,我是博主寧在春主頁

一名喜歡文藝卻踏上程式設計這條道路的小青年。

希望:我們,待別日相見時,都已有所成


難得回到後端肝篇文,又拾起後端了,之後還會接著寫Vue的,肯定會把專欄寫完的。

相關文章