同时,底层云服务的聊天记录时长是有限的,包含一些富文本资源信息(比如图片、视频、语音)也是存在时间限制的,当然了这些都是可以花钱延长的,但是数据就是金钱,这些数据肯定是留着自己手上比较合适,而且售后处理、交易纠纷、风控复盘等逻辑都很依赖这些聊天记录,所以我们必定要存储起来。
同时考虑到主子账号的痛点问题,顺便一起解决,包括后面的客服系统整体搭建都是基于这套设计一起实现。
好了,接下来我们来一步步梳理并设计。
业务场景
上图一,是我们以前的会话模式,存在较大的问题,所以我们需要改成上图二的业务模式。
当然因为业务底层是基于云服务来做的,所以还是会存在一些限制:
对用户来讲,展示的都是店铺级别的会话,用户是在和一个店铺聊,店铺里面必定存在一个客服或者店主服务用户。 一个店铺存在多个客服。 会话中任意成员发出的消息,实时只有当前用户才能收到,但是历史消息会话中所有成员都能看到。
其实我们以前考虑过另外几个方案,这里也放出来一起讨论一下,
PlanA:新增一个小浮窗,或者增加聊天记录查询页面,里面包含 A用户和 B用户下的所有身份(店主+客服),这个方案整体用户体验较差被毙掉。 PlanB:用户A和客服B1, B2聊天,在云服务侧合并一个主会话A-B给客户端拉取,云服务侧并不支持这种过于自定义的需求,方案毙掉。 PlanC:用户A和客服B1,B2聊天,直接一起建一个群,优势很明显,客户端改动较小,服务端改动的话,在接受范围内,弊端就是,新加入群的客服看不到历史聊天记录,群会话的展示样式要做调整,设计方案不够优雅,不是比较合适的解决方案,且用户A发消息,客服B1和客服B2都会收到消息,客服用户体验较差,最后我们也毙掉这个方案。
最后,我们选择当前的方案,首先用户A进入B店铺的会话:
服务端分配给用户A一个B店铺的客服,比如说B1 拉取A用户和B店铺所有历史消息,比如merge(A<=>B, A<=>B1, A<=>B2) 监听B店铺所有客服包含店主发送过来的消息
然后我们一个个来分析上面的逻辑。
第一步:其实就是标准的分流逻辑,可以按照各自的业务,以及分流策略具体实现,这里不展开赘述。
第二步:是这次的核心,放下面说。。。
第三步:云服务本来就支持的,只不过我们需要做一个特殊处理,就是收到B、B1、B2的消息我们都要往一个虚拟会话里面去合并处理。
好了,接下来我们详细说说第二步的逻辑。
核心逻辑实现
这个就有意思了,因为云服务底层是不支持的。他们服务很纯粹私聊就是1v1,所以这里如果纯粹完全依赖云服务,应该是没有特别好的办法实现的,所以IM服务端很早就自己存储了所有的聊天记录。
我们先从基础逻辑开始讲起,IM核心的其中2个逻辑:一个是会话,另外是消息。
里面有2个比较重要的id的设计, 会话id和消息id,底层我们是基于云服务来实现的,所以消息id这一块暂时可以忽略,我们来讨论下会话id的实现。
会话id
会话id有几种实现方案:
fromUserId和toUserId如果都是32位正整数, conversationId = (maxUserId & 0x000000FFFFFFFFL) << 32 | minUserId,这个方案比较简单,任意2个用户的会话,我们都可以直接根据userId直接计算出结果,当然这里也有个硬伤,就是一旦userId 32位正整数不够用了,那么改动的话,可能还需要再补一个下面的第3种方案。 conversationId = maxUserId+"_"+minUserId,但是拼接字符串的话,性能相对较差。 新增一张映射表,记录fromUserId和toUserId对应conversationId。
理论上来讲,第三种实现方案比较万金油,但是在特定场景下以及综合各种因素考虑,我们还是选择了第一个方案。
好了,说完会话id的概念,接下来我们再来看下需求实现。
我们需要支持一次能查出A和B店铺所有人的聊天记录,那么就需要有一个一样的关键字段来存储,正常情况下我们本来就会有个会话id,但是A<=>B1的会话id和A<=>B2的会话id不一样,消息表的存储,不能按照这个会话id来,所以我们保证这2个会话都存在一个唯一标识来拉取。
A:用户 B:店铺店主 B1:店铺客服1 B2:店铺客服2
会话表
消息表
我们的实现方案是这样的,我们都基于店的概念去创建会话,当然也是基于一些基础前提:
一个用户允许开店,这个时候userId是不会变的 一个客服他在A店是userId1,如果去B店当客服,账号是不能带过去的,会创建一个新的账号userId2
所以我们新建了一个主会话的概念,一个主会话可能包含多个子会话,如果要查聊天记录的话,那么只需要从消息表里面根据主会话id筛选即可。
当然了上述的方案改造完之后,消息列表的里面逻辑我们支持到了,后续客户端不支持从云服务SDK拉取消息,只能从服务端拉取合并过之后的聊天记录。
这个时候消息列表改造方案已经ok了,剩余另外一个大模块就是会话列表模块,这个时候也需要做一波改造,这里同样有2个方案:
客户端会话列表整合方案
需要把同一个店铺的会话,根据用户自定义字段合并,因为云服务不支持会话级别的自定义字段,获取会话列表以及未读计数的逻辑极其复杂,各端(iOS, Android, Web)实现不统一。
后续会话级别需要新增免打扰、屏蔽、拉黑,客户端逻辑会更加复杂且可能遗漏,后续服务端做改动不易,所以在前期做了方案过渡之后我们选择了服务端会话列表合并方案。
服务端会话列表整合方案
同理按照上述的会话表设计方案实现,各端的会话列表逻辑统一,客户端逻辑不会那么复杂且难以改动,以后服务端可以控制会话列表,支持动态更新。
后面我们会基于这套设计,再聊聊未读计数的设计以及多店铺客服系统设计。