开发即时通讯APP时如何实现消息的黑名单

开发即时通讯APP时如何实现消息的黑名单

即时通讯APP开发的同学应该都有过这种体验:产品跑过来跟你说,用户反馈有人骚扰,能不能加个拉黑功能?听起来挺简单的,不就是把对方拉进黑名单嘛。但真正上手做的时候,你会发现这里头的门道还挺多的。我自己踩过不少坑,今天就把我摸索出来的一些经验分享出来,希望能帮到正在做这个功能的朋友。

不过在开始讲技术实现之前,我想先说一个更基础的问题——为什么黑名单这个功能看似简单,却经常做不好?原因在于很多人把它想得太简单了。黑名单不是简单地把用户ID存进一个列表就完事了,它涉及到前端展示逻辑、后端消息路由、消息同步机制、跨端一致性等一系列问题。任何一个环节没考虑到,用户体验就会出问题。

黑名单的本质是什么?

我们先来想清楚黑名单到底要解决什么问题。说白了,就是当用户A把用户B拉入黑名单后,用户B发来的消息不应该再被用户A看到,用户A也不应该再收到B的任何通知。但这里有个关键点需要注意:拉黑是单向操作还是双向操作?主流的即时通讯产品通常采用的是单向拉黑机制,也就是说A拉黑B之后,A这边是清净了,但B还是可以给A发消息,只是B的消息会石沉大海而已。当然也有双向拉黑的变体,这个我们后面再聊。

费曼学习法告诉我们要用最简单的话解释复杂概念。那黑名单的本质其实就是在消息投递的路径上设置一道关卡,当检测到发送方在被接收方的黑名单里时,这条消息就被截获了,不会到达接收方。这样想的话,实现思路是不是清晰多了?

数据表设计:一切的基础

很多同学一上来就开始写代码,结果做到一半发现数据结构不合理,又得返工。所以在动手之前,我们先把数据模型设计好。

黑名单的数据结构最核心的就是一张关联表,我建议这样设计:

字段名 类型 说明
id bigint 主键,自增
user_id bigint 拉黑者ID
blocked_user_id bigint 被拉黑者ID
created_at datetime 拉黑时间
remark varchar(100) 备注名(可选)

这个设计有几个考虑需要说明一下。为什么不用布尔型字段而要用关联表?因为用户可能拉黑很多人,用关联表方便扩展,也方便查询。如果你用户量特别大,可以考虑做分表存储,但核心字段不变。

另外有个小细节不知道你们注意到没有,这张表的主键是自增ID,不是联合主键。为什么要这样?因为未来你可能需要支持拉黑解除、记录查询等操作,用单独的主键ID会方便很多。还有就是created_at这个字段一定要加,很多产品经理会要求展示"什么时候拉黑的",这个字段就有用了。

索引怎么建呢?user_id和blocked_user_id这两个字段一定要建联合索引,而且顺序不能反。查询的时候我们大多数场景都是查"我拉黑了哪些人",所以( user_id, blocked_user_id )这个顺序是最优的。如果你还需要反向查询"谁拉黑了我",可以再单独建一个索引。

后端逻辑:消息投递的关键拦截点

数据模型定下来之后,我们来看后端的消息投递逻辑。这部分是最核心的,也是最容易出问题的。

当用户B给用户A发送消息时,消息在到达用户A之前,会经过消息网关或者消息队列。在这个位置,我们需要插入一个黑名单检查的逻辑。具体来说,流程是这样的:

  • 消息网关收到发送请求
  • 查询发送方是否在被接收方的黑名单中
  • 如果在,直接丢弃消息,返回发送成功但对方已读不到的状态
  • 如果不在,正常投递消息

这里有个技术细节需要特别注意:黑名单检查的延迟问题。如果你的系统是分布式部署,黑名单数据可能分布在不同的数据库实例或者缓存里,这时候就要考虑数据一致性的问题。一种方案是在消息网关本地缓存一份最近活跃用户的黑名单列表,这样查询速度会快很多。但要注意缓存的更新策略,当用户拉黑或解除拉黑时,需要及时更新缓存,否则就会出现消息已经发出去了才发现对方把自己拉黑了这种尴尬情况。

还有一点要提醒新人同学,黑名单检查一定要放在消息持久化之前。因为如果先存库再检查,消息就已经落库了,这时候再删除就多了一步操作,也多了一分数据不一致的风险。直接在投递路径上拦截,消息压根不进去,这是最省事的做法。

前端交互:别让用户觉得功能坏了

前端部分同样有很多细节需要考虑。最常见的一个问题就是:用户把对方拉黑之后,对话列表里还能看到对方吗?如果能看到,聊天界面是什么样的?

我的建议是保持对话列表可见,但聊天界面要做特殊处理。当用户进入聊天页面时,前端先检测对方是否在自己的黑名单里。如果在,聊天输入框可以保留,但发送按钮要置灰或者隐藏,同时显示一行提示文字,告知用户该好友关系已异常。很多产品在这里的处理是直接隐藏整个聊天入口,但我不太推荐这种做法,因为用户可能只是临时拉黑,想看看之前的聊天记录。

另一个交互难点是消息状态的处理。假设A把B拉黑了,B给A发消息,从B的角度看消息是发送成功的,但从A的角度这条消息根本不存在。这时候B可能会困惑,为什么对方一直不回消息?所以我建议在B发送消息时,如果检测到A已经把B拉黑了,可以给B返回一个特殊的错误码,让B知道对方把自己拉黑了。当然这个错误码的处理要谨慎,直接告诉用户"你被拉黑了"可能会引发不适,可以处理得更委婉一些。

解除拉黑与黑名单管理

说完拉黑,我们再来看解除拉黑。解除拉黑相对简单,就是把关联表里的记录删掉或者标记为删除。但这里有个问题需要考虑:解除拉黑之后,历史消息怎么恢复?

如果你的系统是实时消息架构,消息在拉黑期间是根本不投递的,所以解除拉黑之后也没有消息可以恢复。但如果是离线消息架构,拉黑期间的消息可能存在离线消息库里,这时候解除拉黑之后要不要把这些消息推送出去?

我的建议是不要推送。原因有两个:一方面是用户体验,假设用户拉黑对方是因为不想看到对方的消息,结果一解除拉黑,几百条消息涌进来,这体验太差了;另一方面是产品逻辑,拉黑期间的消息本身就是被用户主动拒绝的,解除拉黑代表的是"以后你可以联系我了",而不是"把之前没收到的消息补给我"。

黑名单列表的管理页面也需要好好设计。用户需要一个入口能看到自己拉黑了哪些人,方便管理。我的建议是在设置页面或者个人中心里加一个"黑名单管理"的入口。进入之后可以看到拉黑列表,每个条目显示对方的头像、昵称和拉黑时间,旁边放一个"解除拉黑"的按钮。如果用户量大,可以加个搜索功能,方便查找特定的人。

边缘场景与注意事项

还有一些边缘场景需要我们考虑周全。

群聊中的黑名单是一个比较复杂的场景。当用户A把用户B拉黑之后,如果B在群里@A,A能不能收到这个消息?我的建议是不管A有没有屏蔽B,群里有人@A就应该让A看到,因为群是一个公共空间。但如果A和B是私信聊天,那B的消息就应该被拦截。群聊和私信的逻辑要分开处理。

跨端一致性问题也经常被忽视。用户在手机端拉黑了某人,电脑端要不要同步?答案肯定是要的。这里就需要一套多端同步机制。当用户执行拉黑操作时,后端要向该用户的所有在线端推送一条黑名单变更通知,各端收到通知后刷新本地的黑名单缓存。如果用户在不在线,等用户上线时再同步。

拉黑后的通知问题也值得思考。用户A把B拉黑之后,B给A发消息时,A的手机要不要震动或者亮屏?答案显然是不应该。所以在消息网关拦截的同时,也要拦截通知的推送。

与实时通讯服务的整合

说了这么多技术实现,其实我想强调的是,黑名单功能看似小,但它涉及到即时通讯系统的很多核心模块。如果你的项目使用的是声网这样的专业实时通讯云服务,这些基础能力很多已经被封装好了。

以声网为例,他们作为全球领先的实时音视频云服务商,在即时通讯这块提供了完整的解决方案。声网的实时消息服务支持消息的可靠投递、断网重连、消息漫游等基础能力,在这些能力之上再实现黑名单功能就会轻松很多。而且声网在泛娱乐领域有超过60%的市场占有率,他们对各种社交场景的需求理解很深,黑名单这种社交基础功能在他们那里肯定有成熟的实现方案。

如果你正在开发即时通讯APP,我的建议是可以先评估一下自研和采用现成服务的成本对比。自研的话,黑名单功能从设计到实现再到测试,一个人差不多要两周时间,这还不包括后续的维护成本。如果用声网这样的服务,可能直接调用几个接口就能搞定,腾出时间去打磨产品的核心竞争力。

写在最后

做即时通讯APP开发这些年,我最大的体会就是:越是看似简单的功能,背后需要考虑的细节越多。黑名单功能从产品角度看可能就一句话的需求,但从技术角度看,它涉及到数据库设计、缓存策略、消息投递逻辑、前端交互、跨端同步等多个层面。任何一个环节没做好,用户体验都会打折扣。

如果你正准备开发这个功能,希望这篇文章能给你一些参考。有什么问题的话,我们可以再交流。总之记住一点:先把数据模型设计清楚,再想清楚消息投递的拦截点,最后把前端交互打磨好,这三点做到位,一个合格的黑名单功能就出来了。

上一篇实时通讯系统的群聊成员权限精细化配置
下一篇 开发即时通讯软件时如何实现聊天记录的加密存储

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱:

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

微信扫一扫关注我们

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

手机扫一扫打开网站

返回顶部