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

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

 更新時(shí)間: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ā)送、內(nèi)嵌圖片、中文亂碼處理等核心場(chǎng)景,需要的朋友可以參考下

一、技術(shù)選型與依賴配置

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(簡(jiǎn)化代碼,可選) -->
        <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 內(nèi)置 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服務(wù)器配置
    host: smtp.qq.com
    port: 587
    username: your_email@qq.com
    password: your_smtp_authorization_code  # 注意:是授權(quán)碼,不是郵箱密碼
    
    # 編碼配置(根治中文亂碼)
    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

# 服務(wù)器端口
server:
  port: 8080

常用SMTP服務(wù)器配置參考

郵箱服務(wù)商SMTP地址端口認(rèn)證方式
QQ郵箱smtp.qq.com587 (TLS) / 465 (SSL)授權(quán)碼
163郵箱smtp.163.com465 (SSL) / 25授權(quán)碼
Gmailsmtp.gmail.com587 (TLS)應(yīng)用專用密碼
Outlooksmtp.office365.com587 (TLS)郵箱密碼或應(yīng)用密碼

二、核心工具類實(shí)現(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. 內(nèi)嵌圖片郵件發(fā)送(圖片顯示在正文中)
 * 5. 混合場(chǎng)景:同時(shí)包含內(nèi)嵌圖片和附件
 * </p>
 *
 * @author auth
 * @since 2026-02-13
 */
@Slf4j
@Component
public class EmailUtil {

    @Autowired
    private JavaMailSender mailSender;

    /**
     * 發(fā)送純文本郵件
     *
     * @param from    發(fā)件人郵箱
     * @param to      收件人郵箱(支持多個(gè),用逗號(hào)分隔)
     * @param subject 郵件主題
     * @param content 郵件內(nèi)容
     */
    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ā)送失??!收件人:{}, 主題:{}, 錯(cuò)誤信息:{}", to, subject, e.getMessage(), e);
            throw new RuntimeException("郵件發(fā)送失?。? + e.getMessage(), e);
        }
    }


    /**
     *  發(fā)送純文本帶附件郵件
     * @param from          發(fā)件人郵箱
     * @param to            收件人郵箱(支持多個(gè),用逗號(hào)分隔)
     * @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();
            // 第二個(gè)參數(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);

            // 添加附件(自動(dòng)處理中文文件名)
            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ā)送失??!收件人:{}, 主題:{}, 錯(cuò)誤信息:{}", to, subject, e.getMessage(), e);
            throw new RuntimeException("郵件發(fā)送失敗:" + e.getMessage(), e);
        }
    }


    /**
     * 發(fā)送HTML郵件
     *
     * @param from         發(fā)件人郵箱
     * @param to           收件人郵箱
     * @param subject      郵件主題
     * @param htmlContent  HTML格式的郵件內(nèi)容
     */
    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ā)送失敗!收件人:{}, 主題:{}, 錯(cuò)誤信息:{}", to, subject, e.getMessage(), e);
            throw new RuntimeException("郵件發(fā)送失?。? + e.getMessage(), e);
        }
    }

    /**
     * 發(fā)送帶附件的郵件(支持中文附件名)
     *
     * @param from         發(fā)件人郵箱
     * @param to           收件人郵箱
     * @param subject      郵件主題
     * @param htmlContent  HTML格式的郵件內(nèi)容
     * @param attachments  附件文件列表
     */
    public void sendEmailWithAttachments(String from, String to, String subject,
                                         String htmlContent, List<File> attachments) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            // 第二個(gè)參數(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);

            // 添加附件(自動(dòng)處理中文文件名)
            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ā)送失??!收件人:{}, 主題:{}, 錯(cuò)誤信息:{}", to, subject, e.getMessage(), e);
            throw new RuntimeException("郵件發(fā)送失?。? + e.getMessage(), e);
        }
    }

    /**
     * 發(fā)送帶內(nèi)嵌圖片的郵件(圖片顯示在正文中)
     *
     * @param from         發(fā)件人郵箱
     * @param to           收件人郵箱
     * @param subject      郵件主題
     * @param htmlContent  HTML格式的郵件內(nèi)容(通過cid:xxx引用圖片)
     * @param inlineImages 內(nèi)嵌圖片列表
     */
    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);

            // 添加內(nèi)嵌圖片
            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("添加內(nèi)嵌圖片:CID={}, 文件名:{}", 
                                inlineImage.getContentId(), inlineImage.getFile().getName());
                    } else {
                        log.warn("內(nèi)嵌圖片文件不存在,跳過:CID={}", 
                                inlineImage != null ? inlineImage.getContentId() : "null");
                    }
                }
            }

            mailSender.send(message);
            log.info("內(nèi)嵌圖片郵件發(fā)送成功!收件人:{}, 主題:{}, 圖片數(shù)量:{}", to, subject,
                    inlineImages != null ? inlineImages.size() : 0);
        } catch (Exception e) {
            log.error("內(nèi)嵌圖片郵件發(fā)送失??!收件人:{}, 主題:{}, 錯(cuò)誤信息:{}", to, subject, e.getMessage(), e);
            throw new RuntimeException("郵件發(fā)送失?。? + e.getMessage(), e);
        }
    }

    /**
     * 發(fā)送復(fù)雜郵件(同時(shí)包含內(nèi)嵌圖片和附件)
     *
     * @param from         發(fā)件人郵箱
     * @param to           收件人郵箱
     * @param subject      郵件主題
     * @param htmlContent  HTML格式的郵件內(nèi)容
     * @param inlineImages 內(nèi)嵌圖片列表
     * @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);

            // 添加內(nèi)嵌圖片
            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("添加內(nèi)嵌圖片: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ù)雜郵件發(fā)送成功!收件人:{}, 主題:{}, 圖片數(shù)量:{}, 附件數(shù)量:{}", 
                    to, subject, inlineImages != null ? inlineImages.size() : 0,
                    attachments != null ? attachments.size() : 0);
        } catch (Exception e) {
            log.error("復(fù)雜郵件發(fā)送失?。∈占耍簕}, 主題:{}, 錯(cuò)誤信息:{}", to, subject, e.getMessage(), e);
            throw new RuntimeException("郵件發(fā)送失?。? + e.getMessage(), e);
        }
    }

    /**
     * 編碼文本(處理中文亂碼)
     * 使用RFC 2047標(biāo)準(zhǔn)編碼
     *
     * @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;
        }
    }

    /**
     * 內(nèi)嵌圖片包裝類
     */
    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;
        }
    }
}

工具類設(shè)計(jì)亮點(diǎn)

  1. 完整的日志記錄:成功/失敗均有詳細(xì)日志,便于排查問題
  2. 異常處理:所有異常統(tǒng)一捕獲并記錄,重新拋出為運(yùn)行時(shí)異常
  3. 中文亂碼根治:通過 MimeUtility.encodeText() 統(tǒng)一處理主題和附件名
  4. 參數(shù)校驗(yàn):對(duì)文件存在性、null值進(jìn)行校驗(yàn)
  5. 支持多種場(chǎng)景:純文本、HTML、附件、內(nèi)嵌圖片、混合場(chǎng)景

2.2 Spring Boot 啟動(dòng)類

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("郵件服務(wù)啟動(dòng)成功!");
        System.out.println("========================================");
    }
}

三、使用示例

3.1 測(cè)試控制器

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ā)送測(cè)試控制器
 */
@RestController
@RequestMapping("/email")
public class EmailController {

    @Autowired
    private EmailUtil emailUtil;

    @Value("${spring.mail.username}")
    private String fromEmail;

    /**
     * 測(cè)試1:發(fā)送純文本郵件
     */
    @GetMapping("/sendSimple")
    public String sendSimpleEmail() {
        try {
            String to = "recipient@example.com";
            String subject = "測(cè)試純文本郵件";
            String content = "你好!\n\n這是一封測(cè)試郵件。\n\n祝好!";

            emailUtil.sendSimpleEmail(fromEmail, to, subject, content);

            return "純文本郵件發(fā)送成功!";
        } catch (Exception e) {
            return "發(fā)送失?。? + e.getMessage();
        }
    }

    /**
     * 測(cè)試2:發(fā)送HTML郵件
     */
    @GetMapping("/sendHtml")
    public String sendHtmlEmail() {
        try {
            String to = "recipient@example.com";
            String subject = "測(cè)試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>歡迎使用我們的服務(wù)</h1>" +
                    "  </div>" +
                    "  <div class='content'>" +
                    "    <p>親愛的用戶:</p>" +
                    "    <p>這是一封<b>HTML格式</b>的測(cè)試郵件。</p>" +
                    "    <p>感謝您的使用!</p>" +
                    "  </div>" +
                    "</body>" +
                    "</html>";

            emailUtil.sendHtmlEmail(fromEmail, to, subject, htmlContent);

            return "HTML郵件發(fā)送成功!";
        } catch (Exception e) {
            return "發(fā)送失?。? + e.getMessage();
        }
    }

    /**
     * 測(cè)試3:發(fā)送帶附件的郵件(支持中文附件名)
     */
    @GetMapping("/sendWithAttachments")
    public String sendEmailWithAttachments() {
        try {
            String to = "recipient@example.com";
            String subject = "測(cè)試帶附件的郵件";

            String htmlContent =
                    "<html>" +
                    "<body>" +
                    "  <h2>附件測(cè)試郵件</h2>" +
                    "  <p>你好!</p>" +
                    "  <p>這是一封帶附件的測(cè)試郵件,附件包含中文名稱。</p>" +
                    "  <p>請(qǐng)查收附件。</p>" +
                    "</body>" +
                    "</html>";

            // 準(zhǔn)備附件(包含中文文件名)
            List<File> attachments = new ArrayList<>();
            attachments.add(new File("D:/test/中文文檔.docx"));
            attachments.add(new File("D:/test/數(shù)據(jù)報(bào)表.xlsx"));

            emailUtil.sendEmailWithAttachments(fromEmail, to, subject, htmlContent, attachments);

            return "帶附件郵件發(fā)送成功!";
        } catch (Exception e) {
            return "發(fā)送失?。? + e.getMessage();
        }
    }

    /**
     * 測(cè)試4:發(fā)送帶內(nèi)嵌圖片的郵件(圖片顯示在正文中)
     */
    @GetMapping("/sendWithInlineImages")
    public String sendEmailWithInlineImages() {
        try {
            String to = "recipient@example.com";
            String subject = "測(cè)試內(nèi)嵌圖片郵件";

            // 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>內(nèi)嵌圖片測(cè)試</h1>" +
                    "  </div>" +
                    "  <div class='content'>" +
                    "    <p>親愛的用戶:</p>" +
                    "    <p>這是一封帶內(nèi)嵌圖片的測(cè)試郵件。</p>" +
                    "    <p>以下是公司Logo:</p>" +
                    "    <!-- 通過cid:logo引用內(nèi)嵌圖片 -->" +
                    "    <img src='cid:logo' alt='Logo'>" +
                    "    <p>以下是產(chǎn)品示意圖:</p>" +
                    "    <!-- 通過cid:product引用內(nèi)嵌圖片 -->" +
                    "    <img src='cid:product' alt='產(chǎn)品示意圖'>" +
                    "    <p>圖片直接顯示在郵件正文中,無需下載。</p>" +
                    "  </div>" +
                    "</body>" +
                    "</html>";

            // 準(zhǔn)備內(nèi)嵌圖片(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 "內(nèi)嵌圖片郵件發(fā)送成功!";
        } catch (Exception e) {
            return "發(fā)送失?。? + e.getMessage();
        }
    }

    /**
     * 測(cè)試5:發(fā)送復(fù)雜郵件(同時(shí)包含內(nèi)嵌圖片和附件)
     */
    @GetMapping("/sendComplex")
    public String sendComplexEmail() {
        try {
            String to = "recipient@example.com";
            String subject = "測(cè)試復(fù)雜郵件(內(nèi)嵌圖片 + 附件)";

            // 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>復(fù)雜郵件測(cè)試</h1>" +
                    "  </div>" +
                    "  <div class='content'>" +
                    "    <p>親愛的用戶:</p>" +
                    "    <p>這封郵件同時(shí)包含:</p>" +
                    "    <ul>" +
                    "      <li>內(nèi)嵌圖片(直接顯示在正文中)</li>" +
                    "      <li>附件(需要下載查看)</li>" +
                    "    </ul>" +
                    "    <p>以下是內(nèi)嵌圖片:</p>" +
                    "    <img src='cid:logo' alt='Logo'>" +
                    "    <p>請(qǐng)查收附件文件。</p>" +
                    "  </div>" +
                    "</body>" +
                    "</html>";

            // 內(nèi)嵌圖片
            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ù)報(bào)表.xlsx"));

            emailUtil.sendComplexEmail(fromEmail, to, subject, htmlContent, inlineImages, attachments);

            return "復(fù)雜郵件發(fā)送成功!";
        } catch (Exception e) {
            return "發(fā)送失?。? + e.getMessage();
        }
    }
}

3.2 單元測(cè)試

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;

    /**
     * 測(cè)試純文本郵件
     */
    @Test
    void testSendSimpleEmail() {
        String to = "recipient@example.com";
        String subject = "【測(cè)試】純文本郵件";
        String content = "你好!\n\n這是一封測(cè)試郵件。\n\n祝好!";

        emailUtil.sendSimpleEmail(fromEmail, to, subject, content);
    }

    /**
     * 測(cè)試HTML郵件
     */
    @Test
    void testSendHtmlEmail() {
        String to = "recipient@example.com";
        String subject = "【測(cè)試】HTML郵件";

        String htmlContent =
                "<html>" +
                "<body>" +
                "  <h2>HTML郵件測(cè)試</h2>" +
                "  <p>這是一封<b>HTML格式</b>的測(cè)試郵件。</p>" +
                "</body>" +
                "</html>";

        emailUtil.sendHtmlEmail(fromEmail, to, subject, htmlContent);
    }

    /**
     * 測(cè)試帶附件的郵件
     */
    @Test
    void testSendEmailWithAttachments() {
        String to = "recipient@example.com";
        String subject = "【測(cè)試】帶附件的郵件";

        String htmlContent =
                "<html>" +
                "<body>" +
                "  <h2>附件測(cè)試</h2>" +
                "  <p>請(qǐng)查收附件。</p>" +
                "</body>" +
                "</html>";

        List<File> attachments = new ArrayList<>();
        attachments.add(new File("D:/test/測(cè)試文檔.txt"));

        emailUtil.sendEmailWithAttachments(fromEmail, to, subject, htmlContent, attachments);
    }

    /**
     * 測(cè)試內(nèi)嵌圖片郵件
     */
    @Test
    void testSendEmailWithInlineImages() {
        String to = "recipient@example.com";
        String subject = "【測(cè)試】?jī)?nèi)嵌圖片郵件";

        String htmlContent =
                "<html>" +
                "<body>" +
                "  <h2>內(nèi)嵌圖片測(cè)試</h2>" +
                "  <img src='cid:test' alt='測(cè)試圖片'>" +
                "</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);
    }
}

四、關(guān)鍵技術(shù)與原理

4.1 中文亂碼的根源與解決方案

亂碼產(chǎn)生的原因

郵件協(xié)議(SMTP)基于文本傳輸,只支持7位ASCII字符。當(dāng)傳輸中文等非ASCII字符時(shí),必須進(jìn)行編碼。

解決方案

亂碼類型根本原因解決方案
附件文件名亂碼文件名未按MIME標(biāo)準(zhǔn)編碼(RFC 2047)使用 MimeUtility.encodeText(filename, "UTF-8", "B")
郵件正文亂碼正文未指定charset或使用了非UTF-8編碼MimeMessageHelper 構(gòu)造函數(shù)中指定 UTF-8
主題亂碼主題未進(jìn)行MIME編碼使用 MimeUtility.encodeText(subject, "UTF-8", "B")

編碼方式說明

MimeUtility.encodeText(text, charset, encoding)
  • charset: 必須使用 "UTF-8",確保能處理所有Unicode字符
  • encoding:
    • "B" - Base64編碼(推薦,兼容性最好)
    • "Q" - Quoted-Printable編碼(適用于ASCII字符較多的場(chǎng)景)

生產(chǎn)環(huán)境建議始終使用 “B”(Base64)編碼,因?yàn)楦鬣]件客戶端對(duì)Base64的支持最穩(wěn)定。

4.2 內(nèi)嵌圖片的技術(shù)原理

Multipart 模式選擇

// 錯(cuò)誤:使用默認(rèn)的mixed模式(附件模式)
MimeMultipart multipart = new MimeMultipart();

// 正確:使用related模式(內(nèi)嵌資源模式)
MimeMultipart multipart = new MimeMultipart("related");
Multipart類型用途說明
mixed混合模式用于獨(dú)立的附件(如Word、PDF)
related相關(guān)模式用于內(nèi)嵌資源(圖片、CSS、字體)
alternative替代模式用于同一內(nèi)容的多版本(純文本 + HTML)

Content-ID(CID)設(shè)置規(guī)則

// 1. 在Java代碼中設(shè)置Content-ID
helper.addInline("logo", dataSource);

// 2. 在HTML中引用(注意:必須加上cid:前綴)
<img src="cid:logo" alt="Logo">

重要規(guī)則

  • Content-ID 建議使用字母、數(shù)字、下劃線,避免使用中文或特殊字符
  • HTML引用時(shí)使用 cid: 前綴
  • 同一郵件中的CID必須唯一,否則會(huì)顯示錯(cuò)誤或無法加載

4.3 Spring Boot Mail 自動(dòng)配置原理

Spring Boot 通過 org.springframework.boot.autoconfigure.mail.MailAutoConfiguration 自動(dòng)配置 JavaMailSender

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

開發(fā)者只需

  1. 配置 spring.mail.* 屬性
  2. 注入 JavaMailSenderJavaMailSenderImpl
  3. 使用 MimeMessageHelper 簡(jiǎn)化郵件構(gòu)建

五、常見問題與解決方案

5.1 認(rèn)證失敗

問題

AuthenticationFailedException: 535 Error: authentication failed

原因

  • 使用了郵箱密碼而非授權(quán)碼
  • 授權(quán)碼已過期或被重置
  • SMTP服務(wù)未開啟

解決方案

  1. 登錄郵箱設(shè)置,開啟SMTP服務(wù)
  2. 生成新的授權(quán)碼
  3. application.yml 中使用授權(quán)碼

獲取授權(quán)碼示例(QQ郵箱)

  1. 登錄QQ郵箱 → 設(shè)置 → 賬戶
  2. 找到"POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服務(wù)"
  3. 開啟"IMAP/SMTP服務(wù)"
  4. 按提示發(fā)送短信獲取授權(quán)碼

5.2 連接超時(shí)

問題

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

原因

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

解決方案

  1. 檢查端口配置:
    • TLS加密:端口 587
    • SSL加密:端口 465
  2. 檢查網(wǎng)絡(luò)連接
  3. 配置超時(shí)時(shí)間:
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 內(nèi)嵌圖片顯示為紅叉

問題:圖片顯示為紅叉或無法加載

可能原因及解決方案

可能原因解決方案
CID不匹配檢查HTML中的 cid: 與Java中的 addInline() 的參數(shù)是否一致
圖片文件不存在使用 file.exists() 驗(yàn)證文件是否存在
圖片格式不支持轉(zhuǎn)換為PNG、JPG或GIF
圖片過大壓縮圖片(建議單張不超過500KB)

5.5 Outlook 中內(nèi)嵌圖片顯示為附件

問題:內(nèi)嵌圖片在Outlook中顯示為附件而非正文

解決方案

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

5.6 郵件發(fā)送慢

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

解決方案

  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;
    }
}

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

6.1 郵件發(fā)送建議

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

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. 重試機(jī)制:對(duì)于網(wǎng)絡(luò)異常,可以添加重試邏輯

6.3 安全建議

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

6.4 監(jiān)控建議

  1. 記錄發(fā)送日志:記錄發(fā)送狀態(tài)、耗時(shí)、失敗原因
  2. 監(jiān)控指標(biāo)
    • 發(fā)送成功率
    • 平均發(fā)送耗時(shí)
    • 失敗原因分布
  3. 告警機(jī)制:發(fā)送失敗率超過閾值時(shí)觸發(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

七、總結(jié)

本文提供了一個(gè)完整的、生產(chǎn)級(jí)可用的 Spring Boot 郵件發(fā)送解決方案,核心要點(diǎn):

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

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

相關(guān)文章

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

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

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

    Jetty啟動(dòng)項(xiàng)目中引用json-lib相關(guān)類庫報(bào)錯(cuò)ClassNotFound的解決方案

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

    如何將java或javaweb項(xiàng)目打包為jar包或war包

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

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

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

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

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

    Java將不同的List集合復(fù)制到另一個(gè)集合常見的方法

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

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

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

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

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

    SpringBoot底層注解詳解

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

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

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

最新評(píng)論

两当县| 合水县| 塔城市| 安宁市| 元朗区| 潢川县| 丹江口市| 库尔勒市| 若羌县| 醴陵市| 新邵县| 岢岚县| 岑溪市| 华阴市| 盐亭县| 思茅市| 柞水县| 密山市| 兴和县| 鄂托克前旗| 建水县| 尤溪县| 泸水县| 荣成市| 平邑县| 孟津县| 托里县| 进贤县| 龙门县| 桃源县| 广饶县| 濉溪县| 准格尔旗| 昌黎县| 宣化县| 崇州市| 乡城县| 淳安县| 宝坻区| 黄冈市| 贡觉县|