IM系统中聊天记录模块的设计与实现

  看到很多开发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客户端查询聊天记录界面的截图:

 就到这里了,还有疑问的朋友,请给我留言,我会及时回复的。

IM系统中聊天记录模块的设计与实现,布布扣,bubuko.com

时间: 2024-08-05 11:14:48

IM系统中聊天记录模块的设计与实现的相关文章

(3)MEF插件系统中通信机制的设计和实现

1.背景 一般的WinForm中通过C#自带的Event机制便能很好的实现事件的注册和分发,但是,在插件系统中却不能这么简单的直接用已有的类来完成.一个插件本不包含另外一个插件,它们均是独立解耦的,实现插件和插件间的通信还需要我们设计出一个事件引擎来完成这个需求. 目前很多高级语言中基本都实现了观察者模式,并进行了自己的包装.比如C#中的delegate和event组合,java awt中的Event和addActionListener组合,Flex中的Event.addEventListene

向Android系统中添加模块及产品流程

 添加Android模块  一.基础知识: (1)在Android系统中,编译都是以模块(而不是文件)作为单位的,每个模块都有一个唯一的名称: (2)为了将模块编译到Android系统中,每个模块都需要一个专门的Make文件,也即是"Android.mk"文件: 二.实现hello.c 模块的编写  1. 如在 hardware/test目录下,编写hello.c 2. 在hardw/test目录下,编写Android.mk Android.mk编写的步骤: (1)设置当前模块的编译路

页面中查询模块的设计与实现思路

在做一个物品的展示管理页面,想把增删改查都放到页面中,用axure画了一个页面,比较简单,主要分为上下两部分,上半部分是查询模块,下半部分是表格,用来展示数据.新增按钮单独在页面中,修改按钮在表的每一行数据后面,删除按钮在表格下面,将表格中的数据每一行设为可选,选择后进行删除操作.查询部分没有考虑很多,只是列了几个物品的主要属性,有下拉框有文本框,可选可填,然后查询,其实默认的就是这些查询条件之间是"与"的关系. 图1 自己设计的查询模块??项目在讨论的时候,技术领导说了一下这个地方设

SAP系统的核心模块

SAP系统核心模块都有哪些?SAP全套15模块(ABAP.BASIS.CRM.CO.FI.MM.PLM.PM.PP.WM.BW.HR.PS.QM.SD) --资料来自网络 SAP公司于1972年在德国创立,初创时的五人公司(SystemalyseundProgrammentwicklung),即为今日的 SAP(Systems,Applications,andProductsinDataProcessing)的前身.SAP定位以企业的角度研发企业应用软件为主,为全球企业应用软件供应商,在全球企业

常规功能和模块自定义系统 (cfcmms)—015模块自定义概述(2权限)

常规功能和模块自定义系统 (cfcmms)-015模块自定义概述(2权限) 模块的权限的自定义也是这个系统的重要部分.在本系统中现在模块的权限有三大类:模块操作权限.记录可视范围限定.附加操作权限. 模块操作权限:可以对模块进行浏览.新增.修改.删除.审核.审批等的设置.可以通过生成和隐藏相应的按钮来具体控制权限的设置.如某人无删除权限,则在该模块的操作界面上没有删除按钮.对于一般的管理系统而言这些功能基本上是够用了. 记录可视范围限定:可以在任意模块加上可视范围的限定.在某个父模块上加上可视范

跟我一起学extjs5(13--执行菜单命令在tabPanel中显示模块)

跟我一起学extjs5(13--执行菜单命令在tabPanel中显示模块) 上面设计好了一个模块的主界面,下面通过菜单命令的执行来把这个模块加入到主界面当中.在MainModule.js中有一个函数,生成了当前的菜单数据: // 根据data.systemMenu生成菜单条和菜单按钮下面使用的菜单数据 getMenus : function() { var items = []; var menuData = this.get('systemMenu'); // 取得定义好的菜单数据 Ext.A

电商系统中的商品模型的分析与设计&mdash;续

前言     在<电商系统中的商品模型的分析与设计>中,对电商系统商品模型有一个粗浅的描述,后来有博友对货品和商品的区别以及属性有一些疑问.我也对此做一些研究,再次简单的对商品模型做一个介绍. 从SPU.SKU开始     首先我们需要澄清上篇中的这两个概念,在上篇文章中"货品"是指一种概念物品,这种物品并不是一个具体的实物,当它具备具体的属性.价格时,才是一种实物,也就是商品."商品"就是库存中一个具体的实物.例如:iphone6,就是一种货品,但用户

电商系统中的商品模型的分析与设计

前言 在电商系统中,商品模型至关重要,是整个电商的核心,下面通过一个简单的分析,设计一个基础的商品模型. 商品模型的演化 在以前,那时CMS很流行,最常见的模型是栏目-文章模型.于是做电商的时候,自然就继承了这种一对多的关系.只是栏目变成了分类,文章变成了商品.商品也具备了独特的业务属性.现在很多电商网站上左侧的菜单,也就是这个分类. 后来我们慢慢发现一个问题,只有分类并不能适应所有的需求,比如nike鞋和nikeT恤,用户可能希望先看nike的所有商品,这个模型就不能满足.我们想在这个关系中,

高效跑批设计思路——针对系统中的批量、日终任务

1. 跑批是什么 顾名思义,就是应用程序对数据的批量处理. 跑批有以下特性: 大数据量:批量任务一般伴随着大量的数据处理: 自动化:要求制定时间或频率自动运行: 性能:要求在指定时间内完成批处理任务. 2. 跑批应用场景 在开发中常见的跑批应用场景如下(以目前做的系统举例): 定时的数据状态更新:到期失效 数据的计算:计算罚息.计提 文件处理:与其他应用系统同步,如还款计划同步 生成文件:对账.提供同步文件 在跑批开发中有一些存有潜在隐患的处理方式需要指出来: 对文件内或数据库数据一次性读取.查