# 等级体系 Flutter 对接

## 地址

Base URL：`https://<gateway-host>`

| 功能 | Method | Path |
| --- | --- | --- |
| 我的等级总览 | `GET` | `/api/v1/levels/me/overview` |
| 单轨道等级详情 | `GET` | `/api/v1/levels/tracks/{track}` |
| 我的等级奖励记录 | `GET` | `/api/v1/levels/rewards` |

## 请求参数

Headers：

| Header | 必填 | 说明 |
| --- | --- | --- |
| `Authorization` | 是 | `Bearer <access_token>` |
| `X-App-Code` | 否 | App 编码，默认 `lalu` |

`track`：

| 值 | 说明 |
| --- | --- |
| `wealth` | 财富等级，送出礼物价值累计 |
| `game` | 游戏等级，游戏消耗累计 |
| `charm` | 魅力等级，收到礼物价值累计 |

### `GET /api/v1/levels/me/overview`

无 query。

### `GET /api/v1/levels/tracks/{track}`

Path：

| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `track` | 是 | `wealth` / `game` / `charm` |

### `GET /api/v1/levels/rewards`

Query：

| 字段 | 必填 | 默认 | 说明 |
| --- | --- | --- | --- |
| `track` | 否 | 全部 | `wealth` / `game` / `charm` |
| `status` | 否 | 全部 | `pending` / `running` / `granted` / `failed` |
| `page` | 否 | `1` | 页码，从 1 开始 |
| `page_size` | 否 | `20` | 每页数量，最大 `100` |

## 返回值

统一 envelope：

```json
{
  "code": "OK",
  "message": "ok",
  "request_id": "req_xxx",
  "data": {}
}
```

### 我的等级总览

```json
{
  "code": "OK",
  "message": "ok",
  "request_id": "req_xxx",
  "data": {
    "tracks": [
      {
        "track": "wealth",
        "name": "财富等级",
        "level": 12,
        "tier_id": 2,
        "total_value": 158000,
        "current_level_required_value": 150000,
        "next_level": 13,
        "next_level_required_value": 180000,
        "display_avatar_frame_resource_id": 7001,
        "display_badge_resource_id": 7002,
        "reward_pending_count": 1,
        "sort_order": 10
      }
    ],
    "server_time_ms": 1710000000000
  }
}
```

`tracks[]`：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `track` | string | 等级轨道 |
| `name` | string | 轨道展示名 |
| `level` | int | 当前等级 |
| `tier_id` | int64 | 当前命中的等级层 ID，没有则为 `0` |
| `total_value` | int64 | 当前累计值 |
| `current_level_required_value` | int64 | 当前等级门槛值 |
| `next_level` | int | 下一等级，没有则为 `0` |
| `next_level_required_value` | int64 | 下一等级门槛值，没有则为 `0` |
| `display_avatar_frame_resource_id` | int64 | 当前等级层头像框资源 ID，没有则为 `0` |
| `display_badge_resource_id` | int64 | 当前等级层徽章资源 ID，没有则为 `0` |
| `reward_pending_count` | int64 | 待发放或发放中的等级奖励数量 |
| `sort_order` | int | 排序 |

### 单轨道等级详情

```json
{
  "code": "OK",
  "message": "ok",
  "request_id": "req_xxx",
  "data": {
    "overview": {
      "track": "wealth",
      "name": "财富等级",
      "level": 12,
      "tier_id": 2,
      "total_value": 158000,
      "current_level_required_value": 150000,
      "next_level": 13,
      "next_level_required_value": 180000,
      "display_avatar_frame_resource_id": 7001,
      "display_badge_resource_id": 7002,
      "reward_pending_count": 1,
      "sort_order": 10
    },
    "rules": [
      {
        "track": "wealth",
        "level": 13,
        "required_value": 180000,
        "name": "财富13级",
        "status": "active",
        "reward_resource_group_id": 9001,
        "sort_order": 13,
        "display_config_json": "{\"long_badge_resource_id\":7003}",
        "created_by_admin_id": 1,
        "updated_by_admin_id": 1,
        "created_at_ms": 1710000000000,
        "updated_at_ms": 1710000000000
      }
    ],
    "tiers": [
      {
        "tier_id": 2,
        "track": "wealth",
        "min_level": 11,
        "max_level": 20,
        "name": "财富11-20级",
        "display_avatar_frame_resource_id": 7001,
        "display_badge_resource_id": 7002,
        "reward_resource_group_id": 9002,
        "status": "active",
        "display_config_json": "{}",
        "created_by_admin_id": 1,
        "updated_by_admin_id": 1,
        "created_at_ms": 1710000000000,
        "updated_at_ms": 1710000000000
      }
    ],
    "server_time_ms": 1710000000000
  }
}
```

`rules[]`：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `track` | string | 等级轨道 |
| `level` | int | 等级 |
| `required_value` | int64 | 达到该等级需要的累计值 |
| `name` | string | 等级展示名 |
| `status` | string | `active` / `disabled` |
| `reward_resource_group_id` | int64 | 达到该级发放的资源组 ID，没有则为 `0` |
| `sort_order` | int | 排序 |
| `display_config_json` | string | JSON 字符串；长徽章素材取 `long_badge_resource_id` |
| `created_at_ms` | int64 | 创建时间 |
| `updated_at_ms` | int64 | 更新时间 |

`tiers[]`：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `tier_id` | int64 | 等级层 ID |
| `track` | string | 等级轨道 |
| `min_level` | int | 起始等级 |
| `max_level` | int | 结束等级 |
| `name` | string | 等级层展示名 |
| `display_avatar_frame_resource_id` | int64 | 该等级层头像框资源 ID，没有则为 `0` |
| `display_badge_resource_id` | int64 | 该等级层徽章资源 ID，没有则为 `0` |
| `reward_resource_group_id` | int64 | 进入该等级层发放的资源组 ID，没有则为 `0` |
| `status` | string | `active` / `disabled` |
| `display_config_json` | string | JSON 字符串 |
| `created_at_ms` | int64 | 创建时间 |
| `updated_at_ms` | int64 | 更新时间 |

### 我的等级奖励记录

```json
{
  "code": "OK",
  "message": "ok",
  "request_id": "req_xxx",
  "data": {
    "rewards": [
      {
        "reward_job_id": "greward_xxx",
        "user_id": 10001,
        "track": "wealth",
        "reward_source_type": "level",
        "reward_source_id": "13",
        "resource_group_id": 9001,
        "wallet_command_id": "growth_level_reward:greward_xxx",
        "wallet_grant_id": "grant_xxx",
        "status": "granted",
        "attempt_count": 1,
        "failure_reason": "",
        "created_at_ms": 1710000000000,
        "updated_at_ms": 1710000001000
      }
    ],
    "total": 1,
    "page": 1,
    "page_size": 20
  }
}
```

`rewards[]`：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `reward_job_id` | string | 奖励任务 ID |
| `user_id` | int64 | 当前用户 ID |
| `track` | string | 等级轨道 |
| `reward_source_type` | string | `level` 单级奖励；`tier` 等级层奖励 |
| `reward_source_id` | string | `level` 时为等级；`tier` 时为等级层 ID |
| `resource_group_id` | int64 | 发放资源组 ID |
| `wallet_command_id` | string | 钱包发放幂等命令 ID |
| `wallet_grant_id` | string | 钱包发放记录 ID；未成功时为空 |
| `status` | string | `pending` / `running` / `granted` / `failed` |
| `attempt_count` | int | 发放尝试次数 |
| `failure_reason` | string | 失败原因 |
| `created_at_ms` | int64 | 创建时间 |
| `updated_at_ms` | int64 | 更新时间 |

## 错误处理

错误 envelope：

```json
{
  "code": "INVALID_ARGUMENT",
  "message": "invalid argument",
  "request_id": "req_xxx"
}
```

| HTTP | `code` | 场景 | Flutter 处理 |
| --- | --- | --- | --- |
| `400` | `INVALID_ARGUMENT` | `track` 非法、`page` / `page_size` 非正整数 | 不重试，修正参数 |
| `401` | `UNAUTHORIZED` | token 缺失、无效或过期 | 重新登录或刷新 token |
| `403` | `PROFILE_REQUIRED` | 当前用户资料未完成 | 跳转资料补全 |
| `404` | `NOT_FOUND` | 后端返回目标不存在 | 刷新页面或展示空态 |
| `409` | `CONFLICT` | 后端状态冲突 | 刷新后重试 |
| `502` | `UPSTREAM_ERROR` | activity-service / wallet-service 暂不可用 | 可提示稍后重试 |
| `500` | `INTERNAL_ERROR` | 服务端内部错误 | 记录 `request_id` |

只有 `code == "OK"` 时读取 `data`。

## IM 消息接收

当前无等级专属 `TIMCustomElem`。

等级奖励资源组内包含钱包资产时，可能收到钱包 C2C 自定义消息：

| 字段 | 值 |
| --- | --- |
| `MsgType` | `TIMCustomElem` |
| `Desc` | `WalletBalanceChanged` |
| `Ext` / `extension` | `wallet_notice` |
| `CloudCustomData` | 与 `TIMCustomElem.Data` 相同的 JSON 字符串 |

`TIMCustomElem.Data`：

```json
{
  "event_type": "WalletBalanceChanged",
  "event_id": "wev_xxx",
  "app_code": "lalu",
  "transaction_id": "tx_xxx",
  "command_id": "resource_group_grant:growth_level_reward:greward_xxx:item:1:asset:COIN",
  "user_id": "10001",
  "asset_type": "COIN",
  "available_delta": 100,
  "frozen_delta": 0,
  "available_after": 1000,
  "frozen_after": 0,
  "balance_version": 18,
  "created_at_ms": 1710000000000,
  "source_created_at_ms": 1710000000000,
  "metadata": {}
}
```

处理：

| 条件 | 处理 |
| --- | --- |
| `event_type != "WalletBalanceChanged"` | 忽略 |
| `event_id` 已处理 | 忽略 |
| `asset_type == "COIN"` | 更新金币余额 |
| `command_id` 包含 `growth_level_reward` | 刷新 `/api/v1/levels/me/overview` 和 `/api/v1/levels/rewards` |
