看到很多开发IM系统的朋友都想实现聊天记录存储和查询这一不可或缺的功能,这里我就把自己前段时间为傲瑞通(OrayTalk)开发聊天记录模块的经验分享出来,供需要的朋友参考下。

一.总体设计

1.存储位置  

  从一开始我们就打算在服务端和客户端本地同时存储聊天记录,而且,在客户端查看聊天记录时,可以选择是从本地加载、还是从服务器加载。这样做的好处有两个:

(1)从本地加载聊天记录速度非常快。

(2)当更换了登录的机器,在任何地方任何时刻都可以从服务器加载完整的聊天记录,记录永远不会丢失。

2.存储方案

(1)在服务端存储聊天记录当然使用我们主流的数据库SqlServer或Mysql等。

(2)在客户端,我们开始选择的是使用序列化技术,但是,考虑到当聊天记录数据量庞大时,序列化方案就不够灵活了,而且性能也跟不上。所以,最后决定使用轻量级的数据库Sqlite。

3.ORM框架

  DataRabbit的最新版本增加了对Sqlite的支持,并且对不同数据库的操作API是完全一致的,所以我们使用DataRabbit写了一个小组件来完成聊天记录的存储与查询等数据库访问操作。而无论是客户端还是服务端的聊天记录存储相关的工作,都交给这个组件来完成。

二.具体实现

1.ChatMessageRecord类

  一条聊天记录基本上包含了以下几个内容:发送人、接收人、内容、时间等。并且,我们想将两人聊天及群聊天抽象成同一个模型,于是,聊天记录的Entity类ChatMessageRecord设计成如下模样:

  1. public class ChatMessageRecord
  2. {
  3. #region AutoID
  4. private long autoID = ;
  5. /// <summary>
  6. /// 自增ID,编号。
  7. /// </summary>
  8. public long AutoID
  9. {
  10. get { return autoID; }
  11. set { autoID = value; }
  12. }
  13. #endregion
  14.  
  15. #region SpeakerID
  16. private string speakerID = "";
  17. /// <summary>
  18. /// 发言人的ID。
  19. /// </summary>
  20. public string SpeakerID
  21. {
  22. get { return speakerID; }
  23. set { speakerID = value; }
  24. }
  25. #endregion
  26.  
  27. #region AudienceID
  28. private string audienceID = "";
  29. /// <summary>
  30. /// 听众ID,可以为GroupID。
  31. /// </summary>
  32. public string AudienceID
  33. {
  34. get { return audienceID; }
  35. set { audienceID = value; }
  36. }
  37. #endregion
  38.  
  39. #region OccureTime
  40. private DateTime occureTime = DateTime.Now;
  41. /// <summary>
  42. /// 聊天记录发生的时间。
  43. /// </summary>
  44. public DateTime OccureTime
  45. {
  46. get { return occureTime; }
  47. set { occureTime = value; }
  48. }
  49. #endregion
  50.  
  51. #region ContentRtf
  52. private string contentRtf = "";
  53. /// <summary>
  54. /// 聊天的内容。
  55. /// </summary>
  56. public string ContentRtf
  57. {
  58. get { return contentRtf; }
  59. set { contentRtf = value; }
  60. }
  61. #endregion
  62.  
  63. #region IsGroupChat
  64. private bool isGroupChat = false;
  65. /// <summary>
  66. /// 是否为群聊记录。
  67. /// </summary>
  68. public bool IsGroupChat
  69. {
  70. get { return isGroupChat; }
  71. set { isGroupChat = value; }
  72. }
  73. #endregion
  74. }

    在ChatMessageRecord的定义中,聊天内容字段被设计为string类型,这是因为在OrayTalk中,聊天内容是富文本RTF格式的。如果需要,可以更改为byte[]类型,这样通过自定义的序列化操作就可以承载更复杂的聊天格式。

  最后一个字段IsGroupChat表明当前记录是否为群聊记录,如果是群聊记录,那么,AudienceID就不是好友的ID了,而是目标群组的ID。

  最后请注意:ChatMessageRecord实体与数据库中的ChatMessageRecord表是完全映射的关系,这才使得DataRabbit的ORM数据访问成为可能。

2.ChatRecordPage类

  当我们请求聊天记录时,由于记录数量可能非常庞大,所以,采用分页是不可避免的。我们用ChatRecordPage来封装查询返回的一页聊天记录:

根据ChatRecordPage中的TotalCount字段,查询者可以知道符合条件的记录数是多少,如此,就可以知道总共有多少页。

3.IChatRecordPersister接口

  无论是客户端还是服务端存储与查询聊天记录,我们都使用同一个接口IChatRecordPersister来进行抽象:

  1. public interface IChatRecordPersister
  2. {
  3. /// <summary>
  4. /// 插入一条聊天记录(包括群聊天记录)。
  5. /// </summary>
  6. void InsertChatMessageRecord(ChatMessageRecord record);
  7.  
  8. /// <summary>
  9. /// 获取一页与好友的聊天记录。
  10. /// </summary>
  11. /// <param name="timeScope">日期范围</param>
  12. /// <param name="myID">自己的UserID</param>
  13. /// <param name="friendID">好友的ID</param>
  14. /// <param name="pageSize">页大小</param>
  15. /// <param name="pageIndex">页索引</param>
  16. /// <returns>聊天记录页</returns>
  17. ChatRecordPage GetChatRecordPage(DateTimeScope timeScope, string myID, string friendID, int pageSize, int pageIndex);
  18.  
  19. /// <summary>
  20. /// 获取一页群聊天记录。
  21. /// </summary>
  22. /// <param name="timeScope">日期范围</param>
  23. /// <param name="groupID">群ID</param>
  24. /// <param name="pageSize">页大小</param>
  25. /// <param name="pageIndex">页索引</param>
  26. /// <returns>聊天记录页</returns>
  27. ChatRecordPage GetGroupChatRecordPage(DateTimeScope timeScope, string groupID, int pageSize, int pageIndex);
  28. }

(1)插入游戏记录时,与好友聊天记录以及群聊天记录使用同一个InsertChatMessageRecord方法即可,只是在构造ChatMessageRecord对象时,字段的赋值有所区别。

(2)使用DataRabbit实现该接口时(如ChatRecordPersister类),通过属性DataBaseType来控制访问的是否为Sqlite数据库。然后在服务端使用ChatRecordPersister存取聊天记录时,就将DataBaseType设置为SqlServer;客户端则设置为Sqlite。

三.可能的Remoting的接口

  当我们从服务器加载聊天记录时,可以考虑使用Remoting技术来实现,如果是这样,只需要在服务端把IChatRecordPersister接口暴露为Remoting服务,然后客户端使用这一Remoting服务进行聊天记录查询。这样一来,客户端在切换从本地加载和从服务器加载时,只需要切换IChatRecordPersister为本地ChatRecordPersister对象的引用或remoting远程引用即可。整个的代码实现将会非常简洁一致。

  到这里,关于聊天记录模块的设计与实现就介绍得差不多了,依照这样的思路,大家在自己的IM系统中增加聊天记录的功能应该是很简单的了。最后,上一张OrayTalk客户端查询聊天记录界面的截图:

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

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

  1. MEF插件系统中通信机制的设计和实现

    MEF插件系统中通信机制的设计和实现 1.背景 一般的WinForm中通过C#自带的Event机制便能很好的实现事件的注册和分发,但是,在插件系统中却不能这么简单的直接用已有的类来完成.一个插件本不包 ...

  2. Java生鲜电商平台-生鲜系统中微服务架构设计与分析实战

    Java生鲜电商平台-生鲜系统中微服务架构设计与分析实战 说明: Java生鲜系统中微服务的拆分应该如何架构设计与分析呢?以下是我的实战中的设计与经验分析. 目录 1. 微服务简介2. 当前现状3. ...

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

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 一般的WinForm中通过C#自带的Event机制便能很好的实 ...

  4. [Hyperledger] Fabric系统中 peer模块的 gossip服务详解

    最近一直在看fabric系统中的核心模块之一——peer模块.在看peer的配置文件core.yaml的信息时,对其中的gossip配置选项很感兴趣.看了一上午,还是不能明白这个选项到底什么意思呢?表 ...

  5. MySQL-THINKPHP 商城系统一 商品模块的设计

    在此之前,先了解下关于SPU及SKU的知识 SPU是商品信息聚合的最小单位,是一组可复用.易检索的标准化信息的集合,该集合描述了一个产品的特性.通俗点讲,属性值.特性相同的商品就可以称为一个SPU. ...

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

    跟我一起学extjs5(13--运行菜单命令在tabPanel中显示模块)         上面设计好了一个模块的主界面,以下通过菜单命令的运行来把这个模块增加到主界面其中. 在MainModule. ...

  7. 电商系统中的商品模型的分析与设计—续

    前言     在<电商系统中的商品模型的分析与设计>中,对电商系统商品模型有一个粗浅的描述,后来有博友对货品和商品的区别以及属性有一些疑问.我也对此做一些研究,再次简单的对商品模型做一个介 ...

  8. 系统中异常公共处理模块 in spring boot

    最近在用spring boot 做微服务,所以对于异常信息的 [友好展示]有要求,我设计了两点: 一. 在业务逻辑代码中,异常的抛出 我做了限定,一般只会是三种: 1. OmcException // ...

  9. Java开源生鲜电商平台-财务系统模块的设计与架构(源码可下载)

    Java开源生鲜电商平台-财务系统模块的设计与架构(源码可下载) 前言:任何一个平台也好,系统也好,挣钱养活团队这个是无可厚非的,那么对于一个生鲜B2B平台盈利模式( 查看:http://www.cn ...

随机推荐

  1. ITextSharp导出PDF表格和图片(C#)

    文章主要介绍使用ITextSharp导出PDF表格和图片的简单操作说明,以下为ITextSharp.dll下载链接 分享链接:http://pan.baidu.com/s/1nuc6glj 密码:3g ...

  2. EF支持mysq相关配置数码

    最近,项目考虑到安装部署方面:希望可以使用MySQL数据库,毕竟比较小巧.方便. 后来,自己通过测试发现EF可以支持mysql数据库,而且也可以通过codefirst模式进行开发:使用起来,跟sqls ...

  3. 如何更改OS系统下截图生成图片格式 jpg pdf

    Mac系统下快速截屏的快捷键: 1.截全屏: shift + command + 3 2.选取截屏 shift + command + 4 生成的图片,系统默认格式忘了,反正不好用,用下面命令更改生成 ...

  4. SVN版本控制系统

    SVN 版本控制系统 1.SVN作用 防止代码丢失 : 因为没有哪个项目能够一次性开发完成 代码版本回退 : 你可以在开发过程中找到以前上传到服务器上面的所有版本 多人代码整合 : 公司中多个人开发同 ...

  5. Ubuntu16.04下面配置java环境变量

    我在ubuntu 16.04下面配置java环境变量的时候,开始在网上查信息的时候,没太注意ubuntu的版本,结果在.bashrc下面设置,在.profile下面设置,都不成功, 后面才想起来搜索u ...

  6. 子进程 已安装 pre-removal 脚本 返回了错误号 1或2 与 子进程 已安装 post-installation 脚本 返回了错误号 1或2

    今天在ubuntu kylin上安装了virtualbox, 后来我想删除了再装个新一点的,结果正常的情况下删除不了,我就把找到的virtualbox的目录全部都删除了, 再通过apt-get rem ...

  7. 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 ...

  8. JavaScript-BOM-history:保存当前窗口打开后成功访问过的url历史记录栈

    history:保存当前窗口打开后成功访问过的url历史记录栈history.go(n):前进n步前进一步:history.go(1);后退一步:history.go(-1);刷新:history.g ...

  9. 通过inflate获取布局,设置layoutparams无效

    给ll——addtiem当设置layoutparams无效时,试着修改上一个布局的属性

  10. MongoDB学习比较-07 C#驱动操作MongoDB

    下载驱动 驱动的下载有两种方式:一种是在C#项目中通过NuGet进行安装,另一种是通过下面的链接:https://github.com/mongodb/mongo-csharp-driver/rele ...