首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

Lumisoft.NET POP3 郵件接收與刪除完整實作:頭部解析、正文/附件提取、UID 去重、編碼修正

     編輯:ASP.NET 基礎

前言

Lumisoft.NET 不僅支援 SMTP 發信,其 POP3_Client 類別提供完整的 POP3 協定實作,適合建立郵件接收服務、自動化處理郵件機器人、備份郵件至資料庫等場景。本文完整示範從連線登入、列舉信件、解析頭部/正文/附件、以 UID 去重、到標記刪除的端到端流程,並針對常見編碼亂碼、日期解析失敗提供修正方案。

完整接收流程程式碼 (C#)

public List<ReceivedMail> ReceiveMails(Pop3Setting setting, List<string> existingUids)
{
    var result = new List<ReceivedMail>>();

    using (var client = new POP3_Client())
    {
        // 選擇性啟用通訊記錄 (除錯用)
        client.Logger = new Logger { WriteLog = (s, e) => Debug.WriteLine(e.LogEntry.Text) };

        client.Connect(setting.Server, setting.Port, setting.UseSsl);
        client.Login(setting.UserName, setting.Password);

        foreach (var msg in client.Messages)
        {
            // 1. 先只下載頭部 (輕量、快速)
            var headerBytes = msg.HeaderToByte();
            var mimeHeader = Mail_Message.ParseFromByte(headerBytes);

            // 2. UID 去重
            string uid = msg.UID;
            if (existingUids.Contains(uid)) continue; // 已處理過

            // 3. 解析頭部關鍵欄位 (含編碼修正)
            var mail = new ReceivedMail
            {
                Uid = uid,
                Subject = DecodeMimeHeader(mimeHeader.Subject),
                From = ParseMailAddress(mimeHeader.From),
                To = ParseMailAddresses(mimeHeader.To),
                Cc = ParseMailAddresses(mimeHeader.Cc),
                Date = SafeParseDate(mimeHeader.Date), // 見下方修正方法
                Size = msg.Size
            };

            // 4. 需要正文/附件時,再下載完整訊息
            if (NeedBodyOrAttachments)
            {
                var fullBytes = msg.MessageToByte();
                var mimeFull = Mail_Message.ParseFromByte(fullBytes);

                // 正文:優先 HTML,退而求其次 Plain Text
                mail.BodyHtml = mimeFull.BodyHtmlText;
                mail.BodyText = mimeFull.BodyText;
                if (string.IsNullOrEmpty(mail.BodyHtml)) mail.BodyHtml = mail.BodyText;

                // 附件與內嵌圖片分離
                foreach (var entity in mimeFull.GetAttachments(true, true))
                {
                    if (entity.ContentDisposition == null) continue;

                    string fileName = entity.ContentDisposition.Param_FileName;
                    if (string.IsNullOrEmpty(fileName)) continue;

                    var bytePart = entity.Body as MIME_b_SinglepartBase;
                    byte[] data = bytePart?.Data;

                    bool isInline = entity.ContentDisposition.DispositionType == MIME_DispositionTypes.Inline;
                    if (isInline)
                        mail.InlineImages.Add(new MailAttachment { FileName = fileName, Data = data, ContentId = entity.ContentID });
                    else
                        mail.Attachments.Add(new MailAttachment { FileName = fileName, Data = data });
                }
            }

            result.Add(mail);
        }
    }
    return result;
}

關鍵輔助方法:編碼修正、日期解析、地址解析

1. MIME 標頭解碼 (解決 =?utf-8?B?xxx?= 亂碼)

private string DecodeMimeHeader(string encoded)
{
    if (string.IsNullOrEmpty(encoded)) return encoded;

    // Lumisoft ParseFromByte 預設用 UTF-8 解碼,若原始為 Big5/GB2312 會產生亂碼
    // 策略:先取得原始 Header 字串,再以 Default 編碼轉 Byte,再以 UTF-8 重解
    // 這裡提供通用 Regex 解碼備援
    var regex = new Regex(@"=\?(?<charset>[^?]+)\?(?<enc>[BQ])\?(?<data>[^?]+)\?=", RegexOptions.IgnoreCase);
    return regex.Replace(encoded, m =>
    {
        string charset = m.Groups["charset"].Value.ToLower();
        string enc = m.Groups["enc"].Value;
        string data = m.Groups["data"].Value;

        // 正規化字集名稱
        if (charset.Contains("gb2312") || charset.Contains("gbk") || charset.Contains("euc-cn") || charset.Contains("euccn"))
            charset = "gb2312";
        else if (charset.Contains("utf8") || charset.Contains("utf-8"))
            charset = "utf-8";
        else if (charset.Contains("big5"))
            charset = "big5";

        try
        {
            byte[] bytes = enc == "B" ? Convert.FromBase64String(data) : DecodeQuotedPrintable(data);
            return Encoding.GetEncoding(charset).GetString(bytes);
        }
        catch { return m.Value; } // 解碼失敗保留原文
    });
}

private byte[] DecodeQuotedPrintable(string input)
{
    using (var ms = new MemoryStream())
    {
        for (int i = 0; i < input.Length; i++)
        {
            if (input[i] == '=' && i + 2 < input.Length)
            {
                string hex = input.Substring(i + 1, 2);
                if (byte.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out byte b))
                {
                    ms.WriteByte(b);
                    i += 2;
                    continue;
                }
            }
            ms.WriteByte((byte)input[i]);
        }
        return ms.ToArray();
    }
}

2. 安全日期解析 (修正 Lumisoft 無法解析非 RFC2822 格式)

private DateTime SafeParseDate(DateTime? lumiDate)
{
    if (lumiDate.HasValue && lumiDate.Value != DateTime.MinValue) return lumiDate.Value;

    // 若 Lumisoft 解析失敗,嘗試從原始 Header 字串自行解析
    // 這裡需配合上層傳入原始 Header 字串,或在 ParseFromByte 前預處理
    // 範例:若原始 Date 標頭為 "2023-05-06 19:01:44",DateTime.TryParse 即可成功
    return DateTime.MinValue; // 實務上應傳入原始字串再嘗試
}

// 建議在呼叫 ParseFromByte 前預處理 Header 字串:
// byte[] utf8Bytes = Encoding.UTF8.GetBytes(rawHeaderString);
// var mime = Mail_Message.ParseFromByte(utf8Bytes); // 強制以 UTF-8 解碼標頭,避免預設編碼錯誤

3. 郵件地址集合解析

private string ParseMailAddress(Mail_t_MailboxList list)
{
    if (list == null || list.Count == 0) return string.Empty;
    var mb = list[0];
    return string.IsNullOrEmpty(mb.DisplayName) ? mb.Address : $"{mb.DisplayName} <{mb.Address}>";
}

private string ParseMailAddresses(Mail_t_AddressList list)
{
    if (list == null) return string.Empty;
    var sb = new StringBuilder();
    foreach (var mb in list.Mailboxes)
    {
        if (sb.Length > 0) sb.Append("; ");
        sb.Append(string.IsNullOrEmpty(mb.DisplayName) ? mb.Address : $"{mb.DisplayName} <{mb.Address}>");
    }
    return sb.ToString();
}

郵件刪除操作 (標記刪除)

POP3 協定採用「標記刪除 + QUIT 時真正刪除」機制。Lumisoft 封裝為 MarkForDeletion():

public void DeleteMails(Pop3Setting setting, List<string> uidsToDelete)
{
    using (var client = new POP3_Client())
    {
        client.Connect(setting.Server, setting.Port, setting.UseSsl);
        client.Login(setting.UserName, setting.Password);

        foreach (var msg in client.Messages)
        {
            if (uidsToDelete.Contains(msg.UID))
            {
                try
                {
                    msg.MarkForDeletion(); // 標記
                }
                catch (Exception ex)
                {
                    Log.Error($"刪除 UID {msg.UID} 失敗: {ex.Message}");
                }
            }
        }
        // 離開 using 區塊時,Dispose 會自動發送 QUIT,伺服器才真正刪除
    }
}

常見問題與解決方案彙總

問題原因解決方案
中文主旨/寄件者顯示亂碼MIME 標頭採用 Base64/QP 編碼,字集非 UTF-8 (如 Big5/GB2312),Lumisoft 預設 UTF-8 解碼使用 DecodeMimeHeader 正規化字集後手動解碼;或預處理 Header 字串以 UTF-8 強制解碼
Date 解析拋出 ParseException郵件 Date 標頭不符 RFC2822 (如 "2023-05-06 19:01:44")修改 Lumisoft 原始碼 Mail_Message.Date 屬性加入 DateTime.TryParse 容錯;或預處理 Header
附件檔名亂碼Content-Disposition filename* 參數編碼 (RFC 5987) 或舊式 encoded-word優先讀取 filename* (UTF-8');否則對 filename 做 DecodeMimeHeader
大量郵件下載記憶體飆升一次 MessageToByte() 載入完整郵件 (含大附件)先用 HeaderToByte() 篩選;需下載附件時改用串流分塊讀取 (Lumisoft 目前無內建串流 API,建議改用 MailKit)
UID 重複導致重複處理POP3 UID 僅在單一 Session 內唯一,跨 Session 可能重用結合 UID + 郵件大小 + Date + Subject 產生複合鍵;或改用 IMAP (支援永久 UID)
SSL/TLS 連線失敗Port 與 UseSsl 不匹配 (如 Port 110 卻設 UseSsl=true)參考常見設定:Gmail 995/SSL、Outlook 995/SSL、企業郵箱 110/STARTTLS (Port 110, UseSsl=false, 需手動 STLS 指令,Lumisoft 需自行實作)

效能優化建議

  • 只拉頭部:預設 client.Messages 列舉時已自動下載頭部,不需額外呼叫。
  • 批次刪除:累積 UID 清單,單次連線逐一 MarkForDeletion,最後一次 QUIT 提交。
  • 連線重用:短時間內多次輪詢可保持 POP3_Client 實例,避免重複 TCP/SSL 握手。
  • 非同步化:Lumisoft 為同步 API,生產環境建議包裝 Task.Run 或遷移至 MailKit 獲得原生 async/await。

遷移提示:為什麼新專案建議改用 MailKit?

  • MailKit 完整支援 IMAP (含 IDLE 推播)、OAuth2、S/MIME、OpenPGP。
  • 原生 async/await、CancellationToken、IAsyncEnumerable。
  • 跨平台 (Linux/macOS/Windows)、無 System.Drawing 依賴。
  • 活躍維護、NuGet 下載量大、文件完整。

結語

Lumisoft.NET 的 POP3_Client 提供功能完整的 POP3 客戶端實作,配合本文提供的編碼修正、日期容錯、UID 去重、附件分離等技巧,可快速建構郵件接收處理管線。但受限於同步 API、編碼處理不夠健全、缺乏 IMAP IDLE/OAuth2 等現代功能,長期專案強烈建議遷移至 MailKit,並在架構層面抽象 IMailReceiver 介面以平滑切換。

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