首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JavaMail 發送帶附件郵件完整指南:從 MIME 結構到生產級封裝

     編輯:Java 相關知識

JavaMail 核心概念:MIME 多部分消息

發送帶附件的郵件,本質上是構造一棵 MimeMultipart 樹:

  • 根節點:MimeMultipart("mixed") —— 混合類型,同時包含正文與附件;
  • 子節點 1:MimeBodyPart 設置 text/html 正文;
  • 子節點 N:每個附件一個 MimeBodyPart,通過 FileDataSource 綁定文件並設置 Content-Disposition: attachment。

生產級封裝:MailUtil 工具類

public final class MailUtil {
    private MailUtil() {} // 工具類禁止實例化

    /** 發送郵件統一入口 */
    public static Result send(MailConfig config) {
        try {
            Session session = createSession(config);
            MimeMessage message = buildMessage(session, config);
            Transport.send(message);
            return Result.success("發送成功");
        } catch (AuthenticationFailedException e) {
            return Result.fail("認證失敗:用戶名/密碼錯誤");
        } catch (MessagingException e) {
            return Result.fail("郵件構造/發送異常:" + e.getMessage());
        } catch (Exception e) {
            return Result.fail("未知錯誤:" + e.getMessage());
        }
    }

    private static Session createSession(MailConfig config) {
        Properties props = new Properties();
        props.put("mail.smtp.host", config.getHost());
        props.put("mail.smtp.port", config.getPort());
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.starttls.enable", "true"); // TLS 加密
        props.put("mail.smtp.timeout", "10000");
        props.put("mail.smtp.connectiontimeout", "10000");
        
        return Session.getInstance(props, new Authenticator() {
            @Override protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(config.getUsername(), config.getPassword());
            }
        });
    }

    private static MimeMessage buildMessage(Session session, MailConfig config) 
            throws MessagingException, UnsupportedEncodingException {
        MimeMessage msg = new MimeMessage(session);
        msg.setFrom(new InternetAddress(config.getFrom(), config.getPersonal(), "UTF-8"));
        msg.setRecipients(Message.RecipientType.TO, parseAddresses(config.getTo()));
        msg.setSubject(config.getSubject(), "UTF-8");
        msg.setSentDate(new Date());

        // 構建 Multipart 容器
        MimeMultipart multipart = new MimeMultipart("mixed");
        
        // 1. HTML 正文
        MimeBodyPart htmlPart = new MimeBodyPart();
        htmlPart.setContent(config.getHtmlContent(), "text/html;charset=UTF-8");
        multipart.addBodyPart(htmlPart);

        // 2. 附件們
        for (String filePath : config.getAttachments()) {
            MimeBodyPart attachPart = new MimeBodyPart();
            FileDataSource fds = new FileDataSource(filePath);
            attachPart.setDataHandler(new DataHandler(fds));
            // 解決中文附件名亂碼 (RFC 2231 編碼)
            attachPart.setFileName(MimeUtility.encodeText(fds.getName(), "UTF-8", null));
            multipart.addBodyPart(attachPart);
        }

        msg.setContent(multipart);
        msg.saveChanges(); // 必須調用,更新 Headers
        return msg;
    }

    private static InternetAddress[] parseAddresses(String commaSeparated) 
            throws AddressException, UnsupportedEncodingException {
        return Arrays.stream(commaSeparated.split("\\s*,\\s*"))
                .map(s -> new InternetAddress(s.trim()))
                .toArray(InternetAddress[]::new);
    }

    /** 配置類 */
    @Data @Builder @AllArgsConstructor @NoArgsConstructor
    public static class MailConfig {
        private String host;        // smtp.qq.com
        private int port;           // 465/587
        private String username;    // 發件賬號
        private String password;    // 授權碼(非登錄密碼)
        private String from;        // 發件地址
        private String personal;    // 發件人顯示名
        private String to;          // 收件人,逗號分隔
        private String subject;     // 主題
        private String htmlContent; // HTML 正文
        private List attachments; // 附件絕對路徑列表
    }

    /** 結果封裝 */
    @Data @AllArgsConstructor
    public static class Result {
        private boolean success;
        private String message;
        public static Result success(String msg) { return new Result(true, msg); }
        public static Result fail(String msg) { return new Result(false, msg); }
    }
}

調用示例

MailConfig config = MailConfig.builder()
    .host("smtp.qq.com").port(465)
    .username("xxx@qq.com").password("auth_code_xxx")
    .from("xxx@qq.com").personal("系統通知")
    .to("user1@example.com, user2@example.com")
    .subject("【重要】月度報表")
    .htmlContent("

您好

請查收附件報表。

") .attachments(Arrays.asList("/data/report.pdf", "/data/data.xlsx")) .build(); MailUtil.Result result = MailUtil.send(config); System.out.println(result.getMessage());

常見坑點避雷指南

現象原因解決方案
163/郵箱顯示「有附件」圖標但無附件Multipart 子類型用錯,或未正確設置 Disposition根節點用 mixed,附件 Part 必須 setDisposition(Part.ATTACHMENT)
中文附件名亂碼未進行 RFC 2231 編碼MimeUtility.encodeText(name, "UTF-8", null)
發送超時未設置連接/讀取超時Props 設置 mail.smtp.timeout / connectiontimeout
SSL/TLS 握手失敗端口/協議不匹配465 用 SSL (props: mail.smtp.ssl.enable=true),587 用 STARTTLS
大附件發送失敗郵箱服務商限制單封大小(通常 20-50MB)分卷壓縮、雲存儲生成下載鏈接、或分多封發送

總結

JavaMail 發送帶附件郵件的核心在於正確構建 MimeMultipart("mixed") 樹結構,並妥善處理編碼、超時、認證等工程化細節。本文提供的 MailUtil 封裝了完整的異常處理、TLS 加密、中文附件名支持,可直接用於生產環境。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved