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

JavaMail 解析複合郵件 (HTML+圖片+附件) 遞迴處理實戰

     編輯:Java 相關知識

現代郵件多為 Multipart 結構:multipart/alternative (純文本/HTML 替代) 嵌套 multipart/related (內嵌圖片) 及 multipart/mixed (附件)。本文深入剖析 MIME 結構樹,提供健壯的遞迴解析代碼,正確提取 HTML 正文、內嵌圖片 CID 對應、附件儲存,解決「只取第一層 Part 導致內容遺失」的常見陷阱。

一、關鍵 MIME 類型辨識

MIME Type用途
text/plain純文本正文
text/htmlHTML 格式正文
multipart/alternative同一內容的多種表現形式 (擇一顯示)
multipart/related相關資源聚合 (HTML + 內嵌圖片)
multipart/mixed混合內容 (正文 + 附件)

二、郵件結構樹範例

Message (multipart/mixed)
 ├─ Part 0: multipart/related
 │    ├─ Part 0.0: multipart/alternative
 │    │    ├─ Part 0.0.0: text/plain
 │    │    └─ Part 0.0.1: text/html (含 )
 │    └─ Part 0.1: image/png (Content-ID: )
 └─ Part 1: application/pdf (附件, Content-Disposition: attachment)

核心洞見:Message.getContent() 回傳頂層 Multipart,第一層 Part 常非葉節點,必須遞迴遍歷至葉節點 才能判斷實際內容類型。

三、健壯遞迴解析器實作


public class MailContentParser {
    
    public static String parseMessage(Message msg, boolean preferHtml) 
            throws MessagingException, IOException {
        Object content = msg.getContent();
        if (content instanceof Multipart) {
            return parseMultipart((Multipart) content, preferHtml);
        } else if (content instanceof String) {
            return (String) content; // 簡單郵件
        }
        return "";
    }

    private static String parseMultipart(Multipart mp, boolean preferHtml) 
            throws MessagingException, IOException {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < mp.getCount(); i++) {
            BodyPart part = mp.getBodyPart(i);
            result.append(parsePart(part, preferHtml));
        }
        return result.toString();
    }

    private static String parsePart(Part part, boolean preferHtml) 
            throws MessagingException, IOException {
        String contentType = part.getContentType();
        if (contentType == null) return "";

        // 1. 處理 multipart/alternative: 根據偏好選擇 HTML 或 Plain
        if (part.isMimeType("multipart/alternative")) {
            Multipart mp = (Multipart) part.getContent();
            String html = "", plain = "";
            for (int i = 0; i < mp.getCount(); i++) {
                BodyPart bp = mp.getBodyPart(i);
                if (bp.isMimeType("text/html")) html = parsePart(bp, preferHtml);
                else if (bp.isMimeType("text/plain")) plain = parsePart(bp, preferHtml);
            }
            return preferHtml ? (html.isEmpty() ? plain : html) : (plain.isEmpty() ? html : plain);
        }

        // 2. 處理其他 multipart (related/mixed): 遞迴遍歷所有子部分
        if (part.isMimeType("multipart/*")) {
            return parseMultipart((Multipart) part.getContent(), preferHtml);
        }

        // 3. 葉節點: 直接內容
        if (part.isMimeType("text/html")) {
            return "
" + part.getContent() + "
"; // 實際應處理 CID 圖片替換 } if (part.isMimeType("text/plain")) { return "
" + part.getContent() + "
"; } // 4. 附件處理 (Content-Disposition: attachment) String disposition = part.getDisposition(); if (Part.ATTACHMENT.equalsIgnoreCase(disposition) || part.getFileName() != null) { String fileName = part.getFileName(); // 儲存附件邏輯: saveAttachment(fileName, part.getInputStream()); return "[附件: " + fileName + "]"; } // 5. 內嵌資源 (inline, 如圖片) - 實際應建立 CID->資源映射供 HTML 替換 if (Part.INLINE.equalsIgnoreCase(disposition)) { return "[內嵌資源: " + part.getContentType() + "]"; } return "[未知部分: " + contentType + "]"; } // 附件儲存輔助方法 private static void saveAttachment(String fileName, InputStream in) throws IOException { File file = new File(fileName); for (int i = 0; file.exists(); i++) file = new File(fileName + "_" + i); try (FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos); BufferedInputStream bis = new BufferedInputStream(in)) { byte[] buf = new byte[4096]; int len; while ((len = bis.read(buf)) != -1) bos.write(buf, 0, len); } } }

四、進階議題:CID 圖片替換與完整呈現

HTML 正文中 <img src="cid:part1.01@host"> 引用內嵌圖片。完整解析需兩階段:

  1. 掃描階段:遞迴收集所有 Part.INLINE 部分,建立 Map (Key: Content-ID 去除 <><>)
  2. 渲染階段:解析 HTML 部分時,將 cid:xxx 替換為 data:image/...;base64,... 或暫存檔案路徑

五、常見坑點提醒

  • 編碼問題:使用 MimeUtility.decodeText() 處理主題、附件檔名 (RFC 2047/2231)
  • 大郵件記憶體:避免一次性 getContent() 載入巨大附件,考慮流式處理
  • 非標準結構:部分郵件客戶端生成不符規範結構,解析器需具防禦性 (null 檢查、異常捕獲)
  • 已讀標記:Folder.READ_WRITE 開啟並 setFlag(Flags.Flag.SEEN, true) 才會標記已讀
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved