SpringBoot實現(xiàn)郵件發(fā)送的完整解決方案(附件發(fā)送、內嵌圖片與中文亂碼處理)
一、技術選型與依賴配置
1.1 Maven 依賴配置(JDK 1.8 兼容)
<?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>com.xxx</groupId>
<artifactId>springboot-email-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>springboot-email-demo</name>
<description>Spring Boot 郵件發(fā)送完整解決方案</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</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>
<dependencies>
<!-- Spring Boot Mail Starter(Jakarta Mail 1.6.2,JDK 1.8 兼容) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Spring Boot Web(可選,用于提供接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok(簡化代碼,可選) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test -->
<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>
重要說明:
- Spring Boot 2.7.18 內置 Jakarta Mail 1.6.2,完美兼容 JDK 1.8
- 如果使用 Spring Boot 3.x,需要 JDK 17+,且郵件API包名從
javax.mail變?yōu)?jakarta.mail
1.2 application.yml 配置
spring:
mail:
# SMTP服務器配置
host: smtp.qq.com
port: 587
username: your_email@qq.com
password: your_smtp_authorization_code # 注意:是授權碼,不是郵箱密碼
# 編碼配置(根治中文亂碼)
default-encoding: UTF-8
# 協(xié)議配置
protocol: smtp
# TLS加密配置
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
ssl:
protocols: TLSv1.2
connectiontimeout: 10000
timeout: 10000
writetimeout: 10000
# 服務器端口
server:
port: 8080
常用SMTP服務器配置參考:
| 郵箱服務商 | SMTP地址 | 端口 | 認證方式 |
|---|---|---|---|
| QQ郵箱 | smtp.qq.com | 587 (TLS) / 465 (SSL) | 授權碼 |
| 163郵箱 | smtp.163.com | 465 (SSL) / 25 | 授權碼 |
| Gmail | smtp.gmail.com | 587 (TLS) | 應用專用密碼 |
| Outlook | smtp.office365.com | 587 (TLS) | 郵箱密碼或應用密碼 |
二、核心工具類實現(xiàn)
2.1 郵件發(fā)送工具類
package com.xxx.email.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
* 郵件發(fā)送工具類
* <p>
* 支持功能:
* 1. 純文本郵件發(fā)送
* 2. HTML郵件發(fā)送
* 3. 帶附件郵件發(fā)送(支持中文附件名)
* 4. 內嵌圖片郵件發(fā)送(圖片顯示在正文中)
* 5. 混合場景:同時包含內嵌圖片和附件
* </p>
*
* @author auth
* @since 2026-02-13
*/
@Slf4j
@Component
public class EmailUtil {
@Autowired
private JavaMailSender mailSender;
/**
* 發(fā)送純文本郵件
*
* @param from 發(fā)件人郵箱
* @param to 收件人郵箱(支持多個,用逗號分隔)
* @param subject 郵件主題
* @param content 郵件內容
*/
public void sendSimpleEmail(String from, String to, String subject, String content) {
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(to.split(","));
message.setSubject(subject);
message.setText(content);
mailSender.send(message);
log.info("純文本郵件發(fā)送成功!收件人:{}, 主題:{}", to, subject);
} catch (Exception e) {
log.error("純文本郵件發(fā)送失??!收件人:{}, 主題:{}, 錯誤信息:{}", to, subject, e.getMessage(), e);
throw new RuntimeException("郵件發(fā)送失?。? + e.getMessage(), e);
}
}
/**
* 發(fā)送純文本帶附件郵件
* @param from 發(fā)件人郵箱
* @param to 收件人郵箱(支持多個,用逗號分隔)
* @param subject 郵件主題
* @param content 郵件主題
* @param attachments 附件文件列表
*/
public void sendSimpleEmailAttachments(String from, String to, String subject, String content, List<File> attachments) {
try {
MimeMessage message = mailSender.createMimeMessage();
// 第二個參數(shù)true表示創(chuàng)建multipart消息
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to.split(","));
helper.setSubject(subject);
helper.setText(content);
// 添加附件(自動處理中文文件名)
if (attachments != null && !attachments.isEmpty()) {
for (File file : attachments) {
if (file != null && file.exists()) {
helper.addAttachment(encodeFileName(file.getName()), file);
log.debug("添加附件:{}", file.getName());
} else {
log.warn("附件文件不存在,跳過:{}", file != null ? file.getName() : "null");
}
}
}
mailSender.send(message);
log.info("純文本帶附件郵件發(fā)送成功!收件人:{}, 主題:{}, 附件數(shù)量:{}", to, subject,
attachments != null ? attachments.size() : 0);
} catch (Exception e) {
log.error("純文本帶附件郵件發(fā)送失?。∈占耍簕}, 主題:{}, 錯誤信息:{}", to, subject, e.getMessage(), e);
throw new RuntimeException("郵件發(fā)送失?。? + e.getMessage(), e);
}
}
/**
* 發(fā)送HTML郵件
*
* @param from 發(fā)件人郵箱
* @param to 收件人郵箱
* @param subject 郵件主題
* @param htmlContent HTML格式的郵件內容
*/
public void sendHtmlEmail(String from, String to, String subject, String htmlContent) {
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to.split(","));
helper.setSubject(encodeText(subject));
helper.setText(htmlContent, true);
mailSender.send(message);
log.info("HTML郵件發(fā)送成功!收件人:{}, 主題:{}", to, subject);
} catch (Exception e) {
log.error("HTML郵件發(fā)送失?。∈占耍簕}, 主題:{}, 錯誤信息:{}", to, subject, e.getMessage(), e);
throw new RuntimeException("郵件發(fā)送失?。? + e.getMessage(), e);
}
}
/**
* 發(fā)送帶附件的郵件(支持中文附件名)
*
* @param from 發(fā)件人郵箱
* @param to 收件人郵箱
* @param subject 郵件主題
* @param htmlContent HTML格式的郵件內容
* @param attachments 附件文件列表
*/
public void sendEmailWithAttachments(String from, String to, String subject,
String htmlContent, List<File> attachments) {
try {
MimeMessage message = mailSender.createMimeMessage();
// 第二個參數(shù)true表示創(chuàng)建multipart消息
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to.split(","));
helper.setSubject(encodeText(subject));
helper.setText(htmlContent, true);
// 添加附件(自動處理中文文件名)
if (attachments != null && !attachments.isEmpty()) {
for (File file : attachments) {
if (file != null && file.exists()) {
helper.addAttachment(encodeFileName(file.getName()), file);
log.debug("添加附件:{}", file.getName());
} else {
log.warn("附件文件不存在,跳過:{}", file != null ? file.getName() : "null");
}
}
}
mailSender.send(message);
log.info("帶附件郵件發(fā)送成功!收件人:{}, 主題:{}, 附件數(shù)量:{}", to, subject,
attachments != null ? attachments.size() : 0);
} catch (Exception e) {
log.error("帶附件郵件發(fā)送失敗!收件人:{}, 主題:{}, 錯誤信息:{}", to, subject, e.getMessage(), e);
throw new RuntimeException("郵件發(fā)送失?。? + e.getMessage(), e);
}
}
/**
* 發(fā)送帶內嵌圖片的郵件(圖片顯示在正文中)
*
* @param from 發(fā)件人郵箱
* @param to 收件人郵箱
* @param subject 郵件主題
* @param htmlContent HTML格式的郵件內容(通過cid:xxx引用圖片)
* @param inlineImages 內嵌圖片列表
*/
public void sendEmailWithInlineImages(String from, String to, String subject,
String htmlContent, List<InlineImage> inlineImages) {
try {
MimeMessage message = mailSender.createMimeMessage();
// 必須使用true創(chuàng)建multipart消息
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to.split(","));
helper.setSubject(encodeText(subject));
helper.setText(htmlContent, true);
// 添加內嵌圖片
if (inlineImages != null && !inlineImages.isEmpty()) {
for (InlineImage inlineImage : inlineImages) {
if (inlineImage != null && inlineImage.getFile() != null && inlineImage.getFile().exists()) {
DataSource dataSource = new FileDataSource(inlineImage.getFile());
helper.addInline(inlineImage.getContentId(), dataSource);
log.debug("添加內嵌圖片:CID={}, 文件名:{}",
inlineImage.getContentId(), inlineImage.getFile().getName());
} else {
log.warn("內嵌圖片文件不存在,跳過:CID={}",
inlineImage != null ? inlineImage.getContentId() : "null");
}
}
}
mailSender.send(message);
log.info("內嵌圖片郵件發(fā)送成功!收件人:{}, 主題:{}, 圖片數(shù)量:{}", to, subject,
inlineImages != null ? inlineImages.size() : 0);
} catch (Exception e) {
log.error("內嵌圖片郵件發(fā)送失?。∈占耍簕}, 主題:{}, 錯誤信息:{}", to, subject, e.getMessage(), e);
throw new RuntimeException("郵件發(fā)送失?。? + e.getMessage(), e);
}
}
/**
* 發(fā)送復雜郵件(同時包含內嵌圖片和附件)
*
* @param from 發(fā)件人郵箱
* @param to 收件人郵箱
* @param subject 郵件主題
* @param htmlContent HTML格式的郵件內容
* @param inlineImages 內嵌圖片列表
* @param attachments 附件文件列表
*/
public void sendComplexEmail(String from, String to, String subject,
String htmlContent, List<InlineImage> inlineImages,
List<File> attachments) {
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to.split(","));
helper.setSubject(encodeText(subject));
helper.setText(htmlContent, true);
// 添加內嵌圖片
if (inlineImages != null && !inlineImages.isEmpty()) {
for (InlineImage inlineImage : inlineImages) {
if (inlineImage != null && inlineImage.getFile() != null && inlineImage.getFile().exists()) {
DataSource dataSource = new FileDataSource(inlineImage.getFile());
helper.addInline(inlineImage.getContentId(), dataSource);
log.debug("添加內嵌圖片:CID={}, 文件名:{}",
inlineImage.getContentId(), inlineImage.getFile().getName());
}
}
}
// 添加附件
if (attachments != null && !attachments.isEmpty()) {
for (File file : attachments) {
if (file != null && file.exists()) {
helper.addAttachment(encodeFileName(file.getName()), file);
log.debug("添加附件:{}", file.getName());
}
}
}
mailSender.send(message);
log.info("復雜郵件發(fā)送成功!收件人:{}, 主題:{}, 圖片數(shù)量:{}, 附件數(shù)量:{}",
to, subject, inlineImages != null ? inlineImages.size() : 0,
attachments != null ? attachments.size() : 0);
} catch (Exception e) {
log.error("復雜郵件發(fā)送失??!收件人:{}, 主題:{}, 錯誤信息:{}", to, subject, e.getMessage(), e);
throw new RuntimeException("郵件發(fā)送失敗:" + e.getMessage(), e);
}
}
/**
* 編碼文本(處理中文亂碼)
* 使用RFC 2047標準編碼
*
* @param text 原始文本
* @return 編碼后的文本
*/
private String encodeText(String text) {
if (text == null || text.isEmpty()) {
return text;
}
try {
// 使用Base64編碼(B編碼),UTF-8字符集
return MimeUtility.encodeText(text, "UTF-8", "B");
} catch (UnsupportedEncodingException e) {
log.warn("文本編碼失敗,返回原始文本:{}", text, e);
return text;
}
}
/**
* 編碼文件名(處理中文附件名亂碼)
*
* @param fileName 原始文件名
* @return 編碼后的文件名
*/
private String encodeFileName(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return fileName;
}
try {
// 使用Base64編碼(B編碼),UTF-8字符集
return MimeUtility.encodeText(fileName, "UTF-8", "B");
} catch (UnsupportedEncodingException e) {
log.warn("文件名編碼失敗,返回原始文件名:{}", fileName, e);
return fileName;
}
}
/**
* 內嵌圖片包裝類
*/
public static class InlineImage {
/**
* 圖片文件
*/
private File file;
/**
* Content-ID(用于HTML中通過cid:引用)
*/
private String contentId;
public InlineImage(File file, String contentId) {
this.file = file;
this.contentId = contentId;
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public String getContentId() {
return contentId;
}
public void setContentId(String contentId) {
this.contentId = contentId;
}
}
}
工具類設計亮點:
- 完整的日志記錄:成功/失敗均有詳細日志,便于排查問題
- 異常處理:所有異常統(tǒng)一捕獲并記錄,重新拋出為運行時異常
- 中文亂碼根治:通過
MimeUtility.encodeText()統(tǒng)一處理主題和附件名 - 參數(shù)校驗:對文件存在性、null值進行校驗
- 支持多種場景:純文本、HTML、附件、內嵌圖片、混合場景
2.2 Spring Boot 啟動類
package com.xxx.email;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EmailApplication {
public static void main(String[] args) {
SpringApplication.run(EmailApplication.class, args);
System.out.println("========================================");
System.out.println("郵件服務啟動成功!");
System.out.println("========================================");
}
}
三、使用示例
3.1 測試控制器
package com.xxx.email.controller;
import com.example.email.util.EmailUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 郵件發(fā)送測試控制器
*/
@RestController
@RequestMapping("/email")
public class EmailController {
@Autowired
private EmailUtil emailUtil;
@Value("${spring.mail.username}")
private String fromEmail;
/**
* 測試1:發(fā)送純文本郵件
*/
@GetMapping("/sendSimple")
public String sendSimpleEmail() {
try {
String to = "recipient@example.com";
String subject = "測試純文本郵件";
String content = "你好!\n\n這是一封測試郵件。\n\n祝好!";
emailUtil.sendSimpleEmail(fromEmail, to, subject, content);
return "純文本郵件發(fā)送成功!";
} catch (Exception e) {
return "發(fā)送失?。? + e.getMessage();
}
}
/**
* 測試2:發(fā)送HTML郵件
*/
@GetMapping("/sendHtml")
public String sendHtmlEmail() {
try {
String to = "recipient@example.com";
String subject = "測試HTML郵件";
String htmlContent =
"<html>" +
"<head>" +
" <style>" +
" body { font-family: Arial, sans-serif; padding: 20px; }" +
" .header { background: #4CAF50; color: white; padding: 20px; text-align: center; }" +
" .content { margin-top: 20px; line-height: 1.6; }" +
" </style>" +
"</head>" +
"<body>" +
" <div class='header'>" +
" <h1>歡迎使用我們的服務</h1>" +
" </div>" +
" <div class='content'>" +
" <p>親愛的用戶:</p>" +
" <p>這是一封<b>HTML格式</b>的測試郵件。</p>" +
" <p>感謝您的使用!</p>" +
" </div>" +
"</body>" +
"</html>";
emailUtil.sendHtmlEmail(fromEmail, to, subject, htmlContent);
return "HTML郵件發(fā)送成功!";
} catch (Exception e) {
return "發(fā)送失?。? + e.getMessage();
}
}
/**
* 測試3:發(fā)送帶附件的郵件(支持中文附件名)
*/
@GetMapping("/sendWithAttachments")
public String sendEmailWithAttachments() {
try {
String to = "recipient@example.com";
String subject = "測試帶附件的郵件";
String htmlContent =
"<html>" +
"<body>" +
" <h2>附件測試郵件</h2>" +
" <p>你好!</p>" +
" <p>這是一封帶附件的測試郵件,附件包含中文名稱。</p>" +
" <p>請查收附件。</p>" +
"</body>" +
"</html>";
// 準備附件(包含中文文件名)
List<File> attachments = new ArrayList<>();
attachments.add(new File("D:/test/中文文檔.docx"));
attachments.add(new File("D:/test/數(shù)據(jù)報表.xlsx"));
emailUtil.sendEmailWithAttachments(fromEmail, to, subject, htmlContent, attachments);
return "帶附件郵件發(fā)送成功!";
} catch (Exception e) {
return "發(fā)送失敗:" + e.getMessage();
}
}
/**
* 測試4:發(fā)送帶內嵌圖片的郵件(圖片顯示在正文中)
*/
@GetMapping("/sendWithInlineImages")
public String sendEmailWithInlineImages() {
try {
String to = "recipient@example.com";
String subject = "測試內嵌圖片郵件";
// HTML正文:通過cid:引用圖片
String htmlContent =
"<html>" +
"<head>" +
" <style>" +
" body { font-family: Arial, sans-serif; padding: 20px; }" +
" .header { background: #4CAF50; color: white; padding: 20px; text-align: center; }" +
" .content { margin-top: 20px; line-height: 1.6; }" +
" img { max-width: 100%; height: auto; display: block; margin: 20px auto; }" +
" </style>" +
"</head>" +
"<body>" +
" <div class='header'>" +
" <h1>內嵌圖片測試</h1>" +
" </div>" +
" <div class='content'>" +
" <p>親愛的用戶:</p>" +
" <p>這是一封帶內嵌圖片的測試郵件。</p>" +
" <p>以下是公司Logo:</p>" +
" <!-- 通過cid:logo引用內嵌圖片 -->" +
" <img src='cid:logo' alt='Logo'>" +
" <p>以下是產(chǎn)品示意圖:</p>" +
" <!-- 通過cid:product引用內嵌圖片 -->" +
" <img src='cid:product' alt='產(chǎn)品示意圖'>" +
" <p>圖片直接顯示在郵件正文中,無需下載。</p>" +
" </div>" +
"</body>" +
"</html>";
// 準備內嵌圖片(File + Content-ID)
List<EmailUtil.InlineImage> inlineImages = new ArrayList<>();
inlineImages.add(new EmailUtil.InlineImage(new File("D:/test/logo.png"), "logo"));
inlineImages.add(new EmailUtil.InlineImage(new File("D:/test/product.png"), "product"));
emailUtil.sendEmailWithInlineImages(fromEmail, to, subject, htmlContent, inlineImages);
return "內嵌圖片郵件發(fā)送成功!";
} catch (Exception e) {
return "發(fā)送失?。? + e.getMessage();
}
}
/**
* 測試5:發(fā)送復雜郵件(同時包含內嵌圖片和附件)
*/
@GetMapping("/sendComplex")
public String sendComplexEmail() {
try {
String to = "recipient@example.com";
String subject = "測試復雜郵件(內嵌圖片 + 附件)";
// HTML正文
String htmlContent =
"<html>" +
"<head>" +
" <style>" +
" body { font-family: Arial, sans-serif; padding: 20px; }" +
" .header { background: #4CAF50; color: white; padding: 20px; text-align: center; }" +
" .content { margin-top: 20px; line-height: 1.6; }" +
" img { max-width: 100%; height: auto; display: block; margin: 20px auto; }" +
" </style>" +
"</head>" +
"<body>" +
" <div class='header'>" +
" <h1>復雜郵件測試</h1>" +
" </div>" +
" <div class='content'>" +
" <p>親愛的用戶:</p>" +
" <p>這封郵件同時包含:</p>" +
" <ul>" +
" <li>內嵌圖片(直接顯示在正文中)</li>" +
" <li>附件(需要下載查看)</li>" +
" </ul>" +
" <p>以下是內嵌圖片:</p>" +
" <img src='cid:logo' alt='Logo'>" +
" <p>請查收附件文件。</p>" +
" </div>" +
"</body>" +
"</html>";
// 內嵌圖片
List<EmailUtil.InlineImage> inlineImages = new ArrayList<>();
inlineImages.add(new EmailUtil.InlineImage(new File("D:/test/logo.png"), "logo"));
// 附件
List<File> attachments = new ArrayList<>();
attachments.add(new File("D:/test/中文文檔.docx"));
attachments.add(new File("D:/test/數(shù)據(jù)報表.xlsx"));
emailUtil.sendComplexEmail(fromEmail, to, subject, htmlContent, inlineImages, attachments);
return "復雜郵件發(fā)送成功!";
} catch (Exception e) {
return "發(fā)送失?。? + e.getMessage();
}
}
}
3.2 單元測試
package com.xxx.email;
import com.example.email.util.EmailUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
class EmailApplicationTests {
@Autowired
private EmailUtil emailUtil;
@Value("${spring.mail.username}")
private String fromEmail;
/**
* 測試純文本郵件
*/
@Test
void testSendSimpleEmail() {
String to = "recipient@example.com";
String subject = "【測試】純文本郵件";
String content = "你好!\n\n這是一封測試郵件。\n\n祝好!";
emailUtil.sendSimpleEmail(fromEmail, to, subject, content);
}
/**
* 測試HTML郵件
*/
@Test
void testSendHtmlEmail() {
String to = "recipient@example.com";
String subject = "【測試】HTML郵件";
String htmlContent =
"<html>" +
"<body>" +
" <h2>HTML郵件測試</h2>" +
" <p>這是一封<b>HTML格式</b>的測試郵件。</p>" +
"</body>" +
"</html>";
emailUtil.sendHtmlEmail(fromEmail, to, subject, htmlContent);
}
/**
* 測試帶附件的郵件
*/
@Test
void testSendEmailWithAttachments() {
String to = "recipient@example.com";
String subject = "【測試】帶附件的郵件";
String htmlContent =
"<html>" +
"<body>" +
" <h2>附件測試</h2>" +
" <p>請查收附件。</p>" +
"</body>" +
"</html>";
List<File> attachments = new ArrayList<>();
attachments.add(new File("D:/test/測試文檔.txt"));
emailUtil.sendEmailWithAttachments(fromEmail, to, subject, htmlContent, attachments);
}
/**
* 測試內嵌圖片郵件
*/
@Test
void testSendEmailWithInlineImages() {
String to = "recipient@example.com";
String subject = "【測試】內嵌圖片郵件";
String htmlContent =
"<html>" +
"<body>" +
" <h2>內嵌圖片測試</h2>" +
" <img src='cid:test' alt='測試圖片'>" +
"</body>" +
"</html>";
List<EmailUtil.InlineImage> inlineImages = new ArrayList<>();
inlineImages.add(new EmailUtil.InlineImage(new File("D:/test/test.png"), "test"));
emailUtil.sendEmailWithInlineImages(fromEmail, to, subject, htmlContent, inlineImages);
}
}
四、關鍵技術與原理
4.1 中文亂碼的根源與解決方案
亂碼產(chǎn)生的原因
郵件協(xié)議(SMTP)基于文本傳輸,只支持7位ASCII字符。當傳輸中文等非ASCII字符時,必須進行編碼。
解決方案
| 亂碼類型 | 根本原因 | 解決方案 |
|---|---|---|
| 附件文件名亂碼 | 文件名未按MIME標準編碼(RFC 2047) | 使用 MimeUtility.encodeText(filename, "UTF-8", "B") |
| 郵件正文亂碼 | 正文未指定charset或使用了非UTF-8編碼 | 在 MimeMessageHelper 構造函數(shù)中指定 UTF-8 |
| 主題亂碼 | 主題未進行MIME編碼 | 使用 MimeUtility.encodeText(subject, "UTF-8", "B") |
編碼方式說明
MimeUtility.encodeText(text, charset, encoding)
- charset: 必須使用
"UTF-8",確保能處理所有Unicode字符 - encoding:
"B"- Base64編碼(推薦,兼容性最好)"Q"- Quoted-Printable編碼(適用于ASCII字符較多的場景)
生產(chǎn)環(huán)境建議始終使用 “B”(Base64)編碼,因為各郵件客戶端對Base64的支持最穩(wěn)定。
4.2 內嵌圖片的技術原理
Multipart 模式選擇
// 錯誤:使用默認的mixed模式(附件模式)
MimeMultipart multipart = new MimeMultipart();
// 正確:使用related模式(內嵌資源模式)
MimeMultipart multipart = new MimeMultipart("related");
| Multipart類型 | 用途 | 說明 |
|---|---|---|
mixed | 混合模式 | 用于獨立的附件(如Word、PDF) |
related | 相關模式 | 用于內嵌資源(圖片、CSS、字體) |
alternative | 替代模式 | 用于同一內容的多版本(純文本 + HTML) |
Content-ID(CID)設置規(guī)則
// 1. 在Java代碼中設置Content-ID
helper.addInline("logo", dataSource);
// 2. 在HTML中引用(注意:必須加上cid:前綴)
<img src="cid:logo" alt="Logo">
重要規(guī)則:
- Content-ID 建議使用字母、數(shù)字、下劃線,避免使用中文或特殊字符
- HTML引用時使用
cid:前綴 - 同一郵件中的CID必須唯一,否則會顯示錯誤或無法加載
4.3 Spring Boot Mail 自動配置原理
Spring Boot 通過 org.springframework.boot.autoconfigure.mail.MailAutoConfiguration 自動配置 JavaMailSender:
- 自動檢測配置:從
application.yml中讀取spring.mail.*配置 - 創(chuàng)建 Session:根據(jù)配置創(chuàng)建
JavaMailSender - 注入 Bean:將
JavaMailSender注入到Spring容器中
開發(fā)者只需:
- 配置
spring.mail.*屬性 - 注入
JavaMailSender或JavaMailSenderImpl - 使用
MimeMessageHelper簡化郵件構建
五、常見問題與解決方案
5.1 認證失敗
問題:
AuthenticationFailedException: 535 Error: authentication failed
原因:
- 使用了郵箱密碼而非授權碼
- 授權碼已過期或被重置
- SMTP服務未開啟
解決方案:
- 登錄郵箱設置,開啟SMTP服務
- 生成新的授權碼
- 在
application.yml中使用授權碼
獲取授權碼示例(QQ郵箱):
- 登錄QQ郵箱 → 設置 → 賬戶
- 找到"POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服務"
- 開啟"IMAP/SMTP服務"
- 按提示發(fā)送短信獲取授權碼
5.2 連接超時
問題:
MailConnectException: Couldn't connect to host, port: smtp.qq.com
原因:
- 端口配置錯誤
- 網(wǎng)絡問題
- 防火墻攔截
解決方案:
- 檢查端口配置:
- TLS加密:端口 587
- SSL加密:端口 465
- 檢查網(wǎng)絡連接
- 配置超時時間:
spring:
mail:
properties:
mail:
smtp:
connectiontimeout: 10000
timeout: 10000
writetimeout: 10000
5.3 附件文件名亂碼
問題:附件文件名顯示為亂碼
解決方案:
使用 MimeUtility.encodeText() 編碼文件名:
private String encodeFileName(String fileName) {
try {
return MimeUtility.encodeText(fileName, "UTF-8", "B");
} catch (UnsupportedEncodingException e) {
return fileName;
}
}
5.4 內嵌圖片顯示為紅叉
問題:圖片顯示為紅叉或無法加載
可能原因及解決方案:
| 可能原因 | 解決方案 |
|---|---|
| CID不匹配 | 檢查HTML中的 cid: 與Java中的 addInline() 的參數(shù)是否一致 |
| 圖片文件不存在 | 使用 file.exists() 驗證文件是否存在 |
| 圖片格式不支持 | 轉換為PNG、JPG或GIF |
| 圖片過大 | 壓縮圖片(建議單張不超過500KB) |
5.5 Outlook 中內嵌圖片顯示為附件
問題:內嵌圖片在Outlook中顯示為附件而非正文
解決方案:
- 確保設置了正確的Multipart模式
- 設置文件名:
helper.addInline("logo", dataSource);
5.6 郵件發(fā)送慢
問題:郵件發(fā)送耗時較長
解決方案:
- 使用異步發(fā)送(推薦):
@Service
public class AsyncEmailService {
@Autowired
private EmailUtil emailUtil;
@Async("emailTaskExecutor")
public void sendEmailAsync(String from, String to, String subject, String content) {
emailUtil.sendHtmlEmail(from, to, subject, content);
}
}
- 配置線程池:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("emailTaskExecutor")
public TaskExecutor emailTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Email-Async-");
executor.initialize();
return executor;
}
}
六、最佳實踐與優(yōu)化建議
6.1 郵件發(fā)送建議
- 控制附件大小:單個附件不超過10MB,總大小不超過25MB(大多數(shù)郵箱的限制)
- 圖片優(yōu)化:
- 使用PNG或JPG格式
- 控制單張圖片大小在500KB以內
- 使用合適的分辨率(72-150 DPI)
- HTML規(guī)范:
- 使用內聯(lián)CSS樣式
- 避免使用JavaScript
- 使用table布局提升兼容性
- 多客戶端測試:在Gmail、Outlook、QQ郵箱等主流客戶端中測試顯示效果
6.2 異常處理建議
- 統(tǒng)一異常處理:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
log.error("系統(tǒng)異常", e);
return ResponseEntity.status(500).body("系統(tǒng)異常:" + e.getMessage());
}
}
- 重試機制:對于網(wǎng)絡異常,可以添加重試邏輯
6.3 安全建議
- 敏感信息加密:使用
application-{profile}.yml區(qū)分環(huán)境,敏感信息加密存儲 - 限制發(fā)送頻率:防止被濫用
- 添加DKIM簽名:提升郵件信譽度(可選)
6.4 監(jiān)控建議
- 記錄發(fā)送日志:記錄發(fā)送狀態(tài)、耗時、失敗原因
- 監(jiān)控指標:
- 發(fā)送成功率
- 平均發(fā)送耗時
- 失敗原因分布
- 告警機制:發(fā)送失敗率超過閾值時觸發(fā)告警
6.5 生產(chǎn)環(huán)境配置示例
spring:
profiles:
active: prod
mail:
host: ${SMTP_HOST:smtp.qq.com}
port: ${SMTP_PORT:587}
username: ${SMTP_USERNAME}
password: ${SMTP_PASSWORD}
default-encoding: UTF-8
properties:
mail:
smtp:
auth: true
starttls:
enable: true
connectiontimeout: 15000
timeout: 15000
writetimeout: 15000
# 日志配置
logging:
level:
org.springframework.mail: DEBUG
file:
name: logs/email-service.log
七、總結
本文提供了一個完整的、生產(chǎn)級可用的 Spring Boot 郵件發(fā)送解決方案,核心要點:
- 技術選型:Spring Boot 2.7.18 + Jakarta Mail 1.6.2,兼容 JDK 1.8
- 核心工具類:
EmailUtil提供了完整的郵件發(fā)送功能,包括:- 純文本郵件
- HTML郵件
- 帶附件郵件(支持中文附件名)
- 內嵌圖片郵件
- 復雜郵件(內嵌圖片 + 附件)
- 中文亂碼根治:使用
MimeUtility.encodeText()統(tǒng)一處理 - 內嵌圖片原理:通過 Content-ID 建立 HTML 與圖片的映射關系
- 異常處理:完整的日志記錄和異常處理機制
- 最佳實踐:異步發(fā)送、監(jiān)控告警、安全配置等
以上就是SpringBoot實現(xiàn)郵件發(fā)送的完整解決方案(附件發(fā)送、內嵌圖片與中文亂碼處理)的詳細內容,更多關于SpringBoot郵件發(fā)送的資料請關注腳本之家其它相關文章!
相關文章
Jetty啟動項目中引用json-lib相關類庫報錯ClassNotFound的解決方案
今天小編就為大家分享一篇關于Jetty啟動項目中引用json-lib相關類庫報錯ClassNotFound的解決方案,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12
Java通過Process類Runtime.getRuntime().exec()執(zhí)行bat腳本程序
用Java編寫應用時,有時需要在程序中調用另一個現(xiàn)成的可執(zhí)行程序或系統(tǒng)命令,這篇文章主要給大家介紹了關于Java如何通過Process類Runtime.getRuntime().exec()執(zhí)行bat腳本程序的相關資料,需要的朋友可以參考下2024-01-01
springboot controller參數(shù)注入方式
這篇文章主要介紹了springboot controller參數(shù)注入方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05

