使用Spring Boot開發郵件系統
- 前言
網際網路發展到現在,郵件服務已經成為網際網路企業中必備功能之一,應用場景非常廣泛,比較常見的有:使用者註冊、忘記密碼、監控提醒、企業營銷等。
大多數網際網路企業都會將郵件傳送抽取為一個獨立的微服務,對外提供介面來支援各種型別的郵件傳送。
- 郵件協議
傳送郵件的本質是將一個人的資訊傳輸給另外一個人,那麼如何傳輸就需要商量好標準,這些標準就是協議。最初只有兩個協議:
· SMTP 協議
SMTP 的全稱是 “Simple Mail Transfer Protocol”,即簡單郵件傳輸協議。它是一組用於從源地址到目的地址傳輸郵件的規範,通過它來控制郵件的中轉方式。它的一個重要特點是它能夠在傳送中接力傳送郵件,即郵件可以通過不同網路上的主機接力式傳送。
SMTP 認證,簡單地說就是要求必須在提供了賬戶名和密碼之後才可以登入 SMTP 伺服器,這就使得那些垃圾郵件的散播者無可乘之機。增加 SMTP 認證的目的是為了使使用者避免受到垃圾郵件的侵擾。SMTP主要負責底層的郵件系統如何將郵件從一臺機器傳至另外一臺機器。
· POP3 協議
POP3 是 Post Office Protocol 3 的簡稱,即郵局協議的第3個版本,它規定怎樣將個人計算機連線到 Internet 的郵件伺服器和下載電子郵件的電子協議。
它是因特網電子郵件的第一個離線協議標準,POP3 允許使用者從伺服器上把郵件儲存到本地主機(即自己的計算機)上,同時刪除儲存在郵件伺服器上的郵件。
POP 協議支援“離線”郵件處理。其具體過程是:郵件傳送到伺服器上,電子郵件客戶端呼叫郵件客戶機程式以連線伺服器,並下載所有未閱讀的電子郵件。
這種離線訪問模式是一種儲存轉發服務,將郵件從郵件伺服器端送到個人終端機器上,一般是 PC 機或 MAC。
一旦郵件傳送到 PC 機或 MAC上,郵件伺服器上的郵件將會被刪除。但目前的 POP3 郵件伺服器大都可以“只下載郵件,伺服器端並不刪除”,也就是改進的 POP3 協議。
SMTP 和 POP3 是最初的兩個協議,隨著郵件的不斷髮展後來又增加了兩個協議:
· IMAP 協議
全稱 Internet Mail Access Protocol(互動式郵件存取協議),IMAP 是斯坦福大學在 1986 年開發的研發的一種郵件獲取協議,即互動式郵件存取協議,它是跟 POP3 類似郵件訪問標準協議之一。
不同的是,開啟了 IMAP 後,在電子郵件客戶端收取的郵件仍然保留在伺服器上,同時在客戶端上的操作都會反饋到伺服器上,如:刪除郵件,標記已讀等,伺服器上的郵件也會做相應的動作。
所以無論從瀏覽器登入郵箱或者客戶端軟體登入郵箱,看到的郵件以及狀態都是一致的。
IMAP 的一個與 POP3 的區別是:IMAP 它只下載郵件的主題,並不是把所有的郵件內容都下載下來,而是你郵箱當中還保留著郵件的副本,沒有把你原郵箱中的郵件刪除,你用郵件客戶軟體閱讀郵件時才下載郵件的內容。
較好支援這兩種協議的郵件客戶端有:Foxmail、Outlook 等。
· Mime 協議
由於 SMTP 這個協議開始是基於純 ASCⅡ文字的,在二進位制檔案上處理得並不好。後來開發了用來編碼二進位制檔案的標準,如 MIME,以使其通過 SMTP 來傳輸。
今天,大多數 SMTP 伺服器都支援 8 位 MIME 擴充套件,它使二進位制檔案的傳輸變得幾乎和純文字一樣簡單。
用一張圖來看傳送郵件過程中的協議使用:
- 郵件傳送流程
發信人在使用者代理上編輯郵件,並寫清楚收件人的郵箱地址;
使用者代理根據發信人編輯的資訊,生成一封符合郵件格式的郵件;
使用者代理把郵件傳送到發信人的郵件伺服器上,郵件伺服器上面有一個緩衝佇列,傳送到郵件伺服器上面的郵件都會加入到緩衝佇列中,等待郵件伺服器上的 SMTP 客戶端進行傳送;
發信人的郵件伺服器使用 SMTP 協議把這封郵件傳送到收件人的郵件伺服器上
收件人的郵件伺服器收到郵件後,把這封郵件放到收件人在這個伺服器上的信箱中;
收件人使用使用者代理來收取郵件。首先使用者代理使用 POP3 協議來連線收件人所在的郵件伺服器,身份驗證成功後,使用者代理就可以把郵件伺服器上面的收件人郵箱裡面的郵件讀取出來,並展示給收件人。
這就是郵件傳送的一個完整流程。
- 簡單使用
最早期的時候使用 JavaMail 相關 API 來開發,需要自己去封裝訊息體,程式碼量比較龐大;
後來 Spring 推出了 JavaMailSender 簡化了郵件傳送過程,JavaMailSender 提供了強大的郵件傳送功能,可支援各種型別的郵件傳送。
現在 Spring Boot 在 JavaMailSender 的基礎上又進行了封裝,就有了現在的 spring-boot-starter-mail,讓郵件傳送流程更加簡潔和完善。
下面給大家介紹如何使用 Spring Boot 傳送郵件。
<1> pom 包配置
引入加 spring-boot-starter-mail 依賴包:
<2> 配置檔案
在 application.properties 中新增郵箱配置,不同的郵箱引數稍有不同,下面列舉幾個常用郵箱配置:
163 郵箱配置:
注意:測試時需要將 spring.mail.username 和 spring.mail.password 改成自己郵箱對應的登入名和密碼,這裡的密碼不是郵箱的登入密碼,是開啟 POP3 之後設定的客戶端授權密碼。
<3> 文字郵件傳送
Spring 已經幫我們內建了 JavaMailSender,直接在專案中引用即可。我們封裝一個 MailService 類來實現普通的郵件傳送方法。
文字郵件抄送使用:message.copyTo(copyTo) 來實現。
from,即為郵件傳送者,一般設定在配置檔案中
to,郵件接收者,此引數可以為陣列,同時傳送多人
subject,郵件主題
content,郵件的主體
郵件傳送者 from 一般採用固定的形式寫到配置檔案中。
<4> 編寫 test 類進行測試
稍微等待幾秒,就可以在郵箱中找到此郵件內容了。至此一個簡單的文字郵件傳送就完成了。
- 富文字郵件
在日常使用的過程中,我們通常在郵件中加入圖片或者附件來豐富郵件的內容,下面講介紹如何使用 Spring Boot 來發送富文字郵件。
傳送 HTML 格式郵件
郵件傳送支援以 HTML 語法去構建自定義的郵件格式,Spring Boot 支援使用 HTML 傳送郵件。
我們在 MailService 中新增支援 HTML 郵件傳送的方法.
富文字郵件抄送使用:helper.addCc(cc) 來實現。
和文字郵件傳送程式碼對比,富文字郵件傳送使用 MimeMessageHelper 類。MimeMessageHelper 支援傳送複雜郵件模板,支援文字、附件、HTML、圖片等,接下來會一一使用到。
在測試類中構建 HTML 內容,測試傳送
郵件內容大寫了一段話,下面為接收到的效果:
由此我們發現傳送 HTML 郵件,就是需要拼接一段 HTML 的 String 字串交給 MimeMessageHelper 來處理,最後由郵件客戶端負責渲染顯示內容。
傳送帶附件的郵件
在 MailService 新增 sendAttachmentsMail 方法,傳送帶附件的郵件主要是使用 FileSystemResource 對檔案進行封裝,在新增到 MimeMessageHelper 中。
新增多個附件可以使用多條 helper.addAttachment(fileName, file)
在測試類中新增測試方法
附件可以是圖片、壓縮包、Word 等任何檔案,但是郵件廠商一般都會對附件大小有限制,太大的附件建議使用網盤上傳後,在郵件中給出連結。
效果圖如下:
傳送帶靜態資源的郵件
郵件中的靜態資源一般指圖片,在 MailService 新增 sendInlineResourceMail 方法。
在測試類中新增測試方法
新增多個圖片可以使用多條 和helper.addInline(rscId, res) 來實現
效果圖如下:
(可以關注該作者的公眾號) 以上是郵件傳送的基礎服務,已演示支援各種型別郵件。
- 郵件系統
如果只是想在系統中做一個郵件工具類的話,以上的內容基本就可以滿足要求了。要做成一個郵件系統的話還需要考慮以下幾方面:
對外提供傳送郵件的服務介面
固定格式郵件是否考慮使用模板
傳送郵件時出現網路錯誤,是否考慮適當的重試機制
郵件系統是否考慮非同步化,提升服務響應時間
是否開發郵件後臺管理系統,開發出對應的管理軟體,通過頁面傳送郵件,統計傳送郵件成功率等資料。
常見異常處理措施
6.1 對外提供介面
作為一個獨立的郵件系統,需要對外提供介面呼叫,我們以簡單文字郵件為例做個演示:
首先需要定義個例項返回物件:
預設成功的返回碼為:00,返回訊息為:傳送成功。
建立一個 MailController 類對外提供 HTTP 請求介面。
外部請求過來時首先進行引數校驗,如果引數有誤返回請求;傳送郵件出現異常時返回錯誤,正常情況下返回 00; 注意在 Service 層如果對異常資訊進行了捕獲的話,需要將異常資訊拋到上層。
類似上述程式碼。
按照這個思路也可以提供傳送帶圖片、帶附件的郵件,同時也可以封裝傳送多人郵件,群發郵件等複雜情況。
6.2 郵件模板
通常我們使用郵件傳送服務的時候,都會有一些固定的場景,比如重置密碼、註冊確認等,給每個使用者傳送的內容可能只有小部分是變化的。
所以,很多時候我們會使用模板引擎來為各類郵件設定成模板,這樣我們只需要在傳送時去替換變化部分的引數即可。
我們會經常收到這樣的郵件:
尊敬的 neo 使用者:
恭喜您註冊成為 xxx 網的使用者,同時感謝您對 xxx 的關注與支援並歡迎您使用 xx 的產品與服務。
…
郵件正文只有 neo 這個使用者名稱在變化,郵件其它內容均不變,如果每次傳送郵件都需拼接 HTML 程式碼,程式不夠優雅,並且每次郵件正文有變化都需修改程式碼非常不方便。
因此對於這類郵件,都建議做成郵件模板來處理,模板的本質很簡單,就是在模板中替換變化的引數,轉換為 HTML 字串即可,這裡以 Thymeleaf 為例來演示。
Thymeleaf 是 Spring 官方推薦的前端模板引擎,類似 Velocity、FreeMarker 等模板引擎,相較與其他的模板引擎,Thymeleaf 開箱即用的特性。
它提供標準和 Spring 標準兩種方言,可以直接套用模板實現 JSTL、 OGNL 表示式效果,避免每天套模板、該 Jstl、改標籤的困擾。
Thymeleaf 在有網路和無網路的環境下皆可執行,即它可以讓美工在瀏覽器檢視頁面的靜態效果,也可以讓程式設計師在伺服器檢視帶資料的動態頁面效果。
下面我們來演示使用 Thymeleaf 製作郵件模板:
-
新增依賴包
-
在 resorces/templates 下建立 emailTemplate.html
emailTemplate.html 檔案內容即為郵件的正文內容模板。
我們發現上述的模板中只有 id 是一個動態的值,傳送過程中會根據傳入的 id 值來替換連結中的 {id}。
- 解析模板併發送
我們發現最後呼叫的還是 sendHtmlMail 的方法,郵件模板的作用只是處理 HTML 生成部分,通過 Thymeleaf 模板引擎解析固定的模板,再更具引數來動態替換其中的變數,最後通過前面的 HTML 傳送的方法傳送郵件。
效果圖如下:
6.3 傳送失敗
因為各種原因,總會有郵件傳送失敗的情況,比如:郵件傳送過於頻繁、網路異常等。在出現這種情況的時候,我們一般會考慮重新重試傳送郵件,會分為以下幾個步驟來實現:
接收到傳送郵件請求,首先記錄請求並且入庫。
呼叫郵件傳送介面傳送郵件,並且將傳送結果記錄入庫。
啟動定時系統掃描時間段內,未傳送成功並且重試次數小於3次的郵件,進行再次傳送.
重新發送郵件的時間,建議以 2 的次方間隔時間,比如:2、4、8、16 …
常見的錯誤返回碼:
421 HL:ICC 該IP同時併發連線數過大,超過了網易的限制,被臨時禁止連線。
451 Requested mail action not taken: too much fail authentication 登入失敗次數過多,被臨時禁止登入。請檢查密碼與帳號驗證設定
553 authentication is required,密碼配置不正確
554 DT:SPM 傳送的郵件內容包含了未被許可的資訊,或被系統識別為垃圾郵件。請檢查是否有使用者傳送病毒或者垃圾郵件;
550 Invalid User 請求的使用者不存在
554 MI:STC 發件人當天內累計郵件數量超過限制,當天不再接受該發件人的投信。
如果使用一個郵箱頻繁傳送相同內容郵件,也會被認定為垃圾郵件,報 554 DT:SPM 錯誤
如果使用網易郵箱可以檢視這裡的提示:企業退信的常見問題?
6.4 非同步傳送
很多時候郵件傳送並不是主業務必須關注的結果,比如通知類、提醒類的業務可以允許延時或者失敗。這個時候可以採用非同步的方式來發送郵件,加快主交易執行速度。
在實際專案中可以採用訊息中介軟體 MQ 傳送郵件,具體做法是建立一個郵件傳送的訊息佇列,在業務中有需要用到郵件傳送功能時,給對應訊息佇列按照規定引數傳送一條訊息,郵件系統監聽此佇列,當有訊息過來時,處理郵件傳送的邏輯。
6.5 管理後臺
考慮做一個完善的郵件系統,可以設計一個獨立的郵件管理後臺,不但可以讓系統之間呼叫時使用,也可以提供圖形化介面讓公司的運營、市場部的同事來發送郵件,查詢郵件的傳送進度,統計郵件傳送成功率。
也可以設定一些程式碼鉤子,統計使用者點選固定連結次數,方便公司營銷人員監控郵件營銷轉化率。
一個非常完善的郵件系統需要考慮的因素非常多,比如是否設定白名單、黑名單來做郵件接收人的過濾機制,是否給使用者提供郵件退訂的介面等。
因此在初期郵件傳送的基本功能完成之後,再結合公司業務,快速迭代的逐步完善郵件系統,是一個推薦的做法。
- 總結
使用 Spring Boot 整合傳送郵件的功能非常簡單,只需要簡單編碼就可以實現傳送普通文字郵件、帶附件郵件、HTML 格式郵件、帶圖片郵件等。
如果需要做成一個郵件系統還需要考慮很多因素,比如:郵箱傳送失敗重試機制、防止郵件被識別為垃圾郵件,固定時間內傳送郵件的限制等。
在微服務架構中,常常將一些基礎功能下沉下來,作為獨立的服務來使用,郵件系統作為平臺的基礎功能,特別適合做為獨立的微服務來支援整個系統。