IM系統中聊天記錄模組的設計與實現

zhuweisky發表於2014-07-16

  看到很多開發IM系統的朋友都想實現聊天記錄儲存和查詢這一不可或缺的功能,這裡我就把自己前段時間為傲瑞通(OrayTalk)開發聊天記錄模組的經驗分享出來,供需要的朋友參考下。

一.總體設計

1.儲存位置  

  從一開始我們就打算在服務端和客戶端本地同時儲存聊天記錄,而且,在客戶端檢視聊天記錄時,可以選擇是從本地載入、還是從伺服器載入。這樣做的好處有兩個:

(1)從本地載入聊天記錄速度非常快。

(2)當更換了登入的機器,在任何地方任何時刻都可以從伺服器載入完整的聊天記錄,記錄永遠不會丟失。

2.儲存方案

(1)在服務端儲存聊天記錄當然使用我們主流的資料庫SqlServer或Mysql等。

(2)在客戶端,我們開始選擇的是使用序列化技術,但是,考慮到當聊天記錄資料量龐大時,序列化方案就不夠靈活了,而且效能也跟不上。所以,最後決定使用輕量級的資料庫Sqlite。

3.ORM框架

  DataRabbit的最新版本增加了對Sqlite的支援,並且對不同資料庫的操作API是完全一致的,所以我們使用DataRabbit寫了一個小元件來完成聊天記錄的儲存與查詢等資料庫訪問操作。而無論是客戶端還是服務端的聊天記錄儲存相關的工作,都交給這個元件來完成。

 

二.具體實現

1.ChatMessageRecord類

  一條聊天記錄基本上包含了以下幾個內容:傳送人、接收人、內容、時間等。並且,我們想將兩人聊天及群聊天抽象成同一個模型,於是,聊天記錄的Entity類ChatMessageRecord設計成如下模樣:

    public class ChatMessageRecord
    {     
        #region AutoID
        private long autoID = 0;
        /// <summary>
        /// 自增ID,編號。
        /// </summary>
        public long AutoID
        {
            get { return autoID; }
            set { autoID = value; }
        }
        #endregion

        #region SpeakerID
        private string speakerID = "";
        /// <summary>
        /// 發言人的ID。
        /// </summary>
        public string SpeakerID
        {
            get { return speakerID; }
            set { speakerID = value; }
        }
        #endregion

        #region AudienceID
        private string audienceID = "";
        /// <summary>
        /// 聽眾ID,可以為GroupID。
        /// </summary>
        public string AudienceID
        {
            get { return audienceID; }
            set { audienceID = value; }
        }
        #endregion

        #region OccureTime
        private DateTime occureTime = DateTime.Now;
        /// <summary>
        /// 聊天記錄發生的時間。
        /// </summary>
        public DateTime OccureTime
        {
            get { return occureTime; }
            set { occureTime = value; }
        }
        #endregion

        #region ContentRtf
        private string contentRtf = "";
        /// <summary>
        /// 聊天的內容。
        /// </summary>
        public string ContentRtf
        {
            get { return contentRtf; }
            set { contentRtf = value; }
        }
        #endregion

        #region IsGroupChat
        private bool isGroupChat = false;
        /// <summary>
        /// 是否為群聊記錄。
        /// </summary>
        public bool IsGroupChat
        {
            get { return isGroupChat; }
            set { isGroupChat = value; }
        }
        #endregion
    } 

    在ChatMessageRecord的定義中,聊天內容欄位被設計為string型別,這是因為在OrayTalk中,聊天內容是富文字RTF格式的。如果需要,可以更改為byte[]型別,這樣通過自定義的序列化操作就可以承載更復雜的聊天格式。

  最後一個欄位IsGroupChat表明當前記錄是否為群聊記錄,如果是群聊記錄,那麼,AudienceID就不是好友的ID了,而是目標群組的ID。

  最後請注意:ChatMessageRecord實體與資料庫中的ChatMessageRecord表是完全對映的關係,這才使得DataRabbit的ORM資料訪問成為可能。

2.ChatRecordPage類

  當我們請求聊天記錄時,由於記錄數量可能非常龐大,所以,採用分頁是不可避免的。我們用ChatRecordPage來封裝查詢返回的一頁聊天記錄:

根據ChatRecordPage中的TotalCount欄位,查詢者可以知道符合條件的記錄數是多少,如此,就可以知道總共有多少頁。

3.IChatRecordPersister介面

  無論是客戶端還是服務端儲存與查詢聊天記錄,我們都使用同一個介面IChatRecordPersister來進行抽象:

    public interface IChatRecordPersister
    {
        /// <summary>
        /// 插入一條聊天記錄(包括群聊天記錄)。
        /// </summary>  
        void InsertChatMessageRecord(ChatMessageRecord record);

        
        /// <summary>
        /// 獲取一頁與好友的聊天記錄。
        /// </summary>
        /// <param name="timeScope">日期範圍</param>
        /// <param name="myID">自己的UserID</param>
        /// <param name="friendID">好友的ID</param>
        /// <param name="pageSize">頁大小</param>
        /// <param name="pageIndex">頁索引</param>      
        /// <returns>聊天記錄頁</returns>
        ChatRecordPage GetChatRecordPage(DateTimeScope timeScope, string myID, string friendID, int pageSize, int pageIndex);

        /// <summary>
        /// 獲取一頁群聊天記錄。
        /// </summary>
        /// <param name="timeScope">日期範圍</param>
        /// <param name="groupID">群ID</param>
        /// <param name="pageSize">頁大小</param>
        /// <param name="pageIndex">頁索引</param>     
        /// <returns>聊天記錄頁</returns>
        ChatRecordPage GetGroupChatRecordPage(DateTimeScope timeScope, string groupID, int pageSize, int pageIndex);        
    }

(1)插入遊戲記錄時,與好友聊天記錄以及群聊天記錄使用同一個InsertChatMessageRecord方法即可,只是在構造ChatMessageRecord物件時,欄位的賦值有所區別。

(2)使用DataRabbit實現該介面時(如ChatRecordPersister類),通過屬性DataBaseType來控制訪問的是否為Sqlite資料庫。然後在服務端使用ChatRecordPersister存取聊天記錄時,就將DataBaseType設定為SqlServer;客戶端則設定為Sqlite。

 

三.可能的Remoting的介面

  當我們從伺服器載入聊天記錄時,可以考慮使用Remoting技術來實現,如果是這樣,只需要在服務端把IChatRecordPersister介面暴露為Remoting服務,然後客戶端使用這一Remoting服務進行聊天記錄查詢。這樣一來,客戶端在切換從本地載入和從伺服器載入時,只需要切換IChatRecordPersister為本地ChatRecordPersister物件的引用或remoting遠端引用即可。整個的程式碼實現將會非常簡潔一致。

  到這裡,關於聊天記錄模組的設計與實現就介紹得差不多了,依照這樣的思路,大家在自己的IM系統中增加聊天記錄的功能應該是很簡單的了。最後,上一張OrayTalk客戶端查詢聊天記錄介面的截圖:

      

 就到這裡了,還有疑問的朋友,請給我留言,我會及時回覆的。

 

相關文章