当前位置: 代码网 > it编程>编程语言>Java > SpringBoot实现邮件发送的完整解决方案(附件发送、内嵌图片与中文乱码处理)

SpringBoot实现邮件发送的完整解决方案(附件发送、内嵌图片与中文乱码处理)

2026年02月15日 Java 我要评论
一、技术选型与依赖配置1.1 maven 依赖配置(jdk 1.8 兼容)<?xml version="1.0" encoding="utf-8"?><project xmlns="

一、技术选型与依赖配置

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 邮件发送完整解决方案</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 变为 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
    
    # 协议配置
    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.com587 (tls) / 465 (ssl)授权码
163邮箱smtp.163.com465 (ssl) / 25授权码
gmailsmtp.gmail.com587 (tls)应用专用密码
outlooksmtp.office365.com587 (tls)邮箱密码或应用密码

二、核心工具类实现

2.1 邮件发送工具类

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;

/**
 * 邮件发送工具类
 * <p>
 * 支持功能:
 * 1. 纯文本邮件发送
 * 2. html邮件发送
 * 3. 带附件邮件发送(支持中文附件名)
 * 4. 内嵌图片邮件发送(图片显示在正文中)
 * 5. 混合场景:同时包含内嵌图片和附件
 * </p>
 *
 * @author auth
 * @since 2026-02-13
 */
@slf4j
@component
public class emailutil {

    @autowired
    private javamailsender mailsender;

    /**
     * 发送纯文本邮件
     *
     * @param from    发件人邮箱
     * @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("纯文本邮件发送成功!收件人:{}, 主题:{}", to, subject);
        } catch (exception e) {
            log.error("纯文本邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
            throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
        }
    }


    /**
     *  发送纯文本带附件邮件
     * @param from          发件人邮箱
     * @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();
            // 第二个参数true表示创建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("纯文本带附件邮件发送成功!收件人:{}, 主题:{}, 附件数量:{}", to, subject,
                    attachments != null ? attachments.size() : 0);
        } catch (exception e) {
            log.error("纯文本带附件邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
            throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
        }
    }


    /**
     * 发送html邮件
     *
     * @param from         发件人邮箱
     * @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邮件发送成功!收件人:{}, 主题:{}", to, subject);
        } catch (exception e) {
            log.error("html邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
            throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
        }
    }

    /**
     * 发送带附件的邮件(支持中文附件名)
     *
     * @param from         发件人邮箱
     * @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();
            // 第二个参数true表示创建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("带附件邮件发送成功!收件人:{}, 主题:{}, 附件数量:{}", to, subject, 
                    attachments != null ? attachments.size() : 0);
        } catch (exception e) {
            log.error("带附件邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
            throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
        }
    }

    /**
     * 发送带内嵌图片的邮件(图片显示在正文中)
     *
     * @param from         发件人邮箱
     * @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创建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("内嵌图片邮件发送成功!收件人:{}, 主题:{}, 图片数量:{}", to, subject,
                    inlineimages != null ? inlineimages.size() : 0);
        } catch (exception e) {
            log.error("内嵌图片邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
            throw new runtimeexception("邮件发送失败:" + e.getmessage(), e);
        }
    }

    /**
     * 发送复杂邮件(同时包含内嵌图片和附件)
     *
     * @param from         发件人邮箱
     * @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("复杂邮件发送成功!收件人:{}, 主题:{}, 图片数量:{}, 附件数量:{}", 
                    to, subject, inlineimages != null ? inlineimages.size() : 0,
                    attachments != null ? attachments.size() : 0);
        } catch (exception e) {
            log.error("复杂邮件发送失败!收件人:{}, 主题:{}, 错误信息:{}", to, subject, e.getmessage(), e);
            throw new runtimeexception("邮件发送失败:" + 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;
        }
    }
}

工具类设计亮点

  1. 完整的日志记录:成功/失败均有详细日志,便于排查问题
  2. 异常处理:所有异常统一捕获并记录,重新抛出为运行时异常
  3. 中文乱码根治:通过 mimeutility.encodetext() 统一处理主题和附件名
  4. 参数校验:对文件存在性、null值进行校验
  5. 支持多种场景:纯文本、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;

/**
 * 邮件发送测试控制器
 */
@restcontroller
@requestmapping("/email")
public class emailcontroller {

    @autowired
    private emailutil emailutil;

    @value("${spring.mail.username}")
    private string fromemail;

    /**
     * 测试1:发送纯文本邮件
     */
    @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 "纯文本邮件发送成功!";
        } catch (exception e) {
            return "发送失败:" + e.getmessage();
        }
    }

    /**
     * 测试2:发送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邮件发送成功!";
        } catch (exception e) {
            return "发送失败:" + e.getmessage();
        }
    }

    /**
     * 测试3:发送带附件的邮件(支持中文附件名)
     */
    @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/数据报表.xlsx"));

            emailutil.sendemailwithattachments(fromemail, to, subject, htmlcontent, attachments);

            return "带附件邮件发送成功!";
        } catch (exception e) {
            return "发送失败:" + e.getmessage();
        }
    }

    /**
     * 测试4:发送带内嵌图片的邮件(图片显示在正文中)
     */
    @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>以下是产品示意图:</p>" +
                    "    <!-- 通过cid:product引用内嵌图片 -->" +
                    "    <img src='cid:product' alt='产品示意图'>" +
                    "    <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 "内嵌图片邮件发送成功!";
        } catch (exception e) {
            return "发送失败:" + e.getmessage();
        }
    }

    /**
     * 测试5:发送复杂邮件(同时包含内嵌图片和附件)
     */
    @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/数据报表.xlsx"));

            emailutil.sendcomplexemail(fromemail, to, subject, htmlcontent, inlineimages, attachments);

            return "复杂邮件发送成功!";
        } catch (exception e) {
            return "发送失败:" + 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 中文乱码的根源与解决方案

乱码产生的原因

邮件协议(smtp)基于文本传输,只支持7位ascii字符。当传输中文等非ascii字符时,必须进行编码。

解决方案

乱码类型根本原因解决方案
附件文件名乱码文件名未按mime标准编码(rfc 2047)使用 mimeutility.encodetext(filename, "utf-8", "b")
邮件正文乱码正文未指定charset或使用了非utf-8编码mimemessagehelper 构造函数中指定 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字符较多的场景)

生产环境建议始终使用 “b”(base64)编码,因为各邮件客户端对base64的支持最稳定。

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)设置规则

// 1. 在java代码中设置content-id
helper.addinline("logo", datasource);

// 2. 在html中引用(注意:必须加上cid:前缀)
<img src="cid:logo" alt="logo">

重要规则

  • content-id 建议使用字母、数字、下划线,避免使用中文或特殊字符
  • html引用时使用 cid: 前缀
  • 同一邮件中的cid必须唯一,否则会显示错误或无法加载

4.3 spring boot mail 自动配置原理

spring boot 通过 org.springframework.boot.autoconfigure.mail.mailautoconfiguration 自动配置 javamailsender

  1. 自动检测配置:从 application.yml 中读取 spring.mail.* 配置
  2. 创建 session:根据配置创建 javamailsender
  3. 注入 bean:将 javamailsender 注入到spring容器中

开发者只需

  1. 配置 spring.mail.* 属性
  2. 注入 javamailsenderjavamailsenderimpl
  3. 使用 mimemessagehelper 简化邮件构建

五、常见问题与解决方案

5.1 认证失败

问题

authenticationfailedexception: 535 error: authentication failed

原因

  • 使用了邮箱密码而非授权码
  • 授权码已过期或被重置
  • smtp服务未开启

解决方案

  1. 登录邮箱设置,开启smtp服务
  2. 生成新的授权码
  3. application.yml 中使用授权码

获取授权码示例(qq邮箱)

  1. 登录qq邮箱 → 设置 → 账户
  2. 找到"pop3/imap/smtp/exchange/carddav/caldav服务"
  3. 开启"imap/smtp服务"
  4. 按提示发送短信获取授权码

5.2 连接超时

问题

mailconnectexception: couldn't connect to host, port: smtp.qq.com

原因

  • 端口配置错误
  • 网络问题
  • 防火墙拦截

解决方案

  1. 检查端口配置:
    • tls加密:端口 587
    • ssl加密:端口 465
  2. 检查网络连接
  3. 配置超时时间:
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() 的参数是否一致
图片文件不存在使用 file.exists() 验证文件是否存在
图片格式不支持转换为png、jpg或gif
图片过大压缩图片(建议单张不超过500kb)

5.5 outlook 中内嵌图片显示为附件

问题:内嵌图片在outlook中显示为附件而非正文

解决方案

  1. 确保设置了正确的multipart模式
  2. 设置文件名:
helper.addinline("logo", datasource);

5.6 邮件发送慢

问题:邮件发送耗时较长

解决方案

  1. 使用异步发送(推荐):
@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);
    }
}
  1. 配置线程池:
@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;
    }
}

六、最佳实践与优化建议

6.1 邮件发送建议

  1. 控制附件大小:单个附件不超过10mb,总大小不超过25mb(大多数邮箱的限制)
  2. 图片优化
    • 使用png或jpg格式
    • 控制单张图片大小在500kb以内
    • 使用合适的分辨率(72-150 dpi)
  3. html规范
    • 使用内联css样式
    • 避免使用javascript
    • 使用table布局提升兼容性
  4. 多客户端测试:在gmail、outlook、qq邮箱等主流客户端中测试显示效果

6.2 异常处理建议

  1. 统一异常处理
@controlleradvice
public class globalexceptionhandler {

    @exceptionhandler(exception.class)
    public responseentity<string> handleexception(exception e) {
        log.error("系统异常", e);
        return responseentity.status(500).body("系统异常:" + e.getmessage());
    }
}
  1. 重试机制:对于网络异常,可以添加重试逻辑

6.3 安全建议

  1. 敏感信息加密:使用 application-{profile}.yml 区分环境,敏感信息加密存储
  2. 限制发送频率:防止被滥用
  3. 添加dkim签名:提升邮件信誉度(可选)

6.4 监控建议

  1. 记录发送日志:记录发送状态、耗时、失败原因
  2. 监控指标
    • 发送成功率
    • 平均发送耗时
    • 失败原因分布
  3. 告警机制:发送失败率超过阈值时触发告警

6.5 生产环境配置示例

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

七、总结

本文提供了一个完整的、生产级可用的 spring boot 邮件发送解决方案,核心要点:

  1. 技术选型:spring boot 2.7.18 + jakarta mail 1.6.2,兼容 jdk 1.8
  2. 核心工具类emailutil 提供了完整的邮件发送功能,包括:
    • 纯文本邮件
    • html邮件
    • 带附件邮件(支持中文附件名)
    • 内嵌图片邮件
    • 复杂邮件(内嵌图片 + 附件)
  3. 中文乱码根治:使用 mimeutility.encodetext() 统一处理
  4. 内嵌图片原理:通过 content-id 建立 html 与图片的映射关系
  5. 异常处理:完整的日志记录和异常处理机制
  6. 最佳实践:异步发送、监控告警、安全配置等

以上就是springboot实现邮件发送的完整解决方案(附件发送、内嵌图片与中文乱码处理)的详细内容,更多关于springboot邮件发送的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com