# Voice Room Client API Flow 本文定义用户进入语音房后的客户端接口调用顺序、触发条件和服务边界。本文只描述 App 侧要调用的 gateway HTTP 接口、腾讯云 IM/RTC SDK 动作,以及不能由客户端直接调用的内部守卫接口。 ## Scope | Area | Owner | Rule | | --- | --- | --- | | HTTP 入口 | `gateway-service` | 鉴权、profile gate、协议转换、腾讯 IM/RTC token 签发 | | 房间业务状态 | `room-service` | Room Cell、presence、麦位、房管、snapshot、outbox | | 房间长连接和公屏 | 腾讯云 IM SDK | 客户端登录、房间群、公屏、系统消息接收 | | 真实音频频道 | 腾讯云 RTC SDK | 进 RTC 房、切换 audience/anchor、发音频 | | 用户主数据 | `user-service` | 登录、资料完成状态、用户区域、push token | 关键边界: - 客户端进入语音房必须先 `JoinRoom` 写入 room-service 业务 presence,再进 IM 群和 RTC 房。 - `room-service` 不保存客户端 socket,也不保存腾讯云 RTC 连接态。 - 腾讯云 IM 群成员关系不是业务 presence;是否允许进群、发言、签 RTC token 仍以 room-service 守卫为准。 - gateway 只使用 access token 中的 `user_id` 作为 actor,客户端不能自报 `actor_user_id`、`owner_user_id`、`host_user_id`。 - 所有 `/api/v1` 业务响应使用 `{code,message,request_id,data}` envelope。 ## Minimal Enter Room Flow ```mermaid sequenceDiagram participant C as Client participant G as gateway-service participant R as room-service participant IM as Tencent IM SDK participant RTC as Tencent RTC SDK C->>G: POST /api/v1/rooms/join G->>R: JoinRoom(actor_user_id from token) R-->>G: room snapshot + user presence G-->>C: room snapshot C->>G: GET /api/v1/im/usersig G-->>C: IM user_sig C->>IM: login(user_id, user_sig) C->>IM: joinGroup(room_id) C->>G: POST /api/v1/rtc/token G->>R: VerifyRoomPresence(room_id, user_id) G-->>C: RTC user_sig, str_room_id, role=audience C->>RTC: enterRoom(strRoomId, audience) loop while room page is active C->>G: POST /api/v1/rooms/heartbeat end ``` ### Required Order | Step | Interface | Required | Reason | | --- | --- | --- | --- | | 1 | `POST /api/v1/rooms/join` | yes | 建立业务 presence,后续 IM/RTC 守卫依赖它 | | 2 | `GET /api/v1/im/usersig` | yes | 登录腾讯云 IM,接收房间群消息和系统事件 | | 3 | Tencent IM SDK `login` + `joinGroup` | yes | 本仓库不承载房间长连接 | | 4 | `POST /api/v1/rtc/token` | yes for voice room audio | 签发 TRTC 票据;当前返回 `role=audience` | | 5 | Tencent RTC SDK `enterRoom` | yes for voice room audio | 进入真实音频频道旁听 | | 6 | `POST /api/v1/rooms/heartbeat` | yes | 刷新业务 presence,避免 stale timeout | 如果产品允许“只看公屏不听语音”,第 4、5 步可以延迟到用户打开音频时。但语音房默认应进入 RTC audience,以保证打开房间即可听到声音。 ## Core Interfaces ### Join Room ```http POST /api/v1/rooms/join Authorization: Bearer Content-Type: application/json ``` Request: ```json { "room_id": "lalu_xxx", "command_id": "cmd_join___", "role": "audience" } ``` Response `data`: ```json { "result": { "applied": true, "room_version": 2, "server_time_ms": 1778000000000 }, "user": { "user_id": 10001, "role": "audience", "joined_at_ms": 1778000000000, "last_seen_at_ms": 1778000000000 }, "room": {} } ``` Rules: - `room_id` 必须来自房间列表、分享或恢复接口,不能由客户端拼业务含义。 - `command_id` 用于房间命令幂等;客户端重试同一次进房必须复用同一个值。 - `role` 只作为展示默认值,不能通过自报 `admin/host/owner` 获得权限。 - JoinRoom 成功后才能登录 IM 进群和获取 RTC token。 ### IM UserSig ```http GET /api/v1/im/usersig Authorization: Bearer ``` Response `data`: ```json { "sdk_app_id": 20036101, "user_id": "10001", "user_sig": "xxx", "expire_at_ms": 1778086400000 } ``` Rules: - 当前 gateway 路由是 `GET /api/v1/im/usersig`。 - `user_id` 是内部不可变长 `user_id` 的十进制字符串,必须直接作为腾讯云 IM identifier。 - UserSig 只用于腾讯云 IM SDK 登录,不用于本仓库鉴权。 - 未完成资料用户会被 profile gate 拒绝。 Client SDK action: ```text Tencent IM SDK login(user_id, user_sig) Tencent IM SDK joinGroup(room_id) ``` ### RTC Token ```http POST /api/v1/rtc/token Authorization: Bearer Content-Type: application/json ``` Request: ```json { "room_id": "lalu_xxx" } ``` Response `data`: ```json { "sdk_app_id": 20036101, "user_id": "10001", "rtc_user_id": "10001", "user_sig": "xxx", "expire_at_ms": 1778007200000, "room_id": "lalu_xxx", "rtc_room_id": "lalu_xxx", "rtc_room_id_type": "string", "str_room_id": "lalu_xxx", "app_scene": "voice_chat_room", "role": "audience" } ``` Rules: - gateway 会先调用 room-service `VerifyRoomPresence`,用户不在房间、被踢、被 ban 时不会签发 RTC token。 - 当前只支持 TRTC `strRoomId`,客户端不能把 `room_id` 转为数字 `roomId`。 - 当前 token 返回 `role=audience`;上麦成功后由客户端切 `anchor` 并确认发流。 - token 过期时重新调用该接口,不使用 refresh token。 Client SDK action: ```text Tencent RTC SDK enterRoom(strRoomId=resp.str_room_id, userId=resp.rtc_user_id, role=audience) ``` ### Heartbeat ```http POST /api/v1/rooms/heartbeat Authorization: Bearer Content-Type: application/json ``` Request: ```json { "room_id": "lalu_xxx", "command_id": "cmd_heartbeat___" } ``` Rules: - 只刷新已经存在的业务 presence,不能替代 JoinRoom。 - 用户已经离房、被踢或房间关闭时,客户端应停止心跳并清理本地房间态。 - 心跳间隔由客户端配置控制,但必须小于 room-service 的 presence stale 窗口。 ### Leave Room ```http POST /api/v1/rooms/leave Authorization: Bearer Content-Type: application/json ``` Request: ```json { "room_id": "lalu_xxx", "command_id": "cmd_leave___" } ``` Rules: - 主动关闭房间页、切换账号、被客户端确认退出时调用。 - 如果用户在麦上,LeaveRoom 会释放麦位。 - LeaveRoom 后客户端必须停止 heartbeat、退出 RTC 房、退出或忽略 IM 房间群消息。 ## Mic Flow ### Mic Up ```http POST /api/v1/rooms/mic/up Authorization: Bearer Content-Type: application/json ``` Request: ```json { "room_id": "lalu_xxx", "command_id": "cmd_mic_up___", "seat_no": 1 } ``` Response `data`: ```json { "result": {}, "seat_no": 1, "mic_session_id": "mic_xxx", "publish_deadline_ms": 1778000015000, "room": {} } ``` Client actions after success: 1. 保存 `mic_session_id`、`room.version` 和 `publish_deadline_ms`。 2. 确保已有 RTC token;没有或过期则调用 `POST /api/v1/rtc/token`。 3. RTC SDK 切换 `anchor`。 4. RTC SDK 调 `startLocalAudio`。 5. SDK 本地发流成功后调用 `POST /api/v1/rooms/mic/publishing/confirm`。 Rules: - `MicUp` 成功只代表业务麦位占用成功,不代表 RTC 已经发布音频成功。 - 服务端状态先进入 `pending_publish`。 - 超过 `publish_deadline_ms` 未确认,room-service 会自动 MicDown,reason 为 `publish_timeout`。 ### Confirm Mic Publishing ```http POST /api/v1/rooms/mic/publishing/confirm Authorization: Bearer Content-Type: application/json ``` Request: ```json { "room_id": "lalu_xxx", "command_id": "cmd_confirm_publish___", "target_user_id": 0, "mic_session_id": "mic_xxx", "room_version": 12, "event_time_ms": 1778000001000, "source": "client" } ``` Rules: - `target_user_id=0` 表示确认当前 actor;RTC webhook 路径才需要显式 target。 - `mic_session_id` 必须等于当前麦位会话。 - `room_version` 和 `event_time_ms` 用于丢弃旧事件,避免旧 audience/leave 事件清理新会话。 - 成功后麦位 `publish_state=publishing`。 ### Mic Down ```http POST /api/v1/rooms/mic/down Authorization: Bearer Content-Type: application/json ``` Request: ```json { "room_id": "lalu_xxx", "command_id": "cmd_mic_down___", "target_user_id": 0, "reason": "user_action" } ``` Rules: - 普通用户下自己麦时 `target_user_id` 可传 0 或自己 user_id。 - 房管下别人麦时传目标用户 `target_user_id`。 - 客户端收到成功响应或房间系统消息后必须停止本地音频并切回 `audience`。 ### Change Mic Seat ```http POST /api/v1/rooms/mic/change Authorization: Bearer Content-Type: application/json ``` Request: ```json { "room_id": "lalu_xxx", "command_id": "cmd_change_mic___", "target_user_id": 10002, "seat_no": 2 } ``` Rules: - 换麦位只更新业务麦位和 UI,不需要重新进入 RTC。 - 如果 target 当前在发流,客户端继续保持 RTC 上行。 ## Room Management APIs 这些接口只允许有权限的 owner/host/admin 调用;gateway 不判断权限,最终由 room-service Room Cell 判断。 | Interface | Body | Result | | --- | --- | --- | | `POST /api/v1/rooms/mic/lock` | `room_id, command_id, seat_no, locked` | 锁/解锁麦位 | | `POST /api/v1/rooms/chat/enabled` | `room_id, command_id, enabled` | 开/关公屏,影响发言守卫 | | `POST /api/v1/rooms/admin/set` | `room_id, command_id, target_user_id, is_admin` | 设置或取消房管 | | `POST /api/v1/rooms/host/transfer` | `room_id, command_id, target_user_id` | 转移主持人 | | `POST /api/v1/rooms/user/mute` | `room_id, command_id, target_user_id, muted, reason` | 禁言或解禁 | | `POST /api/v1/rooms/user/kick` | `room_id, command_id, target_user_id, reason` | 踢人并 ban,释放 presence 和麦位 | | `POST /api/v1/rooms/user/unban` | `room_id, command_id, target_user_id, reason` | 解除房间 ban | Management event handling: - 管理动作成功后,room-service 写 command log、snapshot 和 outbox。 - 客户端应通过腾讯云 IM 房间系统消息更新 UI。 - 如果本地 UI 状态和系统消息冲突,以最新 room snapshot 和 `room_version` 为准。 ## Gift And Interaction ```http POST /api/v1/rooms/gift/send Authorization: Bearer Content-Type: application/json ``` Request: ```json { "room_id": "lalu_xxx", "command_id": "cmd_send_gift___", "target_user_id": 10002, "gift_id": "rose", "gift_count": 1 } ``` Rules: - `SendGift` 必须先由 wallet-service 扣费成功,再落房间表现。 - 房间表现、热度、本地榜通过 room snapshot 和房间系统消息同步。 - 公屏文本消息不走 gateway HTTP;客户端使用腾讯云 IM 群消息。 ## Restore And Reconnect ### Current Room ```http GET /api/v1/rooms/current Authorization: Bearer ``` Response `data`: ```json { "has_current_room": true, "room_id": "lalu_xxx", "room_version": 12, "role": "audience", "mic_session_id": "", "publish_state": "", "need_join_im_group": true, "need_rtc_token": false, "server_time_ms": 1778000000000 } ``` Client restore rules: | Response | Client Action | | --- | --- | | `has_current_room=false` | 清理本地房间态,不展示恢复入口 | | `need_join_im_group=true` | 调 `GET /api/v1/im/usersig`,重新登录 IM 并 join group | | `need_rtc_token=true` | 调 `POST /api/v1/rtc/token`,重新进入 RTC | | `publish_state=pending_publish` | 重新执行 SDK 发流并确认当前 `mic_session_id` | | `publish_state=publishing` | 确认本地 RTC 上行是否仍存在;不确定时重新发流并确认 | `rooms/current` 是只读恢复探测,不会隐式 JoinRoom,不刷新 heartbeat,也不改变 Room Cell。 ### Reconnect Order 1. access token 有效时先调 `GET /api/v1/rooms/current`。 2. 如果 `has_current_room=false`,回房间列表或主界面。 3. 如果 `has_current_room=true`,先恢复 IM,再恢复 RTC。 4. 如果用户原本在麦上,必须使用返回的 `mic_session_id` 继续确认或等待服务端超时下麦。 5. 恢复成功后恢复 `POST /api/v1/rooms/heartbeat` 定时器。 ## Server Callback Boundaries 客户端不要直接调用这些接口或内部 RPC: | Interface | Caller | Purpose | | --- | --- | --- | | `POST /api/v1/tencent-im/callback` | Tencent IM | 入群、发言等服务端回调入口 | | `POST /api/v1/tencent-rtc/callback` | Tencent RTC | RTC 音频事件回调入口 | | `room-service.VerifyRoomPresence` | gateway / callback | 判断用户是否仍在房间、是否被 ban | | `room-service.CheckSpeakPermission` | gateway / callback | 判断用户是否允许发公屏 | 客户端只消费这些回调产生的房间系统消息和最新 snapshot,不直接操作内部守卫。 ## Error Handling | Error | Meaning | Client Action | | --- | --- | --- | | `UNAUTHORIZED` / `AUTH_REQUIRED` | access token 无效 | refresh 成功后重试;refresh 失败回登录 | | `PROFILE_REQUIRED` | 资料未完成 | 跳资料补全页,不能进入房间 | | `NOT_FOUND` | 房间或用户事实不存在 | 清本地目标房间,回列表 | | `ROOM_CLOSED` | 房间已关闭 | 清房间态,回列表 | | `PERMISSION_DENIED` | 被踢、被 ban、无权限 | 停止对应操作,按系统消息更新 UI | | `CONFLICT` | 麦位占用、命令冲突、状态冲突 | 拉最新 snapshot 或等待房间系统消息修正 | | `UPSTREAM_ERROR` | 内部服务或腾讯云依赖异常 | 保留本地登录态,展示重试 | 不要因为普通网络失败或 5xx 清空登录态。只有 refresh 明确失败、session revoked 或用户主动 logout 才清 token。 ## Room Snapshot Sync 当前客户端拿完整房间状态的来源: - `JoinRoom` 响应里的 room snapshot。 - 房间命令响应里的 room snapshot。 - 腾讯云 IM 房间系统消息。 - `rooms/current` 的恢复摘要。 - `GET /api/v1/rooms/snapshot?room_id=` 主动同步接口。 ```http GET /api/v1/rooms/snapshot?room_id= ``` 该接口只读 Room Cell/snapshot,不刷新 presence,不隐式 JoinRoom,不替代 heartbeat。gateway 使用 access token user_id 作为 viewer,room-service 要求 viewer 仍在该房间;未进房、被踢或被 ban 的用户不能读取完整在线用户和麦位快照。