最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot實現(xiàn)郵件發(fā)送的完整解決方案(附件發(fā)送、內嵌圖片與中文亂碼處理)

 更新時間:2026年02月15日 08:39:48   作者:J_liaty  
本文基于 Spring Boot 2.x + JDK 1.8 + Jakarta Mail 1.6.2,提供生產(chǎn)環(huá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.com587 (TLS) / 465 (SSL)授權碼
163郵箱smtp.163.com465 (SSL) / 25授權碼
Gmailsmtp.gmail.com587 (TLS)應用專用密碼
Outlooksmtp.office365.com587 (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;
        }
    }
}

工具類設計亮點

  1. 完整的日志記錄:成功/失敗均有詳細日志,便于排查問題
  2. 異常處理:所有異常統(tǒng)一捕獲并記錄,重新拋出為運行時異常
  3. 中文亂碼根治:通過 MimeUtility.encodeText() 統(tǒng)一處理主題和附件名
  4. 參數(shù)校驗:對文件存在性、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;

/**
 * 郵件發(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

  1. 自動檢測配置:從 application.yml 中讀取 spring.mail.* 配置
  2. 創(chuàng)建 Session:根據(jù)配置創(chuàng)建 JavaMailSender
  3. 注入 Bean:將 JavaMailSender 注入到Spring容器中

開發(fā)者只需

  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. 按提示發(fā)送短信獲取授權碼

5.2 連接超時

問題

MailConnectException: Couldn't connect to host, port: smtp.qq.com

原因

  • 端口配置錯誤
  • 網(wǎng)絡問題
  • 防火墻攔截

解決方案

  1. 檢查端口配置:
    • TLS加密:端口 587
    • SSL加密:端口 465
  2. 檢查網(wǎng)絡連接
  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() 的參數(shù)是否一致
圖片文件不存在使用 file.exists() 驗證文件是否存在
圖片格式不支持轉換為PNG、JPG或GIF
圖片過大壓縮圖片(建議單張不超過500KB)

5.5 Outlook 中內嵌圖片顯示為附件

問題:內嵌圖片在Outlook中顯示為附件而非正文

解決方案

  1. 確保設置了正確的Multipart模式
  2. 設置文件名:
helper.addInline("logo", dataSource);

5.6 郵件發(fā)送慢

問題:郵件發(fā)送耗時較長

解決方案

  1. 使用異步發(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);
    }
}
  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;
    }
}

六、最佳實踐與優(yōu)化建議

6.1 郵件發(fā)送建議

  1. 控制附件大小:單個附件不超過10MB,總大小不超過25MB(大多數(shù)郵箱的限制)
  2. 圖片優(yōu)化
    • 使用PNG或JPG格式
    • 控制單張圖片大小在500KB以內
    • 使用合適的分辨率(72-150 DPI)
  3. HTML規(guī)范
    • 使用內聯(lián)CSS樣式
    • 避免使用JavaScript
    • 使用table布局提升兼容性
  4. 多客戶端測試:在Gmail、Outlook、QQ郵箱等主流客戶端中測試顯示效果

6.2 異常處理建議

  1. 統(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());
    }
}
  1. 重試機制:對于網(wǎng)絡異常,可以添加重試邏輯

6.3 安全建議

  1. 敏感信息加密:使用 application-{profile}.yml 區(qū)分環(huán)境,敏感信息加密存儲
  2. 限制發(fā)送頻率:防止被濫用
  3. 添加DKIM簽名:提升郵件信譽度(可選)

6.4 監(jiān)控建議

  1. 記錄發(fā)送日志:記錄發(fā)送狀態(tài)、耗時、失敗原因
  2. 監(jiān)控指標
    • 發(fā)送成功率
    • 平均發(fā)送耗時
    • 失敗原因分布
  3. 告警機制:發(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ā)送解決方案,核心要點:

  1. 技術選型:Spring Boot 2.7.18 + Jakarta Mail 1.6.2,兼容 JDK 1.8
  2. 核心工具類EmailUtil 提供了完整的郵件發(fā)送功能,包括:
    • 純文本郵件
    • HTML郵件
    • 帶附件郵件(支持中文附件名)
    • 內嵌圖片郵件
    • 復雜郵件(內嵌圖片 + 附件)
  3. 中文亂碼根治:使用 MimeUtility.encodeText() 統(tǒng)一處理
  4. 內嵌圖片原理:通過 Content-ID 建立 HTML 與圖片的映射關系
  5. 異常處理:完整的日志記錄和異常處理機制
  6. 最佳實踐:異步發(fā)送、監(jiān)控告警、安全配置等

以上就是SpringBoot實現(xiàn)郵件發(fā)送的完整解決方案(附件發(fā)送、內嵌圖片與中文亂碼處理)的詳細內容,更多關于SpringBoot郵件發(fā)送的資料請關注腳本之家其它相關文章!

相關文章

  • java同步鎖的正確使用方法(必看篇)

    java同步鎖的正確使用方法(必看篇)

    下面小編就為大家?guī)硪黄猨ava同步鎖的正確使用方法(必看篇)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • Jetty啟動項目中引用json-lib相關類庫報錯ClassNotFound的解決方案

    Jetty啟動項目中引用json-lib相關類庫報錯ClassNotFound的解決方案

    今天小編就為大家分享一篇關于Jetty啟動項目中引用json-lib相關類庫報錯ClassNotFound的解決方案,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • 如何將java或javaweb項目打包為jar包或war包

    如何將java或javaweb項目打包為jar包或war包

    本文主要介紹了如何將java或javaweb項目打包為jar包或war包,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-07-07
  • Mybatis select記錄封裝的實現(xiàn)

    Mybatis select記錄封裝的實現(xiàn)

    這篇文章主要介紹了Mybatis select記錄封裝的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-10-10
  • Java實現(xiàn)MD5加密及解密的代碼實例分享

    Java實現(xiàn)MD5加密及解密的代碼實例分享

    如果對安全性的需求不是太高,MD5仍是使用非常方便和普及的加密方式,比如Java中自帶的MessageDigest類就提供了支持,這里就為大家?guī)鞪ava實現(xiàn)MD5加密及解密的代碼實例分享:
    2016-06-06
  • Java將不同的List集合復制到另一個集合常見的方法

    Java將不同的List集合復制到另一個集合常見的方法

    在Java中,有時候我們需要將一個List對象的屬性值復制到另一個List對象中,使得兩個對象的屬性值相同,這篇文章主要介紹了Java將不同的List集合復制到另一個集合常見的方法,需要的朋友可以參考下
    2024-09-09
  • java異步調用的4種實現(xiàn)方法

    java異步調用的4種實現(xiàn)方法

    日常開發(fā)中,會經(jīng)常遇到說,前臺調服務,本文主要介紹了java異步調用的4種實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Java通過Process類Runtime.getRuntime().exec()執(zhí)行bat腳本程序

    Java通過Process類Runtime.getRuntime().exec()執(zhí)行bat腳本程序

    用Java編寫應用時,有時需要在程序中調用另一個現(xiàn)成的可執(zhí)行程序或系統(tǒng)命令,這篇文章主要給大家介紹了關于Java如何通過Process類Runtime.getRuntime().exec()執(zhí)行bat腳本程序的相關資料,需要的朋友可以參考下
    2024-01-01
  • SpringBoot底層注解詳解

    SpringBoot底層注解詳解

    這篇文章主要介紹了SpringBoot底層注解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2023-05-05
  • springboot controller參數(shù)注入方式

    springboot controller參數(shù)注入方式

    這篇文章主要介紹了springboot controller參數(shù)注入方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05

最新評論

兰考县| 星子县| 札达县| 洮南市| 平罗县| 和政县| 曲松县| 南开区| 东丽区| 余姚市| 田阳县| 青岛市| 凤山县| 分宜县| 胶州市| 南郑县| 尼勒克县| 宁阳县| 宁远县| 苏尼特右旗| 奉节县| 吉安县| 沈丘县| 新泰市| 通山县| 甘谷县| 贡觉县| 东明县| 土默特右旗| 伊金霍洛旗| 鞍山市| 德保县| 富裕县| 定结县| 泰宁县| 青铜峡市| 饶河县| 鄯善县| 科技| 元江| 兴化市|