【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:IM 即时通讯模块,一个被低估的「全功能聊天系统」

大家好,我是你们的 RuoYi-Vue-Pro 源码拆解系列作者-腻害兔。今天这一期,我们来啃一块硬骨头——IM 即时通讯模块(yudao-module-im)

说实话,在开始分析之前,我以为这个模块无非就是「WebSocket 推个消息、数据库存个聊天记录」的玩具级别实现。但当我把 183 个源文件、18 张数据库表、40+ 种消息类型全部读完之后,我只能说:这个 IM 模块的完成度,远超我的预期,甚至在某些设计细节上,比不少商业 IM 产品还要讲究。

废话不多说,直接上干货!


一、今日模块概览

一句话总结:yudao-module-im 是一个支持私聊、群聊、频道三种会话模式,内置 40+ 种消息类型、好友关系管理、表情包系统、敏感词过滤、LiveKit 音视频通话、实时统计看板的完整 IM 系统。

它解决的核心问题是:让企业级后台管理系统不再只是「冷冰冰的数据管理工具」,而是具备「人与人实时沟通」能力的现代化协作平台。你可以把它理解为一个「嵌入式企业微信」——虽然不可能做到微信的体量,但在企业内部沟通、客服支持、跨部门协作这些场景下,它已经足够好用了。


二、技术选型分析

2.1 实时通信层:WebSocket + 多消息中间件

RuoYi 的 IM 实时推送基于自研的 yudao-spring-boot-starter-websocket 模块,底层使用 Spring 原生的 WebSocket 能力。但真正有意思的是它的 消息分发层设计——支持 4 种 sender 类型:

sender-type适用场景底层实现
local单机部署内存 Map 存储 Session
redis小规模集群Redis Pub/Sub
rocketmq大规模集群RocketMQ Topic
rabbitmq中规模集群RabbitMQ Exchange
kafka大数据生态集群Kafka Topic

为什么这样设计? 因为 WebSocket Session 是存在单机内存里的。当你的服务从 1 台扩展到 10 台时,用户 A 连在服务器 1 上,用户 B 连在服务器 2 上,B 要给 A 发消息就必须跨服务器推送。这就是消息中间件出场的时候——通过 Pub/Sub 机制,让所有服务器都能收到消息,再各自推送给自己维护的 Session。

为什么不用 Netty? Netty 确实是 IM 领域的主流选择(比如微信、钉钉的长连接网关都是基于 Netty 自研的),但 Netty 意味着要脱离 Spring 生态,自己搞一套 HTTP + WebSocket 的协议栈。对于 RuoYi 这种「以 Spring Boot 为核心」的项目来说,用原生 WebSocket 是最自然的选择——代价是单机连接数上限不如 Netty,但对于企业级内部 IM 场景(几百到几千人同时在线),完全够用。

划重点: 这个 sender 的抽象设计是一个非常值得学习的架构模式——「本地优先 + 可插拔的分布式扩展」。小项目用 local 零依赖启动,大项目切 MQ 无缝升级,这才是好的框架设计。

2.2 音视频通话层:LiveKit

RTC(实时通信)这块,RuoYi 选了 LiveKit 而不是声网(Agora)或腾讯 TRTC。

LiveKit 是一个开源的 WebRTC 基础设施,支持音视频通话、屏幕共享、房间管理等功能。选择 LiveKit 的核心原因有三个:

  1. 开源免费:声网和 TRTC 都是按分钟计费的商业服务,而 LiveKit 可以自建部署,零成本。
  2. WebRTC 原生支持:LiveKit 基于 WebRTC 标准协议,前端可以直接用浏览器 API 对接,不需要安装插件。
  3. Twirp API + Webhook:LiveKit 提供了标准的 REST API(Twirp 协议)和 Webhook 回调机制,RuoYi 可以方便地创建/结束通话房间、接收通话状态变更事件。

从代码里可以看到,RuoYi 的 LiveKit 集成包括:JWT Token 签发(yudao.im.rtc.apiKey + apiSecret)、Twirp Server API 调用(创建 Room、删除 Room)、Webhook 签名验证(livekit.sha256 HMAC)、以及僵尸通话清理(超过阈值自动结束异常通话)。

如果换成声网/TRTC 会怎样? 好处是省去了自建 LiveKit Server 的运维成本,坏处是产生了持续的费用依赖,而且和第三方 SDK 的耦合会更紧。对于「企业内部 IM」这个定位来说,LiveKit 自建是更合理的选择。

2.3 消息存储层:三表分离 + 游标拉取

消息存储是 IM 系统最核心的设计决策之一。RuoYi 采用了 三表分离 策略:

表名用途特点
im_private_message私聊消息按 receiverId 维度存储
im_group_message群聊消息按 groupId 维度存储,支持 @、已读回执
im_channel_message频道消息按 channelId 维度存储,支持富文本素材

为什么不用一张大表? 因为三种会话的消息拉取逻辑完全不同。私聊是「拉对方发给我的消息」,群聊是「拉某个群的所有消息」,频道是「拉某个频道的推送内容」。如果放在一张表里,查询条件会越来越复杂,索引也很难设计。

游标拉取(Cursor-based Pull) 是另一个亮点。RuoYi 没有用传统的 offset/limit 分页,而是用 messageId < ? 的游标方式做增量拉取。这样做的好处是:不会因为中间有消息被删除而导致分页错位,也避免了深分页的性能问题。

2.4 敏感词过滤:Guava Cache + houbb 库

敏感词过滤用的是 houbb-sensitive-word 库(基于 DFA/Trie 树算法),配合 Guava LoadingCache 做租户级别的缓存。每 1 分钟异步检查 max(updateTime) 是否有变化,有变化才重新加载。

为什么不用 Elasticsearch 的敏感词方案? 因为敏感词过滤是一个「高频、低延迟」的操作——每条消息发送前都要过一遍,所以必须在内存中完成。Elasticsearch 的全文检索能力在这里是大材小用,而且延迟不可控。


三、需求溯源推演

3.1 谁在什么场景下需要这个模块?

让我们倒推一下这个 IM 模块的产品需求文档(PRD)可能长什么样:

场景一:企业内部沟通

「我们是一个 200 人的制造企业,用了 RuoYi 做 ERP/OA 系统。现在员工要沟通一个问题,还得切到微信或者钉钉。如果系统里直接就有聊天功能,能直接 @同事、发订单截图、甚至开个语音会议,那效率提升太大了。」

场景二:客服支持

「我们是一个 SaaS 平台,客户遇到问题需要在系统里直接联系我们。发邮件太慢,打电话太贵,如果有个在线聊天窗口,客服可以直接看到客户的订单信息,边聊边处理,体验会好很多。」

场景三:跨部门协作

「我们公司有研发、产品、运营、销售四个部门,跨部门协作经常找不到人。如果系统里能看到组织架构、直接发起聊天或群组讨论,比在微信群里翻聊天记录高效多了。」

3.2 功能演进路径

从代码结构来看,这个 IM 模块的演进路径大概是这样的:

V1.0(MVP):私聊 + 文本消息 → 解决「能聊天」的基本需求 V1.5:群聊 + 群管理 → 解决「多人协作」的需求 V2.0:图片/语音/视频/文件消息 + 表情包 → 解决「富媒体沟通」的需求 V2.5:好友管理 + 好友申请 → 解决「社交关系链」的需求 V3.0:音视频通话(LiveKit) → 解决「面对面沟通」的需求 V3.5:频道 + 素材管理 → 解决「内容分发」的需求 V4.0:敏感词过滤 + 统计看板 → 解决「合规运营」的需求

这个演进路径非常合理——先做核心通信能力,再逐步丰富社交关系和内容管理,最后补上运营和合规工具。


四、竞品对标分析

我们把 RuoYi 的 IM 模块和几个同类开源项目做个对比:

维度RuoYi-Vue-Pro IMJeecgBootPigSpringBlade
会话模式私聊 + 群聊 + 频道无原生 IM无原生 IM无原生 IM
消息类型40+ 种(文本/图片/语音/视频/文件/卡片/位置/合并/引用/撤回/回执等)---
音视频通话LiveKit 集成
好友系统完整(申请/接受/拒绝/拉黑/置顶/备注)
表情包系统表情包 + 用户自定义表情
敏感词Trie 树过滤 + 租户级缓存
消息拉取游标增量拉取---
已读回执私聊 + 群聊都支持
统计看板用户/消息/群组多维度统计
WebSocket 分发5 种 sender(local/redis/rocketmq/rabbitmq/kafka)STOMP

结论:在开源后台管理系统的 IM 模块中,RuoYi-Vue-Pro 的实现是目前我见过最完整的,没有之一。 JeecgBoot、Pig、SpringBlade 这些竞品要么没有原生 IM 模块,要么只是一个简单的 WebSocket 聊天室 demo。RuoYi 在 IM 上的投入明显是下了功夫的。

但和真正的专业 IM 产品比呢? 比如融云、环信、网易云信这些商业 IM PaaS,RuoYi 的 IM 模块在以下方面还有差距:

  1. 消息可靠性:专业 IM 有 ACK 确认机制 + 消息重发 + 消息去重,RuoYi 目前依赖 WebSocket 的可靠性,没有显式的 ACK 层。
  2. 海量消息性能:专业 IM 会用时序数据库(如 HBase/TiKV)存储消息,RuoYi 用的是 MySQL,百万级消息后查询性能会下降。
  3. 多端同步:专业 IM 支持手机/平板/PC 多端同时在线 + 消息同步,RuoYi 目前主要是 Web 端。
  4. 离线推送:专业 IM 有 APNs/FCM 推送通道,RuoYi 没有移动端推送能力。

踩坑提醒: 如果你的项目需要在生产环境跑大规模 IM(万级 DAU 以上),建议还是用专业的 IM PaaS 服务。RuoYi 的 IM 模块更适合「企业内部小规模沟通」或「SaaS 平台的轻量客服」场景。


五、核心业务流程

5.1 私聊消息发送流程

这里有几个设计细节值得注意:

clientMessageId 的作用:客户端在发送消息时自带一个 clientMessageId,服务端用它做幂等性控制——如果收到相同的 clientMessageId,说明是网络重试,直接返回之前的结果。这是 IM 系统的标准做法,防止弱网环境下消息重复发送。

消息发送和推送的解耦:消息先落库,再推送。即使推送失败(对方不在线),消息也已经安全存储在数据库里了。等对方上线后,通过增量拉取接口获取离线消息。这就是经典的 写扩散(Write Fanout) 模式。

5.2 群聊消息发送流程

群聊比私聊复杂得多,核心区别在于:

  1. @功能:消息的 atUserIds 字段记录被 @ 的用户 ID 列表,-1 表示 @所有人(ImCommonConstants.AT_USER_ID_ALL)。
  2. 已读回执:群聊的已读状态用 receiptStatus 字段追踪(NO_RECEIPT → PENDING → DONE),每个成员需要单独上报读位置。
  3. 群系统消息:群创建、成员加入/退出、群信息变更、禁言/解禁等事件,都通过特殊的消息类型(ImContentTypeEnum 中 1501-1533 段)发送到群里,确保所有成员都能看到群状态变化。

5.3 好友申请流程

好友系统采用了经典的 申请-审批 模式:

有个细节很贴心:ImFriendDO 是 双向记录——A 加 B 为好友时,会同时创建两条记录(A→B 和 B→A),各自维护 silent(消息免打扰)、displayName(备注名)、pinned(置顶)等独立状态。这种设计避免了「一条记录要存两个人的状态」的尴尬,查询也更高效。

5.4 音视频通话流程

RTC 通话集成了 LiveKit,流程如下:

  1. 发起通话:调用 createCall() → 生成 LiveKit Room → 创建 im_rtc_call 记录 → 邀请参与者 → WebSocket 推送通话邀请
  2. 接受/拒绝:参与者操作后更新 im_rtc_participant 状态 → 如果接受,前端通过 LiveKit SDK 加入 Room
  3. 通话中:LiveKit Server 处理音视频流,RuoYi 后端只负责信令(signaling)和状态管理
  4. 结束通话:更新 call 状态和结束原因 → 通过 LiveKit Webhook 回调同步最终状态
  5. 僵尸清理:定时任务检查超过 cleanupZombieThresholdMinutes(默认 5 分钟)仍未开始的通话,自动结束

注意: RuoYi 的后端不处理任何音视频数据流——这是正确的架构决策。音视频流通过 WebRTC 在客户端和 LiveKit Server 之间直连(P2P 或 SFU),后端只负责「信令」和「状态管理」,这样不会给业务服务器带来带宽压力。


六、数据模型解读

6.1 核心表关系

6.2 关键设计解读

im_conversation_read(会话读位置表)

这张表是整个 IM 系统的「已读状态」核心。每条记录存储了「某个用户在某个会话中读到了哪条消息」。设计上有几个精妙之处:

  • 单调递增约束:读位置只能往前走,不能后退。代码中通过 WHERE message_id < ? 确保这一点,避免了乱序更新导致已读状态回退。
  • 三合一设计:私聊、群聊、频道三种会话的读位置都存在同一张表里,通过 conversationType 字段区分。这比「每种会话一张读位置表」的方案简洁得多。

im_group(群信息表)

  • pinnedMessageIds 字段使用了 LongListTypeHandler(MyBatis Plus 的自定义 TypeHandler),把多个 messageId 以 JSON 数组形式存在一个字段里。这是一种「反范式」设计——理论上可以单独建一张 im_group_pinned_message 关联表,但考虑到置顶消息数量很少(配置上限 5 条),存在群表里反而查询更高效。
  • joinApproval 字段控制加群是否需要审批,banned 控制是否全员禁言,mutedAll 控制是否全员禁言(这两个字段看起来有重叠,但代码里 banned 可能是解散级别的,mutedAll 是临时禁言)。

im_private_message / im_group_message(消息表)

  • content 字段存储的是 JSON 格式的消息内容,不同类型的消息有不同的 JSON 结构(比如文本消息是 {"text": "hello"},图片消息是 {"url": "...", "width": 100, "height": 200})。这种设计叫 EAV(Entity-Attribute-Value)的变体——用一张表存储所有类型的消息,通过 JSON 字段实现灵活的内容结构。
  • receiverUserIds 字段在群消息表中存储了消息的接收者列表。这是一种「写扩散」设计——发送一条群消息时,会显式记录所有应该收到这条消息的用户 ID。好处是拉取消息时不需要再查群成员表,坏处是群成员很多时这个字段会很大。

七、产品设计亮点与槽点

7.1 让我眼前一亮的設計

亮点一:40+ 种消息类型的精细分类

大多数开源 IM 只支持文本、图片、语音、视频几种消息类型。RuoYi 的 ImContentTypeEnum 定义了 40+ 种类型,每种类型还有 persistent(是否持久化)和 normal(是否计入未读数)两个标志位。

这意味着:撤回消息(RECALL)不会被持久化为新消息,而是在原消息上做状态变更;已读回执(READ)不会增加未读计数;群系统消息(如「张三加入了群聊」)会持久化但不计入未读。这种精细控制体现了设计者对 IM 产品体验的深入理解。

亮点二:事务感知的 WebSocket 推送

ImWebSocketServiceImpl 中有一个非常巧妙的设计——事务提交后再推送。具体来说,当一条消息在数据库事务中创建时,WebSocket 推送不会立即执行,而是注册一个事务回调(TransactionSynchronizationManager.registerSynchronization),等事务成功提交后才异步推送。

为什么这很重要?因为如果事务还没提交就推送了消息,用户收到推送后点击查看详情,但此时数据库事务还没 commit,查询可能查不到这条消息——这就是经典的「推送比数据库快」的问题。RuoYi 通过事务感知推送彻底解决了这个问题。

亮点三:表情包系统的三层架构

表情包设计了三层结构:系统表情包(im_face_pack)→ 表情项(im_face_pack_item)→ 用户私有表情(im_face_user_item)。用户可以收藏系统表情,也可以上传自己的自定义表情(上限 200 个)。这个设计和微信/QQ 的表情系统如出一辙。

亮点四:频道(Channel)模式

除了私聊和群聊,RuoYi 还引入了「频道」概念。频道和群的区别在于:频道是「一对多」的内容分发模式(类似 Telegram Channel 或微信公众号),有素材管理(富文本/外链),适合做公告板、知识库、内容推送。这个设计在开源 IM 中非常少见。

7.2 可以改进的地方

槽点一:缺少消息 ACK 确认机制

目前消息发送后直接推送 WebSocket,如果推送失败(用户不在线或网络中断),消息虽然已经落库,但没有显式的 ACK 机制来确认对方是否真的收到了。对于企业级 IM 来说,建议增加客户端的 ACK 确认 + 超时重推机制。

槽点二:消息搜索能力缺失

从代码中没有看到消息全文搜索的接口。当消息量大了之后,用户想找到之前聊过的某个关键词,只能靠滚动翻聊天记录。建议增加 Elasticsearch 全文索引,支持消息内容的模糊搜索。

槽点三:文件消息没有独立的存储管理

图片、语音、视频、文件消息的 URL 直接指向文件存储(OSS/MinIO),但没有看到文件生命周期管理的逻辑——比如过期文件清理、存储空间统计、文件缩略图生成等。对于长期运行的 IM 系统,文件存储会成为一个不可忽视的成本。

槽点四:IM 表的 SQL 只在 H2 测试库中

生产环境的 MySQL/PostgreSQL 等 SQL 文件中没有 IM 表的 CREATE TABLE 语句,只有 H2 测试库中有。这意味着部署到生产环境时,需要手动建表或者依赖其他机制。对于想要快速体验的用户来说,这是一个小障碍。


八、发散性思考

8.1 这个模块还能做什么?

AI 智能客服接入:RuoYi 已经有 AI 模块(yudao-module-ai),如果把 AI 模块的大模型能力和 IM 模块打通,就能实现「智能客服机器人」——用户发消息时,先过一遍 AI 模型,如果 AI 能回答就直接回复,回答不了再转人工。这个功能在产品层面几乎是「零开发成本」的,因为两个模块都在同一个系统里。

工单系统集成:IM 的频道模式天然适合做工单流转——客户在群里提出问题,系统自动创建工单,客服在群里回复,工单状态同步更新。把 IM 和工单系统打通,可以实现「沟通即工单」的体验。

消息驱动的工作流:结合 BPM 模块,可以在群聊中直接发起审批——比如「@张三 请审批这个订单」,张三点击消息卡片就能完成审批操作。这种「ChatOps」模式在 Slack 和飞书中已经很成熟了。

8.2 如果让我重新设计

如果让我重新设计这个 IM 模块,我会做以下调整:

  1. 消息存储分层:热数据(最近 7 天)放 MySQL,温数据(7-90 天)放 TiDB/OceanBase,冷数据(90 天以上)归档到对象存储。这样既保证了查询性能,又控制了存储成本。

  2. 引入消息队列做异步解耦:目前消息发送是同步流程(发消息 → 落库 → 推送),如果推送环节出现延迟,会影响消息发送的响应时间。建议用 RocketMQ 把推送环节异步化,消息落库后立即返回,推送由消费者异步完成。

  3. 增加端到端加密(E2EE):对于企业级 IM,消息的隐私安全非常重要。建议在私聊场景中引入端到端加密——消息在发送端加密,只有接收端能解密,服务器只存储密文。Signal 协议的 Double Ratchet 算法是业界标准。

  4. 消息撤回的时间窗口可配置:目前撤回超时是 5 分钟(recallTimeoutMinutes),建议做成可配置的,不同企业可以设置不同的撤回策略。

8.3 技术思路迁移

RuoYi IM 模块中有几个设计思路可以迁移到其他场景:

  • 事务感知的 WebSocket 推送 → 可以用在任何「先落库再推送」的场景,比如订单状态变更通知、审批提醒等。
  • 游标增量拉取 → 可以用在 Feed 流、通知列表、操作日志等需要「加载更多」的场景。
  • 三表分离 + 统一读位置 → 可以用在任何多类型会话/多类型通知的系统。
  • 敏感词的租户级缓存 → 可以用在任何多租户的内容审核场景,比如电商平台的商品描述审核、UGC 社区的评论过滤。

九、关键代码导读

9.1 ImPrivateMessageServiceImpl.java

路径:yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java

为什么值得读:这是整个私聊消息的核心实现。包含了消息发送(敏感词过滤 → 生成 ID → 落库 → 更新读位置 → WebSocket 推送)、增量拉取(游标分页)、已读上报等核心逻辑。特别是事务感知推送的实现,值得每个做 IM 的同学仔细研读。

9.2 ImContentTypeEnum.java

路径:yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImContentTypeEnum.java

为什么值得读:40+ 种消息类型的定义,每种类型都有 persistent 和 normal 两个标志位。这个枚举的设计思路——用编号段区分消息类别(100 段=基础消息、1200 段=好友通知、1500 段=群系统消息、1600 段=RTC 通知、2100 段=撤回/回执),是一个非常值得借鉴的分类方法。

9.3 ImRtcCallServiceImpl.java

路径:yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java

为什么值得读:LiveKit 集成的核心实现。包含了通话创建(Redis 分布式锁防重)、邀请参与者、通话结束、Webhook 回调处理、僵尸通话清理等完整生命周期管理。特别是 JWT Token 签发和 Webhook 签名验证的代码,是和 LiveKit 对接的关键。

9.4 ImWebSocketServiceImpl.java

路径:yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImpl.java

为什么值得读:事务感知推送的实现精髓所在。代码量不大,但设计思想很深刻——通过 TransactionSynchronizationManager 注册事务回调,确保 WebSocket 推送在数据库事务提交后才执行。这个模式可以推广到所有需要「先持久化再通知」的业务场景。

9.5 ImSensitiveWordServiceImpl.java

路径:yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitive/ImSensitiveWordServiceImpl.java

为什么值得读:敏感词过滤的完整实现。Guava LoadingCache + 定时异步刷新 + max(updateTime) 变更检测的三级缓存策略,以及 houbb sensitive-word 库的 DFA/Trie 树匹配配置,都是可以在其他内容审核场景中直接复用的。


十、总结

RuoYi-Vue-Pro 的 IM 模块是一个被很多人低估的存在。大多数人在评估 RuoYi 时,关注的是权限管理、代码生成、工作流这些「传统强项」,但 IM 模块的完成度其实已经超越了绝大多数同类开源项目。

从产品经理的视角来看,这个模块的价值在于:它让 RuoYi 从一个「后台管理框架」升级为一个「企业协作平台」。想象一下,你的 ERP 系统里直接就能和同事聊订单、在 OA 系统里直接发起语音会议、在 CRM 系统里直接和客户沟通——这种「沟通融入业务」的体验,才是企业数字化的终极形态。

从技术视角来看,IM 模块的架构设计也有很多值得学习的地方:事务感知推送、游标增量拉取、三表分离存储、可插拔的 WebSocket 分发、LiveKit RTC 集成、租户级敏感词缓存……每一个设计决策背后,都有清晰的问题驱动和取舍逻辑。

下一篇预告:IM 模块啃完了,但我们的系列还没结束!接下来还有两块硬骨头——MES 制造执行模块(yudao-module-mes)WMS 仓库管理模块(yudao-module-wms)。MES 负责车间排程、设备管理、质检追溯,WMS 负责库位管理、批次追溯、出入库流程,这两个模块是 RuoYi 从「管理后台」走向「工业级 ERP」的关键拼图。敬请期待~


觉得有用的话,点个赞支持一下呗~ 你们的点赞是我持续更新的动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值