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);
    }

控制檯輸出如下:
這裏寫圖片描述

我的郵箱狀況:

這裏寫圖片描述

打開其中一封郵件:
這裏寫圖片描述

7、可能會有坑,填吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章