1. 程式人生 > >Spring Boot 郵件傳送的 5 種姿勢!

Spring Boot 郵件傳送的 5 種姿勢!

郵件傳送其實是一個非常常見的需求,使用者註冊,找回密碼等地方,都會用到,使用 JavaSE 程式碼傳送郵件,步驟還是挺繁瑣的,Spring Boot 中對於郵件傳送,提供了相關的自動化配置類,使得郵件傳送變得非常容易,本文我們就來一探究竟!看看使用 Spring Boot 傳送郵件的 5 中姿勢。

郵件基礎

我們經常會聽到各種各樣的郵件協議,比如 SMTP、POP3、IMAP ,那麼這些協議有什麼作用,有什麼區別?我們先來討論一下這個問題。

SMTP 是一個基於 TCP/IP 的應用層協議,江湖地位有點類似於 HTTP,SMTP 伺服器預設監聽的埠號為 25 。看到這裡,小夥伴們可能會想到既然 SMTP 協議是基於 TCP/IP 的應用層協議,那麼我是不是也可以通過 Socket 傳送一封郵件呢?回答是肯定的。

生活中我們投遞一封郵件要經過如下幾個步驟:

  1. 深圳的小王先將郵件投遞到深圳的郵局
  2. 深圳的郵局將郵件運送到上海的郵局
  3. 上海的小張來郵局取郵件

這是一個縮減版的生活中郵件傳送過程。這三個步驟可以分別對應我們的郵件傳送過程,假設從 [email protected] 傳送郵件到 [email protected]

  1. [email protected] 先將郵件投遞到騰訊的郵件伺服器
  2. 騰訊的郵件伺服器將我們的郵件投遞到網易的郵件伺服器
  3. [email protected] 登入網易的郵件伺服器檢視郵件

郵件投遞大致就是這個過程,這個過程就涉及到了多個協議,我們來分別看一下。

SMTP 協議全稱為 Simple Mail Transfer Protocol,譯作簡單郵件傳輸協議,它定義了郵件客戶端軟體與 SMTP 伺服器之間,以及 SMTP 伺服器與 SMTP 伺服器之間的通訊規則。

也就是說 [email protected] 使用者先將郵件投遞到騰訊的 SMTP 伺服器這個過程就使用了 SMTP 協議,然後騰訊的 SMTP 伺服器將郵件投遞到網易的 SMTP 伺服器這個過程也依然使用了 SMTP 協議,SMTP 伺服器就是用來收郵件。

而 POP3 協議全稱為 Post Office Protocol ,譯作郵局協議,它定義了郵件客戶端與 POP3 伺服器之間的通訊規則,那麼該協議在什麼場景下會用到呢?當郵件到達網易的 SMTP 伺服器之後, [email protected] 使用者需要登入伺服器檢視郵件,這個時候就該協議就用上了:郵件服務商都會為每一個使用者提供專門的郵件儲存空間,SMTP 伺服器收到郵件之後,就將郵件儲存到相應使用者的郵件儲存空間中,如果使用者要讀取郵件,就需要通過郵件服務商的 POP3 郵件伺服器來完成。

最後,可能也有小夥伴們聽說過 IMAP 協議,這個協議是對 POP3 協議的擴充套件,功能更強,作用類似,這裡不再贅述。

準備工作

目前國內大部分的郵件服務商都不允許直接使用使用者名稱/密碼的方式來在程式碼中傳送郵件,都是要先申請授權碼,這裡以 QQ 郵箱為例,向大家演示授權碼的申請流程:首先我們需要先登入 QQ 郵箱網頁版,點選上方的設定按鈕:

然後點選賬戶選項卡:

在賬戶選項卡中找到開啟POP3/SMTP選項,如下:

點選開啟,開啟相關功能,開啟過程需要手機號碼驗證,按照步驟操作即可,不贅述。開啟成功之後,即可獲取一個授權碼,將該號碼儲存好,一會使用。

專案建立

接下來,我們就可以建立專案了,Spring Boot 中,對於郵件傳送提供了自動配置類,開發者只需要加入相關依賴,然後配置一下郵箱的基本資訊,就可以傳送郵件了。

  • 首先建立一個 Spring Boot 專案,引入郵件傳送依賴:

建立完成後,專案依賴如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • 配置郵箱基本資訊

專案建立成功後,接下來在 application.properties 中配置郵箱的基本資訊:

spring.mail.host=smtp.qq.com
spring.mail.port=587
[email protected]
spring.mail.password=ubknfzhjkhrbbabe
spring.mail.default-encoding=UTF-8
spring.mail.properties.mail.smtp.socketFactoryClass=javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.debug=true

配置含義分別如下:

  • 配置 SMTP 伺服器地址
  • SMTP 伺服器的埠
  • 配置郵箱使用者名稱
  • 配置密碼,注意,不是真正的密碼,而是剛剛申請到的授權碼
  • 預設的郵件編碼
  • 配飾 SSL 加密工廠
  • 表示開啟 DEBUG 模式,這樣,郵件傳送過程的日誌會在控制檯打印出來,方便排查錯誤

如果不知道 smtp 伺服器的埠或者地址的的話,可以參考 騰訊的郵箱文件

  • https://service.mail.qq.com/cgi-bin/help?subtype=1&&id=28&&no=371

做完這些之後,Spring Boot 就會自動幫我們配置好郵件傳送類,相關的配置在 org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration 類中,部分原始碼如下:

@Configuration
@ConditionalOnClass({ MimeMessage.class, MimeType.class, MailSender.class })
@ConditionalOnMissingBean(MailSender.class)
@Conditional(MailSenderCondition.class)
@EnableConfigurationProperties(MailProperties.class)
@Import({ MailSenderJndiConfiguration.class, MailSenderPropertiesConfiguration.class })
public class MailSenderAutoConfiguration {
}

從這段程式碼中,可以看到,匯入了另外一個配置 MailSenderPropertiesConfiguration 類,這個類中,提供了郵件傳送相關的工具類:

@Configuration
@ConditionalOnProperty(prefix = "spring.mail", name = "host")
class MailSenderPropertiesConfiguration {
        private final MailProperties properties;
        MailSenderPropertiesConfiguration(MailProperties properties) {
                this.properties = properties;
        }
        @Bean
        @ConditionalOnMissingBean
        public JavaMailSenderImpl mailSender() {
                JavaMailSenderImpl sender = new JavaMailSenderImpl();
                applyProperties(sender);
                return sender;
        }
}

可以看到,這裡建立了一個 JavaMailSenderImpl 的例項, JavaMailSenderImplJavaMailSender 的一個實現,我們將使用 JavaMailSenderImpl 來完成郵件的傳送工作。

做完如上兩步,郵件傳送的準備工作就算是完成了,接下來就可以直接傳送郵件了。

具體的傳送,有 5 種不同的方式,我們一個一個來看。

傳送簡單郵件

簡單郵件就是指郵件內容是一個普通的文字文件:

@Autowired
JavaMailSender javaMailSender;
@Test
public void sendSimpleMail() {
    SimpleMailMessage message = new SimpleMailMessage();
    message.setSubject("這是一封測試郵件");
    message.setFrom("[email protected]");
    message.setTo("[email protected]");
    message.setCc("[email protected]");
    message.setBcc("[email protected]");
    message.setSentDate(new Date());
    message.setText("這是測試郵件的正文");
    javaMailSender.send(message);
}

從上往下,程式碼含義分別如下:

  1. 構建一個郵件物件
  2. 設定郵件主題
  3. 設定郵件傳送者
  4. 設定郵件接收者,可以有多個接收者
  5. 設定郵件抄送人,可以有多個抄送人
  6. 設定隱祕抄送人,可以有多個
  7. 設定郵件傳送日期
  8. 設定郵件的正文
  9. 傳送郵件

最後執行該方法,就可以實現郵件的傳送,傳送效果圖如下:

傳送帶附件的郵件

郵件的附件可以是圖片,也可以是普通檔案,都是支援的。

@Test
public void sendAttachFileMail() throws MessagingException {
    MimeMessage mimeMessage = javaMailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
    helper.setSubject("這是一封測試郵件");
    helper.setFrom("[email protected]");
    helper.setTo("[email protected]");
    helper.setCc("[email protected]");
    helper.setBcc("[email protected]");
    helper.setSentDate(new Date());
    helper.setText("這是測試郵件的正文");
    helper.addAttachment("javaboy.jpg",new File("C:\\Users\\sang\\Downloads\\javaboy.png"));
    javaMailSender.send(mimeMessage);
}

注意這裡在構建郵件物件上和前文有所差異,這裡是通過 javaMailSender 來獲取一個複雜郵件物件,然後再利用 MimeMessageHelper 對郵件進行配置,MimeMessageHelper 是一個郵件配置的輔助工具類,建立時候的 true 表示構建一個 multipart message 型別的郵件,有了 MimeMessageHelper 之後,我們針對郵件的配置都是由 MimeMessageHelper 來代勞。

最後通過 addAttachment 方法來新增一個附件。

執行該方法,郵件傳送效果圖如下:

傳送帶圖片資源的郵件

圖片資源和附件有什麼區別呢?圖片資源是放在郵件正文中的,即一開啟郵件,就能看到圖片。但是一般來說,不建議使用這種方式,一些公司會對郵件內容的大小有限制(因為這種方式是將圖片一起傳送的)。

@Test
public void sendImgResMail() throws MessagingException {
    MimeMessage mimeMessage = javaMailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
    helper.setSubject("這是一封測試郵件");
    helper.setFrom("[email protected]");
    helper.setTo("[email protected]");
    helper.setCc("[email protected]");
    helper.setBcc("[email protected]");
    helper.setSentDate(new Date());
    helper.setText("<p>hello 大家好,這是一封測試郵件,這封郵件包含兩種圖片,分別如下</p><p>第一張圖片:</p><img src='cid:p01'/><p>第二張圖片:</p><img src='cid:p02'/>",true);
    helper.addInline("p01",new FileSystemResource(new File("C:\\Users\\sang\\Downloads\\javaboy.png")));
    helper.addInline("p02",new FileSystemResource(new File("C:\\Users\\sang\\Downloads\\javaboy2.png")));
    javaMailSender.send(mimeMessage);
}

這裡的郵件 text 是一個 HTML 文字,裡邊涉及到的圖片資源先用一個佔位符佔著,setText 方法的第二個引數 true 表示第一個引數是一個 HTML 文字。

setText 之後,再通過 addInline 方法來新增圖片資源。

最後執行該方法,傳送郵件,效果如下:

在公司實際開發中,第一種和第三種都不是使用最多的郵件傳送方案。因為正常來說,郵件的內容都是比較的豐富的,所以大部分郵件都是通過 HTML 來呈現的,如果直接拼接 HTML 字串,這樣以後不好維護,為了解決這個問題,一般郵件傳送,都會有相應的郵件模板。最具代表性的兩個模板就是 Freemarker 模板和 Thyemeleaf 模板了。

使用 Freemarker 作郵件模板

首先需要引入 Freemarker 依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

然後在 resources/templates 目錄下建立一個 mail.ftl 作為郵件傳送模板:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>hello 歡迎加入 xxx 大家庭,您的入職資訊如下:</p>
<table border="1">
    <tr>
        <td>姓名</td>
        <td>${username}</td>
    </tr>
    <tr>
        <td>工號</td>
        <td>${num}</td>
    </tr>
    <tr>
        <td>薪水</td>
        <td>${salary}</td>
    </tr>
</table>
<div style="color: #ff1a0e">一起努力創造輝煌</div>
</body>
</html>

接下來,將郵件模板渲染成 HTML ,然後傳送即可。

@Test
public void sendFreemarkerMail() throws MessagingException, IOException, TemplateException {
    MimeMessage mimeMessage = javaMailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
    helper.setSubject("這是一封測試郵件");
    helper.setFrom("[email protected]");
    helper.setTo("[email protected]");
    helper.setCc("[email protected]");
    helper.setBcc("[email protected]");
    helper.setSentDate(new Date());
    //構建 Freemarker 的基本配置
    Configuration configuration = new Configuration(Configuration.VERSION_2_3_0);
    // 配置模板位置
    ClassLoader loader = MailApplication.class.getClassLoader();
    configuration.setClassLoaderForTemplateLoading(loader, "templates");
    //載入模板
    Template template = configuration.getTemplate("mail.ftl");
    User user = new User();
    user.setUsername("javaboy");
    user.setNum(1);
    user.setSalary((double) 99999);
    StringWriter out = new StringWriter();
    //模板渲染,渲染的結果將被儲存到 out 中 ,將out 中的 html 字串傳送即可
    template.process(user, out);
    helper.setText(out.toString(),true);
    javaMailSender.send(mimeMessage);
}

需要注意的是,雖然引入了 Freemarker 的自動化配置,但是我們在這裡是直接 new Configuration 來重新配置 Freemarker 的,所以 Freemarker 預設的配置這裡不生效,因此,在填寫模板位置時,值為 templates

呼叫該方法,傳送郵件,效果圖如下:

使用 Thymeleaf 作郵件模板

推薦在 Spring Boot 中使用 Thymeleaf 來構建郵件模板。因為 Thymeleaf 的自動化配置提供了一個 TemplateEngine,通過 TemplateEngine 可以方便的將 Thymeleaf 模板渲染為 HTML ,同時,Thymeleaf 的自動化配置在這裡是繼續有效的 。

首先,引入 Thymeleaf 依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

然後,建立 Thymeleaf 郵件模板:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>hello 歡迎加入 xxx 大家庭,您的入職資訊如下:</p>
<table border="1">
    <tr>
        <td>姓名</td>
        <td th:text="${username}"></td>
    </tr>
    <tr>
        <td>工號</td>
        <td th:text="${num}"></td>
    </tr>
    <tr>
        <td>薪水</td>
        <td th:text="${salary}"></td>
    </tr>
</table>
<div style="color: #ff1a0e">一起努力創造輝煌</div>
</body>
</html>

接下來發送郵件:

@Autowired
TemplateEngine templateEngine;

@Test
public void sendThymeleafMail() throws MessagingException {
    MimeMessage mimeMessage = javaMailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
    helper.setSubject("這是一封測試郵件");
    helper.setFrom("[email protected]");
    helper.setTo("[email protected]");
    helper.setCc("[email protected]");
    helper.setBcc("[email protected]");
    helper.setSentDate(new Date());
    Context context = new Context();
    context.setVariable("username", "javaboy");
    context.setVariable("num","000001");
    context.setVariable("salary", "99999");
    String process = templateEngine.process("mail.html", context);
    helper.setText(process,true);
    javaMailSender.send(mimeMessage);
}

呼叫該方法,傳送郵件,效果圖如下:

好了,這就是我們今天說的 5 種郵件傳送姿勢,不知道你掌握了沒有呢?

本文案例已經上傳到 GitHub:https://github.com/lenve/javaboy-code-samples。

有問題歡迎留言討論。

關注公眾號【江南一點雨】,專注於 Spring Boot+微服務以及前後端分離等全棧技術,定期視訊教程分享,關注後回覆 Java ,領取鬆哥為你精心準備的 Java 乾貨!