消息状态消息状态类型维护消息状态是实现离线消息和已读未读功能的关键,参考枚举类型MessageStatus,消息共有4个状态
状态
|
状态值
|
状态变更时机
|
未送达(PENDING)
|
0
|
消息入库后默认状态就PENDING
|
已送达(DELIVERED)
|
1
|
消息成功推送到客户端后
|
已撤回(RECALL)
|
2
|
用户撤回消息后
|
已读(READED)
|
3
|
用户看到消息后(点击聊天列表)
|
消息状态记录私聊消息和群聊消息的状态记录采用的是不同的方案。私聊消息的记录状态相对比较简单,消息表有status字段,直接存数据库中即可。群聊消息可能会存在部分用户已读,部分用户未读的情况,无法通过单一字段记录。考虑到写扩散造成的数据库IO读写压力,这里我选择了通过redis去记录:KEY: im:readed:group:position:{groupId}:{userId}VALUE: 该用户在该群已读的最大消息id记录了用户在某个群的已读的最大消息id后,只要是小于或等于这个id的消息都是已读消息,否则就是未读消息,而不必记录每条消息是否已读。离线消息拉取离线消息离线消息的实现并不复杂,核心思路是前端通过localstorge缓存了已接收消息的最大id,在用户登录后,主动向服务器拉取比这个消息id大的所有消息。前端相关代码:
文件
|
方法
|
作用
|
home.vue
|
loadPrivateOfflineMessage
|
向服务器拉取私聊离线消息
|
home.vue
|
loadGroupOfflineMessage
|
向服务器拉取群聊离线消息
|
后端相关代码:
类
|
方法
|
说明
|
PrivateMessageServiceImpl
|
loadOfflineMessage
|
拉取最近3个月的离线私聊消息
|
GroupMessageServiceImpl
|
loadOfflineMessage
|
拉取最近3个月的离线群聊消息
|
离线消息被拉取后,消息状态修改为已送达(DELIVERED)状态。已读未读显示已读状态变更用户在页面进行以下之一操作,都会将当前会话的消息变更为已读状态:点击了某个会话列表,进入会话聊天窗口鼠标在聊天窗口滑动回复了对方消息前端相关代码:
文件
|
方法
|
作用
|
ChatBox.vue
|
readedMessage
|
向服务器请求变更消息状态为已读状态
|
后端相关代码:
类
|
方法
|
说明
|
PrivateMessageServiceImpl
|
readedMessage
|
将会话中的所有私聊消息都更改为已读状态
|
离线时状态变更问题目前消息内容和状态都是缓存到前端的localstorage,这样也带来了一个问题:web端接收到消息后,用户没看到消息就退出了,随后用户在app端登录并看到了消息,此时消息变更为已读状态,但是web端的缓存依然记录为未读状态。为了解决这个问题,在前端加入主动更新状态机制:在用户打开聊天窗口时,主动从服务器拉取最大已读消息id,并将前端缓存中小于这个id的消息更新为已读状态前端相关代码:
文件
|
方法
|
作用
|
ChatBox.vue
|
loadReaded
|
向服务器拉取已读最大消息
|
后端相关代码:
类
|
方法
|
说明
|
PrivateMessageServiceImpl
|
getMaxReadedId
|
获取会话中已读消息的最大id
|
回执消息(群聊已读未读)方案选择群聊的消息默认不会显示已读人数,只有当用户手动点亮“消息回执”图标后,发送的消息才会显示已读人数。为什么不像私聊消息那样,每条消息都显示已读人数呢?假如在一个500人的群发了100条回执消息,群里的用户每看到一条消息,都会自动发送一条回执信令,那么消息的扩散量就是100*500×500=2500w。显然如果存在过量的回执消息,对系统的消耗是相当大的。最终是参考了企业微信的设计,通过让用户手动开启的方式,可以明显降低回执消息的数量。技术实现群组中存在多个用户,很可能会存在A用户已读,B用户未读的情况,所以群消息的状态,不能像私聊消息一样,简单的用一个消息状态字段存储。方案一:存到数据库中,im_group_message表增加一个readed_user_ids字段,将已读的用户id全部记录到数据到里面。方案二:存到redis中,使用HashMap结构。大KEY: im:readed:group:position:{groupId}, 小KEY: {userId}, 值: 已读的最大消息id根据前面讨论的消息扩散情况,如果采用方案一,每条消息将会对数据库进行500次更新操作,可能会对数据库造成一定压力,所以我们选择了性能更强劲的redis去记录回执消息的已读状态,即方案二。在方案二中,为每个群聊分配一个大KEY,该KEY记录了一个群聊中所有用户的已读消息的位置。假如有一个4人小群,redis记录该群的数据如下:
用户名
|
用户id
|
最大已读消息id
|
张三
|
2
|
120
|
李四
|
3
|
200
|
王五
|
4
|
180
|
老六
|
5
|
170
|
当用户点击了一条id为165消息,想查看已读用户列表时,服务器只需取出这个大KEY的数据,筛出最大已读消息id大于等于165的用户列表,即“李四”、“王五”、“老六”相关代码:
类
|
方法
|
说明
|
GroupMessageServiceImpl
|
readedMessage
|
记录用户在群里已读消息位置
|
GroupMessageServiceImpl
|
findReadedUsers
|
获取某条消息的已读成员列表
|
暂无评论内容