明月當天,不知道你有沒有思念的人
前言
之前其實已經寫過SpringBoot異步發送郵件,但是今天在一個小項目中要用到發送郵件時,我突然覺得郵件發送人只有一個,並且固定寫在yml文件中,就是非常的不妥當,就想着怎么整成一個動態的。
在寫之前已經翻過很多博客了,該踩的坑都踩的差不多了,我是實現之后寫的文章,有問題大家可以一起交流。
小聲bb(對於CSDN我真的逐漸變得麻木了,簡稱CV大法現場,雖然我本人也是CSDN的一名小小博主,也是資深用戶,對於文章的這塊很多時候真的沒法說,除了能說加油也沒有了吧)。
於是就有了下面這篇文章啦....
一、需求分析
默認大家都已經會 SpringBoot 集成 郵件發送啦哈,不行的,點一下上文的鏈接啦。
我先說說我想要達到什么樣的效果:
- 郵件發送人可以是多個,yml文件中是兜底配置(即數據庫中沒有一個可用時,使用yml文件中配置的郵件發送人)
- 項目啟動后,我也可以臨時增加郵件發送人,或者禁用掉某個郵件發送人(操作完也無需重啟項目即可生效)
- 發送郵件內容為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
安全協議的非對稱加密的高度安全可靠性,可防止郵件泄露。SMTPS
和SMTP
協議一樣,也是用來發送郵件的,只是更安全些,防止郵件被黑客截取泄密,還可實現郵件發送者抗抵賴功能。防止發送者發送之后刪除已發郵件,拒不承認發送過這樣一份郵件。端口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文件,我們大致知道了要建立張什么樣的數據表了哈。
這些大家都可以自定義哈,根據自己需求來建哈。
根據數據表建一個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;
}
}
三、測試
模板大致就是如下狀態吧。
是添加進去的
多點了一次哈。
我再點擊發送郵件,因為是隨機數的方式,我們多測試幾次,總會用到這個錯誤的郵件發送人的,用到了就表示我們已經成功啦哈。
因為添加的隨便輸入的,肯定是失敗的哈。但是可以確定我們用到了我們項目啟動后加入的郵件發送人啦。 你們可以填入爭取的試一試。
結束了結束啦。
沒寫小demo,沒啥源碼。
后語
大家一起加油!!!如若文章中有不足之處,請大家及時指出,在此鄭重感謝。
紙上得來終覺淺,絕知此事要躬行。
大家好,我是博主
寧在春
:主頁一名喜歡文藝卻踏上編程這條道路的小青年。
希望:
我們,待別日相見時,都已有所成
。
難得回到后端肝篇文,又拾起后端了,之后還會接着寫Vue的,肯定會把專欄寫完的。