自建 IM 的服务拆分:双令牌、WebSocket 和离线消息

自建一套即时通讯,最先容易做错的是边界:把注册登录、文件上传、WebSocket 推送和运营查询放进同一个进程。流量形态完全不同。本文按 CaoIM 现在的实现,记录一种拆法,以及一条消息实际怎么走完。

CaoIM 是一套可以自己部署的 IM。服务端是 Java 17、Spring Boot,库是 MySQL。客户端是 Flutter,同一套代码覆盖 Android、iOS、Windows、macOS 和 Web。

进程怎么切

模块 职责 默认端口 库
im-server 好友、群、历史、已读、离线同步、一对一通话信令、WebSocket 8080,上下文 /api cao_im_db
app-server 注册、登录、资料、文件上传 8081 cao_im_app_server
im-admin 运营后台 API 和 Vue 页面 API 8082,页面开发端口 5173 管理员表在 cao_im_db
push-server 用户不在线时的系统通知 8083 设备表在 cao_im_db

im-server 再分成 im-core 和 im-boot。前者是实体、服务和 Feign 契约,后者是可执行进程。app-server 和 im-admin 依赖 im-core,通过 Feign 调消息服务,请求头带 X-Internal-Token。这个内部令牌三边必须一致。客户端不依赖这个 JAR,只访问 HTTP 和 WebSocket。

后台不直接写 im_message 这类业务表。用户启停、解散群、消息检索、敏感词和系统配置,都走 im-server 的 /api/admin/**。管理员自己的账号、角色、菜单和审计在 admin_* 表,由后台服务维护。

两套令牌

客户端日常登录走 app-server,不走消息服务的登录接口。登录成功返回:

  • token:访问 app-server,改资料、上传文件

  • imToken、imRefreshToken:im-server 签发,用来建立长连接

两套 JWT 的密钥各自独立。消息服务上的 /user/login 仍然保留,给直连消息服务的场景,不是客户端的主路径。

公网部署时,不建议把长期的 imToken 放在 WebSocket 地址里。客户端先带 Authorization 调用 GET /api/ws-ticket,换一张 60 秒、只能使用一次的票据,再连接 ?ticket=。票据放在 Redis。旧客户端若仍使用 ?token=,服务端目前会放行并打日志,便于过渡。

一条消息的路径

文本和表情由 SDK 经 WebSocket 发给 im-server。图片、语音、文件、短视频先上传到 app-server,消息体里只保存 URL。服务端写入 MySQL 之后:

  1. 给发送方回执,表示服务端已接受

  2. 接收方在线,立刻从他所在的那根连接推下去

  3. 接收方不在线,等他上线后按离线接口拉取

  4. 若打开了 push-server,再尝试一条系统通知

接收方把消息展示出来之后,再上报送达回执和已读回执。私聊回执落到「已读」,群回执落到「已读人数」。当前正在查看的会话不再累计未读。

消息类型与服务端约定为:0 文本,1 图片,2 文件,3 语音,4 视频,99 系统提示。

上行帧的 type 包括 private、group、recall_message、read_receipt、delivery_ack,以及拉离线、群成员变更。心跳是 ping,服务端回 pong。

文本帧大致如下:

复制代码
{ "type": "private", "toId": 123, "content": "Hello", "msgType": 0 }

文件不走这根连接。app-server 的单文件上限默认 50MB,短视频按这个上限,普通文件在客户端侧限制为 10MB。存储可以是本地目录,也可以是 S3 兼容存储(MinIO、阿里云 OSS、腾讯云 COS)。file.domain 必须是接收方设备能够访问的地址。写成 127.0.0.1 时,只有跑服务的那台机器能打开图片。

在线状态在进程内,多节点要粘滞

单机时,在线会话放在 im-server 进程里。连在 A 进程上的用户,B 进程推不进去。一台机器开发可以不启 Redis,配置里把 caoim.require-redis-for-connect 设为 false。

多节点时,在线状态、节点间投递和通话状态放到 Redis,每个进程有自己的 node-id。客户端先请求 /api/route,再直连返回的那台 wsUrl,避免连接被负载均衡打到另一台机器上。Nginx 只适合做 HTTP 入口;WebSocket 若必须经过 Nginx,要打开 Upgrade,并把读超时拉长。

仓库里的 k6 脚本用来看长连接保持、私聊吞吐、群扇出和断线补拉,那是压测场景,不是对外承诺的容量数字。群扇出仍然会打到在线成员的连接上,500 人的群和 5 万人的群不是同一件事。

历史消息可以按天龄归档到 Mongo,热数据仍留在 MySQL。归档是可选项,不开启时查询路径不变。

落库加密不是端到端

配置了消息密钥之后,im_message.content 在写入前用 AES-GCM 加密,读出后解密,密文以 enc:v1: 开头。密钥为空则保持明文,旧数据可以继续读。

加密发生在落库这一跳。推送、接口返回和敏感词看到的仍是明文,否则后台检索和关键词过滤会失效。因此这不是端到端加密,持有密钥的服务端看得到正文。正文加密之后不能再对内容做 SQL LIKE,搜索要先按会话、用户、时间缩小范围,解密后再过滤。收藏和附件文件目前仍是明文。

传输层在公网应走 TLS:账号和文件一个域名,消息和 wss 另一个域名,反代到本机的 8081 和 8080。管理端和 push-server 不必对公网开放。

通话只转发信令

一对一呼叫的上行类型是 call_invite、call_accept、call_reject、call_cancel、call_hangup、call_signal。video: true 表示视频。服务端转发 SDP 和 ICE,并写通话记录,不转发媒体。

媒体由客户端用 WebRTC 建立。仓库里的示例只配置了公共 STUN(stun:stun.l.google.com:19302)。同一局域网一般能通。对称 NAT 下跨网部署经常没有声音和画面,需要单独的 TURN,本仓库不附带。群通话没有实现。对方不在线时,呼叫直接结束,不会靠离线推送把通话拉起。

push-server 默认 delivery: log,通知只写入 push_log,手机不会响。要真正下发,需要自己接厂商通道。同一用户、同一业务在两分钟内只投递一次。推送失败不影响消息已经发送成功。

本地跑起来

环境是 JDK 17、Maven 3.6、MySQL 5.7(utf8mb4)。系统里若默认还是 JDK 8,这套代码编译不过。

复制代码
mysql -u root -p < im-server/sql/schema.sql
mysql -u root -p < app-server/sql/schema.sql

cd im-server
mvn clean install -DskipTests

cd im-boot
mvn spring-boot:run

另开终端启动 app-server。已有旧库时不要重复执行会删表的脚本,按 sql/migrate_*.sql 补字段。至少改掉数据库密码、两边的 jwt.secret,以及三份必须相同的内部令牌。

启动后可以用这些地址确认进程:

地址 用途
http://localhost:8080/api/health/check 消息服务健康检查
http://localhost:8080/api/swagger-ui/index.html 消息服务接口
http://localhost:8081/swagger-ui/index.html 登录与上传接口

生产环境关掉或限制 Swagger。/api/debug/** 会返回在线用户,不要对公网开放。

还没做的部分

语音转文字没有。语音文件已经在 app-server,识别如果要加,应放在服务端调用本机模型,不要打进客户端安装包。支付、小程序、视频号不在这个仓库的范围内。

源码在 GitHub、Gitee 的 cao-im 组织,入口仓库是 im-server,其余模块从 README 的仓库表可以找到。个人学习和非商用可以直接使用。协议不是 MIT:商用同样不收费,但需要先取得授权。未授权不要用于公司产品和向客户交付。

相关推荐
Raymond168810 天前
AI配音工具长文本合成架构技术解析:全局音色锚定与声学状态跟踪
人工智能·架构·实时互动·视觉检测·音视频·语音识别
几何心凉12 天前
端到端具身交互智能正式上线:让 AI 通过屏幕和机器人,真正走进现实世界
前端·人工智能·实时互动·机器人·交互
voipmaker15 天前
选煤厂智能化:UWB 人员定位系统 WebAPI 对接融合通信平台实战
物联网·实时互动·json·音视频·实时音视频·web·视频
美狐美颜SDK开放平台2 个月前
从视频处理到实时渲染:直播APP中视频美颜SDK的关键技术解析
前端·人工智能·架构·实时互动·音视频·视频美颜sdk
DolitD2 个月前
实时云渲染多应用并发:CELL容器技术突破3D隔离瓶颈
云原生·容器·实时互动·图形渲染·数据可视化
深圳市宝华视联2 个月前
医院内网部署手术示教系统注意事项
嵌入式硬件·实时互动·音视频·实时音视频·视频编解码·嵌入式实时数据库
美狐美颜SDK开放平台2 个月前
视频美颜SDK是什么?直播APP开发中美颜功能实现方式介绍
深度学习·架构·实时互动·音视频·视频美颜sdk
光锥智能2 个月前
争抢“实时互动”,爱诗科技的世界模型走到哪了
人工智能·科技·实时互动
视频技术分享2 个月前
技术拆解与实战优化:国产化视频会议系统核心原理与落地策略
其他·重构·架构·实时互动·音视频