一:簡述
在日常中的工作中難免會遇到程序集成郵件發送功能、接收功能;此篇文章我將使用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方法百度搜特別多的案例發送郵件。
