# Flutter 礼物墙对接 本文描述 Flutter App 查询用户礼物墙的 HTTP 接口。礼物墙事实由 `wallet-service` 在送礼扣费成功后通过异步投影聚合,客户端只展示 gateway 返回的当前投影,不从房间消息或本地缓存推算收礼数量。 ## 接口 `GET /api/v1/users/me/gift-wall` `GET /api/v1/users/{user_id}/gift-wall` 登录接口,需要 `Authorization: Bearer `。 - `GET /api/v1/users/me/gift-wall` 查询当前登录用户。 - `GET /api/v1/users/{user_id}/gift-wall` 查询资料卡目标用户,`user_id` 是路径参数,必须是正整数。 一致性边界:礼物墙是最终一致读模型,送礼成功后允许延迟数秒到几十秒才出现在接口结果中。客户端不要用该接口判断刚刚送礼是否成功;送礼成功状态仍以送礼接口响应和房间 IM 消息为准。 请求头: | Header | 必填 | 说明 | | --- | --- | --- | | `Authorization` | 是 | App 登录 access token。 | | `X-App-Code` | 否 | App 编码,默认 `lalu`。 | 示例: ```http GET /api/v1/users/me/gift-wall Authorization: Bearer X-App-Code: lalu ``` ```http GET /api/v1/users/10002/gift-wall Authorization: Bearer X-App-Code: lalu ``` 成功响应: ```json { "code": "OK", "message": "ok", "request_id": "req_abc", "data": { "items": [ { "gift_id": "rose", "gift_name": "Rose", "resource_id": 11, "resource": { "resource_id": 11, "resource_code": "gift_rose", "resource_type": "gift", "name": "Rose", "status": "active", "grantable": true, "grant_strategy": "increase_quantity", "usage_scopes": ["gift"], "asset_url": "https://cdn.example/rose.png", "preview_url": "", "animation_url": "", "metadata_json": "{}", "sort_order": 10, "created_at_ms": 1710000000000, "updated_at_ms": 1710000000000 }, "resource_snapshot_json": "{\"ResourceID\":11}", "gift_type_code": "normal", "presentation_json": "{}", "gift_count": 5, "total_value": 50, "total_coin_value": 50, "total_diamond_value": 0, "total_gift_point": 50, "total_heat_value": 50, "charge_asset_type": "COIN", "last_price_version": "v1", "first_received_at_ms": 1710000000000, "last_received_at_ms": 1710000005000, "sort_order": 10 } ], "gift_kind_count": 1, "gift_total_count": 5, "total_value": 50, "total_coin_value": 50, "total_diamond_value": 0, "total_gift_point": 50, "total_heat_value": 50 } } ``` 字段规则: | 字段 | 说明 | | --- | --- | | `items` | 按 `gift_id` 聚合后的礼物列表,固定按后台礼物排序、价值和最近收礼时间排序。 | | `gift_count` | 当前用户收到该礼物的累计个数。 | | `gift_kind_count` | 当前用户收到过的礼物种类数,等于 `items.length`。 | | `gift_total_count` | 当前用户收到的所有礼物数量总和。 | | `total_value` | 礼物总价值,按送礼时服务端结算的 `charge_amount` 累加。 | | `total_coin_value` / `total_diamond_value` | 按收费资产拆分的总价值;当前普通礼物通常只读 `total_coin_value`。 | | `total_gift_point` | 主播礼物积分累计值,不等同于可提现余额。 | | `total_heat_value` | 房间热度累计值,可用于展示贡献热度,不要反推价格。 | | `charge_asset_type` | 该礼物历史上只用一种资产收费时为 `COIN` 或 `DIAMOND`;发生过资产切换时为 `MIXED`。 | | `resource` | 礼物展示资源快照,客户端优先用 `asset_url` / `preview_url` / `animation_url` 渲染。 | | `resource_snapshot_json` | 原始资源快照 JSON,主要用于客户端兜底和日志排查。 | | `*_ms` | Unix epoch milliseconds;展示层按用户本地时区格式化。 | ## Flutter 解析示例 ```dart class GiftWall { GiftWall({ required this.items, required this.giftKindCount, required this.giftTotalCount, required this.totalValue, }); final List items; final int giftKindCount; final int giftTotalCount; final int totalValue; factory GiftWall.fromJson(Map json) { return GiftWall( items: (json['items'] as List? ?? const []) .map((item) => GiftWallItem.fromJson(item as Map)) .toList(), giftKindCount: (json['gift_kind_count'] as num?)?.toInt() ?? 0, giftTotalCount: (json['gift_total_count'] as num?)?.toInt() ?? 0, totalValue: (json['total_value'] as num?)?.toInt() ?? 0, ); } } class GiftWallItem { GiftWallItem({ required this.giftId, required this.giftName, required this.giftCount, required this.totalValue, required this.assetUrl, }); final String giftId; final String giftName; final int giftCount; final int totalValue; final String assetUrl; factory GiftWallItem.fromJson(Map json) { final resource = json['resource'] as Map? ?? const {}; return GiftWallItem( giftId: json['gift_id'] as String? ?? '', giftName: json['gift_name'] as String? ?? '', giftCount: (json['gift_count'] as num?)?.toInt() ?? 0, totalValue: (json['total_value'] as num?)?.toInt() ?? 0, assetUrl: resource['asset_url'] as String? ?? '', ); } } ``` 调用时先解析外层 envelope:只有 `code == "OK"` 时读取 `data`;其他错误按统一网关错误处理,并把 `request_id` 写入客户端日志。