# 进房接口 Flutter 对接 ## 接口地址 ```http POST /api/v1/rooms/join Authorization: Bearer Content-Type: application/json ``` 新版本进房默认使用 `response_mode=lite`。`full` 仍保留给旧客户端兼容,不建议新 Flutter 首屏使用。 ## 参数 Body: | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `room_id` | string | 是 | 房间 ID。 | | `command_id` | string | 是 | 本次进房命令 ID。同一次点击超时重试复用同一个值,下一次重新进房换新值。 | | `role` | string | 否 | 不传默认 `audience`。房主身份由服务端按房间 owner 判断,客户端不要靠传 `owner` 获取权限。 | | `password` | string | 否 | 密码房传房间密码,非密码房不传或传空。 | | `response_mode` | string | 是 | 新版固定传 `lite`。可选值:`lite` / `full`。 | 请求示例: ```json { "room_id": "lalu_abc123", "command_id": "cmd_join_room_lalu_abc123_10001_1778000001000", "role": "audience", "password": "", "response_mode": "lite" } ``` 也支持 query 参数: ```http POST /api/v1/rooms/join?response_mode=lite ``` Flutter 建议放在 body 里,避免调用层漏拼 query。 ## 返回值 统一外壳: ```json { "code": "OK", "message": "ok", "request_id": "req_xxx", "data": {} } ``` 成功返回示例: ```json { "code": "OK", "message": "ok", "request_id": "req_xxx", "data": { "result": { "applied": true, "room_version": 8, "server_time_ms": 1778000001123 }, "room": { "room_id": "lalu_abc123", "im_group_id": "lalu_abc123", "room_short_id": "10001", "title": "Live Room", "cover_url": "https://cdn.example/room.png", "background_url": "https://cdn.example/bg.png", "description": "welcome", "owner_user_id": "10001", "mode": "voice", "status": "active", "chat_enabled": true, "locked": false, "heat": 88, "online_count": 200, "seat_count": 10, "occupied_seat_count": 2, "visible_region_id": 1001, "version": 8 }, "viewer": { "user_id": "10001", "role": "audience", "joined_at_ms": 1778000001000, "last_seen_at_ms": 1778000001100 }, "seats": [ { "seat_no": 1, "user_id": "10002", "locked": false, "publish_state": "publishing", "mic_session_id": "mic_10002", "publish_deadline_ms": 1778000030000, "mic_muted": false, "seat_status": "publishing", "mic_heartbeat_at_ms": 1778000000000, "gift_value": 0 }, { "seat_no": 2, "locked": false, "mic_muted": false, "gift_value": 0 } ], "profiles": [ { "user_id": "10001", "username": "host", "avatar": "https://cdn.example/avatar.png", "display_user_id": "10001" }, { "user_id": "10002", "username": "speaker", "avatar": "https://cdn.example/avatar2.png", "display_user_id": "10002" } ], "im": { "group_id": "lalu_abc123", "need_join_group": true }, "rtc": { "need_token": true, "available": true, "token": { "sdk_app_id": 1400000000, "user_id": "10001", "rtc_user_id": "10001", "user_sig": "xxx", "expire_at_ms": 1778007200000, "room_id": "lalu_abc123", "rtc_room_id": "lalu_abc123", "rtc_room_id_type": "string", "str_room_id": "lalu_abc123", "app_scene": "voice_chat_room", "role": "user" } }, "server_time_ms": 1778000001123 } } ``` 字段说明: | 字段 | 说明 | | --- | --- | | `data.result.applied` | 本次进房是否真正写入。重复 `command_id` 重试可能为 `false`。 | | `data.result.room_version` | 房间版本,用于客户端状态更新判断。 | | `data.room.room_id` | 房间 ID。 | | `data.room.im_group_id` | 腾讯 IM 房间群 ID。当前通常等于 `room_id`,客户端仍以返回值为准。 | | `data.room.online_count` | 房间在线人数。`lite` 不返回在线用户明细,只返回人数。 | | `data.room.seat_count` | 麦位总数。 | | `data.room.occupied_seat_count` | 已占用麦位数。 | | `data.viewer` | 当前登录用户在房间内的 presence。 | | `data.viewer.role` | 当前用户房间角色,房主会由服务端返回 `owner`。 | | `data.seats` | 首屏麦位状态。 | | `data.seats[].user_id` | 麦上用户 ID;空麦不返回该字段。 | | `data.seats[].publish_state` | 麦上发流状态,常见值:`pending_publish` / `publishing`。 | | `data.seats[].mic_session_id` | 麦位发流会话 ID。麦上心跳、发流确认需要使用它。 | | `data.seats[].seat_status` | 麦位展示状态,常见值:`empty` / `locked` / `occupied` / `publishing` / `muted`。 | | `data.profiles` | 首屏基础资料,只包含房主、当前用户、麦上用户。 | | `data.profiles[].username` | 昵称。 | | `data.profiles[].avatar` | 头像。 | | `data.profiles[].display_user_id` | 展示 ID。 | | `data.im.group_id` | Flutter 调腾讯 IM `joinGroup` 使用的群 ID。 | | `data.im.need_join_group` | 为 `true` 时需要加入腾讯 IM 群。 | | `data.rtc.available` | 是否拿到 RTC 票据。 | | `data.rtc.token` | RTC 进房票据。`available=false` 时可能为空。 | | `data.rtc.reason` | RTC 不可用原因。业务进房成功时,RTC 不可用不代表进房失败。 | | `data.server_time_ms` | 服务端时间,UTC epoch milliseconds。 | `lite` 不返回这些内容: | 字段 | 说明 | | --- | --- | | `contribution_rank` | 贡献榜不在进房首屏主包返回。 | | `profiles[].avatar_frame` | 头像框后置拉。 | | `profiles[].profile_card` | 资料卡后置拉。 | | `profiles[].vehicle` | 座驾后置拉。 | | `profiles[].mic_seat_animation` | 麦位动画后置拉。 | | `profiles[].badges` | 等级/成就徽章后置拉。 | | `profiles[].vip` | VIP 展示后置拉。 | ## 相关 IM 进房成功后 Flutter 执行: ```text 1. POST /api/v1/rooms/join,body 传 response_mode=lite 2. 如果 data.im.need_join_group=true,调用腾讯 IM joinGroup(data.im.group_id) 3. joinGroup 成功或已在群内,都按进房 IM 成功处理 4. 页面开始房间 heartbeat 5. 如果 data.rtc.available=true,用 data.rtc.token 进入 RTC ``` 说明: | 项 | 说明 | | --- | --- | | IM 登录 | 进房接口不返回 IM UserSig。Flutter 仍然通过 `GET /api/v1/im/usersig` 获取并登录腾讯 IM。 | | IM 入群 | 必须先 `JoinRoom` 成功,再调腾讯 IM `joinGroup`。不要先入 IM 群。 | | 入房广播 | 服务端会发 `RoomUserJoined` 房间事件。Flutter 按现有房间 IM 事件逻辑展示入房飘窗。 | | 重复进房 | 用户重新打开房间页或重连时,服务端仍会给展示用入房事件,Flutter 正常展示即可。 | | 入场座驾 | `lite` 进房不携带座驾快照。座驾、头像框、资料卡、麦位动画等展示资料在进房 UI 出来后异步补齐。 | ## 后置资料接口 进房首屏出来后,再按需要异步拉展示资料: ```http GET /api/v1/users/room-display-profiles:batch?user_ids=10001,10002 Authorization: Bearer ``` 参数: | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `user_ids` | string | 是 | 逗号分隔的用户 ID。也支持多个 `user_id=10001&user_id=10002`。 | 返回值: ```json { "code": "OK", "message": "ok", "request_id": "req_xxx", "data": { "profiles": [ { "user_id": "10001", "username": "host", "avatar": "https://cdn.example/avatar.png", "display_user_id": "10001", "avatar_frame": {}, "profile_card": {}, "vehicle": {}, "mic_seat_animation": {}, "badges": [], "vip": {}, "level": {}, "level_badges": {}, "charm": 0 } ] } } ``` 当前用户自己的装扮也可以后置拉: ```http GET /api/v1/appearance GET /api/v1/users/me/appearance ``` ## 常见错误 | code | HTTP | 说明 | | --- | --- | --- | | `INVALID_JSON` | 400 | 请求体不是合法 JSON。 | | `INVALID_ARGUMENT` | 400 | 参数错误,例如 `response_mode` 不是 `lite` 或 `full`。 | | `UNAUTHORIZED` | 401 | 未登录或 token 无效。 | | `PROFILE_REQUIRED` | 403 | 用户资料未补全。 | | `PERMISSION_DENIED` | 403 | 密码错误、被踢/被禁等权限问题。 | | `ROOM_CLOSED` | 409 | 房间已关闭;非房主不能进入。 | | `NOT_FOUND` | 404 | 房间不存在。 | | `UPSTREAM_ERROR` | 502 | 上游服务异常。 |