开发即时通讯软件时如何实现消息的@提醒

开发即时通讯软件时如何实现消息的@提醒

即时通讯开发这个话题,我想起之前和几个开发者朋友一起讨论产品需求的时候,大家都觉得一个小小的@功能看起来很简单,但真正要做的时候才发现里面的门道比想象中多多了。今天我就把自己踩过的坑和总结的经验分享出来,希望对正在做这块功能的同学有所帮助。

为什么@功能看似简单却暗藏玄机

从产品形态来看,@提醒就是在一段文字里标记特定用户,被标记的用户会收到提醒通知。这个需求太常见了,微信群里@一下同事,钉钉里@整个部门,飞书里@相关成员,看起来就是一个选人加插入符号的流程。但如果你以为这事儿加个输入框组件就能搞定,那后续的问题绝对会让你怀疑人生。

我见过不少团队第一版实现是这样的:用户在输入框里输入@字符,然后弹出一个用户列表,用户选完人之后把用户ID和昵称插入到文本里。发送的时候把整段文字和被@用户的ID列表一起发到服务器。看起来逻辑清晰,实现简单,对吧?但上线之后问题就来了。

首先是消息解析的问题。假设用户A在群里发了一条消息:"@张三 明天开会",这条消息存到数据库的时候应该怎么存?如果只是存原始文本,那其他人收到消息之后,怎么知道"张三"对应的是哪个用户?如果群里有两个叫张三的人怎么办?如果被@的用户改名了,历史消息里的@是不是就失效了?

然后是通知推送的问题。普通消息只需要发给群里的所有人,但@消息不一样,只有被@的用户需要收到强提醒,其他人可以不用理。如果群里有一百个人,每条@消息都要给这一百人发一遍,然后服务器再判断哪些是真正需要通知的,这显然是不合理的。

还有边界情况需要处理。比如一个人在群里@了十个用户,这十条通知怎么发?用户离线的时候消息怎么保留?用户在多设备登录的时候是不是每个设备都通知?这些看起来都是小问题,但组合在一起就能让人头大。

技术实现的核心思路

要处理好@功能,我们需要从数据存储、消息解析、通知分发三个层面来考虑。每个环节都有不同的技术选择,而不同的选择会带来不同的实现复杂度和性能表现。

数据存储层的处理方式

关于消息内容怎么存,行业里主要有两种方案。第一种是存原始文本加元数据,把完整的消息内容原样存储,同时在一个单独的字段里记录这条消息包含了哪些被@的用户ID。接收方渲染消息的时候,先显示原始文本,然后把用户名渲染成可点击的超链接样式。

第二种是存结构化的内容,把消息拆分成普通文本片段和@用户片段,@片段里保存用户ID、显示昵称、跳转链接等信息。渲染的时候直接遍历这个结构列表就行。这两种方案各有优劣,第一种实现简单但解析麻烦,第二种解析容易但存储结构复杂。

我个人的建议是采用第一种方案,但要对@用户的存储做特殊处理。每条消息除了内容字段外,增加一个mentions字段,这是一个用户ID数组。在群里发消息的时候,客户端负责解析出@了哪些人,把这些ID数组传给服务器。服务器存储的时候,原始文本该怎么存就怎么存,mentions数组单独存一份。

这样做的好处是什么呢?通知分发的时候,服务器不需要去解析消息内容,直接读mentions数组就知道该通知哪些人。而且查询历史消息的时候,如果想找所有@了我的消息,直接按mentions字段过滤就行,不需要全文检索。当然弊端也有,如果用户改了昵称,历史消息里显示的名字就不会更新,这需要产品层面做一些取舍。

消息解析的几种方案

客户端收到消息之后,需要把文本里的@识别出来并渲染成特殊样式。这里有几种常见的实现思路。

最直接的是正则表达式匹配。在消息发送前,我们约定@用户的格式必须符合某种模式,比如@[userId:username],正则写成@\[(\d+):([^\]]+)\]这样的格式。收到消息之后用正则找出所有匹配,分割成普通文本和@用户块,分别渲染。这种方案优点是实现简单,缺点是正则的性能在长消息上可能不太理想,而且格式比较固定,用户没法自定义。

另一种方案是标记语言的方式。类似于Markdown的链接语法,我们可以用@{{userId|username}}或者类似的自定义语法。发送消息时客户端把@操作转换成这种标记语言,收到消息后先解析标记语言生成抽象语法树,再渲染成UI。这种方案更灵活,但需要客户端和服务器都支持解析逻辑。

还有一种偏前端的方案,纯粹依赖前端组件。比如用一个RichTextEditor组件,用户输入@的时候组件自动弹出用户选择器,选择完成后组件内部维护一个带有元数据的内容结构。发送的时候把结构化的内容序列化成JSON,接收方反序列化成结构直接渲染。这种方案把复杂性都放在前端,服务器只需要透传数据就行,但要求所有客户端都使用同一套组件实现。

实际项目中,我见过最多的是第一种和第三种的结合。消息体用带标记的文本格式存储,客户端解析渲染的时候用正则做简单处理,渲染出来的@用户显示昵称,点击之后跳转到用户profile页面。这里有个细节需要注意,为了用户体验,收到消息后最好把昵称渲染成蓝色或者其他高亮样式,让用户一眼就能看出哪些人被@了。

通知分发的技术难点

@消息的通知分发是整个功能里最容易出问题的环节。我见过因为通知逻辑没做好,导致用户收到重复通知的;也见过因为通知逻辑太复杂,把服务器性能拖垮的。这里分享几个关键的技术决策点。

首先是通知范围的确定。@消息的推送范围应该仅限于被@的用户,但这个"用户"在群聊场景下需要特别处理。假设群里有A、B、C三个人,A发消息@B,那么这条消息应该推给谁?答案是推给A、B、C三个人,因为这是一条群消息。但B应该收到特殊通知告诉他"有人@你了",而A和C只需要收到普通的消息同步。

为了实现这个效果,消息体里需要一个字段标记哪些用户需要强通知。比如mentions字段记录被@的用户ID列表,服务器推送消息的时候,给每个在线用户检查一下:如果用户在mentions列表里,就发一条包含提醒标记的消息;如果不在,就发普通消息。这个逻辑看起来简单,但在大规模群聊场景下,mentions列表可能很长,遍历检查的开销就不可忽略了。

一个优化思路是改变数据结构。不在消息体里存mentions数组,而是存一个mentionsCount整数和一个被@用户的布隆过滤器。发送消息时,客户端把@用户列表发过来,服务器计算布隆过滤器存储起来。推送的时候,对于每个接收方,用布隆过滤器的membership test判断是否被@。布隆过滤器的空间占用远小于完整的用户ID列表,判断速度也更快,特别适合处理大量@用户的场景。

另一个要注意的是离线消息的处理。用户离线期间收到@消息,服务器需要替他保存这条消息,并且标记这条消息包含了@提醒。等用户下次上线的时候,推送离线消息的同时要告诉他"你有一条@消息"。如果用户多个设备登录,通知应该只发一次而不是每个设备都发。这需要服务器维护用户的在线状态和设备映射关系。

声网在即时通讯领域的实践

说到即时通讯的技术实现,这里想提一下声网。作为全球领先的实时音视频与即时通讯云服务商,声网在即时通讯这块积累了大量实践经验。他们提供的实时消息服务支持多种消息类型,其中包括@消息的实现方案。

声网的解决方案里,消息体会包含一个专门的mentions字段,客户端在发送@消息时需要填充这个字段。服务端在消息投递时会自动识别mentions信息,给被@的用户发送特殊的回调通知。这种设计把复杂的通知逻辑交给服务端处理,客户端只需要按规范填充数据就行,大大降低了开发成本。

从技术架构来看,声网的即时通讯服务基于他们自建的全球软件定义实时网SD-RTN®,在全球多个节点部署了服务器,能够实现消息的快速投递。他们官方数据显示,端到端延迟可以控制在极低水平,这对于@消息这种需要即时反馈的场景特别重要。毕竟用户@别人之后,肯定是希望对方尽快收到提醒的。

另外,声网的SDK封装了消息解析的逻辑,客户端接入方不需要自己处理@格式的转换和渲染,这又省了不少事情。我之前看过他们的一些客户案例,像秀场直播、社交1v1这些场景都有成熟的@功能实践。如果团队在即时通讯这块人力有限,接入声网的解决方案确实能加快开发进度。

实现@功能容易踩的坑

结合自己和身边朋友的开发经历,我总结了几个实现@功能时特别容易踩的坑,分享出来给大家提个醒。

第一个坑是用户选择器的交互设计。很多团队在做@功能时,用户列表的弹出位置、筛选逻辑、键盘处理这些细节没有处理好,导致用户体验很糟糕。比如弹出用户列表后用户按空格想取消,结果触发了列表第一项的选中;比如用户列表没有做搜索过滤,群里有几千人时根本找不到要@的人;比如弹出列表遮挡了输入框,用户看不到自己打到哪了。这些交互细节看起来小,但很影响用户的日常使用频率。

第二个坑是@范围的定义模糊。群里@和私聊里@的处理逻辑应该不一样,但很多团队早期没有做区分。比如群主@全员这个功能,需要特殊处理全员这个标记,不能真的把几千个用户ID都发给服务器。还有@群组和@个人的区分,有些产品支持@某个角色,比如@所有管理员,这时候服务端需要维护角色和用户的映射关系,客户端需要提供选择角色的界面。这些边缘情况在产品文档里可能一句话带过,但技术实现起来要考虑的细节很多。

第三个坑是编辑消息时的@处理。用户编辑一条已经发送的消息,如果删除了某个@用户,消息体的mentions字段要不要同步更新?如果删除了@用户但保留了名字,通知还发不发?还有用户多次编辑同一条消息,mentions数组的历史版本怎么管理。这些问题在消息量小的时候不明显,但一旦消息数量上来,数据一致性的问题就会暴露出来。

第四个坑是性能优化不到位。我见过有团队实现@功能时,每次用户输入@都实时请求服务器获取用户列表,结果用户在群里快速输入几个@,服务器瞬间多了几百个请求。还有在消息列表里渲染@消息时,每条消息都做一次正则解析,导致列表滑动卡顿。这些都是性能优化没做好的表现,需要缓存的要缓存,能延迟加载的要延迟加载。

最佳实践建议

聊了这么多,最后给大家几点实操建议吧。

技术选型方面,我的建议是不要重复造轮子。如果团队里没有特别资深的即时通讯工程师,接入成熟的第三方服务是更稳妥的选择。声网这类专业服务商在即时通讯领域深耕多年,该踩的坑都踩过了,解决方案也比较成熟。团队可以把节省下来的精力放在产品业务逻辑上,而不是基础通讯能力的打磨上。

产品设计方面,建议@功能做渐进式支持。第一版先支持群聊里@个人,验证基本流程没问题后再加入@全员、@角色等高级功能。交互上@入口要明显,但也不能太打扰用户的正常输入。如果群里用户很多,建议在输入框上方或者侧边弹出用户选择列表,而不是覆盖输入区域。

数据存储方面,mentions字段要建索引。一方面是方便查询历史消息里哪些@了我,另一方面是方便统计某个用户被@的次数。如果业务上有这个需求的话,做个排行功能也未尝不可。另外mentions数据最好保留历史版本,方便审计和追溯。

通知策略方面,建议给用户开关权利。有的人希望收到所有@提醒,有的人只想收到私聊@不要收到群聊@。这个开关要设计在消息通知的设置里,让用户可以按自己的偏好配置。服务器在推送之前,先检查用户设置,符合条件的才推送强提醒。

差不多就聊到这里吧。@功能作为即时通讯软件的基础能力之一,技术难度不大但细节很多。希望这篇文章能给正在做这块功能的同学一些参考,少走一些弯路。如果有什么问题,欢迎大家一起讨论交流。

上一篇开发即时通讯 APP 时如何实现消息的黑名单
下一篇 企业即时通讯方案的服务器的监控工具

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱:

工作时间:周一至周五,9:00-17:30,节假日休息
关注微信
微信扫一扫关注我们

微信扫一扫关注我们

手机访问
手机扫一扫打开网站

手机扫一扫打开网站

返回顶部