612 lines
21 KiB
Markdown
612 lines
21 KiB
Markdown
# 语音房宝箱 Flutter 对接
|
||
|
||
本文描述 Flutter App 对接语音房宝箱所需的 HTTP 接口、请求参数、返回值、错误处理和腾讯云 IM 自定义消息 key。宝箱进度、开箱和发奖事实由 `room-service` Room Cell 持有;客户端只展示服务端返回或 IM 推送的状态,不能本地计算能量、奖励或开箱结果。
|
||
|
||
## 基础约定
|
||
|
||
本地开发地址:
|
||
|
||
```text
|
||
http://127.0.0.1:13000
|
||
```
|
||
|
||
业务接口统一响应 envelope:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_xxx",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `code` | 成功固定 `OK`;失败为稳定错误码。 |
|
||
| `message` | 短错误文案,不用于客户端分支判断。 |
|
||
| `request_id` | 服务端链路追踪 ID;客户端日志要记录。 |
|
||
| `data` | 成功业务数据;失败时通常不存在。 |
|
||
|
||
通用请求头:
|
||
|
||
| Header | 必填 | 说明 |
|
||
| --- | --- | --- |
|
||
| `Authorization` | 是 | `Bearer <access_token>`。 |
|
||
| `X-App-Code` | 否 | App 编码,默认 `lalu`;登录态接口最终以 token 内 app_code 为准。 |
|
||
| `Content-Type` | POST 必填 | `application/json`。 |
|
||
|
||
所有 `*_ms` 都是 Unix epoch milliseconds。业务切日按 UTC,不按用户本地时区。
|
||
|
||
## 客户端流程
|
||
|
||
1. 用户成功 `JoinRoom` 后调用 `GET /api/v1/rooms/{room_id}/treasure` 初始化 7 级物料和当前进度。
|
||
2. 用户送礼调用 `POST /api/v1/rooms/gift/send`;成功响应里的 `data.treasure` 可立即更新当前宝箱状态。
|
||
3. 房间内监听腾讯 IM 群 `TIMCustomElem.Ext=room_system_message`,处理宝箱进度、倒计时、开箱和发奖。
|
||
4. App 全局或区域播报监听 `TIMCustomElem.Ext=im_broadcast` 且 `broadcast_type=room_treasure`。
|
||
5. 任意 IM 丢失、字段解析失败、版本倒退或页面重进时,重新调用 `GET /api/v1/rooms/{room_id}/treasure` 纠正本地状态。
|
||
|
||
客户端必须用 `event_id` 去重,用 `room_version` 丢弃旧房间消息。
|
||
|
||
## 获取宝箱信息
|
||
|
||
```http
|
||
GET /api/v1/rooms/{room_id}/treasure
|
||
Authorization: Bearer <access_token>
|
||
X-App-Code: lalu
|
||
```
|
||
|
||
路径参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `room_id` | string | 是 | 当前语音房 ID,例如 `lalu_534d076a-7cbd-4b23-a297-0c10a3c2fa72`。 |
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_xxx",
|
||
"data": {
|
||
"enabled": true,
|
||
"levels": [
|
||
{
|
||
"level": 1,
|
||
"energy_threshold": 1000,
|
||
"cover_url": "https://cdn.example/chest/lv1/cover.png",
|
||
"animation_url": "https://cdn.example/chest/lv1/idle.svga",
|
||
"opening_animation_url": "https://cdn.example/chest/lv1/opening.svga",
|
||
"opened_image_url": "https://cdn.example/chest/lv1/opened.png",
|
||
"in_room_rewards": [
|
||
{
|
||
"reward_item_id": "lv1_in_room_1",
|
||
"resource_group_id": 1001,
|
||
"weight": 10000,
|
||
"display_name": "Gold Pack",
|
||
"icon_url": "https://cdn.example/reward/gold.png"
|
||
}
|
||
],
|
||
"top1_rewards": [],
|
||
"igniter_rewards": []
|
||
}
|
||
],
|
||
"state": {
|
||
"current_level": 1,
|
||
"current_progress": 0,
|
||
"energy_threshold": 1000,
|
||
"status": "idle",
|
||
"reset_at_ms": 1779321600000,
|
||
"top1_user_id": "",
|
||
"igniter_user_id": "",
|
||
"box_id": "",
|
||
"config_version": 3,
|
||
"last_rewards": []
|
||
},
|
||
"broadcast_scope": "region",
|
||
"open_delay_ms": 30000,
|
||
"broadcast_delay_ms": 0,
|
||
"reward_stack_policy": "allow_stack",
|
||
"server_time_ms": 1779259000000
|
||
}
|
||
}
|
||
```
|
||
|
||
`data` 字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `enabled` | bool | 宝箱总开关。`false` 时客户端隐藏或置灰宝箱入口。 |
|
||
| `levels` | array | 固定 7 个等级的物料、阈值和奖励池。 |
|
||
| `state` | object | 当前房间当前 UTC 日的宝箱状态。 |
|
||
| `broadcast_scope` | string | `none`、`region`、`global`。 |
|
||
| `open_delay_ms` | int64 | 满能量后到开箱的倒计时配置。 |
|
||
| `broadcast_delay_ms` | int64 | 后台配置字段;当前房间事件会立即进入播报链路。 |
|
||
| `reward_stack_policy` | string | `allow_stack` 或 `priority_only`。 |
|
||
| `server_time_ms` | int64 | 服务端当前时间,倒计时要以它校准本地时钟偏差。 |
|
||
|
||
`levels[]` 字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `level` | int32 | 等级,1 到 7。 |
|
||
| `energy_threshold` | int64 | 当前等级满能量阈值。 |
|
||
| `cover_url` | string | 静态封面图。 |
|
||
| `animation_url` | string | 未开箱/蓄能态动效。 |
|
||
| `opening_animation_url` | string | 开箱动效。 |
|
||
| `opened_image_url` | string | 开箱后展示图。 |
|
||
| `in_room_rewards` | array | 开箱时仍在房用户的奖励候选展示。 |
|
||
| `top1_rewards` | array | 倒计时开始时贡献第一用户奖励候选展示。 |
|
||
| `igniter_rewards` | array | 点火人奖励候选展示。 |
|
||
|
||
奖励候选字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `reward_item_id` | string | 后台配置的奖励项 ID。 |
|
||
| `resource_group_id` | int64 | 钱包资源组 ID。 |
|
||
| `weight` | int64 | 抽奖权重,只用于展示或调试,不参与客户端抽奖。 |
|
||
| `display_name` | string | 奖励展示名称。 |
|
||
| `icon_url` | string | 奖励图标。 |
|
||
|
||
`state` 字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `current_level` | int32 | 当前等级,1 到 7。 |
|
||
| `current_progress` | int64 | 当前等级已累计有效能量。 |
|
||
| `energy_threshold` | int64 | 当前等级阈值。 |
|
||
| `status` | string | 当前返回 `idle` 或 `countdown`。 |
|
||
| `countdown_started_at_ms` | int64 | 倒计时开始时间;非倒计时可为空或 0。 |
|
||
| `open_at_ms` | int64 | 预计开箱时间;`status=countdown` 时有效。 |
|
||
| `opened_at_ms` | int64 | 最近一次开箱时间。 |
|
||
| `reset_at_ms` | int64 | 下一个 UTC 0 点;到点后进度按新 UTC 日重置。 |
|
||
| `top1_user_id` | string | 倒计时开始时锁定的贡献第一;GET 接口中按字符串返回。 |
|
||
| `igniter_user_id` | string | 点火人;GET 接口中按字符串返回。 |
|
||
| `box_id` | string | 当前倒计时宝箱或最近状态的单轮幂等 ID。 |
|
||
| `config_version` | int64 | 当前状态关联的后台配置版本。 |
|
||
| `last_rewards` | array | 最近一次开箱发奖结果;客户端可用于展示开箱结果兜底。 |
|
||
|
||
`last_rewards[]` 字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `reward_role` | string | `in_room`、`top1`、`igniter`。 |
|
||
| `user_id` | string | 获奖用户 ID 字符串。 |
|
||
| `reward_item_id` | string | 奖励项 ID。 |
|
||
| `resource_group_id` | int64 | 资源组 ID。 |
|
||
| `display_name` | string | 奖励展示名称。 |
|
||
| `icon_url` | string | 奖励图标。 |
|
||
| `grant_id` | string | 钱包发放记录 ID。 |
|
||
| `status` | string | 当前成功发放为 `succeeded`。 |
|
||
|
||
展示规则:
|
||
|
||
- 进度条使用 `current_progress / energy_threshold`,并且最大显示 100%。
|
||
- 倒计时使用 `open_at_ms - server_time_ms` 初始化,再用本地 timer 驱动。
|
||
- 不要把送礼溢出能量展示到下一等级;服务端规则是溢出作废。
|
||
- 如果 `status=countdown`,继续送礼不会改变宝箱进度、点火人、贡献第一或 `open_at_ms`。
|
||
|
||
## 送礼并更新宝箱
|
||
|
||
```http
|
||
POST /api/v1/rooms/gift/send
|
||
Authorization: Bearer <access_token>
|
||
X-App-Code: lalu
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"room_id": "lalu_xxx",
|
||
"command_id": "cmd_send_gift_lalu_xxx_12345678_1779259000000",
|
||
"target_type": "user",
|
||
"target_user_ids": [12345678],
|
||
"gift_id": "rocket",
|
||
"gift_count": 1
|
||
}
|
||
```
|
||
|
||
参数:
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `room_id` | string | 是 | 当前房间 ID。 |
|
||
| `command_id` | string | 是 | 本次送礼动作幂等键;同一次 HTTP 重试必须复用。 |
|
||
| `target_type` | string | 否 | 当前只支持 `user`;为空按 `user` 处理。 |
|
||
| `target_user_ids` | int64[] | 是 | 收礼用户列表;`target_type=user` 时必须且只能 1 个。 |
|
||
| `target_user_id` | int64 | 否 | 旧客户端兼容字段;新客户端优先用 `target_user_ids`。 |
|
||
| `gift_id` | string | 是 | 礼物 ID。 |
|
||
| `gift_count` | int32 | 是 | 礼物数量,必须大于 0。 |
|
||
|
||
成功响应核心字段:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_xxx",
|
||
"data": {
|
||
"result": {
|
||
"applied": true,
|
||
"room_version": 7,
|
||
"server_time_ms": 1779259502200
|
||
},
|
||
"billing_receipt_id": "bill_xxx",
|
||
"room_heat": 500,
|
||
"gift_rank": [
|
||
{
|
||
"user_id": 12345678,
|
||
"score": 500,
|
||
"gift_value": 500,
|
||
"updated_at_ms": 1779259502104
|
||
}
|
||
],
|
||
"room": {},
|
||
"treasure": {
|
||
"current_level": 2,
|
||
"current_progress": 200,
|
||
"energy_threshold": 200,
|
||
"status": "countdown",
|
||
"countdown_started_at_ms": 1779259502104,
|
||
"open_at_ms": 1779259512104,
|
||
"reset_at_ms": 1779321600000,
|
||
"top1_user_id": 12345678,
|
||
"igniter_user_id": 12345678,
|
||
"box_id": "room_treasure_box_xxx",
|
||
"config_version": 3
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
字段说明:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `data.result.applied` | bool | 命令是否产生新状态;同一 `command_id` 重试可能返回历史结果。 |
|
||
| `data.result.room_version` | int64 | 房间版本,用于和 IM 消息去重、排序。 |
|
||
| `data.billing_receipt_id` | string | 钱包扣费回执。 |
|
||
| `data.room_heat` | int64 | 送礼后的房间热度。 |
|
||
| `data.gift_rank` | array | 送礼后的本地贡献榜快照。 |
|
||
| `data.room` | object | 房间快照。 |
|
||
| `data.treasure` | object | 送礼后宝箱状态;送礼人可立即用它刷新 UI。 |
|
||
|
||
注意:`GET /treasure` 的用户 ID 字段按字符串返回,`POST /gift/send` 透出的 protobuf 状态里部分用户 ID 可能是数字。Flutter 侧建议用统一 helper 兼容 `String` 和 `num`。
|
||
|
||
```dart
|
||
String readId(dynamic value) {
|
||
if (value == null) return '';
|
||
if (value is String) return value;
|
||
if (value is num) return value.toInt().toString();
|
||
return '';
|
||
}
|
||
```
|
||
|
||
幂等规则:
|
||
|
||
| 场景 | 处理 |
|
||
| --- | --- |
|
||
| HTTP 超时后重试同一次送礼 | 复用原 `command_id`。 |
|
||
| 用户点了第二次送礼按钮 | 生成新的 `command_id`。 |
|
||
| 同一个 `command_id` 换了礼物、数量或目标 | 服务端返回冲突错误。 |
|
||
|
||
## 错误处理
|
||
|
||
失败响应:
|
||
|
||
```json
|
||
{
|
||
"code": "PERMISSION_DENIED",
|
||
"message": "permission denied",
|
||
"request_id": "req_xxx"
|
||
}
|
||
```
|
||
|
||
通用错误:
|
||
|
||
| HTTP | `code` | 场景 | Flutter 处理 |
|
||
| --- | --- | --- | --- |
|
||
| 400 | `INVALID_JSON` | 请求体不是合法 JSON。 | 修正客户端请求,不重试。 |
|
||
| 400 | `INVALID_ARGUMENT` | 缺字段、数量非法、room_id 格式非法、暂不支持的 `target_type`。 | 修正参数,不自动重试。 |
|
||
| 401 | `UNAUTHORIZED` | token 缺失、过期或无效。 | 走登录刷新或重新登录。 |
|
||
| 403 | `PROFILE_REQUIRED` | 用户资料未完成。 | 跳转资料补全流程。 |
|
||
| 403 | `PERMISSION_DENIED` | 不在房间、被 ban、无权限读取或操作。 | 退出房间页或刷新房间状态。 |
|
||
| 404 | `NOT_FOUND` | 房间不存在、送礼人不在房、收礼人不在房、礼物不可用。 | 刷新房间状态;礼物不可用时刷新礼物面板。 |
|
||
| 409 | `ROOM_CLOSED` | 房间已关闭。 | 退出房间页并刷新列表。 |
|
||
| 409 | `CONFLICT` | 状态或唯一约束冲突。 | 刷新后重试。 |
|
||
| 409 | `IDEMPOTENCY_CONFLICT` | `command_id` 复用但请求内容不同。 | 新用户动作生成新 `command_id`;重试旧动作必须保持原请求体。 |
|
||
| 409 | `INSUFFICIENT_BALANCE` | 钱包余额不足。 | 弹充值/余额不足提示。 |
|
||
| 409 | `LEDGER_CONFLICT` | 钱包并发账本冲突。 | 可短延迟重试一次,仍失败则刷新余额。 |
|
||
| 502 | `UPSTREAM_ERROR` | room/wallet 等依赖暂不可用。 | 提示稍后重试,可保留原 `command_id` 重试同一次送礼。 |
|
||
| 500 | `INTERNAL_ERROR` | 服务端内部错误。 | 提示失败并记录 `request_id`。 |
|
||
|
||
宝箱相关额外规则:
|
||
|
||
- 获取宝箱信息要求当前用户仍在房间业务 presence 内;离房后调用会返回 `PERMISSION_DENIED` 或 `NOT_FOUND`。
|
||
- 宝箱关闭时,`GET /treasure` 仍可能返回 `enabled=false` 和 7 级配置;客户端不展示进度入口即可。
|
||
- 发奖自动完成,没有 App 领取接口;客户端不要补发或重复调用钱包接口。
|
||
|
||
## 房间群 IM 消息
|
||
|
||
房间内宝箱进度、倒计时、开箱和发奖通过腾讯云 IM 房间群 `TIMCustomElem` 投递。
|
||
|
||
| 字段 | 值 |
|
||
| --- | --- |
|
||
| `MsgType` | `TIMCustomElem` |
|
||
| `MsgContent.Ext` | `room_system_message` |
|
||
| `MsgContent.Desc` | 等于 `Data.event_type` |
|
||
| `MsgContent.Data` | JSON 字符串 |
|
||
| `CloudCustomData` | 与 `MsgContent.Data` 相同的 JSON 字符串 |
|
||
|
||
`Data` 外层结构:
|
||
|
||
```json
|
||
{
|
||
"event_id": "evt_xxx",
|
||
"room_id": "lalu_xxx",
|
||
"event_type": "room_treasure_progress_changed",
|
||
"actor_user_id": 12345678,
|
||
"target_user_id": 0,
|
||
"room_version": 8,
|
||
"attributes": {
|
||
"box_id": "room_treasure_box_xxx"
|
||
}
|
||
}
|
||
```
|
||
|
||
通用字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `event_id` | string | IM 去重键。 |
|
||
| `room_id` | string | 房间 ID。 |
|
||
| `event_type` | string | 事件类型,见下表。 |
|
||
| `actor_user_id` | int64 | 触发用户;倒计时/开箱通常为点火人。 |
|
||
| `target_user_id` | int64 | 目标用户;倒计时/开箱通常为 top1。 |
|
||
| `room_version` | int64 | 房间版本,本地版本更高时丢弃旧消息。 |
|
||
| `attributes` | map<string,string> | 业务字段;值全部按字符串传输,Flutter 侧按需转 int/bool/JSON。 |
|
||
|
||
宝箱房间群事件:
|
||
|
||
| `event_type` | `Desc` | 用途 |
|
||
| --- | --- | --- |
|
||
| `room_treasure_progress_changed` | `room_treasure_progress_changed` | 有效宝箱能量增加。 |
|
||
| `room_treasure_countdown_started` | `room_treasure_countdown_started` | 当前等级满能量,进入倒计时。 |
|
||
| `room_treasure_opened` | `room_treasure_opened` | 宝箱已打开,包含公开奖励摘要。 |
|
||
| `room_treasure_reward_granted` | `room_treasure_reward_granted` | 发奖结果事件,包含奖励列表。 |
|
||
|
||
### room_treasure_progress_changed
|
||
|
||
示例:
|
||
|
||
```json
|
||
{
|
||
"event_id": "evt_progress",
|
||
"room_id": "lalu_xxx",
|
||
"event_type": "room_treasure_progress_changed",
|
||
"actor_user_id": 12345678,
|
||
"room_version": 6,
|
||
"attributes": {
|
||
"box_id": "room_treasure_box_xxx",
|
||
"level": "2",
|
||
"added_energy": "300",
|
||
"effective_added_energy": "200",
|
||
"overflow_energy": "100",
|
||
"current_progress": "200",
|
||
"energy_threshold": "200",
|
||
"status": "countdown",
|
||
"reset_at_ms": "1779321600000",
|
||
"gift_id": "rocket",
|
||
"gift_count": "3",
|
||
"command_id": "cmd_send_gift_xxx"
|
||
}
|
||
}
|
||
```
|
||
|
||
处理:
|
||
|
||
- 更新当前等级进度为 `current_progress`。
|
||
- `effective_added_energy` 是本次实际计入宝箱的能量。
|
||
- `overflow_energy` 是作废能量,不能加到下一等级。
|
||
- 如果 `status=countdown`,立即切换倒计时态,等待 `room_treasure_countdown_started` 补齐 `open_at_ms`;如该消息丢失,调用 GET 兜底。
|
||
|
||
### room_treasure_countdown_started
|
||
|
||
示例:
|
||
|
||
```json
|
||
{
|
||
"event_id": "evt_countdown",
|
||
"room_id": "lalu_xxx",
|
||
"event_type": "room_treasure_countdown_started",
|
||
"actor_user_id": 12345678,
|
||
"target_user_id": 12345678,
|
||
"room_version": 6,
|
||
"attributes": {
|
||
"box_id": "room_treasure_box_xxx",
|
||
"level": "2",
|
||
"current_progress": "200",
|
||
"energy_threshold": "200",
|
||
"countdown_start_ms": "1779259502104",
|
||
"open_at_ms": "1779259512104",
|
||
"broadcast_scope": "region",
|
||
"visible_region_id": "1001",
|
||
"top1_user_id": "12345678",
|
||
"igniter_user_id": "12345678",
|
||
"command_id": "cmd_send_gift_xxx"
|
||
}
|
||
}
|
||
```
|
||
|
||
处理:
|
||
|
||
- 按 `open_at_ms` 展示开箱倒计时。
|
||
- `top1_user_id` 和 `igniter_user_id` 在此刻锁定;后续离房或下线也不影响这两类奖励发放。
|
||
- 倒计时期间再收到普通 `room_gift_sent`,只更新礼物表现和热度,不改变宝箱倒计时。
|
||
|
||
注意:房间群 IM 字段名是 `countdown_start_ms`,HTTP state 字段名是 `countdown_started_at_ms`。
|
||
|
||
### room_treasure_opened
|
||
|
||
示例:
|
||
|
||
```json
|
||
{
|
||
"event_id": "evt_opened",
|
||
"room_id": "lalu_xxx",
|
||
"event_type": "room_treasure_opened",
|
||
"actor_user_id": 12345678,
|
||
"target_user_id": 12345678,
|
||
"room_version": 8,
|
||
"attributes": {
|
||
"box_id": "room_treasure_box_xxx",
|
||
"level": "2",
|
||
"next_level": "3",
|
||
"opened_at_ms": "1779259512668",
|
||
"reset_at_ms": "1779321600000",
|
||
"top1_user_id": "12345678",
|
||
"igniter_user_id": "12345678",
|
||
"rewards_json": "[{\"reward_role\":\"in_room\",\"user_id\":12345678,\"reward_item_id\":\"lv2_in_room_1\",\"resource_group_id\":1001,\"display_name\":\"Gold Pack\",\"icon_url\":\"https://cdn.example/reward/gold.png\",\"grant_id\":\"rgr_xxx\",\"status\":\"succeeded\"}]"
|
||
}
|
||
}
|
||
```
|
||
|
||
处理:
|
||
|
||
- 播放当前等级 `opening_animation_url`,再展示 `opened_image_url` 或奖励弹窗。
|
||
- 解析 `rewards_json` 得到奖励列表;如果解析失败,调用 GET 获取 `state.last_rewards`。
|
||
- 本地状态进入 `next_level`,进度归 0,状态归 `idle`。
|
||
|
||
### room_treasure_reward_granted
|
||
|
||
示例:
|
||
|
||
```json
|
||
{
|
||
"event_id": "evt_reward",
|
||
"room_id": "lalu_xxx",
|
||
"event_type": "room_treasure_reward_granted",
|
||
"room_version": 8,
|
||
"attributes": {
|
||
"box_id": "room_treasure_box_xxx",
|
||
"level": "2",
|
||
"rewards_json": "[{\"reward_role\":\"top1\",\"user_id\":12345678,\"reward_item_id\":\"lv2_top1_1\",\"resource_group_id\":1002,\"display_name\":\"Top Reward\",\"icon_url\":\"https://cdn.example/reward/top.png\",\"grant_id\":\"rgr_xxx\",\"status\":\"succeeded\"}]"
|
||
}
|
||
}
|
||
```
|
||
|
||
处理:
|
||
|
||
- 解析 `rewards_json`。
|
||
- 当前登录用户在奖励列表中时,展示“获得奖励”提示。
|
||
- 同一用户可能同时命中 `in_room`、`top1`、`igniter`,是否叠加由 `reward_stack_policy` 决定;客户端按服务端发下来的列表展示。
|
||
|
||
当前没有单独的 C2C 宝箱中奖私信,也没有 App 消息 Tab 宝箱入箱消息。后续如新增私有通知,应以新的 C2C `Ext` 文档为准,不复用房间群消息假装私信。
|
||
|
||
## 区域/全局播报 IM
|
||
|
||
满能量进入倒计时后,如果后台配置允许播报,`activity-service` 会向区域或全局播报群发送 `TIMCustomElem`。
|
||
|
||
| 字段 | 值 |
|
||
| --- | --- |
|
||
| `MsgType` | `TIMCustomElem` |
|
||
| `MsgContent.Ext` | `im_broadcast` |
|
||
| `MsgContent.Desc` | `room_treasure` |
|
||
| `MsgContent.Data` | JSON 字符串 |
|
||
| `CloudCustomData` | 与 `MsgContent.Data` 相同的 JSON 字符串 |
|
||
|
||
`Data` 示例:
|
||
|
||
```json
|
||
{
|
||
"event_id": "room_treasure_broadcast:lalu_xxx:room_treasure_box_xxx",
|
||
"broadcast_type": "room_treasure",
|
||
"scope": "region",
|
||
"app_code": "lalu",
|
||
"region_id": 1001,
|
||
"room_id": "lalu_xxx",
|
||
"box_id": "room_treasure_box_xxx",
|
||
"level": 2,
|
||
"open_at_ms": 1779259512104,
|
||
"top1_user_id": 12345678,
|
||
"igniter_user_id": 12345678,
|
||
"sent_at_ms": 1779259502200,
|
||
"room_version": 6,
|
||
"source_event_id": "evt_countdown",
|
||
"current_progress": 200,
|
||
"energy_threshold": 200,
|
||
"countdown_started_at_ms": 1779259502104,
|
||
"action": {
|
||
"type": "enter_room",
|
||
"room_id": "lalu_xxx"
|
||
}
|
||
}
|
||
```
|
||
|
||
处理:
|
||
|
||
- `broadcast_type=room_treasure` 时展示宝箱倒计时播报。
|
||
- 点击播报按 `action.type=enter_room` 跳转或进房。
|
||
- `scope=region` 表示区域播报群;`scope=global` 表示全局播报群。
|
||
- 播报只负责引导和展示,不代替房间内宝箱状态;进房后仍调用 GET 初始化。
|
||
|
||
## 普通送礼房间 IM
|
||
|
||
送礼成功还会产生普通房间群消息:
|
||
|
||
| 字段 | 值 |
|
||
| --- | --- |
|
||
| `MsgContent.Ext` | `room_system_message` |
|
||
| `MsgContent.Desc` | `room_gift_sent` |
|
||
| `Data.event_type` | `room_gift_sent` |
|
||
|
||
宝箱 UI 不应该只靠 `room_gift_sent` 推算能量;能量以 `room_treasure_progress_changed`、`room_treasure_countdown_started`、`POST /gift/send.data.treasure` 或 GET 结果为准。
|
||
|
||
## Flutter 解析建议
|
||
|
||
IM `attributes` 的值都是字符串,建议统一封装解析:
|
||
|
||
```dart
|
||
int readInt(dynamic value, [int fallback = 0]) {
|
||
if (value == null) return fallback;
|
||
if (value is num) return value.toInt();
|
||
if (value is String) return int.tryParse(value) ?? fallback;
|
||
return fallback;
|
||
}
|
||
|
||
String readString(dynamic value) {
|
||
if (value == null) return '';
|
||
if (value is String) return value;
|
||
if (value is num) return value.toInt().toString();
|
||
return value.toString();
|
||
}
|
||
|
||
List<Map<String, dynamic>> readRewardsJson(dynamic value) {
|
||
final raw = readString(value);
|
||
if (raw.isEmpty) return const [];
|
||
final decoded = jsonDecode(raw);
|
||
if (decoded is! List) return const [];
|
||
return decoded.whereType<Map>().map((item) {
|
||
return item.map((key, val) => MapEntry(key.toString(), val));
|
||
}).toList();
|
||
}
|
||
```
|
||
|
||
推荐本地状态更新策略:
|
||
|
||
| 输入 | 处理 |
|
||
| --- | --- |
|
||
| `GET /treasure` | 覆盖本地完整宝箱状态和 7 级物料。 |
|
||
| `POST /gift/send.data.treasure` | 送礼人立即刷新当前状态。 |
|
||
| `room_treasure_progress_changed` | 同房间、版本不旧时更新进度。 |
|
||
| `room_treasure_countdown_started` | 切换倒计时,并锁定 top1/点火人展示。 |
|
||
| `room_treasure_opened` | 播放开箱动效,状态切到下一等级 idle。 |
|
||
| `room_treasure_reward_granted` | 展示当前用户中奖提示;按 `event_id` 去重。 |
|
||
| `im_broadcast + room_treasure` | 展示区域/全局播报入口,不直接改当前房间状态。 |
|
||
|
||
如果客户端发现本地状态和服务端事件无法合并,例如当前 `box_id` 不一致、`level` 倒退、`room_version` 缺失,直接调用 `GET /api/v1/rooms/{room_id}/treasure` 重新同步。
|