# 房间锁房 Flutter 对接 ## 地址 本地: ```text http://127.0.0.1:13000 ``` 接口: | 方法 | 地址 | 说明 | | --- | --- | --- | | `POST` | `/api/v1/rooms/password` | 房主设置或清空房间密码 | | `POST` | `/api/v1/rooms/join` | 进入房间;锁房时必须传密码 | | `GET` | `/api/v1/rooms` | 房间列表和搜索,返回 `locked` | | `GET` | `/api/v1/rooms/feeds` | Mine 页房间流,返回 `locked` | 请求头: | Header | 必填 | 说明 | | --- | --- | --- | | `Authorization` | 是 | `Bearer ` | | `X-App-Code` | 否 | App 编码,例如 `lalu` | ## 设置房间密码 `POST /api/v1/rooms/password` 参数: | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | `room_id` | 是 | string | 房间 ID | | `command_id` | 是 | string | 本次设置动作的幂等键,同一次重试复用 | | `locked` | 是 | bool | `true` 设置密码,`false` 清空密码 | | `password` | `locked=true` 必填 | string | 房间密码,服务端 trim 后最长 64 字符;`locked=false` 时忽略 | 请求: ```json { "room_id": "room_10001", "command_id": "cmd_room_password_room_10001_1710000000", "locked": true, "password": "1234" } ``` 返回: ```json { "code": "OK", "message": "ok", "request_id": "req_abc", "data": { "result": { "applied": true, "room_version": 12, "server_time_ms": 1710000000456 }, "room": { "room_id": "room_10001", "im_group_id": "room_10001", "room_short_id": "100001", "title": "Live Room", "cover_url": "https://cdn.example/room.png", "owner_user_id": "10001", "mode": "voice", "status": "active", "chat_enabled": true, "locked": true, "heat": 1000, "online_count": 12, "seat_count": 15, "occupied_seat_count": 2, "version": 12 }, "seats": [], "server_time_ms": 1710000000456 } } ``` 返回字段: | 字段 | 说明 | | --- | --- | | `data.result.applied` | 本次命令是否改变房间状态 | | `data.result.room_version` | 房间最新版本 | | `data.room.locked` | 最新锁房状态 | | `data.room.version` | 房间快照版本 | | `data.seats` | 当前麦位列表 | ## 清空房间密码 `POST /api/v1/rooms/password` 参数: | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | `room_id` | 是 | string | 房间 ID | | `command_id` | 是 | string | 本次清空动作的幂等键 | | `locked` | 是 | bool | 固定传 `false` | 请求: ```json { "room_id": "room_10001", "command_id": "cmd_room_unlock_room_10001_1710000100", "locked": false } ``` 返回同设置密码接口,`data.room.locked=false`。 ## 进入锁房房间 `POST /api/v1/rooms/join` 参数: | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | `room_id` | 是 | string | 房间 ID | | `command_id` | 是 | string | 本次进房动作的幂等键,同一次重试复用 | | `role` | 否 | string | 当前传 `audience` | | `password` | 锁房时必填 | string | 本次入房密码 | 请求: ```json { "room_id": "room_10001", "command_id": "cmd_join_room_10001_20001_1710000000", "role": "audience", "password": "1234" } ``` 返回: ```json { "code": "OK", "message": "ok", "request_id": "req_abc", "data": { "result": { "applied": true, "room_version": 13, "server_time_ms": 1710000001456 }, "room": { "room_id": "room_10001", "im_group_id": "room_10001", "locked": true, "version": 13 }, "viewer": { "user_id": "20001", "role": "audience", "joined_at_ms": 1710000001456, "last_seen_at_ms": 1710000001456 }, "im": { "group_id": "room_10001", "need_join_group": true }, "rtc": { "need_token": true, "available": true }, "server_time_ms": 1710000001456 } } ``` 返回字段: | 字段 | 说明 | | --- | --- | | `data.room.locked` | 当前房间是否锁房 | | `data.viewer.user_id` | 当前用户 ID | | `data.im.group_id` | JoinRoom 成功后需要加入的腾讯 IM 房间群 | | `data.im.need_join_group` | `true` 时 Flutter 调腾讯 IM `joinGroup` | | `data.rtc.available` | 是否拿到 RTC token | 进房规则: | 场景 | 结果 | | --- | --- | | 未锁房 | `password` 可不传 | | 已锁房且密码正确 | JoinRoom 成功,再加入 IM 房间群 | | 已锁房且密码错误或缺失 | 返回 `403 PERMISSION_DENIED`,不要加入 IM 房间群 | | 房主进自己房间 | 不需要密码 | | 已在房间内重复 JoinRoom | 非房主仍要按当前最新密码校验;密码错误返回 `403 PERMISSION_DENIED` | ## 房间列表 locked 字段 `GET /api/v1/rooms?tab=hot&query=100001` `GET /api/v1/rooms/feeds?tab=followed` 返回: ```json { "code": "OK", "message": "ok", "request_id": "req_abc", "data": { "rooms": [ { "room_id": "room_10001", "im_group_id": "room_10001", "title": "Live Room", "locked": true, "online_count": 12, "visible_region_id": 1, "room_short_id": "100001" } ], "next_cursor": "" } } ``` 字段: | 字段 | 说明 | | --- | --- | | `rooms[].locked` | 是否锁房;`true` 时进房前弹密码输入 | | `rooms[].im_group_id` | 房间 IM 群 ID,只能在 JoinRoom 成功后使用 | | `rooms[].visible_region_id` | 房间区域 | ## 相关 IM ### JoinRoom 成功后加入房间群 接口 `POST /api/v1/rooms/join` 成功后,Flutter 使用: ```text data.im.group_id ``` 调用腾讯 IM SDK 加入房间群。 规则: | 场景 | Flutter 行为 | | --- | --- | | JoinRoom 成功 | 调腾讯 IM `joinGroup(data.im.group_id)` | | JoinRoom 返回 `PERMISSION_DENIED` | 不调用 `joinGroup` | | JoinRoom 返回 `ROOM_CLOSED` 或 `NOT_FOUND` | 不调用 `joinGroup`,刷新列表 | ### 房间密码变更区域通知 房主设置或清空密码后,服务端会发区域播报 IM,不再发房间群 IM。 Flutter 需要监听用户所在区域播报群的自定义消息: ```json { "event_id": "room_password_changed:evt_room_xxx", "broadcast_type": "room_password_changed", "scope": "region", "app_code": "lalu", "region_id": 1, "room_id": "room_10001", "actor_user_id": 10001, "locked": true, "sent_at_ms": 1710000000456, "room_version": 12, "source_event_id": "evt_room_xxx", "action": { "type": "refresh_room", "room_id": "room_10001" } } ``` 字段: | 字段 | 说明 | | --- | --- | | `broadcast_type` | 固定 `room_password_changed` | | `scope` | 固定 `region` | | `region_id` | 区域 ID | | `room_id` | 发生变化的房间 | | `locked` | 最新锁房状态 | | `room_version` | 房间版本;本地只用更大的版本覆盖旧状态 | | `action.type` | 固定 `refresh_room` | 处理: | 当前页面 | 行为 | | --- | --- | | 房间列表页 | 更新对应房间卡片 `locked` | | 房间详情页 | 如果 `room_id` 相同,刷新房间快照或更新本地 `locked` | | 密码弹窗打开中 | 收到同房间更新后,按最新 `locked` 状态决定是否关闭弹窗 | ### 房间群系统消息 `room_password_changed` 不走房间群 IM。房间内用户刷新锁房状态依赖区域播报或接口返回的 `room.locked`。 ## 错误返回 失败统一返回: ```json { "code": "PERMISSION_DENIED", "message": "permission denied", "request_id": "req_abc" } ``` 常见错误: | HTTP | code | 说明 | | --- | --- | --- | | `400` | `INVALID_ARGUMENT` | 参数缺失、设置密码时密码为空或超过 64 字符 | | `401` | `UNAUTHORIZED` | token 无效或过期 | | `403` | `PERMISSION_DENIED` | 非房主设置密码、进房密码错误、用户被 ban | | `404` | `NOT_FOUND` | 房间不存在 | | `409` | `ROOM_CLOSED` | 房间已关闭 | | `409` | `CONFLICT` | 同一个 `command_id` 被不同请求体复用 |