基於Lumisoft.NET組件,使用IMAP協議收取郵件

在早期一直使用Lumisoft.NET組件來進行郵件的處理查找,對於郵件的處理非常方便,之前在隨筆《基於Lumisoft.NET組件的POP3郵件接收和刪除操作》中也介紹過基於POP3和SMPT進行郵件的收發處理,一般郵件伺服器對於Pop3都是支援很好的,常規使用測試多個伺服器都沒問題,所以就沒怎麼研究IMAP協議的處理,本篇隨筆基於原來POP3 的需求擴展了IMAP協議的處理。

 1、創建IMAP收件輔助類

為了方便進行收取IMAP郵件的處理,我們創建一個ImapHelper,並傳入相關用到的一些參數,用來封裝收件的處理操作。

如下輔助類所示,傳入伺服器域名地址,埠,是否SSL,用戶名和密碼等資訊。

 

 

接下來,我們需要連接伺服器,並嘗試獲取授權資訊,如果通過,則可以進行下一步獲取郵件資訊的操作,如下程式碼所示。

        /// <summary>
        /// 收取郵件操作
        /// </summary>
        public void Receive()
        {
            using (var client = new IMAP_Client())
            {
                //創建日誌處理
                client.Logger = new Logger();
                client.Logger.WriteLog += new EventHandler<WriteLogEventArgs>(WriteLog);//響應記錄顯示

                //使用帳號密碼,連接伺服器
                client.Connect(server, port, useSsl);
                //登錄獲取授權操作
                client.Login(username, password);
                //var identity = client.AuthenticatedUserIdentity;

                //獲取各個郵箱目錄的概要資訊
                client.GetFolders(null).ToList().ForEach(f =>
                {

                    Console.WriteLine(f.FolderName);
                    var s = client.FolderStatus(f.FolderName);
                    s.ToList().ForEach(sIt =>
                    {
                        Console.WriteLine("總數:{0},未讀:{1},最近{2}", sIt.MessagesCount, sIt.MessagesCount, sIt.UnseenCount);
                    });

                });

我們登錄獲得授權後,測試獲取各個目錄的概要郵件資訊,如總郵件數量,以及未讀數量等等。

然後通過選擇具體的郵箱目錄,並設置返回資訊包含的內容格式,以及從伺服器返回那些序號的郵件等等,如下程式碼所示。

    //選擇郵箱
    client.SelectFolder("INBOX");
    //首先確定取第x到第n封郵件,"1:*"表示第1封到最後一封
    var seqSet = IMAP_t_SeqSet.Parse("1:*");
    var items = new IMAP_t_Fetch_i[]
    {
        new IMAP_t_Fetch_i_Envelope(),  //郵件的標題、正文等資訊
        new IMAP_t_Fetch_i_Uid(),       //返回郵件的UID號,UID號是唯一標識郵件的一個號碼
        new IMAP_t_Fetch_i_Flags(),     //此郵件的標誌,應該是已讀未讀標誌
        new IMAP_t_Fetch_i_InternalDate(),//貌似是收到的日期
        new IMAP_t_Fetch_i_Rfc822()     //Rfc822是標準的郵件數據流,可以通過Lumisoft.Net.Mail.Mail_Message對象解析出郵件的所有資訊
    };

接著我們通過傳入條件,並給他一個回調匿名函數處理相關的郵件資訊,如下所示。

    //Fetch 第一個參數false時seqSet有效
    client.Fetch(false, seqSet, items, (s, e) =>
    {
        //處理郵件的匿名函數內容
    });

接著我們處理郵件資訊的轉換,吧郵件資訊轉換為Mail_Message對象的資訊,這個包含郵件相關的頭部資訊,正文,以及附件資訊等全部內容。

   var email = e.Value as IMAP_r_u_Fetch;
    if (email.Rfc822 != null)
    {
        email.Rfc822.Stream.Position = 0;
        var mime_message = Mail_Message.ParseFromStream(email.Rfc822.Stream);
        email.Rfc822.Stream.Close();

然後我們把郵件的資訊進一步轉換為我們需要存儲在資料庫的對象資訊,最後寫入資料庫即可。

    receiveInfo.ReceivedDate = DateTime.Now;//接收本地時間
    receiveInfo.Company_ID = this.companyId;
    receiveInfo.User_ID = this.userId;
    receiveInfo.Email = this.email;//接收Email帳號
    receiveInfo.MailConfig_ID = this.mailConfig_ID;//接收Email帳號的配置記錄ID

    //每封Email會有一個在Pop3伺服器範圍內唯一的Id,檢查這個Id是否存在就可以知道以前有沒有接收過這封郵件
    receiveInfo.MailUid = email.UID.UID.ToString();

    try
    {
        //可能會出現【LumiSoft.Net.ParseException: Header field 'Date' parsing failed】異常錯誤。
        receiveInfo.SendDate = mime_message.Date;
    }
    catch (Exception ex)
    {
        receiveInfo.SendDate = Convert.ToDateTime("1900-1-1");//錯誤賦值一個日期
        error = string.Format("轉換郵件的Date出錯:帳號{0} 郵件標題:{1}", username, mime_message.Subject);
        LogTextHelper.Error(error, ex);
    }

    //可能出現亂碼問題,通過函數進行轉換
    receiveInfo.Title = mime_message.Subject;//DecodeString(mime_header.Subject);

    receiveInfo.MailBody = mime_message.BodyText;
    try
    {
        if (!string.IsNullOrEmpty(mime_message.BodyHtmlText))
        {
            receiveInfo.MailBody = mime_message.BodyHtmlText;
        }
    }
    catch
    {
        //屏蔽編碼出現錯誤的問題,錯誤在BodyText存在而BodyHtmlText不存在的時候,訪問BodyHtmlText會出現
    }

寫入資料庫處理,調用我們通用處理類處理數據資訊的存儲即可。

    #region 寫入郵件資訊到資料庫
    int mailId = -1;
    try
    {
        mailId = BLLFactory<MailReceive>.Instance.Insert2(receiveInfo);
    }
    catch (Exception ex)
    {
        error = string.Format("寫入郵件資訊到資料庫出錯:帳號{0}  郵件標題:{1}", username, mime_message.Subject);
        LogTextHelper.Error(error, ex);
    }

    if (mailId <= 0) return; //如果郵件沒有保存,不要保存附件 
    #endregion

2、郵件的附件處理

郵件的附件,包含常規的郵件附件,以及嵌入正文的附件圖片,因此需要進行不同類型的判斷,並一起把附件獲取下來存儲,這樣在顯示的時候,才能正常顯示相關的附件。

其中Mail_Message 對象有一個函數,可以獲取全部這兩類附件的資訊到列表中。

public MIME_Entity[] GetAttachments(bool includeInline, bool includeEmbbedMessage)

這樣我們來調用這個函數,然後進行附件的提取存儲處理即可。

    #region 郵件附件內容
    foreach (var entity in mime_message.GetAttachments(true, true))
    {
        string fileName = "";

        #region 判斷是普通附件還是嵌入的內容附件
        if (entity.ContentDisposition != null &&
            entity.ContentDisposition.DispositionType == MIME_DispositionTypes.Attachment)
        {
            Console.WriteLine("Attachment: " + entity.ContentDisposition.Param_FileName);
            fileName = entity.ContentDisposition.Param_FileName;
        }
        else
        {
            string cid = entity.ContentID.Substring(1, entity.ContentID.Length - 2);
            if (entity.ContentType.Param_Name != null &&
                mime_message.BodyHtmlText.Contains(string.Format("cid:{0}", cid)))
            {
                Console.WriteLine("Embeded image: " + cid);
                fileName = cid;
            }
            else
            {
                Console.WriteLine("Unknown attachment.");
            }
        }

郵件的附件資訊,entity對象需要轉換為MIME_b_SinglepartBase進行處理的。

 var byteObj = entity.Body as MIME_b_SinglepartBase;

因此我們可以通過文件方式存儲它的位元組數據,如下所示。

File.WriteAllBytes(filename, byteObj.Data);

或者調用附件資訊進行存儲處理(可以是本地存儲、或者FTP上傳等方式)

 

 

 如對於測試帶有嵌入圖片,附件資訊的郵件,這樣處理能夠順利獲取所有的附件資訊。

 

因此可以使用郵件管理模組中的定時收發郵件的處理,實現郵件的接收和發送。

 

 

3、163郵箱對於IMAP協議不支援

在測試IMAP協議收取郵件的時候,對於POP3發現大多數郵箱都是支援的。

但雖然163郵箱對POP3的支援不錯,對IMAP協議卻不支援,都是使用授權碼進行登錄,也確實登錄成功了,但是IMAP協議切換郵箱進行郵件收取的時候,就會提示

提示錯誤資訊。

 

  00023 NO SELECT Unsafe Login. Please contact [email protected] for help

 

如有興趣,了解Lumisoft.NET組件的相關使用內容,請參考我相關隨筆,謝謝。

《 基於Lumisoft.NET組件和.NET API實現郵件發送功能的對比

基於Lumisoft.NET實現的郵件發送功能

基於Lumisoft.NET組件開發碰到亂碼等一些問題的解決

基於Lumisoft.NET組件的SMTP帳號登陸檢測

郵件代收代發軟體操作說明

郵件代收代發功能模組的操作介面設計和階段性總結