cover_image

IM主子账号逻辑设计

蝎紫 玩物少年们
2022年04月03日 02:00

本期内容:

  • 背景

  • 业务场景

  • 核心逻辑实现


背景

IM收到的第一个产品化需求就是需要子账号,支持根据业务来源分流回复。

我们底层云服务的逻辑只支持私聊和群聊,私聊就是普通的1V1,群聊包含直播间聊天和普通的群聊。

按照这个逻辑走,能实现但是会存在一些问题:

  • 客服会话创建好之后,只要不删除列表就会一直在,并且会越来越多,一个店铺持续创建客服, 这个用户和这个店铺的会话数也会持续增加。
  • 客服离职之后,用户还是能继续发送消息给客户且无感知,每条消息前置都判断客服状态, 成本较高。
  • 用户可以直接选择和某个客服聊天,不会走分配逻辑,这样的话,分流就毫无意义。


同时,底层云服务的聊天记录时长是有限的,包含一些富文本资源信息(比如图片、视频、语音)也是存在时间限制的,当然了这些都是可以花钱延长的,但是数据就是金钱,这些数据肯定是留着自己手上比较合适,而且售后处理、交易纠纷、风控复盘等逻辑都很依赖这些聊天记录,所以我们必定要存储起来。


同时考虑到主子账号的痛点问题,顺便一起解决,包括后面的客服系统整体搭建都是基于这套设计一起实现。


好了,接下来我们来一步步梳理并设计。



业务场景


图片图片


上图一,是我们以前的会话模式,存在较大的问题,所以我们需要改成上图二的业务模式。


当然因为业务底层是基于云服务来做的,所以还是会存在一些限制:

  • 对用户来讲,展示的都是店铺级别的会话,用户是在和一个店铺聊,店铺里面必定存在一个客服或者店主服务用户。
  • 一个店铺存在多个客服。
  • 会话中任意成员发出的消息,实时只有当前用户才能收到,但是历史消息会话中所有成员都能看到。

其实我们以前考虑过另外几个方案,这里也放出来一起讨论一下,

  • 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)实现不统一。

后续会话级别需要新增免打扰、屏蔽、拉黑,客户端逻辑会更加复杂且可能遗漏,后续服务端做改动不易,所以在前期做了方案过渡之后我们选择了服务端会话列表合并方案。

服务端会话列表整合方案

同理按照上述的会话表设计方案实现,各端的会话列表逻辑统一,客户端逻辑不会那么复杂且难以改动,以后服务端可以控制会话列表,支持动态更新。

后面我们会基于这套设计,再聊聊未读计数的设计以及多店铺客服系统设计。




图片

玩物得志拍品个性化推荐 |精排篇

玩物得志APP通用弹窗设计

多线程在业务中的实践

继续滑动看下一个
玩物少年们
向上滑动看下一个