看到很多開發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客戶端查詢聊天記錄介面的截圖:
就到這裡了,還有疑問的朋友,請給我留言,我會及時回覆的。