SpringBoot + WebSocket 构建四端 IM 即时通讯源码系统,通信协议、消息存储与集群架构分析

发布时间:2026/7/1 10:28:40
SpringBoot + WebSocket 构建四端 IM 即时通讯源码系统,通信协议、消息存储与集群架构分析 IM 即时通讯系统中的聊天页面只是最外层的表现。真正需要重点设计的是长连接接入、消息协议、消息可靠性、离线同步、群聊扩散、多端状态一致性、跨节点投递、文件传输、音视频信令以及数据持久化。系统同时覆盖 Android、iOS、H5、PC 四端后端架构需要处理的不只是“用户 A 给用户 B 发消息”还要考虑用户多端在线、弱网重连、消息重复发送、群聊权限、已读未读、消息撤回、钱包流水、朋友圈动态、文件上传、国际化文案等复杂场景。1. 系统模块拆分IM 系统可以拆成多个相互协作的模块每个模块都对应不同的数据模型和业务边界。模块主要内容用户关系好友、好友申请、删除好友、黑名单、名片分享会话消息单聊、群聊、文本、图片、文件、语音、视频、表情群组管理群成员、群主、管理员、群公告、禁言、群二维码消息状态已读未读、撤回、引用回复、合并转发、离线消息扩展业务红包、钱包、收藏、朋友圈、系统通知实时能力语音通话、视频通话、多人音视频会议多端适配Android、iOS、H5、PC国际化i18n 多语言包从技术角度看以上功能都依赖几个基础能力连接管理、消息路由、协议解析、数据持久化、缓存、状态同步和集群转发。2. 整体架构分层一个四端 IM 系统可以按职责分为客户端层、接入层、业务层、缓存层和持久层。客户端层移动端可以使用 uniapp 覆盖 Android、iOS 和 H5。它主要负责聊天页面、会话列表、朋友圈、扫码、文件选择、图片预览、消息本地缓存、断线重连等能力。PC 端使用 Vue2 实现更适合处理桌面端聊天界面例如左侧会话列表、中间聊天窗口、右侧群资料面板、文件拖拽发送、聊天记录搜索、收藏管理等。接入层接入层负责长连接维护、连接鉴权、协议解析、心跳检测和消息转发。常见接入方式包括协议使用场景Socket 自定义协议App 长连接、低冗余消息传输WebSocketH5、PC 实时通信HTTP登录、好友列表、群资料、历史消息、文件上传Socket 和 WebSocket 负责实时消息HTTP 负责查询类、管理类和上传类接口。不同协议可以共享后端业务能力也可以在部署时拆分为独立接入服务。业务层业务层由 SpringBoot 实现主要处理好友、群聊、红包、钱包、朋友圈、收藏、文件、系统通知、i18n 等模块。通信层只负责收发消息业务层负责判断消息是否允许发送、发送给哪些用户、是否需要落库、是否需要同步到其他端。缓存层Redis 适合处理高频变化的数据例如数据类型Redis 用途在线状态保存用户连接信息连接路由记录用户连接在哪个 IM 节点未读数按用户和会话维度维护临时 token扫码、文件访问、短期授权分布式锁红包领取、并发状态控制热点缓存群成员、会话信息、用户状态持久层MySQL 5.7 用于保存结构化数据例如用户表、好友表、群组表、群成员表、消息表、会话表、文件表、钱包流水表、朋友圈动态表、收藏表等。3. 消息协议设计IM 消息协议需要尽量简洁同时保留扩展能力。文本、图片、文件、表情、自定义表情、红包、名片、位置、引用回复、合并转发等消息都可以抽象成统一结构。{ cmd: MSG_SEND, clientMsgId: c_10001_202606300001, serverMsgId: null, fromUserId: 10001, toId: 20001, conversationId: single_10001_20001, chatType: single, msgType: text, content: { text: 这是一条单聊文本消息 }, extension: { quoteMsgId: null, atUserIds: [] }, timestamp: 1782777600000 }字段设计可以按以下方式理解字段说明cmd消息命令例如发送、撤回、已读、心跳clientMsgId客户端生成用于弱网重试和消息去重serverMsgId服务端生成用于消息排序和历史查询fromUserId发送方用户 IDtoId接收方用户 ID 或群 IDconversationId会话 IDchatType单聊、群聊、系统消息msgType文本、图片、文件、红包、名片等content消息正文extension扩展字段例如引用回复、成员extension字段很重要它可以承载非通用信息避免每新增一种消息形态就修改主协议结构。4. 长连接管理IM 系统需要维护用户和连接之间的映射关系。由于一个用户可能同时登录 Android、iOS、H5、PC在线状态不能简单写成userId - channelId。更合理的结构是userIddeviceserverIdchannelIdlastActiveTime10001androidim-node-1ch-001178277760000010001pcim-node-2ch-8891782777605000Redis 可以用来维护在线状态和连接路由。Service public class OnlineService { private final StringRedisTemplate redisTemplate; public OnlineService(StringRedisTemplate redisTemplate) { this.redisTemplate redisTemplate; } public void bind(Long userId, String device, String serverId, String channelId) { String key im:online: userId; String value serverId : channelId; redisTemplate.opsForHash().put(key, device, value); redisTemplate.opsForHash().put(key, device :lastActive, String.valueOf(System.currentTimeMillis())); redisTemplate.expire(key, Duration.ofMinutes(10)); } public MapObject, Object getConnections(Long userId) { return redisTemplate.opsForHash().entries(im:online: userId); } public void refresh(Long userId) { redisTemplate.expire(im:online: userId, Duration.ofMinutes(10)); } }客户端需要定时发送心跳服务端收到心跳后刷新 Redis 过期时间。如果超过指定时间没有收到心跳就可以清理连接状态。多端在线场景下服务端投递消息时需要判断是否投递到所有端还是只投递到当前活跃端。普通聊天消息通常需要多端同步某些临时状态可以只投递到当前端。5. 消息发送链路一条消息从客户端发送到接收方通常包含以下阶段阶段处理内容客户端发送生成 clientMsgId消息进入发送中状态接入层解析校验连接、解析协议、识别命令业务层校验判断好友关系、群成员权限、禁言状态服务端编号生成 serverMsgId 和发送时间消息落库写入消息表在线投递查找接收方连接并推送离线处理接收方离线时等待后续同步ACK 返回通知发送方服务端已接收ACK 机制是消息可靠性的基础。客户端发送消息后不能直接认为消息成功只有收到服务端 ACK 后才把消息状态从“发送中”更新为“已发送”。弱网环境下客户端可能重复发送同一条消息。服务端可以通过fromUserId clientMsgId做幂等处理避免重复写入消息表。6. 消息存储设计IM 消息存储至少要支持离线消息、历史消息和漫游消息。离线消息接收方不在线时消息不能丢失。用户重新上线后需要根据会话或用户维度同步离线期间产生的消息。历史消息用户在聊天窗口向上滚动时需要分页加载更早的聊天记录。常见查询条件是conversationId sendTime或conversationId msgSeq。漫游消息用户切换设备后仍然需要看到之前的消息。例如手机端发送的消息PC 登录后也应该能同步到对应会话。消息表可以按以下方式设计CREATE TABLE im_message ( id BIGINT PRIMARY KEY AUTO_INCREMENT, server_msg_id VARCHAR(64) NOT NULL, client_msg_id VARCHAR(64) DEFAULT NULL, conversation_id VARCHAR(64) NOT NULL, from_user_id BIGINT NOT NULL, chat_type VARCHAR(20) NOT NULL, msg_type VARCHAR(20) NOT NULL, content JSON NOT NULL, status TINYINT DEFAULT 0, send_time BIGINT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_server_msg_id (server_msg_id), KEY idx_conversation_time (conversation_id, send_time), KEY idx_from_client (from_user_id, client_msg_id) );idx_conversation_time用于会话历史消息分页查询idx_from_client用于客户端消息幂等判断。当消息量持续增长时需要进一步考虑分表策略。可以按时间、会话 ID、用户 ID 等维度拆分避免单表数据量过大影响查询性能。7. 单聊与群聊的技术差异单聊消息链路相对简单。服务端只需要判断双方关系、黑名单、账号状态和接收方在线情况然后完成落库与投递。群聊处理更复杂主要体现在以下几个方面。群成员校验发送群消息前需要判断用户是否在群内、是否被禁言、群是否有效、是否具备发送当前消息类型的权限。群消息扩散群聊消息不能简单理解为“给每个成员复制一条消息”。对于群成员较多的场景消息正文通常只存一份成员维度记录未读状态和读取位置。成员提醒当消息中包含 群成员时需要在消息扩展字段中保存被 的用户 ID。客户端收到消息后根据当前用户 ID 判断是否展示提醒。已读未读单聊已读可以记录双方最后已读消息 ID。群聊已读需要按成员记录lastReadMsgId或lastReadSeq再计算未读数量。8. 消息撤回、引用回复与合并转发消息撤回通常不是物理删除而是修改消息状态并向相关客户端推送撤回事件。撤回流程可以拆成以下几步步骤说明查询消息判断消息是否存在身份校验判断是否为本人消息时间校验判断是否超过允许撤回时间状态更新修改消息状态为已撤回事件推送通知相关在线端刷新消息引用回复可以通过quoteMsgId关联原消息而不是复制完整原消息内容。客户端展示时读取原消息摘要即可。合并转发可以设计为一种特殊消息类型。主消息只展示摘要点击后再加载转发包中的消息列表。这样可以减少主会话中的内容冗余。9. 红包与钱包的数据边界红包可以作为一种消息类型展示在聊天窗口中但红包创建、领取、退款、余额变化不应直接写进普通消息逻辑。可以按职责拆分为模块职责IM 消息模块展示红包消息、推送红包状态红包模块创建、领取、过期、退款钱包模块余额、冻结、流水、退款入账风控模块幂等、并发控制、异常处理领取红包时需要处理并发问题。例如多人同时领取同一个红包时需要保证不会重复领取或超额领取。实现方式可以包括数据库行锁、Redis 分布式锁、队列串行化处理等。资金类数据应保存完整流水。每一次余额变化都需要对应一条流水记录便于状态追踪和异常排查。10. 音视频通话与多人会议语音通话、视频通话和多人音视频会议一般不会直接通过 IM 长连接传输媒体流。IM 更适合作为信令通道。常见信令包括信令含义CALL_INVITE发起通话邀请CALL_ACCEPT接听CALL_REJECT拒绝CALL_CANCEL取消呼叫CALL_HANGUP挂断MEETING_CREATE创建会议MEETING_JOIN加入会议MEETING_LEAVE离开会议媒体流可以交给 RTC 通道处理IM 负责通话邀请、接听状态、房间信息、成员变化、挂断通知等控制消息。这种拆分方式可以减少聊天长连接的传输压力也便于同时支持单聊音视频、群聊音视频和多人会议场景。11. 文件、收藏与朋友圈文件发送通常分成两个阶段。第一阶段客户端通过 HTTP 上传文件服务端返回文件 ID、文件名、大小、类型、访问地址等元数据。第二阶段客户端发送一条文件类型 IM 消息消息体只保存文件元数据不直接携带文件二进制内容。收藏功能可以抽象为统一对象收藏类型来源文本单聊、群聊图片聊天消息、朋友圈文件文件消息语音语音消息视频视频消息、朋友圈聊天记录合并转发朋友圈更接近内容流系统。它可以复用用户体系和好友关系但不建议与聊天消息共用同一张表。朋友圈需要独立处理动态发布、评论、点赞、图片视频资源、可见范围和内容列表。12. 多端同步机制Android、iOS、H5、PC 同时在线时状态同步是一个重点问题。常见场景包括场景同步要求PC 端读过消息移动端未读数同步减少App 端撤回消息H5 和 PC 同步显示撤回状态H5 端收藏文件移动端收藏列表同步更新群资料变更在线端刷新群信息用户修改头像昵称会话列表同步更新这些行为可以通过状态事件同步。事件类型说明READ_RECEIPT已读回执MSG_REVOKE消息撤回CONVERSATION_TOP会话置顶CONVERSATION_MUTE会话免打扰GROUP_UPDATE群资料变更FAVORITE_ADD新增收藏SYSTEM_NOTICE系统通知状态事件可以和普通聊天消息共用长连接通道但客户端渲染时需要区分普通消息和状态变更。13. i18n 多语言包设计四端系统如果分别维护文案容易出现命名不一致、翻译不一致、状态展示不一致的问题。可以使用统一 key 管理多语言文案。key中文英文chat.send发送Sendchat.revoke撤回Revokegroup.notice群公告Group Noticewallet.balance余额Balancemessage.unread未读消息Unread Messagesfile.upload上传文件Upload File前端根据当前语言环境加载不同语言包。后端生成系统通知时也可以根据用户语言偏好返回对应文案。14. 集群部署与跨节点投递单机部署时用户连接、消息处理和投递都在同一节点内完成。集群部署后用户可能连接在不同 IM 节点上。例如用户连接节点用户 Aim-node-1用户 Bim-node-2当用户 A 给用户 B 发送消息时节点 1 需要知道用户 B 当前在哪个节点然后将消息转发到节点 2再由节点 2 投递到用户 B 的连接。连接路由可以保存在 Redis 中userIddeviceserverIdchannelId10001androidim-node-1ch-00120001pcim-node-2ch-889跨节点投递可以通过 Redis Pub/Sub、消息队列、内部 RPC 等方式实现。集群环境需要重点处理以下问题问题处理方向在线状态不准确心跳刷新、过期清理节点异常下线定时修正连接路由跨节点重复投递消息 ID 幂等判断目标用户离线离线消息兜底多端连接覆盖按 userId device 维护连接消息顺序错乱会话维度序号或时间排序15. API 接口边界IM 系统通常会提供大量 HTTP API但并不是所有能力都应该放在长连接里。API 类型示例好友接口好友申请、同意好友、删除好友、好友列表群组接口创建群、加入群、退出群、群成员列表消息接口历史消息、离线消息、撤回消息、已读回执文件接口上传文件、下载文件、获取文件信息钱包接口余额查询、流水查询、红包领取朋友圈接口发布动态、评论、点赞、动态列表收藏接口添加收藏、删除收藏、收藏列表HTTP 更适合查询类、管理类、上传类接口。长连接更适合实时消息、状态事件和通知推送。模块之间也需要保持边界清晰。红包消息可以出现在聊天窗口中但资金变更应由钱包模块处理朋友圈可以复用用户关系链但不应依赖聊天消息表音视频可以复用 IM 信令但媒体流不应走 IM 消息通道。16. 部署与运行关注点开发环境一般包含以下组件组件说明JDK运行 SpringBoot 服务MySQL 5.7存储用户、消息、群组、关系链等数据Redis在线状态、未读数、缓存、锁uniappAndroid、iOS、H5 客户端Vue2PC 客户端WebSocket / Socket 服务长连接接入部署阶段需要关注方向说明SSL/TLS长连接和接口传输加密长连接数量连接数、心跳频率、连接清理Redis 内存在线状态、未读数、缓存过期策略MySQL 索引消息分页、会话查询、关系链查询消息表增长分表、归档、冷热数据拆分文件存储上传、下载、访问权限、容量控制日志采集连接日志、消息日志、异常日志接口限流登录、发消息、上传、扫码等高频接口节点扩容接入层横向扩展、跨节点路由监控指标在线人数、消息量、延迟、失败率对于 IM 系统而言运行阶段需要持续观察连接数、消息发送量、离线消息堆积、接口耗时、Redis 命中率、数据库写入速度、跨节点投递耗时等指标。长连接服务、业务服务、缓存服务和数据库服务之间的状态变化会直接影响消息实时性和多端同步效果。官方演示路径——宠友 IM宠友(IM即时通讯)app,支持语音、文件、图片、视频等多种类型消息的发送,安全可靠,交流轻松,私有化部署,快速开发极简部署支持群聊管理、语音视频通话...功能https://chongyou.info/1/product/im.html