Spring Boot整合郵件傳送

mo尘發表於2024-03-03

整合郵件傳送功能

Spring Boot 2.x整合了mail模組

image-20200614220248741

在pom.xml中引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

郵箱配置

一些必要的名詞解釋

  • 什麼是POP3、SMTP和IMAP?
    他們是三種郵件協議。簡單來說,POP3和IMAP是用來從伺服器上下載郵件的。SMTP適用於傳送或中轉信件時找到下一個目的地。所以我們傳送郵件應該使用SMTP協議。

  • 什麼是郵箱客戶端授權碼?
    郵箱客戶端授權碼是為了避免郵箱密碼被盜後,盜號者透過客戶端登入郵箱而設計的安防功能。

QQ郵箱配置

網頁登入QQ郵箱→設定→開啟相應服務並生成授權碼

image-20200614211908944

spring boot配置:

spring:
  mail:
    host: smtp.qq.com #傳送郵件伺服器
    username: xx@qq.com #QQ郵箱
    password: xxxxxxxxxxx #客戶端授權碼
    protocol: smtp #傳送郵件協議
    properties.mail.smtp.auth: true
    properties.mail.smtp.port: 465 #埠號465或587
    properties.mail.display.sendmail: aaa #可以任意
    properties.mail.display.sendname: bbb #可以任意
    properties.mail.smtp.starttls.enable: true
    properties.mail.smtp.starttls.required: true
    properties.mail.smtp.ssl.enable: true #開啟SSL
    default-encoding: utf-8

網易系(126/163/yeah)郵箱配置

網頁登入網易郵箱→設定→POP3/SMTP/IMAP

image-20200614212409282

spring boot配置:

spring:
  mail:
    host: smtp.126.com #傳送郵件伺服器
    username: xx@126.com #網易郵箱
    password: xxxxxxxx #客戶端授權碼
    protocol: smtp #傳送郵件協議
    properties.mail.smtp.auth: true
    properties.mail.smtp.port: 994 #465或者994
    properties.mail.display.sendmail: aaa #可以任意
    properties.mail.display.sendname: bbb #可以任意
    properties.mail.smtp.starttls.enable: true
    properties.mail.smtp.starttls.required: true
    properties.mail.smtp.ssl.enable: true #開啟SSL
    default-encoding: utf-8
    from: xx@126.com
  • 126郵箱SMTP伺服器地址:smtp.126.com
  • 163郵箱SMTP伺服器地址:smtp.163.com
  • yeah郵箱SMTP伺服器地址:smtp.yeah.net

傳送簡單的文字郵件

寫個郵件服務Service

@Service
public class MailService {
    // Spring官方提供的整合郵件服務的實現類,目前是Java後端傳送郵件和整合郵件服務的主流工具。
    @Resource
    private JavaMailSender mailSender;

    // 從配置檔案中注入發件人的姓名
    @Value("${spring.mail.username}")
    private String fromEmail;

    /**
     * 傳送文字郵件
     *
     * @param to      收件人
     * @param subject 標題
     * @param content 正文
     * @throws MessagingException
     */
    public void sendSimpleMail(String to, String subject, String content) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(fromEmail); // 發件人
        message.setTo(to);	
        message.setSubject(subject);
        message.setText(content);
        mailSender.send(message);
    }
}

在業務中呼叫該服務的方法即可

mailService.sendSimpleMail("xxxxxx@xx.com","普通文字郵件","普通文字郵件內容");

傳送html郵件

為了方便,在原來的Service裡直接新增一個方法

/**
 * 傳送html郵件
 */
public void sendHtmlMail(String to, String subject, String content) throws MessagingException {
    //注意這裡使用的是MimeMessage
    MimeMessage message = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true);
    helper.setFrom(from);
    helper.setTo(to);
    helper.setSubject(subject);
    //第二個引數:格式是否為html
    helper.setText(content, true);

    mailSender.send(message);
}

呼叫該方法,直接傳入html程式碼的字串作為正文引數:

mailService.sendHtmlMail("xxxxxx@xx.com","一封html測試郵件","
			"<div style=\"text-align: center;position: absolute;\" >\n"
            +"<h3>\"一封html測試郵件\"</h3>\n"
            + "<div>一封html測試郵件</div>\n"
            + "</div>");

像上面直接傳遞html字串傳送html郵件,在java類裡寫html程式碼總有點怪怪的,而且有很明顯的缺點,若是要用相同樣式傳送不同的內容,程式碼冗餘度就會增加;而且若是需要傳送一個複雜的html頁面,程式碼看起來就一團亂麻,而且不方便調整郵件的樣式。

我們希望html和java分離開,在java裡就只管java,頁面程式碼乖乖到頁面檔案裡面,需要時直接調取該頁面檔案,整合模板引擎就是一個不錯的解決方案。👇

傳送基於模板的郵件(以模板引擎freemarker為例)

該方法本質上還是傳送html郵件,只不過是有一個把模板轉換成html字串的過程,thymeleaf也可以實現。這個方法還能使你的系統發出的郵件更加美觀。

image-20200614213422214

說明:這裡不詳細介紹freemarker的內容,在這裡只描述它的一個使用場景——生成電子郵件,想要進一步瞭解freemarker請行學習

新增依賴:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

springboot配置

spring:
  freemarker:
    cache: false # 快取配置 開發階段應該配置為false 因為經常會改
    suffix: .html # 模版字尾名 預設為ftl
    charset: UTF-8 # 檔案編碼
    template-loader-path: classpath:/templates/  # 存放模板的資料夾,以resource資料夾為相對路徑

在存放模板的資料夾下寫一個html模板

image-20200614170329668

內容如下:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8" />
    <title>freemarker簡單示例</title>
</head>
<body>
<h1>Hello Freemarker</h1>
    <div>My name is ${myname}</div>
</body>
</html>

仍然為了方便,在原來的Service裡直接新增程式碼

@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;

@Test
public void sendTemplateMail(String to, String subject, String template) throws IOException, TemplateException, MessagingException {
    // 獲得模板
    Template template = freeMarkerConfigurer.getConfiguration().getTemplate(template);
	// 使用Map作為資料模型,定義屬性和值
    Map<String,Object> model = new HashMap<>();
    model.put("myname","ZYF");
    // 傳入資料模型到模板,替代模板中的佔位符,並將模板轉化為html字串
    String templateHtml = FreeMarkerTemplateUtils.processTemplateIntoString(template,model);
	// 該方法本質上還是傳送html郵件,呼叫之前傳送html郵件的方法
    this.sendHtmlMail(to, subject, templateHtml);
}

要用的時候呼叫即可

mailService.sendTemplateMail("xxxxx@xx.com", "基於模板的html郵件", "fremarkertemp.html");

傳送帶附件的郵件

話不多說上程式碼:

/**
 * 傳送帶附件的郵件
 */
public void sendAttachmentsMail(String to, String subject, String content, String filePath) throws MessagingException {
    MimeMessage message = mailSender.createMimeMessage();
    //要帶附件第二個引數設為true
    MimeMessageHelper helper = new MimeMessageHelper(message, true);
    helper.setFrom(fromEmail);
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(content, true);

    FileSystemResource file = new FileSystemResource(new File(filePath));
    String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
    helper.addAttachment(fileName, file);

    mailSender.send(message);
}

呼叫時傳入檔案路徑:

String filePath = "D:\\projects\\springboot\\template.png";
mailService.sendAttachmentsMail("xxxx@xx.com", "帶附件的郵件", "郵件中有附件", filePath);

郵箱服務實戰

這是我某次實踐中郵箱服務的運用,目的是為了能夠定時傳送郵件,由於傳送郵件是耗時操作,為了不妨礙系統處理使用者請求,新增了@Async註解,定時任務將會在獨立的執行緒中被執行,下面放上鍊接:

ScheduleJob.java

MailServiceImpl.java

EmailTemplate.html

application.yml

我的踩坑記錄

郵件伺服器連線失敗

org.springframework.mail.MailSendException: Mail server connection failed; 
...
nested exception is:
  java.net.UnknownHostException: smtp.163.com 
 ...
  1. 網路問題

    控制檯輸入ping smtp.163.com看看是否能ping通;

  2. 配置問題

    檢查一下application.yml的郵件伺服器配置有沒有拼寫或格式錯誤(比如多按了一個空格);

    開啟SSL時使用587埠時是無法連線QQ郵件伺服器的,請使用465埠

授權失敗

org.springframework.mail.MailAuthenticationException: Authentication failed;nested exception is
javax.mail.AuthenticationFailedException: 550 User has no permission
...

這個坑是我在教同學時遇到的

按照上文去開啟郵箱的stmp服務即可解決

訊息傳送失敗

org.springframework.mail.MailSendException: Failed messages: 
 
com.sun.mail.smtp.SMTPSendFailedException: 554 DT:SPM 163 smtp11,D8CowACX7CmSHB5b3SrlCA--.26635S3 
 
1528700051,please see http://mail.163.com/help/help_spam_16.htm?ip=182.138.102.204&hostid=smtp11&time=1528700051
...

點進報錯資訊提供的網址瞧瞧,是網易官方的退信程式碼說明,也就是說我們傳送的訊息被退回來了:

image-20200614214050517

注意到報錯資訊的退信程式碼554,看看554是啥來頭:

image-20200614214201544

原來是被識別為垃圾郵件了,檢查一下郵件的主題及內容,使用合法資訊,我當時是因為郵件內容包含test測試這些字眼所以被攔截了。

總結

spring boot整合郵件服務並不難,就是踩到坑的時候挺煩的,但這也是學習新知識所必須經歷的

相關文章