SpringBoot實現郵件傳送及其工具類封裝
SpringBoot郵件傳送工具類
環境:JDK8、IDEA 依賴:SpringBoot-1.5.10、spring-boot-starter-mail、spring-boot-starter-thymeleaf、spring-boot-starter-web
說明:當在本部落格裡面遇見不清楚的地方時,請移步其他資源補充相關知識,這裡只是介紹我封裝的一個郵件傳送工具類而已(沒有考慮效能優化,如果讀者有建議可以留言,而且測試用例沒有很全面,難免可能會有問題),沒有很詳細的郵件相關知識的介紹,望見諒。
話不多說,show the code
專案總體目錄結構:
1、 pom.xml 依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion >
<groupId>me.chuyf</groupId>
<artifactId>mail</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mail</name>
<description>郵件服務</description>
<parent>
<groupId >org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!--郵件依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!--用於實現模板郵件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、郵件的基本設定
使用application.yml配置郵件的基本設定
spring:
mail:
host: 郵箱服務商的protocol伺服器主機 #smtp.qq.com
protocol: 郵件協議 #smtp
default-encoding: UTF-8
username: 指定郵箱服務商的郵箱賬號 #7557*****@qq.com
password: 郵箱賬號密碼或者三方登入授權碼 #jwgteykojlf*****
test-connection: true
thymeleaf:
cache: false #開發時關閉快取
3、基本的服務架子
package me.chuyf.mail.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
/**
* 郵件服務,實現簡單文字郵件,HTML檔案和附件郵件,模板郵件的傳送
* 支援的環境:JDK 1.8,SpringBoot 1.5.10,需要 mail-start,需要 thymeleaf 模板支援
*/
@Service
public class MailService {
//預設編碼
public static final String DEFAULT_ENCODING = "UTF-8";
//記錄日誌
private Logger logger = LoggerFactory.getLogger(MailService.class);
//本身郵件的傳送者,來自郵件配置
@Value("${spring.mail.username}")
private String userName;
//模板引擎解析物件,用於解析模板
@Autowired
private TemplateEngine templateEngine;
//郵件傳送的物件,用於郵件傳送
@Autowired
private JavaMailSender mailSender;
}
4、普通文字郵件的傳送實現
傳送普通文字郵件的大致流程如下: 1、判斷是否有附件,如果有附件,那麼處理的方式是不一樣的(文字和二進位制的區別) 2、如果是簡單文字郵件,處理郵件傳送的基本事物 3、如果是帶附件的郵件,需要對附件做處理,同時處理郵件的基本事物 4、傳送郵件 這個文字郵件傳送可以實現的功能如下: 1、多收件人、多抄送人、多密送人、可帶附件 2、請注意附件缺失不會導致郵件傳送失敗!請注意附件處理流程細節,免得出bug 不多廢話,上程式碼
/**
* 傳送一個簡單的文字郵件,可以附帶附件:文字郵件傳送的基本方法
* @param subject:郵件主題,即郵件的郵件名稱
* @param content:郵件內容
* @param toWho:需要傳送的人
* @param ccPeoples:需要抄送的人
* @param bccPeoples:需要密送的人
* @param attachments:需要附帶的附件,附件請保證一定要存在,否則將會被忽略掉
*/
private void sendSimpleTextMailActual(String subject,String content,String[] toWho,String[] ccPeoples,String[] bccPeoples,String[] attachments){
//檢驗引數:郵件主題、收件人、郵件內容必須不為空才能夠保證基本的邏輯執行
if(subject == null||toWho == null||toWho.length == 0||content == null){
logger.error("郵件-> {} 無法繼續執行,因為缺少基本的引數:郵件主題、收件人、郵件內容",subject);
throw new RuntimeException("模板郵件無法繼續傳送,因為缺少必要的引數!");
}
logger.info("開始傳送簡單文字郵件:主題->{},收件人->{},抄送人->{},密送人->{},附件->{}",subject,toWho,ccPeoples,bccPeoples,attachments);
//附件處理,需要處理附件時,需要使用二進位制資訊,使用 MimeMessage 類來進行處理
if(attachments != null&&attachments.length > 0){
try{
//附件處理需要進行二進位制傳輸
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true,DEFAULT_ENCODING);
//設定郵件的基本資訊:這些函式都會在後面列出來
boolean continueProcess = handleBasicInfo(helper,subject,content,toWho,ccPeoples,bccPeoples,false);
//如果處理基本資訊出現錯誤
if(!continueProcess){
logger.error("郵件基本資訊出錯: 主題->{}",subject);
return;
}
//處理附件
handleAttachment(helper,subject,attachments);
//傳送該郵件
mailSender.send(mimeMessage);
logger.info("傳送郵件成功: 主題->{}",subject);
}catch(MessagingException e){
e.printStackTrace();
logger.error("傳送郵件失敗: 主題->{}",subject);
}
}else{
//建立一個簡單郵件資訊物件
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
//設定郵件的基本資訊
handleBasicInfo(simpleMailMessage,subject,content,toWho,ccPeoples,bccPeoples);
//傳送郵件
mailSender.send(simpleMailMessage);
logger.info("傳送郵件成功: 主題->{}",subject,toWho,ccPeoples,bccPeoples,attachments);
}
}
/**
* 處理二進位制郵件的基本資訊,比如需要帶附件的文字郵件、HTML檔案、圖片郵件、模板郵件等等
*
* @param mimeMessageHelper:二進位制檔案的包裝類
* @param subject:郵件主題
* @param content:郵件內容
* @param toWho:收件人
* @param ccPeoples:抄送人
* @param bccPeoples:暗送人
* @param isHtml:是否是HTML檔案,用於區分帶附件的簡單文字郵件和真正的HTML檔案
*
* @return :返回這個過程中是否出現異常,當出現異常時會取消郵件的傳送
*/
private boolean handleBasicInfo(MimeMessageHelper mimeMessageHelper,String subject,String content,String[] toWho,String[] ccPeoples,String[] bccPeoples,boolean isHtml){
try{
//設定必要的郵件元素
//設定發件人
mimeMessageHelper.setFrom(userName);
//設定郵件的主題
mimeMessageHelper.setSubject(subject);
//設定郵件的內容,區別是否是HTML郵件
mimeMessageHelper.setText(content,isHtml);
//設定郵件的收件人
mimeMessageHelper.setTo(toWho);
//設定非必要的郵件元素,在使用helper進行封裝時,這些資料都不能夠為空
if(ccPeoples != null)
//設定郵件的抄送人:MimeMessageHelper # Assert.notNull(cc, "Cc address array must not be null");
mimeMessageHelper.setCc(ccPeoples);
if(bccPeoples != null)
//設定郵件的密送人:MimeMessageHelper # Assert.notNull(bcc, "Bcc address array must not be null");
mimeMessageHelper.setBcc(bccPeoples);
return true;
}catch(MessagingException e){
e.printStackTrace();
logger.error("郵件基本資訊出錯->{}",subject);
}
return false;
}
/**
* 用於填充簡單文字郵件的基本資訊
*
* @param simpleMailMessage:文字郵件資訊物件
* @param subject:郵件主題
* @param content:郵件內容
* @param toWho:收件人
* @param ccPeoples:抄送人
* @param bccPeoples:暗送人
*/
private void handleBasicInfo(SimpleMailMessage simpleMailMessage,String subject,String content,String[] toWho,String[] ccPeoples,String[] bccPeoples){
//設定發件人
simpleMailMessage.setFrom(userName);
//設定郵件的主題
simpleMailMessage.setSubject(subject);
//設定郵件的內容
simpleMailMessage.setText(content);
//設定郵件的收件人
simpleMailMessage.setTo(toWho);
//設定郵件的抄送人
simpleMailMessage.setCc(ccPeoples);
//設定郵件的密送人
simpleMailMessage.setBcc(bccPeoples);
}
/**
* 用於處理附件資訊,附件需要 MimeMessage 物件
*
* @param mimeMessageHelper:處理附件的資訊物件
* @param subject:郵件的主題,用於日誌記錄
* @param attachmentFilePaths:附件檔案的路徑,該路徑要求可以定位到本機的一個資源
*/
private void handleAttachment(MimeMessageHelper mimeMessageHelper,String subject,String[] attachmentFilePaths){
//判斷是否需要處理郵件的附件
if(attachmentFilePaths != null&&attachmentFilePaths.length > 0){
FileSystemResource resource;
String fileName;
//迴圈處理郵件的附件
for(String attachmentFilePath : attachmentFilePaths){
//獲取該路徑所對應的檔案資源物件
resource = new FileSystemResource(new File(attachmentFilePath));
//判斷該資源是否存在,當不存在時僅僅會列印一條警告日誌,不會中斷處理程式。
// 也就是說在附件出現異常的情況下,郵件是可以正常傳送的,所以請確定你傳送的郵件附件在本機存在
if(!resource.exists()){
logger.warn("郵件->{} 的附件->{} 不存在!",subject,attachmentFilePath);
//開啟下一個資源的處理
continue;
}
//獲取資源的名稱
fileName = resource.getFilename();
try{
//新增附件
mimeMessageHelper.addAttachment(fileName,resource);
}catch(MessagingException e){
e.printStackTrace();
logger.error("郵件->{} 新增附件->{} 出現異常->{}",subject,attachmentFilePath,e.getMessage());
}
}
}
}
5、模板HTML郵件的傳送實現
發現問題如下: 模板引擎解析HTML檔案時,會將圖片解析為可以直接訪問的伺服器檔案路徑,但是郵件是處於兩個不同的網路,也就是說模板引擎解析的HTML檔案裡面的圖片的路徑是沒有辦法響應的,幸好郵件可以設定內聯的圖片資源,所以可以通過一定的方法來實現對模板HTML檔案裡面的圖片連結進行解析,達到可以在模板郵件裡面新增圖片的目的,為此需要自己封裝一些操作,我的一個解決方式在下面的函式裡面。 不再廢話,上程式碼
需要一個支援郵件圖片內聯和本地資源之間轉換的支撐類,Service的內部類
/**
* 用於支撐HTML內嵌圖片的支援類,擁有可以傳輸內聯圖片的全部基本資訊
*/
public final static class ImageResource {
//佔位符的字首符號,用於替換字串定位,比如:image1 在模板檔案裡面需要寫成 #image1
public static final String PLACEHOLDERPREFIX = "#";
//用於檔案區分,實現圖片檔案內聯郵件傳送
private final String id;
//這個圖片需要填充到那個地方去,這個地方是一個標識,為了和其他標籤區別開來,使用字首加上識別符號來進行區分,比如 :#imageOrigin
private final String placeholder;
//圖片的檔案路徑,該檔案路徑必須是本機檔案系統的絕對路徑,即可以直接 new File 的檔案系統路徑
private final String imageFilePath;
public ImageResource(String placeholder,String imageFilePath){
this.placeholder = placeholder;
this.imageFilePath = imageFilePath;
//自動生成id,用於區分圖片檔案
this.id = String.valueOf(System.nanoTime());
}
public String getId(){
return id;
}
public String getPlaceholder(){
return placeholder;
}
public String getImageFilePath(){
return imageFilePath;
}
@Override
public String toString(){
return "ImageResource{" + "id=" + id + ", placeholder='" + placeholder + '\'' + ", imageFilePath='" + imageFilePath + '\'' + '}';
}
現在可以來實現模板HTML郵件的傳送工具開發了
/**
* 可以用來發送帶有圖片的HTML模板郵件
*
* @param subject:郵件主題
* @param toWho:收件人
* @param ccPeoples:抄送人
* @param bccPeoples:暗送人
* @param attachments:附件
* @param templateName:模板名稱
* @param context:模板解析需要的資料
* @param imageResourceSet:圖片資源的資源物件
*/
private void sendHtmlTemplateMailActual(String subject,String[] toWho,String[] ccPeoples,String[] bccPeoples,String[] attachments,String templateName,Context context,Set<ImageResource> imageResourceSet){
//檢驗引數:郵件主題、收件人、模板名稱必須不為空才能夠保證基本的邏輯執行
if(subject == null||toWho == null||toWho.length == 0||templateName == null){
logger.error("郵件-> {} 無法繼續執行,因為缺少基本的引數:郵件主題、收件人、模板名稱",subject);
throw new RuntimeException("模板郵件無法繼續傳送,因為缺少必要的引數!");
}
//日誌這個郵件的基本資訊
logger.info("傳送HTML模板郵件:主題->{},收件人->{},抄送人->{},密送人->{},附件->{},模板名稱->{},模板解析引數->{},圖片資源->{})",subject,toWho,ccPeoples,bccPeoples,attachments,templateName,context,imageResourceSet);
try{
//context不能夠為空,需要進行檢查
if(context == null){
context = new Context();
logger.info("郵件->{} 的context為空!",subject);
}
//模板引擎處理模板獲取到HTML字串,這裡會可能會丟擲一個繼承於RuntimeException的模板引擎異常
String content = templateEngine.process(templateName,context);
MimeMessage mimeMessage = mailSender.createMimeMessage();
//預設編碼為UTF-8
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true,DEFAULT_ENCODING);
//處理內聯的圖片資源的佔位轉換
content = handleInLineImageResourceContent(helper,subject,content,imageResourceSet);
logger.info("解析郵件結果->{}",content);
//處理基本資訊
boolean continueProcess = handleBasicInfo(helper,subject,content,toWho,ccPeoples,bccPeoples,true);
if(!continueProcess){
logger.error("郵件基本資訊出錯:主題->{}",subject);
return;
}
//內聯資源的資源附加,這個必須要放置在設定基本資訊的操作後面,或者是全部內容解析完畢後才可以,不能邊解析,邊佔位
handleInLineImageResource(helper,subject,imageResourceSet);
//處理附件
handleAttachment(helper,subject,attachments);
//傳送該郵件
mailSender.send(mimeMessage);
logger.info("傳送郵件成功:主題->{}",subject);
}catch(MessagingException e){
e.printStackTrace();
logger.error("傳送郵件失敗:郵件主題->{}",subject);
}
}
/**
* 處理內嵌圖片的模板HTML郵件,返回一個已經修改過後的HTML字串
*
* @param mimeMessageHelper:郵件資訊包裝類
* @param subject:郵件主題
* @param originContent:模板引擎所解析出來的原始HTML郵件
* @param imageResourceSet:圖片資源集合,用於字符集站位填充
*
* @return :返回處理後的郵件內容
*/
private String handleInLineImageResourceContent(MimeMessageHelper mimeMessageHelper,String subject,String originContent,Set<ImageResource> imageResourceSet){
//處理內嵌的HTML圖片檔案
if(imageResourceSet != null&&imageResourceSet.size() > 0){
//資源的佔位符ID
String rscId;
//資源的路徑
String resourcePath = null;
//圖片的位置資訊
String placeHolder;
//圖片資原始檔
FileSystemResource resource;
for(ImageResource imageResource : imageResourceSet){
//獲取圖片資源的基本資訊
rscId = imageResource.getId();
placeHolder = imageResource.getPlaceholder();
resourcePath = imageResource.getImageFilePath();
resource = new FileSystemResource(new File(resourcePath));
//判斷圖片資源是否存在
if(!resource.exists()){
logger.warn("郵件->{} 內聯圖片->{} 找不到",subject,resourcePath);
continue;
}
//替換圖片資源在HTML中的位置
originContent = originContent.replace("\"" + ImageResource.PLACEHOLDERPREFIX + placeHolder + "\"","\'cid:" + rscId + "\'");
}
}
return originContent;
}
/**
* 填充文字資料,因為資料填充必須在設定基本資料後面進行,所以講內容和資料的填充進行分離
*
* @param mimeMessageHelper
* @param subject:郵件主題,用於日誌記錄
* @param imageResourceSet:資源
*/
private void handleInLineImageResource(MimeMessageHelper mimeMessageHelper,String subject,Set<ImageResource> imageResourceSet){
if(imageResourceSet != null&&imageResourceSet.size() > 0){
FileSystemResource resource;
for(ImageResource imageResource : imageResourceSet){
resource = new FileSystemResource(new File(imageResource.getImageFilePath()));
if(!resource.exists()){
logger.warn("郵件->{} 的內聯圖片檔案->{} 不存在!",subject,imageResource);
continue;
}
try{
//新增內聯資源
mimeMessageHelper.addInline(imageResource.getId(),resource);
}catch(MessagingException e){
e.printStackTrace();
logger.error("郵件->{} 的內聯圖片檔案->{} 新增錯誤!",subject,imageResource);
}
}
}
}
/**
* 傳入的引數不能夠為null
*
* @param args
*
* @return
*/
private boolean assertNotNull(Object... args){
return Arrays.stream(args).noneMatch(Objects::isNull);
}
6、接下來就是激動人心的測試環節了
注意免費郵箱設定了每天傳送郵件的限制數,所以在測試的時候注意控制你的郵件傳送量,有時候報錯資訊裡面有連結時,多半是你的郵箱賬號暫時被封禁了,為此我使用 4 個郵箱賬號用於測試。測試前請確保你的配置是正確的。 是騾子是馬,該拉出來溜溜了
測試簡單文字的那個郵件傳送,篇幅和機會有限,測試最複雜的一個情況
package me.chuyf.mail.service;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.test.context.junit4.SpringRunner;
import javax.mail.MessagingException;
import static org.junit.Assert.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MailServiceTest {
@Autowired
private MailService mailService;
@Autowired
private JavaMailSender mailSender;
@Value("${spring.mail.username}")
private String userName;
//請把如下的資訊寫成你自己的郵箱資訊
private String mailQQ = "*****@qq.com";
private String mail163 = "*****@163.com";
private String mail139 = "*****@139.com";
private String mailOutLook = "*****@Outlook.com";
private String zipFile = "E:\\HBuilder\\plugins\\com.pandora.templates.ui_1.0.0.201806081745\\templates\\project\\web.zip";
private String pngFile = "F:\\sdm\\Screenshot_2018-09-06-22-33-58-125_com.tencent.mo.png";
private String jpgFile = "F:\\sdm\\sdm.jpg";
private String content = "簡單的文字內容";
@Test
/**
* 測試傳送文字郵件的介面
*/
public void sendSimpleTextMail() throws InterruptedException{
//為我的Outlook郵箱傳送一封郵件,抄送我的139郵箱,密送QQ郵箱,帶著三個附件
mailService.sendSimpleTextMail("測試帶附件、有抄送、密送的多收件人簡單文字檔案",content,new String[]{mailOutLook},
new String[]{mail139},new String[]{mailQQ},new String[]{zipFile,jpgFile,pngFile});
}
}
控制檯輸出如下:
我的郵箱截圖如下:
隨便開啟密送的QQ郵箱:
測試模板HTML檔案的傳送,先建立一個模板!
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>郵件模板</title>
</head>
<body>
<h2>你好!看見請回復!</h2>
<p>姓名 <span th:text="${name}"></span></p><br/>
<p>年齡 <span th:text="${age}"></span></p><br/>
<p>性別 <span th:text="${sex}"></span></p><br/>
<img src="#sdm" width="100px" height="100px"/><br/>
<img src="#cyf" width="100px" height="100px"/><br/>
</body>
</html>
測試程式碼如下:
@Test
/***
* 測試模板HTML郵件
*/
public void sendHtmlTemplateMail() throws MessagingException{
//模板解析的上下文
Context context = new Context();
context.setVariable("name","楚雲飛");
context.setVariable("age","20");
context.setVariable("sex","男");
//設定內聯的圖片資源
ImageResource imageResource = new ImageResource("sdm",jpgFile);
ImageResource imageResource1 = new ImageResource("cyf",pngFile);
Set<ImageResource> imageResources = new HashSet<>();
imageResources.add(imageResource);
imageResources.add(imageResource1);
mailService.sendHtmlTemplateMail("測試模板引擎的HTML解析",new String[]{mail163},
new String[]{mailOutLook},new String[]{mail139},new String[]{zipFile},
"mailTest",context,imageResources);
}
控制檯輸出如下:
我的郵箱狀況:
開啟其中一封郵件: