一:簡述
在日常中的工作中難免會遇到程式整合郵件傳送功能、接收功能;此篇文章我將使用SpringBoot整合郵件傳送功能和接收功能;若對郵件一些基本協議和傳送流程不懂的請務必參考我之前寫的部落格或者瀏覽網上資料。
【郵件基本概念及傳送方式】 【JavaMail傳送郵件(超詳細)】
二:SpringBoot傳送郵件的基本說明
在我們現在使用的SpringBoot的版本中,底層傳送郵件的技術都是使用一個叫 Jakarta Mail 的,它可有實現SMTP、POP、IMAP等基本的郵件傳送和接收協議,因為前兩篇我都介紹了,這裡就不在多說,直接上乾貨!!
1:基本環境及座標依賴
使用SpringBoot腳手架建立一個SpringBoot專案後匯入下面郵件傳送座標,或者在腳手架建立中也可有選擇
<!--注:我使用的SpringBoot版本是 2.6.0 --> <!--注:jakarta mail版本為1.6.7 jakarta activation版本為1.2.2--> <!--SpringBoot整合郵件傳送啟動器座標--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
2:快速上手傳送郵件
在傳送郵件前我簡單說說JavaMailSender和JavaMailSenderImpl;它們是Spring官方提供的一套郵件功能整合介面及實現,底層呼叫具體的Jakarta Mail技術;可以說是二次封裝;使用SpringBoot提供的介面及實現是後端郵件傳送最主流的整合工具;我們在業務裡直接注入JavaMailSenderImpl後並呼叫send方法。其中簡單的郵件我們可以通過SimpleMailMessage來傳送,對於複雜的帶有附件的我們可以藉助MimeMessageHelper來構建MimeMessage傳送郵件。
application.properties設定基本配置 ### smtp伺服器主機(163的) spring.mail.host=smtp.163.com ### 登入伺服器郵箱賬號 spring.mail.username=antladdie ### 登入伺服器郵箱授權碼(不是郵箱密碼,這個是我們開通SMTP、POP時得到的授權碼) spring.mail.password=xxxxxxxxxxxxxxx
@SpringBootTest class SpringbootJakartamailApplicationTests { //注入郵件傳送物件 @Autowired private JavaMailSender mailSender; /*** * 簡單郵件傳送 */ @Test void testMailA() { //建立簡單的郵件傳送物件 SimpleMailMessage message = new SimpleMailMessage(); message.setFrom("antladdie@163.com"); // 設定發件人郵箱(若配置預設郵箱則不用再設定) message.setTo("xiaofeng504@qq.com"); // 設定收件人郵箱 message.setCc("xiaofeng500@qq.com"); // 設定抄報人郵箱(可以不填寫) message.setBcc("575814158@qq.com"); // 設定密送人郵箱(可以不填寫) message.setSubject("繳費通知"); // 設定郵件主題 message.setText("您手機已欠費100元,請及時繳費!"); // 設定郵件文字內容 message.setSentDate(new Date()); // 設定郵件傳送時間 //傳送 mailSender.send(message); } }
3:整合郵件的基本配置(約定大於配置)
這裡的 Jakarta Mail 的 properties 擴充套件配置不理解的可以參考 官方文件 ; 不過英文的看著頭大,這裡我將其翻譯了
# 屬性名稱:屬性型別 屬性說明 # mail.smtp.user:String SMTP 的預設使用者名稱。 # mail.smtp.host:String 要連線的 SMTP 伺服器。 # mail.smtp.port:int 要連線的 SMTP 伺服器埠,如果 connect() 方法沒有明確指定一個。預設為 25。 # mail.smtp.connectiontimeout:int 以毫秒為單位的套接字連線超時值。這個超時是由 java.net.Socket 實現的。預設為無限超時。 # mail.smtp.timeout:int 以毫秒為單位的套接字讀取超時值。這個超時是由 java.net.Socket 實現的。預設為無限超時。 # mail.smtp.writetimeout:int 以毫秒為單位的套接字寫入超時值。此超時是通過對每個連線使用 java.util.concurrent.ScheduledExecutorService 來實現的, 該服務會在超時到期時安排執行緒關閉套接字。因此,使用此超時的開銷是每個連線一個執行緒。預設為無限超時。 # mail.smtp.from:String 用於 SMTP MAIL 命令的電子郵箱地址。這將設定信封返回地址。預設為 msg.getFrom() 或 InternetAddress.getLocalAddress()。 注意:mail.smtp.user 以前用於此目的。 # mail.smtp.localhost:String SMTP HELO 或 EHLO 命令中使用的本地主機名。預設為InetAddress.getLocalHost().getHostName(). 如果您的 JDK 和名稱服務配置正確,則通常不需要設定。 # mail.smtp.localaddress:String 建立 SMTP 套接字時要繫結到的本地地址(主機名)。預設為 Socket 類選擇的地址。通常不需要設定, 但對於選擇要繫結到的特定本地地址很重要的多宿主主機很有用。 # mail.smtp.localport:int 建立 SMTP 套接字時要繫結到的本地埠號。預設為 Socket 類選擇的埠號。 # mail.smtp.ehlo:boolean 如果為 false,則不要嘗試使用 EHLO 命令登入。預設為真。通常 EHLO 命令失敗將回退到 HELO 命令; 此屬性僅適用於未正確使 EHLO 失敗 或未正確實現 EHLO 的伺服器。 # mail.smtp.auth:boolean 如果為 true,則嘗試使用 AUTH 命令對使用者進行身份驗證。預設為假。 # mail.smtp.auth.mechanisms:String 如果設定,則列出要考慮的身份驗證機制,以及考慮它們的順序。只會使用伺服器支援和當前實現支援的機制。 預設為"LOGIN PLAIN DIGEST-MD5 NTLM",包括當前實現支援的所有認證機制,除了 XOAUTH2。 # mail.smtp.auth.login.disable:boolean 如果為 true,則阻止使用該AUTH LOGIN命令。預設為假。 # mail.smtp.auth.plain.disable:boolean 如果為 true,則阻止使用該AUTH PLAIN命令。預設為假。 # mail.smtp.auth.digest-md5.disable:boolean 如果為 true,則阻止使用該AUTH DIGEST-MD5命令。預設為假。 # mail.smtp.auth.ntlm.disable:boolean 如果為 true,則阻止使用該AUTH NTLM命令。預設為假。 # mail.smtp.auth.ntlm.domain:String NTLM 身份驗證域。 # mail.smtp.auth.ntlm.flags:int NTLM 協議特定標誌。有關詳細資訊,請參閱 http://curl.haxx.se/rfc/ntlm.html#theNtlmFlags。 # mail.smtp.auth.xoauth2.disable:boolean 如果為 true,則阻止使用該AUTHENTICATE XOAUTH2命令。由於 OAuth 2.0 協議需要特殊的訪問令牌而不是密碼,因此預設情況下 禁用此機制。通過將此屬性顯式設定為“false”或將“mail.smtp.auth.mechanisms”屬性設定為“XOAUTH2”來啟用它。 # mail.smtp.submitter:String 要在 MAIL FROM 命令的 AUTH 標記中使用的提交者。通常由郵箱中繼用於傳遞有關郵箱原始提交者的資訊。 另請參閱 的setSubmitter 方法SMTPMessage。郵箱客戶端通常不使用它。 # mail.smtp.dsn.notify:String RCPT 命令的 NOTIFY 選項。NEVER 或 SUCCESS、FAILURE 和 DELAY(以逗號分隔)的某種組合。 # mail.smtp.dsn.ret:String MAIL 命令的 RET 選項。FULL 或 HDRS。 # mail.smtp.allow8bitmime:boolean 如果設定為 true,並且伺服器支援 8BITMIME 擴充套件,則使用“quoted-printable”或“base64”編碼的郵箱的文字部分 如果遵循 8bit 文字的 RFC2045 規則,則將轉換為使用“8bit”編碼。 # mail.smtp.sendpartial:boolean 如果設定為 true,並且訊息具有一些有效地址和一些無效地址,則無論如何都要傳送訊息,並使用 SendFailedException 報告部分失敗。 如果設定為 false(預設值),則如果收件人地址無效,則不會將郵箱傳送給任何收件人。 # mail.smtp.sasl.enable:boolean 如果設定為 true,則嘗試使用 javax.security.sasl 包來選擇登入的身份驗證機制。預設為假。 # mail.smtp.sasl.mechanisms:String 要嘗試使用的 SASL 機制名稱的空格或逗號分隔列表。 # mail.smtp.sasl.authorizationid:String 在 SASL 身份驗證中使用的授權 ID。如果未設定,則使用身份驗證 ID(使用者名稱)。 # mail.smtp.sasl.realm:String 用於 DIGEST-MD5 身份驗證的領域。 # mail.smtp.sasl.usecanonicalhostname:boolean 如果設定為 true,則返回的規範主機名將 InetAddress.getCanonicalHostName 傳遞給 SASL 機制, 而不是用於連線的主機名。預設為假。 # mail.smtp.quitwait:boolean 如果設定為 false,則傳送 QUIT 命令並立即關閉連線。如果設定為 true(預設值),則導致傳輸等待對 QUIT 命令的響應。 # mail.smtp.quitonsessionreject:boolean 如果設定為 false(預設值),會話發起拒絕時不傳送 QUIT 命令並立即關閉連線。 如果設定為 true,則導致傳輸在關閉連線之前傳送 QUIT 命令。 # mail.smtp.reportsuccess:boolean 如果設定為 true,則會導致傳輸SMTPAddressSucceededException 為每個成功的地址包含一個 。還要注意 , 即使所有地址都正確並且訊息已成功傳送,這將導致SendFailedException 從sendMessage方法中 丟擲 a SMTPTransport。 # mail.smtp.socketFactory:SocketFactory 如果設定為實現該javax.net.SocketFactory介面的類,則 該類將用於建立 SMTP 套接字。請注意,這是一個類的例項, 而不是名稱,並且必須使用put方法而不是setProperty方法來設定 。 # mail.smtp.socketFactory.class:String 如果設定,則指定實現javax.net.SocketFactory介面的類的名稱 。此類將用於建立 SMTP 套接字。 # mail.smtp.socketFactory.fallback:boolean 如果設定為 true,則無法使用指定的套接字工廠類建立套接字將導致使用java.net.Socket該類建立套接字。預設為真。 # mail.smtp.socketFactory.port:int 指定使用指定套接字工廠時要連線的埠。如果未設定,將使用預設埠。 # mail.smtp.ssl.enable:boolean 如果設定為 true,則預設使用 SSL 連線並使用 SSL 埠。“smtp”協議預設為 false,“smtps”協議預設為 true。 # mail.smtp.ssl.checkserveridentity:boolean 如果設定為 true,請檢查RFC 2595指定的伺服器標識 。這些基於伺服器證照內容的額外檢查旨在防止中間人攻擊。預設為假。 # mail.smtp.ssl.trust:String 如果設定,並且未指定套接字工廠,則啟用 MailSSLSocketFactory. 如果設定為“*”,則所有主機都是可信的。如果設定為以 空格分隔的主機列表,則這些主機是可信的。否則,信任取決於伺服器提供的證照。 # mail.smtp.ssl.socketFactory:SSLSocketFactory 如果設定為擴充套件 javax.net.ssl.SSLSocketFactory類的類,則此類將用於建立 SMTP SSL 套接字。請注意,這是一個類的例項, 而不是名稱,並且必須使用put方法而不是setProperty方法來設定 。 # mail.smtp.ssl.socketFactory.class:String 如果設定,則指定擴充套件javax.net.ssl.SSLSocketFactory類的類的名稱 。此類將用於建立 SMTP SSL 套接字。 # mail.smtp.ssl.socketFactory.port:int 指定使用指定套接字工廠時要連線的埠。如果未設定,將使用預設埠。 # mail.smtp.ssl.protocols:String 指定將為 SSL 連線啟用的 SSL 協議。屬性值是該javax.net.ssl.SSLSocket.setEnabledProtocols方法 可接受的以空格分隔的標記列表。 # mail.smtp.ssl.ciphersuites:String 指定將為 SSL 連線啟用的 SSL 密碼套件。屬性值是該javax.net.ssl.SSLSocket.setEnabledCipherSuites方法 可接受的以空格分隔的標記列表。 # mail.smtp.starttls.enable:boolean 如果為 true,則啟用該STARTTLS命令(如果伺服器支援)在發出任何登入命令之前將連線切換到受 TLS 保護的連線。 如果伺服器不支援 STARTTLS,則連線繼續而不使用 TLS;mail.smtp.starttls.required 如果不支援 STARTTLS, 請檢視失敗的 屬性。請注意,必須配置適當的信任庫,以便客戶端信任伺服器的證照。預設為假。 # mail.smtp.starttls.required:boolean 如果為 true,則需要使用該STARTTLS命令。如果伺服器不支援 STARTTLS 命令,或者命令失敗,connect 方法就會失敗。預設為假。 # mail.smtp.proxy.host:String 指定將用於連線到郵箱伺服器的 HTTP Web 代理伺服器的主機名。 # mail.smtp.proxy.port:String 指定 HTTP Web 代理伺服器的埠號。預設為埠 80。 # mail.smtp.proxy.user:String 指定用於向 HTTP Web 代理伺服器進行身份驗證的使用者名稱。預設情況下,不進行身份驗證。 # mail.smtp.proxy.password:String 指定用於向 HTTP Web 代理伺服器進行身份驗證的密碼。預設情況下,不進行身份驗證。 # mail.smtp.socks.host:String 指定將用於連線到郵箱伺服器的 SOCKS5 代理伺服器的主機名。 # mail.smtp.socks.port:String 指定 SOCKS5 代理伺服器的埠號。僅當代理伺服器未使用標準埠號 1080 時才需要使用此選項。 # mail.smtp.mailextension:String 附加到 MAIL 命令的擴充套件字串。擴充套件字串可用於指定標準 SMTP 服務擴充套件以及特定於供應商的擴充套件。通常, 應用程式應使用該 SMTPTransport 方法supportsExtension 來驗證伺服器是否支援所需的服務擴充套件。 請參閱RFC 1869 和其他定義特定擴充套件的 RFC。 # mail.smtp.userset:boolean 如果設定為true,則在isConnected方法中使用RSET 命令而不是NOOP 命令。 在某些情況下,sendmail 在多次 NOOP 命令後響應會很慢;使用 RSET 避免了這個 sendmail 問題。預設為假。 # mail.smtp.noop.strict:boolean 如果設定為 true(預設值),則堅持來自 NOOP 命令的 250 響應程式碼以指示成功。該isConnected方法使用 NOOP 命令 來確定 連線是否仍然有效。一些較舊的伺服器在成功時返回錯誤的響應程式碼,一些伺服器根本不執行 NOOP 命令,因此總是返回失敗程式碼。 將此屬性設定為 false 以處理以這種方式損壞的伺服器。通常,當伺服器超時連線時,它會傳送 421 響應程式碼,客戶端將其視 為對其發出的下一個命令的響應。某些伺服器在連線超時時傳送錯誤的失敗響應程式碼。在處理以這種方式損壞的伺服器時, 不要將此屬性設定為 false。 除了列印由Session配置控制的除錯輸出之外,com.sun.mail.smtp 提供程式使用Logger下表中的描述記錄相同的資訊 : 記錄器名稱 日誌級別 目的 com.sun.mail.smtp CONFIG SMTPTransport 的配置 com.sun.mail.smtp FINE 一般除錯輸出 com.sun.mail.smtp.protocol FINEST 完整的協議跟蹤 警告:此包獨有的 API 應視為實驗性的。將來它們可能會以與使用當前 API 的應用程式不相容的方式進行更改
#application.properties基本配置,後面我都使用此配置來傳送郵件 ## 基本配置 ### smtp伺服器主機(163的) spring.mail.host=smtp.163.com ### 連線郵件伺服器埠(預設SMTP 25 POP 110) spring.mail.port=25 ### 服務協議SMTP(代表是傳送郵件) spring.mail.protocol=smtp ### 登入伺服器郵箱賬號 spring.mail.username=antladdie ### 登入伺服器郵箱授權碼(不是郵箱密碼,這個是我們開通SMTP、POP時得到的授權碼) spring.mail.password=xxxxxxxxxxxxx ### 預設郵件的編碼集(MimeMessage 編碼,預設UTF-8) spring.mail.default-encoding=UTF-8 # 補充配置(這裡具體可以參照Jakarta Mail的擴充套件配置) ## 預設傳送方郵箱賬號(當程式未指定發件人郵箱則預設取這個) spring.mail.properties.mail.smtp.from=antladdie@163.com ## 開啟許可權認證 spring.mail.properties.mail.smtp.auth=true ## 郵件接收時間的限制 spring.mail.properties.mail.smtp.timeout=60000 ## 連線時間的限制 spring.mail.properties.mail.smtp.connectiontimeout=60000 ## 郵件傳送時間的限制(毫秒) spring.mail.properties.mail.smtp.writetimeout=60000 ## 日誌列印,郵件傳送過程的日誌會被輸出 spring.mail.properties.mail.debug=true
三:複雜郵件傳送 HTML+圖片資源+附件
@SpringBootTest class SpringbootJakartamailApplicationTests { //注入郵件傳送物件 @Autowired private JavaMailSender mailSender; @Test void testMailB() throws MessagingException { //建立複雜有限傳送物件 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true); messageHelper.setFrom("antladdie@163.com"); // 設定發件人郵箱(若配置預設郵箱則不用再設定) messageHelper.setTo("xiaofeng504@qq.com"); // 設定收件人郵箱 messageHelper.setCc("xiaofeng500@qq.com"); // 設定抄報人郵箱(可以不填寫) messageHelper.setBcc("575814158@qq.com"); // 設定密送人郵箱(可以不填寫) messageHelper.setSubject("繳費通知"); // 設定郵件主題 //獲取專案資源根目錄 resources/file 並準備資源 String rootPath = Objects.requireNonNull(SpringbootJakartamailApplicationTests.class.getClassLoader().getResource("file")).getFile(); FileSystemResource png = new FileSystemResource(new File(rootPath + "/ab.png")); FileSystemResource xls = new FileSystemResource(new File(rootPath + "/student.xls")); FileSystemResource mp3 = new FileSystemResource(new File(rootPath + "/mu.mp3")); FileSystemResource zip = new FileSystemResource(new File(rootPath + "/redis.zip")); //關於附件 資源 HTML 文字的設定 //設定附件 //設定一個 圖片附件 messageHelper.addAttachment(Objects.requireNonNull(png.getFilename()), png); //設定一個 excel附件 messageHelper.addAttachment(Objects.requireNonNull(xls.getFilename()), xls); //設定一個 mp3附件 messageHelper.addAttachment(Objects.requireNonNull(mp3.getFilename()), mp3); //設定一個 zip附件 不過傳送垃圾附件可能會被識別 554 HL:IHU 發信IP因傳送垃圾郵件或存在異常的連線行為 messageHelper.addAttachment(Objects.requireNonNull(zip.getFilename()), zip); //設定郵件內容 cid:資源id 在內容中引用資源 後面true代表是html內容 messageHelper.setText("<h2 style='color:#f00;'>欠費通知:您已欠費200元<img src='cid:p01' alt='' style='width:200px;height:50px;'></h2>", true); //設定資源 FileSystemResource resPng = new FileSystemResource(new File(rootPath + "/b.png")); messageHelper.addInline("p01",resPng); //傳送 mailSender.send(mimeMessage); } }
四:複雜郵件傳送使用Thymeleaf模板
使用模板和不使用模板沒太大區別,只是使用模板則將xx.html檔案渲染成String型別的字串文字再引用
<!--匯入thymeleaf座標--> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.0.12.RELEASE</version> </dependency>
<!doctype html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <h2 style='color:#f00;'> <span th:text="${message}"></span> <img src='cid:p01' alt='' style='width:200px;height:50px;'> </h2> </body> </html>
/*** * 模板解析方法,解析出一個String的html返回 * @return */ public String templateHtml(){ //設定類載入模板處理器 ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver(); //設定字首字尾 resolver.setPrefix("/file/"); resolver.setSuffix(".html"); //建立模板引擎處理器 TemplateEngine engine = new TemplateEngine(); //設定引擎使用的模板檔案 engine.setTemplateResolver(resolver); //建立Context來為模板設定填充資料 Context context = new Context(); //填充模板裡的資料 context.setVariable("message","欠費通知:你已經欠費100元"); //具體處理,把模板和資料合併成一個新的文字 //注:檔案我直接放在resources/templates檔案根目錄下,如果有多層目錄,需要寫明檔案位置(或者設定過字首和字尾) return engine.process("emailTemplate", context); }
5:結尾
講到這,已經對SpringBoot整合郵件傳送功能說完了,若想知道如何接收並解析郵件請參考我上面給出的部落格,使用javaMail傳送和接收郵件;不過話說回來在大部分公司裡使用SMTP、POP3協議傳送和接收是完全夠用的,不過我上次在專案組負責郵箱的傳送使用的是內網的企業郵箱,走的是Exchange協議,這個就和我們之前講的不一樣的,下一篇我將帶大家看看如何使用Exchange方法傳送郵件。