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 = ;
- /// <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系统中聊天记录模块的设计与实现的更多相关文章
- MEF插件系统中通信机制的设计和实现
MEF插件系统中通信机制的设计和实现 1.背景 一般的WinForm中通过C#自带的Event机制便能很好的实现事件的注册和分发,但是,在插件系统中却不能这么简单的直接用已有的类来完成.一个插件本不包 ...
- Java生鲜电商平台-生鲜系统中微服务架构设计与分析实战
Java生鲜电商平台-生鲜系统中微服务架构设计与分析实战 说明: Java生鲜系统中微服务的拆分应该如何架构设计与分析呢?以下是我的实战中的设计与经验分析. 目录 1. 微服务简介2. 当前现状3. ...
- (3)MEF插件系统中通信机制的设计和实现
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 一般的WinForm中通过C#自带的Event机制便能很好的实 ...
- [Hyperledger] Fabric系统中 peer模块的 gossip服务详解
最近一直在看fabric系统中的核心模块之一——peer模块.在看peer的配置文件core.yaml的信息时,对其中的gossip配置选项很感兴趣.看了一上午,还是不能明白这个选项到底什么意思呢?表 ...
- MySQL-THINKPHP 商城系统一 商品模块的设计
在此之前,先了解下关于SPU及SKU的知识 SPU是商品信息聚合的最小单位,是一组可复用.易检索的标准化信息的集合,该集合描述了一个产品的特性.通俗点讲,属性值.特性相同的商品就可以称为一个SPU. ...
- 跟我一起学extjs5(13--运行菜单命令在tabPanel中显示模块)
跟我一起学extjs5(13--运行菜单命令在tabPanel中显示模块) 上面设计好了一个模块的主界面,以下通过菜单命令的运行来把这个模块增加到主界面其中. 在MainModule. ...
- 电商系统中的商品模型的分析与设计—续
前言 在<电商系统中的商品模型的分析与设计>中,对电商系统商品模型有一个粗浅的描述,后来有博友对货品和商品的区别以及属性有一些疑问.我也对此做一些研究,再次简单的对商品模型做一个介 ...
- 系统中异常公共处理模块 in spring boot
最近在用spring boot 做微服务,所以对于异常信息的 [友好展示]有要求,我设计了两点: 一. 在业务逻辑代码中,异常的抛出 我做了限定,一般只会是三种: 1. OmcException // ...
- Java开源生鲜电商平台-财务系统模块的设计与架构(源码可下载)
Java开源生鲜电商平台-财务系统模块的设计与架构(源码可下载) 前言:任何一个平台也好,系统也好,挣钱养活团队这个是无可厚非的,那么对于一个生鲜B2B平台盈利模式( 查看:http://www.cn ...
随机推荐
- ITextSharp导出PDF表格和图片(C#)
文章主要介绍使用ITextSharp导出PDF表格和图片的简单操作说明,以下为ITextSharp.dll下载链接 分享链接:http://pan.baidu.com/s/1nuc6glj 密码:3g ...
- EF支持mysq相关配置数码
最近,项目考虑到安装部署方面:希望可以使用MySQL数据库,毕竟比较小巧.方便. 后来,自己通过测试发现EF可以支持mysql数据库,而且也可以通过codefirst模式进行开发:使用起来,跟sqls ...
- 如何更改OS系统下截图生成图片格式 jpg pdf
Mac系统下快速截屏的快捷键: 1.截全屏: shift + command + 3 2.选取截屏 shift + command + 4 生成的图片,系统默认格式忘了,反正不好用,用下面命令更改生成 ...
- SVN版本控制系统
SVN 版本控制系统 1.SVN作用 防止代码丢失 : 因为没有哪个项目能够一次性开发完成 代码版本回退 : 你可以在开发过程中找到以前上传到服务器上面的所有版本 多人代码整合 : 公司中多个人开发同 ...
- Ubuntu16.04下面配置java环境变量
我在ubuntu 16.04下面配置java环境变量的时候,开始在网上查信息的时候,没太注意ubuntu的版本,结果在.bashrc下面设置,在.profile下面设置,都不成功, 后面才想起来搜索u ...
- 子进程 已安装 pre-removal 脚本 返回了错误号 1或2 与 子进程 已安装 post-installation 脚本 返回了错误号 1或2
今天在ubuntu kylin上安装了virtualbox, 后来我想删除了再装个新一点的,结果正常的情况下删除不了,我就把找到的virtualbox的目录全部都删除了, 再通过apt-get rem ...
- Ubuntu 16.04 启动错误 "a start job is running for hold until boot process finishes up"
老司机也差点翻船... 升级16.04的时候,将默认启动管理器(default display manager)选为gm3(gnome3)了(应该使用默认的lightgm)如果改成gm3,好像是nvi ...
- JavaScript-BOM-history:保存当前窗口打开后成功访问过的url历史记录栈
history:保存当前窗口打开后成功访问过的url历史记录栈history.go(n):前进n步前进一步:history.go(1);后退一步:history.go(-1);刷新:history.g ...
- 通过inflate获取布局,设置layoutparams无效
给ll——addtiem当设置layoutparams无效时,试着修改上一个布局的属性
- MongoDB学习比较-07 C#驱动操作MongoDB
下载驱动 驱动的下载有两种方式:一种是在C#项目中通过NuGet进行安装,另一种是通过下面的链接:https://github.com/mongodb/mongo-csharp-driver/rele ...