SpringBoot整合邮件发送

一、邮件发送核心认知

1. 什么是邮件发送

邮件发送是应用程序中通过邮件服务器将信息传递给指定收件人的功能,支持纯文本、附件、图片、HTML 模板等多种形式。在 Spring Boot 中,借助 Spring 提供的邮件服务封装,可快速实现各类邮件发送需求,无需关注底层协议(如 SMTP)的实现细节。

2. 为什么需要在 Spring Boot 中整合邮件发送

  • 业务场景广泛:用户注册验证、密码重置、订单通知、系统告警、营销推广等场景均需邮件通知。
  • 简化开发流程:Spring Boot 提供了spring-boot-starter-mail启动器,自动配置核心组件,无需手动编写复杂的邮件协议交互代码。
  • 支持多种形式:可轻松实现纯文本邮件、带附件 / 图片的复杂邮件、基于模板的动态邮件(如验证码邮件、欢迎邮件)。
  • 兼容性强:支持 QQ 邮箱、163 邮箱、企业邮箱等主流邮件服务提供商,配置灵活。

3. 核心组件与原理

  • JavaMailSender:Spring 提供的邮件发送核心接口,封装了邮件发送的底层逻辑,支持发送简单邮件和复杂邮件。
  • SimpleMailMessage:用于封装纯文本邮件的基本信息(收件人、主题、内容),使用简单但功能有限。
  • MimeMessage:支持复杂邮件的封装,可包含 HTML 内容、图片、附件等,功能更强大。
  • MimeMessageHelper:Spring 提供的辅助类,简化 MimeMessage 的构建过程,避免直接操作 MimeMessage 的繁琐步骤。
  • 模板引擎(如 Thymeleaf):用于构建动态 HTML 邮件模板,支持参数替换,适用于固定格式、动态内容的邮件(如验证码邮件)。

核心原理:应用程序通过 Spring Boot 配置的邮件服务器信息(主机、端口、账号、授权码),通过 SMTP 协议与邮件服务器建立连接,将封装好的邮件内容提交给服务器,由服务器转发至收件人邮箱。

二、技术栈与环境准备

  • 基础框架:Spring Boot 2.7.x
  • 邮件依赖:spring-boot-starter-mail(Spring Boot 邮件服务启动器)
  • 模板引擎:Thymeleaf(用于动态模板邮件)
  • 开发工具:IDEA、Maven
  • JDK:1.8 及以上
  • 邮件服务:QQ 邮箱(本教程以 QQ 邮箱为例,其他邮箱配置类似)

三、项目搭建与配置

image-20251029221058526

1. 创建 Maven 项目

新建 Spring Boot 项目,工程名为SpringBootMail-Demo,包名设置为com.yqd

2. 添加依赖(pom.xml)

<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>
  <!-- SpringBoot父依赖 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.4</version>
    <relativePath/>
  </parent>
  <groupId>com.yqd</groupId>
  <artifactId>SpringBootMail-Demo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>SpringBootMail-Demo</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <!-- Spring Web:用于提供接口测试邮件发送 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 邮件发送核心依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>

    <!-- Thymeleaf模板引擎:用于动态模板邮件 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <!-- Lombok:简化实体类和工具类代码 -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

3. 邮件服务配置(application.yml)

src/main/resources下创建application.yml文件,配置邮件服务器信息(以 QQ 邮箱为例):

spring:
  # 邮件服务配置
  mail:
    host: smtp.qq.com  # 邮件服务器主机(QQ邮箱SMTP主机)
    port: 587  # 端口号(QQ邮箱SMTP端口,587为非SSL端口,465为SSL端口)
    username: 1723123123@qq.com  # 发件人邮箱账号(替换为你的QQ邮箱)
    password: kkgv412452  # 授权码(不是邮箱密码,需在QQ邮箱中获取)
    default-encoding: UTF-8  # 邮件编码
    properties:
      mail:
        smtp:
          auth: true  # 开启SMTP认证
          starttls:
            enable: true  # 开启TLS加密(适配587端口)
            required: true
  # Thymeleaf模板引擎配置(默认无需修改)
  thymeleaf:
    prefix: classpath:/templates/  # 模板文件路径
    suffix: .html  # 模板文件后缀
    mode: HTML  # 模板模式
    encoding: UTF-8  # 模板编码

# 自定义邮件配置(可选,用于统一管理收件人等信息)
mail:
  to:  36481231231@qq.com  # 默认收件人邮箱(替换为测试用收件人邮箱)

4. 获取 QQ 邮箱授权码(关键步骤)

  1. 登录 QQ 邮箱,点击顶部「设置」→「账户」。
  2. 下拉找到「POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务」,开启「POP3/SMTP 服务」。
  3. 按照提示通过手机短信验证,验证成功后会生成 16 位授权码,复制该授权码填入application.ymlspring.mail.password中。

四、核心代码实现(包名:com.yqd)

1. 邮件发送服务接口(EmailService)

定义邮件发送的核心方法,包含纯文本、带附件 / 图片、模板邮件三种场景:

package com.yqd.service;

import javax.mail.MessagingException;

/**
 * 邮件发送服务接口
 */
public interface EmailService {

    /**
     * 发送纯文本邮件
     * @param to 收件人邮箱
     * @param subject 邮件主题
     * @param content 邮件内容(纯文本)
     */
    void sendSimpleEmail(String to, String subject, String content);

    /**
     * 发送带附件和图片的复杂邮件
     * @param to 收件人邮箱
     * @param subject 邮件主题
     * @param content 邮件内容(支持HTML)
     * @param attachmentPaths 附件路径数组(本地文件路径)
     * @param imgPaths 图片路径数组(本地文件路径)
     * @param imgCids 图片CID(用于HTML中引用图片,需与imgPaths一一对应)
     * @throws MessagingException 邮件发送异常
     */
    void sendComplexEmail(String to, String subject, String content, String[] attachmentPaths, String[] imgPaths, String[] imgCids) throws MessagingException;

    /**
     * 发送模板邮件(基于Thymeleaf)
     * @param to 收件人邮箱
     * @param subject 邮件主题
     * @param templateName 模板文件名(无需后缀,如emailTemplate)
     * @param paramName 模板参数名
     * @param paramValue 模板参数值
     * @throws MessagingException 邮件发送异常
     */
    void sendTemplateEmail(String to, String subject, String templateName, String paramName, Object paramValue) throws MessagingException;
}

2. 邮件发送服务实现类(EmailServiceImpl)

实现接口方法,借助JavaMailSenderMimeMessageHelper完成邮件构建与发送:

package com.yqd.service.impl;

import com.yqd.service.EmailService;
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;

/**
 * 邮件发送服务实现类
 */
@Service
public class EmailServiceImpl implements EmailService {

    // 注入Spring邮件发送核心组件
    @Autowired
    private JavaMailSender mailSender;

    // 注入Thymeleaf模板引擎(用于模板邮件)
    @Autowired
    private TemplateEngine templateEngine;

    // 发件人邮箱(从配置文件读取)
    @Value("${spring.mail.username}")
    private String from;

    /**
     * 发送纯文本邮件
     */
    @Override
    public void sendSimpleEmail(String to, String subject, String content) {
        // 1. 创建纯文本邮件对象
        SimpleMailMessage message = new SimpleMailMessage();
        // 2. 设置邮件信息
        message.setFrom(from);  // 发件人
        message.setTo(to);      // 收件人
        message.setSubject(subject);  // 主题
        message.setText(content);     // 纯文本内容
        // 3. 发送邮件
        mailSender.send(message);
        System.out.println("纯文本邮件发送成功!收件人:" + to);
    }

    /**
     * 发送带附件和图片的复杂邮件
     */
    @Override
    public void sendComplexEmail(String to, String subject, String content, String[] attachmentPaths, String[] imgPaths, String[] imgCids) throws MessagingException {
        // 1. 创建复杂邮件对象(MimeMessage)
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        // 2. 使用MimeMessageHelper辅助构建邮件(true表示支持多部分内容,如附件、图片)
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
        // 3. 设置基础邮件信息
        helper.setFrom(from);
        helper.setTo(to);
        helper.setSubject(subject);
        // 4. 设置HTML内容(true表示内容为HTML格式)
        helper.setText(content, true);

        // 5. 添加附件(如果有)
        if (attachmentPaths != null && attachmentPaths.length > 0) {
            for (String attachmentPath : attachmentPaths) {
                File attachment = new File(attachmentPath);
                if (attachment.exists()) {
                    // 读取本地文件作为附件,参数:附件名称(可自定义)、文件资源
                    FileSystemResource resource = new FileSystemResource(attachment);
                    helper.addAttachment(attachment.getName(), resource);
                }
            }
        }

        // 6. 添加图片(如果有)
        if (imgPaths != null && imgPaths.length > 0 && imgCids != null && imgCids.length > 0) {
            for (int i = 0; i < imgPaths.length; i++) {
                File imgFile = new File(imgPaths[i]);
                if (imgFile.exists()) {
                    FileSystemResource imgResource = new FileSystemResource(imgFile);
                    // 添加图片为内嵌资源,cid用于HTML中引用(格式:<img src="cid:xxx" />)
                    helper.addInline(imgCids[i], imgResource);
                }
            }
        }

        // 7. 发送邮件
        mailSender.send(mimeMessage);
        System.out.println("复杂邮件发送成功!收件人:" + to);
    }

    /**
     * 发送模板邮件
     */
    @Override
    public void sendTemplateEmail(String to, String subject, String templateName, String paramName, Object paramValue) throws MessagingException {
        // 1. 创建Thymeleaf上下文(用于传递参数到模板)
        Context context = new Context();
        context.setVariable(paramName, paramValue);  // 设置模板参数

        // 2. 渲染模板为HTML内容
        String htmlContent = templateEngine.process(templateName, context);

        // 3. 创建复杂邮件对象(模板邮件本质是HTML邮件)
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
        // 4. 设置邮件信息
        helper.setFrom(from);
        helper.setTo(to);
        helper.setSubject(subject);
        helper.setText(htmlContent, true);  // HTML格式内容

        // 5. 发送邮件
        mailSender.send(mimeMessage);
        System.out.println("模板邮件发送成功!收件人:" + to);
    }
}

3. 控制器(EmailController)

提供 HTTP 接口,用于测试三种邮件发送场景:

package com.yqd.controller;

import com.yqd.service.EmailService;
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 javax.mail.MessagingException;

/**
 * 邮件发送控制器(用于接口测试)
 */
@RestController
@RequestMapping("/email")
public class EmailController {

    @Autowired
    private EmailService emailService;

    // 从配置文件读取默认收件人
    @Value("${mail.to}")
    private String defaultTo;

    /**
     * 测试发送纯文本邮件
     */
    @GetMapping("/send/simple")
    public String sendSimpleEmail() {
        try {
            String subject = "【纯文本邮件测试】";
            String content = "这是SpringBoot整合邮件发送的纯文本邮件测试内容!\n发送时间:" + System.currentTimeMillis();
            emailService.sendSimpleEmail(defaultTo, subject, content);
            return "纯文本邮件发送请求已提交,请查收邮箱!";
        } catch (Exception e) {
            return "纯文本邮件发送失败:" + e.getMessage();
        }
    }

    /**
     * 测试发送带附件和图片的复杂邮件
     */
    @GetMapping("/send/complex")
    public String sendComplexEmail() {
        try {
            String subject = "【复杂邮件测试】";
            // HTML内容,引用内嵌图片(cid:img1对应addInline的cid参数)
            String content = "<h3>这是带附件和图片的复杂邮件!</h3>" +
                    "<p>内嵌图片展示:</p>" +
                    "<img src='cid:img1' width='300' height='200' />" +
                    "<p>邮件发送时间:" + System.currentTimeMillis() + "</p>";

            // 附件路径(本地文件路径,需替换为你的实际文件路径)
            String[] attachments = {"D:/test/附件1-测试文档.txt", "D:/test/附件2-数据表格.xlsx"};
            // 图片路径(本地文件路径)
            String[] imgs = {"D:/test/图片1.jpg"};
            // 图片CID(与HTML中cid对应)
            String[] imgCids = {"img1"};

            emailService.sendComplexEmail(defaultTo, subject, content, attachments, imgs, imgCids);
            return "复杂邮件发送请求已提交,请查收邮箱!";
        } catch (MessagingException e) {
            return "复杂邮件发送失败:" + e.getMessage();
        }
    }

    /**
     * 测试发送模板邮件(验证码邮件示例)
     */
    @GetMapping("/send/template")
    public String sendTemplateEmail() {
        try {
            String subject = "【验证码邮件测试】";
            String templateName = "emailTemplate";  // 模板文件名(无后缀)
            String paramName = "vercode";  // 模板参数名
            String paramValue = "654321";  // 动态生成的验证码

            emailService.sendTemplateEmail(defaultTo, subject, templateName, paramName, paramValue);
            return "模板邮件发送请求已提交,请查收邮箱!";
        } catch (MessagingException e) {
            return "模板邮件发送失败:" + e.getMessage();
        }
    }
}

4. Thymeleaf 邮件模板(emailTemplate.html)

src/main/resources/templates下创建模板文件,用于动态生成验证码邮件内容:

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>验证码邮件</title>
    <style>
        .container {
            width: 600px;
            margin: 0 auto;
            padding: 20px;
            border: 1px solid #eee;
            border-radius: 8px;
        }
        .title {
            color: #333;
            font-size: 20px;
            text-align: center;
            margin-bottom: 20px;
        }
        .content {
            color: #666;
            font-size: 16px;
            line-height: 1.8;
        }
        .vercode {
            display: inline-block;
            padding: 8px 20px;
            background-color: #007bff;
            color: white;
            font-size: 18px;
            font-weight: bold;
            border-radius: 4px;
            margin: 10px 0;
        }
        .footer {
            margin-top: 30px;
            color: #999;
            font-size: 14px;
            text-align: center;
        }
    </style>
</head>
<body>
<div class="container">
    <h2 class="title">验证码通知</h2>
    <div class="content">
        <p>尊敬的用户:</p>
        <p>您好!您正在进行身份验证,您的验证码为:</p>
        <p><span class="vercode" th:text="${vercode}"></span></p>
        <p>验证码有效期为15分钟,请在有效期内完成操作。</p>
        <p>请勿将验证码泄露给他人,如非本人操作,请忽略此邮件。</p>
    </div>
    <div class="footer">
        <p>© 2025 SpringBootMail-Demo 版权所有</p>
    </div>
</div>
</body>
</html>

5. 启动类(SpringBootMailDemo)

package com.yqd;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 应用启动类
 */
@SpringBootApplication
public class SpringBootMailDemo {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootMailDemo.class, args);
        System.out.println("===== SpringBootMail-Demo 启动成功!=====");
        System.out.println("测试接口:");
        System.out.println("1. 纯文本邮件:http://localhost:8080/email/send/simple");
        System.out.println("2. 复杂邮件:http://localhost:8080/email/send/complex");
        System.out.println("3. 模板邮件:http://localhost:8080/email/send/template");
    }
}

五、测试流程

1. 环境准备

  • 确保本地 Maven 依赖下载完成。
  • 替换application.yml中的发件人邮箱、授权码、默认收件人邮箱。
  • 复杂邮件测试时,替换EmailController中的附件和图片本地路径(确保文件存在)。

2. 启动项目

运行SpringBootMailDemoApplicationmain方法,控制台输出启动成功信息即表示项目启动完成。

3. 测试三种邮件场景

(1)纯文本邮件

  • 访问接口:http://localhost:8080/email/send/simple
  • 响应结果:纯文本邮件发送请求已提交,请查收邮箱!
  • 验证:登录收件人邮箱,查看是否收到纯文本格式的邮件。

(2)复杂邮件

  • 访问接口:http://localhost:8080/email/send/complex
  • 响应结果:复杂邮件发送请求已提交,请查收邮箱!
  • 验证:登录收件人邮箱,查看邮件是否包含 HTML 内容、内嵌图片和附件。

(3)模板邮件

  • 访问接口:http://localhost:8080/email/send/template
  • 响应结果:模板邮件发送请求已提交,请查收邮箱!
  • 验证:登录收件人邮箱,查看邮件是否为 HTML 格式,且验证码是否正确显示(本示例为 654321)。

六、常见问题与注意事项

  1. 授权码错误:如果报AuthenticationFailedException,检查spring.mail.password是否为 QQ 邮箱的授权码(而非登录密码),授权码是否过期。
  2. 端口或主机错误:不同邮箱的 SMTP 主机和端口不同,例如 163 邮箱的 SMTP 主机为smtp.163.com,端口为 465(SSL),需对应调整配置。
  3. 文件路径错误:复杂邮件测试时,确保附件和图片的本地路径正确,文件存在且可访问。
  4. HTML 格式问题:复杂邮件和模板邮件的内容需符合 HTML 语法,否则可能显示异常。
  5. 编码问题:配置文件中已设置default-encoding: UTF-8,避免中文乱码。
  6. 邮件被拦截:部分邮箱可能将测试邮件归为垃圾邮件,若未收到可查看垃圾邮件文件夹。

七、扩展场景

  • 多收件人 / 抄送人:在MimeMessageHelper中使用setTo(String[] to)setCc(String[] cc)设置多个收件人或抄送人。
  • 动态模板参数:模板邮件支持多个参数,可通过context.setVariables(Map<String, Object> variables)传递参数 Map。
  • 邮件发送失败重试:结合 Spring 的重试机制(@Retryable),处理网络波动导致的发送失败。
  • 异步发送邮件:使用@Async注解将邮件发送方法改为异步,避免阻塞主线程(需在启动类添加@EnableAsync)。
posted @ 2025-10-29 22:18  碧水云天4  阅读(7)  评论(0)    收藏  举报