16 KiB
Room Entry And Reconnect Development Requirements
本文档定义下一阶段必须完成的语音房基础闭环:创建房间、进房、离房、断线重连。目标是让一个房间可以被稳定打开、进入、退出,并在客户端断线或服务短暂抖动后恢复到可继续使用的状态。
Goal
本阶段只解决房间生命周期的最小业务闭环。
| Capability | Required Result |
|---|---|
| 创建房间 | 用户可以创建一个 active 语音房,并获得房间快照 |
| 进房 | 用户可以进入房间业务 presence,并获得当前房间状态 |
| 订阅房间消息 | 用户进房成功后可以登录腾讯云 IM 并加入房间群 |
| 进入音频频道 | JoinRoom 成功响应内嵌腾讯 RTC token,客户端可以立即进入 TRTC audience |
| 离房 | 用户主动退出后,房间 presence 被清理,后续进群和发言必须被业务守卫拒绝 |
| 断线重连 | 用户短暂断线后,可以重新鉴权、恢复房间状态、重新登录腾讯云 IM 并进群 |
| 防越权 | 未进房、被踢、被封禁用户不能继续进群或发公屏 |
Non Goals
本阶段不做这些:
| Not Included | Reason |
|---|---|
| 麦位完整玩法 | 放到下一阶段,避免和进房链路混在一起 |
| RTC 录制/审核 | 首版只处理进房听音频所需 token |
| 金币和礼物 | 需要 wallet-service 先落地 |
| 推荐流和房间列表 | 先保证单房间可用 |
| 多端复杂同步 | 首版只保证同一用户重连恢复 |
| 热门房主备热同步 | 当前仍是单房间单活 + lease 接管 |
Ownership
| Domain | Owner | Rule |
|---|---|---|
| HTTP 房间命令 | gateway-service |
只做鉴权、入参校验、协议转换 |
| 房间业务 presence | room-service |
创建房间、进房、离房、封禁、恢复 |
| 实时连接和房间群 | 腾讯云 IM | 持有客户端长连接、群成员关系、房间消息历史 |
| 身份和 token | user-service |
登录、token、用户状态 |
| 真实语音链路 | 腾讯云 RTC | 客户端持 JoinRoom 内嵌 token 进入音频频道 |
关键边界:
room-service不保存 socket。- 腾讯云 IM 不拥有房间业务 presence;业务守卫仍以
room-service为准。 gateway-service不信任客户端自报owner_user_id、host_user_id或actor_user_id。- 所有业务身份以 access token 解析出的
user_id为准。 - 腾讯云 IM 登录使用
gateway-service签发的 UserSig,UserSig 里的 identifier 使用不可变长user_id。
Presence Model
需要明确区分三种状态:
| State | Owner | Meaning |
|---|---|---|
| 房间业务 presence | room-service |
用户业务上属于这个房间 |
| 房间群连接态 | 腾讯云 IM | 用户当前客户端是否登录 IM、是否在房间群内 |
| RTC 在线态 | 腾讯云 RTC | 用户是否在真实音频频道中 |
本阶段由 room-service 维护业务 presence,通过腾讯云 IM 群承载消息,并由 gateway 在 JoinRoom 成功后签发腾讯 RTC 进房票据。RTC 在线态仍以腾讯云 RTC 为准,不写入 room-service 业务 presence。
External API
Create Room
POST /api/v1/rooms/create
Authorization: Bearer <access_token>
建议请求:
{
"command_id": "cmd_create_room_550e8400",
"mode": "voice",
"room_name": "周末语音房",
"room_avatar": "",
"room_description": "本房间用于轻量聊天和上麦互动"
}
正式客户端不应该传 room_id、owner_user_id 和 host_user_id。command_id 由客户端按创建房间动作生成,同一动作重试必须复用。gateway 必须从 access token 取当前 user_id 写入 RequestMeta.actor_user_id,并生成内部 room_id;room-service 使用该 actor 确定 owner 和默认 host。seat_count 可省略,省略或传 0 时使用后台默认座位数 15;传正数时必须命中后台启用座位数。room_name 必填,room_avatar 为空时写入默认系统头像,房间展示资料统一落在 RoomExt。
响应必须包含:
{
"result": {
"applied": true,
"room_version": 1,
"server_time_ms": 1777000000000
},
"room": {}
}
Join Room
POST /api/v1/rooms/join
Authorization: Bearer <access_token>
请求:
{
"room_id": "room_1001",
"command_id": "cmd_join_room_1001_10002_1",
"role": "audience"
}
响应必须包含首屏房间数据、用户业务 presence、IM 房间群和 RTC token:
{
"result": {},
"room": {
"room_id": "room_1001",
"im_group_id": "room_1001",
"online_count": 26,
"seat_count": 15,
"version": 2
},
"viewer": {
"user_id": "10001",
"role": "audience"
},
"seats": [],
"contribution_rank": [],
"profiles": [],
"im": {
"group_id": "room_1001",
"need_join_group": true
},
"rtc": {
"need_token": true,
"available": true,
"token": {}
},
"server_time_ms": 1777000000000
}
JoinRoom 必须幂等。用户已经在房间时,不能重复增加在线人数或重复广播进房消息,但需要刷新 last_seen_at_ms。
Leave Room
POST /api/v1/rooms/leave
Authorization: Bearer <access_token>
请求:
{
"room_id": "room_1001",
"command_id": "cmd_leave_room_1001_10002_1"
}
LeaveRoom 必须幂等。用户已经不在房间时,返回当前快照,不报业务错误。
Tencent IM Login And Join Group
客户端可以在 App 启动后向 gateway 获取腾讯云 IM UserSig;但房间群必须等 JoinRoom 成功后再加入:
GET /api/v1/im/usersig
Authorization: Bearer <access_token>
响应中的 user_id 是腾讯云 IM identifier,必须等于内部不可变长 user_id 的十进制字符串。
客户端随后使用腾讯云 IM SDK 登录并加入房间群:
{
"sdk_app_id": 1400000000,
"user_id": "10001",
"user_sig": "usersig_xxx",
"room_id": "room_1001",
"im_group_id": "room_1001"
}
进群前业务守卫仍然需要存在。首版可以由客户端先 JoinRoom 再进群;线上必须补腾讯云 IM 回调或 gateway 代理校验:
room-service.VerifyRoomPresence(room_id, user_id, session_id)
只有 present=true 才允许进群或继续接收房间业务消息。被踢或封禁后,room-service 负责写 outbox 并通过腾讯云 IM 推系统事件,客户端必须按事件退房或触发重新同步。
Required Internal Changes
当前基础接口已经存在,但本阶段需要补齐这些行为。
gateway-service
| Requirement | Detail |
|---|---|
| 创建房间身份收敛 | CreateRoom 的 owner/host 必须由 room-service 从 RequestMeta.actor_user_id 收敛,不接受客户端 body 自报 |
| command_id | 写命令请求体使用客户端按用户动作生成的稳定 command_id,用于 room-service command log 幂等 |
| 统一错误映射 | 房间不存在、被踢、无权限、重复命令要映射成稳定 HTTP envelope |
| 请求日志 | 记录 request_id、user_id、room_id、command type |
room-service
| Requirement | Detail |
|---|---|
| CreateRoom | 创建 meta、command log、snapshot、outbox,并安装 Room Cell |
| 单 owner 单房间 | 同一个 RequestMeta.actor_user_id 只能作为 owner 创建一个房间;已有 owner 房间时返回 Conflict |
| JoinRoom 幂等 | 已在房间时刷新 last_seen_at_ms,不重复广播 |
| LeaveRoom 幂等 | 不在房间时返回当前快照 |
| VerifyRoomPresence | 腾讯云 IM 回调或 gateway 代理进群前检查房间存在、用户在房间、未被 ban |
| Presence 过期 | 支持断线宽限期,超过 TTL 后自动清理 |
| Recovery | 节点重启后能通过 snapshot + command log 恢复房间 |
建议新增配置:
room_presence_reconnect_grace: "60s"
room_presence_stale_timeout: "300s"
room_presence_sweep_interval: "30s"
Tencent Cloud IM Integration
| Requirement | Detail |
|---|---|
| UserSig | gateway-service 只给已鉴权用户签发腾讯云 IM UserSig |
| EnsureRoomGroup | room-service.CreateRoom 必须确保腾讯云 IM 群组存在 |
| PublishRoomEvent | 房间状态变更后同步推腾讯云 IM 群系统消息,失败不回滚房间状态 |
| Guard Callback | 线上需要腾讯云 IM 进群/发言回调或等价 gateway 代理调用 VerifyRoomPresence、CheckSpeakPermission |
| History | 房间最近消息优先使用腾讯云 IM 群历史;本仓库不再保存自研 IM 时间线 |
Reconnect Design
断线分三种。
| Case | Expected Behavior |
|---|---|
| IM 短断 | room-service presence 保留,客户端重新登录腾讯云 IM 后重新进群 |
| App 退后台后恢复 | 客户端先调用 RoomHeartbeat 刷新已有 presence;如果返回 NOT_FOUND 再重新 JoinRoom |
| 超过 stale timeout | room-service 自动清理 presence,客户端需要重新进房 |
客户端重连标准流程:
1. 如果 access token 过期,先 RefreshToken
2. 调 POST /api/v1/rooms/heartbeat,刷新房间业务 presence
3. 调 POST /api/v1/im/usersig,拿腾讯云 IM UserSig
4. 使用腾讯云 IM SDK 登录
5. 加入 JoinRoom 返回的 `im.group_id` 对应腾讯云 IM 群
6. 拉取腾讯云 IM 群历史或等待房间系统消息补齐
7. 如果需要音频且旧 RTC token 已过期,调用 `/api/v1/rtc/token` 补签
8. 用 RoomHeartbeat 返回的 RoomSnapshot 覆盖本地房间状态
如果第 2 步返回 `NOT_FOUND`,说明业务 presence 已不存在,客户端必须走完整 `JoinRoom -> IM/RTC 重进` 流程。
服务端原则:
- 断线不能立刻等同于离房。
- 主动
LeaveRoom才是明确离房。 - 超时清理是兜底,不能依赖客户端一定发离房。
- 被踢或封禁后,即使客户端仍在腾讯云 IM 群内,也必须通过回调守卫、系统事件和客户端同步阻断继续发言或展示在房间内。
Data Requirements
RoomUserState 至少需要支持:
| Field | Meaning |
|---|---|
user_id |
房间内业务用户 |
role |
owner, host, admin, audience |
joined_at_ms |
首次进入当前房间时间 |
last_seen_at_ms |
最近一次 JoinRoom 或 RoomHeartbeat 时间 |
建议后续追加:
| Field | Meaning |
|---|---|
connection_state |
online, reconnecting, stale |
reconnect_deadline_ms |
断线宽限截止时间 |
首版可以先不把 connection_state 暴露给客户端,但内部清理逻辑要能基于 last_seen_at_ms 判断 stale。
Event Requirements
房间状态成功变更后,room-service 必须写 outbox。
| Action | Outbox Event | IM System Event |
|---|---|---|
| 创建房间 | RoomCreated |
可不广播 |
| 进房成功 | RoomUserJoined |
room_user_joined |
| 离房成功 | RoomUserLeft |
room_user_left |
| stale 自动离房 | RoomUserLeft |
room_user_left,reason=stale_timeout |
| 被踢 | RoomUserKicked |
room_user_kicked |
同步 PublishRoomEvent 失败不能回滚房间状态,仍由 room outbox 补投到腾讯云 IM 和后续活动系统。
Acceptance Tests
必须覆盖这些测试。
| Test | Expected Result |
|---|---|
| 创建房间成功 | 返回 version=1,owner 在 online_users 内 |
| 创建房间请求带客户端 ID | gateway 忽略 room_id,使用客户端 command_id 并生成内部房间 ID |
| 普通用户 JoinRoom | presence 增加,广播进房事件 |
| 重复 JoinRoom | 不重复广播,不重复增加人数,刷新 last_seen |
| LeaveRoom | presence 移除,进群/发言守卫拒绝后续实时消息权限 |
| 重复 LeaveRoom | 幂等返回,不报错 |
| JoinRoom 后进群 | VerifyRoomPresence 通过,腾讯云 IM 群收到房间消息 |
| 未 JoinRoom 直接进群或发言 | VerifyRoomPresence 或 CheckSpeakPermission 返回 not_in_room |
| Kick 后进群或发言 | 返回 user_banned |
| IM 断开后 60 秒内重连 | RoomHeartbeat 刷新,腾讯云 IM 重新登录和进群成功,历史可拉 |
| 超过 stale timeout | presence 自动清理,需要重新 JoinRoom |
| room-service 重启恢复 | snapshot + command log 恢复后 JoinRoom/subscribe 继续可用 |
Implementation Order
建议按这个顺序做:
- 修正创建房间身份来源:gateway 不接收 body 的 owner/host,room-service 从
RequestMeta.actor_user_id收敛 owner/default host。 - 补齐 JoinRoom 幂等刷新
last_seen_at_ms。 - 增加显式 RoomHeartbeat,只刷新已有 presence,不隐式进房。
- 补齐 LeaveRoom 后对订阅守卫的拒绝行为测试。
- 接入腾讯云 IM UserSig 签发、CreateRoom 建群和房间系统消息发布。
- 增加 stale presence 清理 worker。
- 增加重连集成测试:JoinRoom -> UserSig -> IM login/join group -> disconnect -> heartbeat/reconnect -> history。
- 增加 room-service 重启恢复测试。