13 KiB
Voice Room Basic Loop Implementation
本文档定义语音房 v1 基础闭环的实现顺序和验收边界。目标不是做完整语音房,而是先让“登录用户创建房间、进入房间、登录腾讯云 IM、加入房间群、接收房间系统消息、离房、重连恢复”成为可测闭环。
Room Cell 的完整状态机见:
docs/房间Cell状态机.md
Scope
本阶段必须打通:
| Capability | Owner | Required Result |
|---|---|---|
| 用户身份 | user-service |
gateway 能从 access token 得到稳定 user_id |
| HTTP 命令入口 | gateway-service |
所有房间命令走 /api/v1 envelope |
| 房间业务态 | room-service |
Room Cell 持有 presence、麦位、禁言、踢人、快照和恢复 |
| 腾讯云 IM 登录票据 | gateway-service |
/api/v1/im/usersig 返回 sdk_app_id/user_id/user_sig/expire_at_ms |
| 腾讯云 IM 群组 | room-service |
RoomCreated outbox 由 worker 异步确保 room_id 对应群组存在 |
| 房间系统消息 | room-service |
房间事件只写 outbox,腾讯云 IM 由 outbox worker 异步投递 |
| 实时权限守卫 | gateway-service + room-service |
腾讯云 IM 入群前和群内发言前必须回查房间业务态 |
| 腾讯云 RTC 进房票据 | gateway-service |
JoinRoom 成功后在响应中内嵌 RTC token;/api/v1/rtc/token 只做补偿和刷新 |
本阶段不做:
| Excluded | Reason |
|---|---|
| 自研 WebSocket | 实时连接和消息通道由腾讯云 IM 承担 |
| 全量 IM 历史库 | 群历史优先使用腾讯云 IM 能力 |
| RTC 录制/审核 | 只先接入进房听音频所需的 token 和回调,不处理录制与审核 |
| 复杂单聊会话 | 先保证房间基础闭环 |
| 热门房多活 | 当前仍是单房间单活 + Redis lease 接管 |
Required Config
gateway-service 负责客户端 UserSig 和腾讯云 IM 回调入口:
tencent_im:
sdk_app_id: 20036101
secret_key: "TENCENT_IM_SECRET_KEY"
user_sig_ttl: "24h"
callback_url: "https://api.example.com/api/v1/tencent-im/callback"
callback_auth_token: "TENCENT_IM_CALLBACK_AUTH_TOKEN"
room-service 负责服务端 REST API 建群和系统消息:
tencent_im:
enabled: true
sdk_app_id: 20036101
secret_key: "TENCENT_IM_SECRET_KEY"
admin_identifier: "900100100"
admin_user_sig_ttl: "24h"
endpoint: "adminapisgp.im.qcloud.com"
group_type: "ChatRoom"
request_timeout: "5s"
配置规则:
sdk_app_id、secret_key必须在 gateway 和 room-service 中保持一致。endpoint按腾讯云 IM 应用区域填写;新加坡使用adminapisgp.im.qcloud.com。secret_key和callback_auth_token在线上必须走私有配置源;本地调试配置只用于本地联调。- 本地 Docker 联调时必须让 gateway 和 room-service 使用同一套腾讯云 IM 配置,否则 UserSig、建群和回调守卫会出现跨应用不一致。
Target Flow
sequenceDiagram
participant C as Client
participant G as gateway-service
participant U as user-service
participant R as room-service
participant T as Tencent Cloud IM
participant RTC as Tencent Cloud RTC
C->>G: Login / RefreshToken
G->>U: gRPC auth
U-->>G: access token with user_id
G-->>C: token envelope
C->>G: POST /api/v1/rooms/create(room_name, room_avatar?, room_description?)
G->>R: CreateRoom(actor_user_id, room profile)
R->>R: command log + snapshot + outbox
R-->>G: RoomSnapshot
G-->>C: room envelope
R-->>T: outbox worker create_group(room_id)
C->>G: GET /api/v1/im/usersig
G-->>C: SDKAppID + user_id + UserSig
C->>T: SDK login(user_id, user_sig)
C->>G: POST /api/v1/rooms/join
G->>R: JoinRoom(actor_user_id)
R->>R: update Room Cell presence
R-->>G: RoomSnapshot
G-->>C: initial room envelope + im_group_id + RTC token
R-->>T: outbox worker send_group_msg(room_user_joined)
C->>T: joinGroup(room_id)
T->>G: CallbackBeforeApplyJoinGroup
G->>R: VerifyRoomPresence(room_id, user_id)
R-->>G: present=true
G-->>T: allow
C->>RTC: enterRoom(strRoomId from JoinRoom rtc.token)
C->>T: send group message
T->>G: CallbackBeforeSendMsg
G->>R: CheckSpeakPermission(room_id, user_id)
R-->>G: allowed=true
G-->>T: allow
Gateway Implementation
1. UserSig
已存在入口:
GET /api/v1/im/usersig
Authorization: Bearer <access_token>
实现约束:
user_id必须来自 access token,不接受客户端传入。- 腾讯云 IM identifier 使用内部长
user_id的十进制字符串。 secret_key缺失时必须 fail-closed,返回统一 envelope 错误,不能返回假票据。request_id只做链路追踪,不作为 UserSig 幂等键。
2. Callback Endpoint
新增入口:
POST /api/v1/tencent-im/callback
该入口不使用客户端 JWT。它只接受腾讯云 IM 服务端回调,并按 CallbackCommand 分发。
必须支持的回调:
| CallbackCommand | Purpose | Internal RPC |
|---|---|---|
Group.CallbackBeforeApplyJoinGroup |
入群前确认用户业务上在房间内 | VerifyRoomPresence |
Group.CallbackBeforeSendMsg |
群内发言前确认未被禁言、未被踢、房间允许聊天 | CheckSpeakPermission |
回调处理规则:
- 必须校验回调来源。启用腾讯云 IM 回调鉴权后,使用
callback_auth_token或控制台约定的鉴权参数校验。 CallbackCommand不在白名单内时返回允许,但要记录日志,避免未知回调阻断腾讯云 IM。GroupId映射为room_id。Operator_Account或From_Account映射为内部长user_id,只能接受十进制字符串。- 解析失败时拒绝该次入群或发言,避免绕过业务守卫。
建议结构:
services/gateway-service/internal/transport/http/
tencent_im_callback.go
services/gateway-service/internal/tencentim/
callback.go
verifier.go
command.go
gateway 不保存房间状态,只做回调鉴权、字段解析、调用 room guard RPC 和返回腾讯云 IM 需要的响应。
Room-Service Implementation
1. CreateRoom
CreateRoom 不等待腾讯云 IM 建群。它只提交房间权威状态和 RoomCreated outbox;建群由 outbox worker 异步完成。
同一个 actor_user_id 只能作为 owner 创建一个房间;加入、主持或管理别人的房间不受该 owner 限制影响。
主链路顺序:
validate request
acquire Redis lease
check room meta
check owner has no room
initialize RoomState
write room meta + command log + snapshot + outbox
install Room Cell
return snapshot
如果后续 EnsureRoomGroup 失败:
CreateRoom已经成功,不回滚房间状态。- outbox worker 按指数退避重试,达到上限进入
failed。 - 客户端 IM SDK 加群可能短暂遇到 group-not-ready,需要按产品可接受窗口重试。
- 失败原因不应该泄露腾讯云密钥、UserSig 或完整公网响应。
2. JoinRoom
JoinRoom 只修改 room-service 业务 presence,不创建腾讯云 IM 连接。
幂等规则:
- 用户已在房间内时刷新
last_seen_at_ms。 - 不重复增加在线人数。
- 不重复广播进房系统消息,除非产品明确要求“重新进房提示”。
当前实现如果重复 JoinRoom 直接返回快照,需要补齐 last_seen_at_ms 刷新,否则断线重连无法把 presence 延长。
3. LeaveRoom
LeaveRoom 只移除业务 presence。
要求:
- 用户不在房间内时幂等返回,不报错。
- 用户离房后
VerifyRoomPresence必须返回present=false。 - 如果用户在麦上,离房必须同步释放麦位。
- 离房事件写 outbox,腾讯云 IM 系统消息由 worker 异步投递。
4. Guard RPC
VerifyRoomPresence 是入群前守卫。
返回原因:
| Reason | Meaning |
|---|---|
room_not_found |
房间不存在或不可恢复 |
not_in_room |
用户没有业务 presence |
user_banned |
用户被踢或封禁 |
CheckSpeakPermission 是发言前守卫。
返回原因:
| Reason | Meaning |
|---|---|
room_not_found |
房间不存在或不可恢复 |
not_in_room |
用户没有业务 presence |
user_banned |
用户被踢或封禁 |
user_muted |
用户被禁言 |
chat_disabled |
房间关闭公屏 |
守卫 RPC 不写状态,不发 outbox,只读取当前 Room Cell 或恢复后的 snapshot 状态。
Tencent IM Message Contract
room-service 发给腾讯云 IM 的房间系统消息使用 TIMCustomElem。
客户端只依赖以下稳定字段:
{
"event_id": "evt_xxx",
"room_id": "room_1001",
"event_type": "room_user_joined",
"actor_user_id": 10001,
"target_user_id": 10001,
"seat_no": 1,
"gift_value": 20,
"room_heat": 100,
"room_version": 7,
"attributes": {
"reason": "stale_timeout"
}
}
字段规则:
event_id是客户端去重键。room_version是房间状态排序参考,不替代腾讯云 IM 消息序列。event_type使用 snake_case,不能直接暴露 protobuf 事件名。- 系统事件字段按当前 OpenAPI/proto 契约输出;新增字段必须同步 gateway、room-service 和客户端测试。
首版系统事件:
| Event Type | Source |
|---|---|
room_user_joined |
RoomUserJoined |
room_user_left |
RoomUserLeft |
room_mic_changed |
RoomMicChanged |
room_user_muted |
RoomUserMuted |
room_user_kicked |
RoomUserKicked |
room_gift_sent |
RoomGiftSent |
Recovery Rules
节点重启或接管时:
- 新节点获得 Redis lease。
- 从 MySQL 读取最新 snapshot。
- 回放 snapshot version 之后的 command log。
- 安装 Room Cell。
- 客户端通过
JoinRoom幂等刷新 presence。 - 客户端重新获取 UserSig 或复用未过期 UserSig 登录腾讯云 IM。
- 客户端重新加入房间群,并用最新
RoomSnapshot覆盖本地状态。
恢复不做:
- 不恢复本地 socket。
- 不重新扣费。
- 不重复调用腾讯云 IM 发送历史系统消息。
- 不把 Redis 当恢复来源。
Implementation Order
按这个顺序实现,避免先做复杂能力导致闭环不可测:
- 锁定配置:确认 gateway 和 room-service 的
sdk_app_id、secret_key、新加坡 endpoint、管理员账号一致。 - 补 gateway 回调 handler:实现
/api/v1/tencent-im/callback,支持入群前和发言前回调。 - 补 room guard client:gateway 内部 client 增加
RoomGuardService调用。 - 补 JoinRoom 重连刷新:重复 JoinRoom 刷新
last_seen_at_ms,但不重复发进房系统消息。 - 补 LeaveRoom 麦位释放:离房时如果在麦上,释放麦位并产生一致的房间事件。
- 补 presence stale worker:按
last_seen_at_ms清理超时 presence,写RoomUserLeft。 - 补集成测试:覆盖 UserSig、CreateRoom outbox 建群、JoinRoom、回调守卫、LeaveRoom、重连恢复。
Acceptance Tests
必须有自动化测试覆盖:
| Test | Expected Result |
|---|---|
| UserSig 签发 | 已登录用户拿到 sdk_app_id/user_id/user_sig,匿名用户失败 |
| UserSig 配置缺失 | 返回统一错误,不返回假票据 |
| CreateRoom 后建群失败 | 房间状态已提交,outbox 重试;超限后进入 failed |
| CreateRoom 成功 | MySQL meta/snapshot/command/outbox 都存在 |
| JoinRoom 成功 | presence 增加,返回 RoomSnapshot |
| 重复 JoinRoom | 刷新 last_seen_at_ms,不重复创建 presence |
| 入群前回调允许 | 已进房且未被 ban 的用户返回 allow |
| 入群前回调拒绝 | 未进房或被踢用户返回 deny |
| 发言前回调允许 | 已进房、未禁言、房间聊天开启 |
| 发言前回调拒绝 | 被禁言、被踢、未进房或房间聊天关闭 |
| LeaveRoom 后发言 | CheckSpeakPermission 返回 not_in_room |
| room-service 接管恢复 | snapshot + command log 恢复后守卫 RPC 继续正确 |
| outbox 投递失败 | 房间状态已提交,outbox 进入 retryable 或最终 failed |
Operational Checks
上线前必须人工确认:
- 腾讯云 IM 控制台应用区域是新加坡,REST endpoint 使用
adminapisgp.im.qcloud.com。 - 腾讯云 IM 控制台已配置回调 URL,且 URL 指向 gateway 公网入口。
- 回调鉴权 token 和 gateway 配置一致。
- 已启用
Group.CallbackBeforeApplyJoinGroup和Group.CallbackBeforeSendMsg。 - gateway 和 room-service 的
SDKAppID、SecretKey一致。 - 线上配置没有把真实 secret 写入仓库。
- room outbox 积压、腾讯云 IM REST 错误率、UserSig 签发失败率都有监控。
Done Definition
基础闭环完成的标准:
User Login
-> CreateRoom
-> JoinRoom
-> Get UserSig
-> Tencent IM SDK Login
-> Join Tencent IM Group
-> Callback VerifyRoomPresence allow
-> Send Room Chat
-> Callback CheckSpeakPermission allow
-> Mic/Gift/Kick/Mute system event delivered
-> LeaveRoom
-> Callback guard denies further speak/join
-> Reconnect via JoinRoom + UserSig + IM group join
只要这条链路稳定,后续 RTC、礼物、活动、房间推荐都可以在不破坏边界的前提下继续迭代。