diff --git a/docs/flutter对接/房内贡献榜Flutter对接.md b/docs/flutter对接/房内贡献榜Flutter对接.md new file mode 100644 index 00000000..8ea83c3f --- /dev/null +++ b/docs/flutter对接/房内贡献榜Flutter对接.md @@ -0,0 +1,277 @@ +# 房内贡献榜 Flutter 对接 + +本文描述语音房内 day/week/month 三个 tab 的贡献榜接口。客户端按当前 tab 独立请求,不再从进房、心跳或送礼接口里复用同一份榜单数据。 + +## 接口地址 + +本地开发地址: + +```text +http://127.0.0.1:13000 +``` + +```http +GET /api/v1/rooms/{room_id}/contribution-rank?period=day&limit=20 +Authorization: Bearer +X-App-Code: lalu +``` + +说明: + +| 项 | 说明 | +| --- | --- | +| 鉴权 | 必须登录。gateway 使用 token 里的 `user_id` 作为 viewer。 | +| 房间校验 | viewer 必须仍在当前房间内;不在房间时返回 403。 | +| 响应外壳 | 统一使用 `{code,message,request_id,data}`。 | +| 时间单位 | 所有 `*_ms` 都是 Unix epoch milliseconds,统计窗口按 UTC 计算。 | + +## 参数 + +Path 参数: + +| 字段 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `room_id` | string | 是 | 房间 ID。只支持字母、数字、下划线和中横线,最长 48 位。 | + +Query 参数: + +| 字段 | 类型 | 必填 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `period` | string | 否 | `day` | 榜单周期。可选值:`day`、`week`、`month`。 | +| `limit` | int32 | 否 | `20` | 返回条数。必须大于 0;服务端最大返回 `100`。 | + +周期含义: + +| `period` | 统计窗口 | +| --- | --- | +| `day` | UTC 当日 00:00:00 到次日 00:00:00,区间为 `[start_at_ms, end_at_ms)`。 | +| `week` | UTC 自然周,周一 00:00:00 到下周一 00:00:00,区间为 `[start_at_ms, end_at_ms)`。 | +| `month` | UTC 自然月,1 日 00:00:00 到下月 1 日 00:00:00,区间为 `[start_at_ms, end_at_ms)`。 | + +请求示例: + +```http +GET /api/v1/rooms/room_123/contribution-rank?period=week&limit=20 +Authorization: Bearer +X-App-Code: lalu +``` + +## 返回值 + +统一外壳: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_xxx", + "data": {} +} +``` + +成功返回示例: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_room_rank", + "data": { + "room_id": "room_123", + "period": "week", + "start_at_ms": 1780329600000, + "end_at_ms": 1780934400000, + "total": 2, + "contribution_rank": [ + { + "rank": 1, + "user_id": "43", + "score": 900, + "gift_value": 900, + "updated_at_ms": 1780400001000, + "profile": { + "user_id": "43", + "username": "Luna", + "avatar": "https://cdn.example/avatar/43.png", + "display_user_id": "10043", + "pretty_id": "888888", + "pretty_display_user_id": "888888", + "gender": "female", + "age": 22, + "country": "US", + "country_name": "United States", + "country_display_name": "United States", + "country_flag": "https://cdn.example/flags/us.png", + "vip": {}, + "level": {}, + "level_badges": {}, + "badges": [], + "avatar_frame": {}, + "profile_card": {}, + "vehicle": {}, + "mic_seat_animation": {}, + "charm": 0 + } + }, + { + "rank": 2, + "user_id": "44", + "score": 520, + "gift_value": 520, + "updated_at_ms": 1780390001000, + "profile": { + "user_id": "44", + "username": "Mia", + "avatar": "https://cdn.example/avatar/44.png", + "display_user_id": "10044", + "gender": "", + "age": 0, + "country": "", + "country_name": "", + "country_display_name": "", + "country_flag": "", + "vip": {}, + "level": {}, + "level_badges": {}, + "badges": [], + "avatar_frame": {}, + "profile_card": {}, + "vehicle": {}, + "mic_seat_animation": {}, + "charm": 0 + } + } + ], + "profiles": [ + { + "user_id": "43", + "username": "Luna", + "avatar": "https://cdn.example/avatar/43.png", + "display_user_id": "10043", + "gender": "female", + "age": 22, + "country": "US", + "country_name": "United States", + "country_display_name": "United States", + "country_flag": "https://cdn.example/flags/us.png", + "vip": {}, + "level": {}, + "level_badges": {}, + "badges": [], + "avatar_frame": {}, + "profile_card": {}, + "vehicle": {}, + "mic_seat_animation": {}, + "charm": 0 + }, + { + "user_id": "44", + "username": "Mia", + "avatar": "https://cdn.example/avatar/44.png", + "display_user_id": "10044", + "gender": "", + "age": 0, + "country": "", + "country_name": "", + "country_display_name": "", + "country_flag": "", + "vip": {}, + "level": {}, + "level_badges": {}, + "badges": [], + "avatar_frame": {}, + "profile_card": {}, + "vehicle": {}, + "mic_seat_animation": {}, + "charm": 0 + } + ], + "server_time_ms": 1780400005000 + } +} +``` + +`data` 字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `room_id` | string | 房间 ID。 | +| `period` | string | 服务端归一化后的周期:`day`、`week`、`month`。 | +| `start_at_ms` | int64 | 本次榜单统计窗口开始时间,UTC。 | +| `end_at_ms` | int64 | 本次榜单统计窗口结束时间,UTC。 | +| `total` | int64 | 当前房间、当前周期内有贡献的总用户数。 | +| `contribution_rank` | array | 当前页榜单项。空数组表示当前周期暂无贡献数据。 | +| `profiles` | array | 榜单用户的展示资料列表,顺序和 `contribution_rank` 一致。 | +| `server_time_ms` | int64 | 服务端当前时间,可用于展示更新时间。 | + +`contribution_rank[]` 字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `rank` | int64 | 排名,从 1 开始。 | +| `user_id` | string | 上榜用户 ID,HTTP JSON 固定按字符串解析。 | +| `score` | int64 | 榜单分值;当前等于 `gift_value`。 | +| `gift_value` | int64 | 统计窗口内该用户在本房间送礼贡献总值。 | +| `updated_at_ms` | int64 | 该用户本周期贡献记录更新时间。 | +| `profile` | object? | 当前用户展示资料。资料服务异常或用户资料缺失时可能为空。 | + +`profile` / `profiles[]` 字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `user_id` | string | 用户长 ID。 | +| `username` | string | 昵称。 | +| `avatar` | string | 头像 URL。 | +| `display_user_id` | string | 展示 ID。 | +| `pretty_id` | string | 靓号 ID;没有时为空或不返回。 | +| `pretty_display_user_id` | string | 靓号展示 ID;没有时为空或不返回。 | +| `gender` | string | 性别展示值。 | +| `age` | int32 | 年龄。 | +| `country` | string | 国家码。 | +| `country_name` | string | 国家名称。 | +| `country_display_name` | string | 国家展示名。 | +| `country_flag` | string | 国家旗帜。 | +| `vip` | object | VIP 展示资料。 | +| `level` | object | 等级展示资料。 | +| `level_badges` | object | 等级徽章资料。 | +| `badges` | array | 当前佩戴徽章。 | +| `avatar_frame` | object | 当前佩戴头像框。 | +| `profile_card` | object | 当前佩戴资料卡。 | +| `vehicle` | object | 当前佩戴座驾。 | +| `mic_seat_animation` | object | 当前佩戴麦位动画。 | +| `charm` | int64 | 魅力值。 | + +## 错误码 + +| HTTP | `code` | 场景 | +| --- | --- | --- | +| 400 | `INVALID_ARGUMENT` | `room_id` 格式错误、`period` 不是 `day/week/month`、`limit<=0` 或不是数字。 | +| 401 | `UNAUTHORIZED` | 未登录或 token 无效。 | +| 403 | `PROFILE_REQUIRED` | 登录用户未完成资料流程,gateway profile gate 拦截。 | +| 403 | `PERMISSION_DENIED` | viewer 当前不在该房间,不能读取房内贡献榜。 | +| 502 | `UPSTREAM_ERROR` | room-service 或 user-service 调用失败。 | + +失败返回示例: + +```json +{ + "code": "INVALID_ARGUMENT", + "message": "invalid argument", + "request_id": "req_bad_period" +} +``` + +## Flutter 调用规则 + +| 场景 | 处理 | +| --- | --- | +| 进入房间后首次展示 | `POST /api/v1/rooms/join` 成功、房间页建立后,请求当前默认 tab,例如 `period=day`。 | +| tab 切换 | 按 `room_id + period` 单独请求本接口;不要把 day 的数据复制给 week/month。 | +| 送礼成功后 | 刷新当前展示的 `period`,或收到本地送礼成功事件后延迟刷新;不要依赖送礼接口返回榜单。 | +| 心跳 | `POST /api/v1/rooms/heartbeat` 只维护在线状态,不用它刷新榜单。 | +| 进房首包 | 新版进房继续传 `response_mode=lite`;贡献榜由本接口独立拉取。 | +| 空榜 | `contribution_rank=[]`、`profiles=[]`、`total=0` 时展示空状态。 | + +## 相关 IM + +本接口没有独立 IM 消息。房间内送礼动效、公屏礼物消息仍按现有送礼链路和房间 IM 事件处理;本接口只用于 Flutter 在面板打开、tab 切换或送礼后重拉榜单快照。