# 房间背景图 Flutter 对接 本文描述 Flutter App 对接房间背景图的 HTTP 接口和腾讯云 IM 房间消息。房间背景图是房间维度素材:上传文件只拿 URL,保存接口把 URL 归档到房间素材列表,设置接口才会改变当前房间背景并广播房间 IM 消息。 ## 基础地址 本地开发地址: ```text http://127.0.0.1:13000 ``` 线上环境使用当前 App 配置里的 gateway base URL。下面所有路径都拼在 gateway base URL 后。 ## 请求头 | Header | 必填 | 示例 | 说明 | | --- | --- | --- | --- | | `Authorization` | 是 | `Bearer ` | 登录 access token;服务端从 token 读取当前用户 ID。 | | `X-App-Code` | 否 | `lalu` | App 编码;登录态接口最终以 token 内的 `app_code` 为准。 | | `Content-Type` | POST JSON 必填 | `application/json` | 保存和设置背景使用 JSON。 | | `Content-Type` | 上传必填 | `multipart/form-data` | 上传文件使用 multipart。 | 统一响应 envelope: ```json { "code": "OK", "message": "ok", "request_id": "req_abc", "data": {} } ``` 客户端分支判断只看 `code`,`message` 只用于兜底展示,`request_id` 要写入日志。 ## 调用顺序 ```text 1. 房主选择本地图片 2. POST /api/v1/files/upload 上传图片,拿 data.url 3. POST /api/v1/rooms/backgrounds/save 保存到房间背景素材列表 4. GET /api/v1/rooms/{room_id}/backgrounds 刷新背景列表 5. POST /api/v1/rooms/backgrounds/set 设置当前背景 6. 当前房间内所有用户通过 IM 收到 room_background_changed 7. 房间页用 data.room.background_url 或 IM attributes.room_background_url 刷新背景 ``` ## 上传图片 背景图先走通用文件上传。该接口不改变房间状态,只返回对象存储 URL。 ```http POST /api/v1/files/upload Authorization: Bearer X-App-Code: lalu Content-Type: multipart/form-data file= ``` 请求字段: | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | `file` | 是 | multipart file | 单文件最大 20MB。背景建议传图片文件;服务端通用入口不绑定房间语义。 | 成功响应: ```json { "code": "OK", "message": "ok", "request_id": "req_upload_bg", "data": { "url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png", "object_key": "app/files/10001/1778000000000_xxx.png", "content_type": "image/png", "size_bytes": 345678 } } ``` 客户端只需要把 `data.url` 传给保存背景接口。 ## 保存背景图 保存背景图把 URL 写入当前房间的背景素材列表。保存成功不会改变当前房间背景,也不会发房间 IM 消息。 ```http POST /api/v1/rooms/backgrounds/save Authorization: Bearer X-App-Code: lalu Content-Type: application/json { "room_id": "lalu_abc123", "image_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png" } ``` 请求字段: | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | `room_id` | 是 | string | 房间 ID。 | | `image_url` | 是 | string | 上传接口返回的 `data.url`;服务端 trim 后最长 1024 字符。 | 成功响应: ```json { "code": "OK", "message": "ok", "request_id": "req_bg_save", "data": { "background": { "background_id": 101, "room_id": "lalu_abc123", "image_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png", "created_by_user_id": "10001", "created_at_ms": 1778000000123, "updated_at_ms": 1778000000123 }, "server_time_ms": 1778000000123 } } ``` 字段说明: | 字段 | 类型 | 说明 | | --- | --- | --- | | `background.background_id` | int64 | 设置当前背景时使用。Flutter 如果统一按字符串保存 ID,也可以转成字符串保存;提交时服务端支持数字或数字字符串。 | | `background.image_url` | string | 背景图展示 URL。 | | `background.created_by_user_id` | string | 保存素材的用户 ID。 | | `server_time_ms` | int64 | 服务端时间,Unix epoch milliseconds。 | ## 获取背景图列表 房主打开房间背景编辑面板时调用。 ```http GET /api/v1/rooms/{room_id}/backgrounds Authorization: Bearer X-App-Code: lalu ``` 路径参数: | 参数 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | `room_id` | 是 | string | 房间 ID。 | 成功响应: ```json { "code": "OK", "message": "ok", "request_id": "req_bg_list", "data": { "backgrounds": [ { "background_id": 101, "room_id": "lalu_abc123", "image_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png", "created_by_user_id": "10001", "created_at_ms": 1778000000123, "updated_at_ms": 1778000000123 } ], "selected_background_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png", "server_time_ms": 1778000000456 } } ``` 字段说明: | 字段 | 类型 | 说明 | | --- | --- | --- | | `backgrounds` | array | 房间保存过的背景图,按最近保存优先。 | | `selected_background_url` | string | 当前生效背景;没有设置过时为空或缺省。 | | `server_time_ms` | int64 | 服务端时间。 | ## 设置当前背景 设置当前背景会改变 Room Cell 快照,返回最新房间数据,并向房间 IM 群广播 `room_background_changed`。 ```http POST /api/v1/rooms/backgrounds/set Authorization: Bearer X-App-Code: lalu Content-Type: application/json { "room_id": "lalu_abc123", "command_id": "cmd_background_lalu_abc123_10001_1778000000000", "background_id": "101" } ``` 请求字段: | 字段 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | `room_id` | 是 | string | 房间 ID。 | | `command_id` | 是 | string | 房间命令幂等键;同一次点击超时重试必须复用。 | | `background_id` | 是 | int64/string | 背景素材 ID,必须来自当前房间的背景列表。 | 成功响应: ```json { "code": "OK", "message": "ok", "request_id": "req_bg_set", "data": { "result": { "applied": true, "room_version": 18, "server_time_ms": 1778000000789 }, "room": { "room_id": "lalu_abc123", "im_group_id": "lalu_abc123", "room_short_id": "100001", "title": "Live Room", "cover_url": "https://cdn.example.com/room.png", "background_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png", "description": "welcome", "owner_user_id": "10001", "mode": "voice", "status": "active", "chat_enabled": true, "locked": false, "heat": 1000, "online_count": 12, "seat_count": 10, "occupied_seat_count": 2, "visible_region_id": 1001, "version": 18 }, "background": { "background_id": 101, "room_id": "lalu_abc123", "image_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png", "created_by_user_id": "10001", "created_at_ms": 1778000000123, "updated_at_ms": 1778000000123 }, "server_time_ms": 1778000000789 } } ``` 字段说明: | 字段 | 类型 | 说明 | | --- | --- | --- | | `result.applied` | bool | `true` 表示本次命令产生状态变更;同一 `command_id` 重试可能为 `false`。 | | `result.room_version` | int64 | 命令完成后的房间版本。 | | `room.background_url` | string | 当前生效背景图。客户端设置成功后直接用它刷新 UI。 | | `background` | object | 被设置的背景素材。 | ## 房间详情返回 进房或详情页重建时不需要额外查背景列表。当前生效背景在房间详情里返回: ```http GET /api/v1/rooms/{room_id}/detail Authorization: Bearer X-App-Code: lalu ``` 关键字段: ```json { "data": { "room": { "room_id": "lalu_abc123", "background_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png", "version": 18 } } } ``` 客户端进入房间页时用 `data.room.background_url` 初始化背景。收到 IM 后如果 `room_version` 更大,再覆盖本地背景。 ## 腾讯 IM 消息 设置背景成功后,room-service 会向房间 IM 群发送 TIMCustomElem。Flutter 需要监听群自定义消息并解析 JSON。 消息负载是 `tencentim.RoomEvent` JSON: ```json { "event_id": "evt_xxx", "room_id": "lalu_abc123", "event_type": "room_background_changed", "actor_user_id": 10001, "room_version": 18, "attributes": { "background_id": "101", "room_background_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png" } } ``` 腾讯 IM 自定义消息字段: | 字段 | 值 | 说明 | | --- | --- | --- | | `TIMCustomElem.data` | 上面的 JSON 字符串 | 业务负载。 | | `TIMCustomElem.desc` | `room_background_changed` | 事件类型。 | | `TIMCustomElem.extension` | `room_system_message` | 房间系统消息标记。 | 客户端处理规则: | 条件 | 处理 | | --- | --- | | `event_type != room_background_changed` | 交给其他房间消息处理器。 | | `room_id` 不是当前房间 | 忽略。 | | `event_id` 已处理过 | 忽略,避免重放或 SDK 重复投递。 | | `room_version` 小于本地房间版本 | 忽略旧消息。 | | `room_version` 大于等于本地房间版本 | 设置房间背景为 `attributes.room_background_url`,并更新本地 `room_version`。 | Flutter 伪代码: ```dart void onRoomCustomMessage(String data) { final event = jsonDecode(data) as Map; if (event['event_type'] != 'room_background_changed') return; if (event['room_id'] != currentRoomId) return; final eventId = event['event_id'] as String? ?? ''; if (handledEventIds.contains(eventId)) return; final version = (event['room_version'] as num?)?.toInt() ?? 0; if (version < localRoomVersion) return; final attrs = (event['attributes'] as Map?)?.cast() ?? {}; final backgroundUrl = attrs['room_background_url'] as String? ?? ''; handledEventIds.add(eventId); localRoomVersion = version; setRoomBackground(backgroundUrl); } ``` ## 错误处理 | code | HTTP | 常见场景 | Flutter 处理 | | --- | --- | --- | --- | | `INVALID_ARGUMENT` | 400 | `room_id` 格式不对、`image_url` 为空、`background_id` 为空、上传文件过大或格式不合法。 | 停止当前动作,提示重新选择图片或刷新页面后重试。 | | `UNAUTHORIZED` | 401 | access token 缺失或过期。 | 回登录或刷新 token。 | | `PROFILE_REQUIRED` | 403 | 用户未完成资料。 | 引导补全资料。 | | `PERMISSION_DENIED` | 403 | 当前用户不是房主。 | 关闭编辑入口或提示无权限;不要重试。 | | `NOT_FOUND` | 404 | 房间不存在,或设置的 `background_id` 不属于该房间。 | 刷新我的房间和背景列表。 | | `CONFLICT` | 409 | 房间不是 active,或同一 `command_id` 携带了不同 payload。 | 刷新房间状态;如果是超时重试,确保复用同一个请求体。 | | `UPSTREAM_ERROR` | 502 | gateway、room-service、对象存储或 IM 投递依赖临时不可用。 | 保留用户选择,提示稍后重试。 | 失败响应示例: ```json { "code": "PERMISSION_DENIED", "message": "permission denied", "request_id": "req_bg_set_denied" } ``` ## UI 状态建议 | 场景 | 建议 | | --- | --- | | 房主打开背景设置面板 | 调 `GET /rooms/{room_id}/backgrounds`,用 `selected_background_url` 标记当前背景。 | | 用户选择新图片 | 先本地预览,再上传,上传成功后调用保存接口。 | | 保存成功 | 把返回的 `background` 插入列表顶部,但不切换房间背景。 | | 点击应用背景 | 生成新的 `command_id` 调设置接口;按钮进入 loading,防止重复点击。 | | 设置成功 | 用响应里的 `room.background_url` 立即刷新当前设备 UI。 | | 其他用户在线 | 通过 `room_background_changed` IM 消息刷新背景。 | | 断线重进 | 通过 `/rooms/{room_id}/detail` 的 `room.background_url` 恢复当前背景,不依赖历史 IM。 |