# 资产详情重构 API 变更说明

> 适用版本：asset-detail-refactor 提案上线后
> 文档更新：2026-03-14

---

## 一、现有接口字段变更

### 1. `device_no` 重命名为 `virtual_no`

所有涉及设备标识符的接口，响应中的 `device_no` 字段已统一改名为 `virtual_no`，**JSON key 同步变更**，前端需全局替换。

受影响接口：

| 接口 | 变更字段 |
|------|---------|
| `GET /api/admin/devices`（列表/详情响应） | `device_no` → `virtual_no` |
| `GET /api/admin/devices/import/tasks/:id` | `failed_items[].device_no` → `virtual_no` |
| `GET /api/admin/enterprises/:id/devices`（企业设备列表） | `device_no` → `virtual_no` |
| `GET /api/admin/shop-commission/records` | `device_no` → `virtual_no` |
| `GET /api/admin/my-commission/records` | `device_no` → `virtual_no` |
| 企业卡授权相关响应中的设备字段 | `device_no` → `virtual_no` |

---

### 2. 套餐接口新增 `virtual_ratio` 字段

`GET /api/admin/packages` 及套餐详情响应新增：

| 新增字段 | 类型 | 说明 |
|---------|------|------|
| `virtual_ratio` | float64 | 虚流量比例（real_data_mb / virtual_data_mb）。启用虚流量时计算，否则为 1.0 |

---

### 3. IoT 卡接口新增 `virtual_no` 字段

卡列表/详情响应新增：

| 新增字段 | 类型 | 说明 |
|---------|------|------|
| `virtual_no` | string | 虚拟号（可空） |

---

## 二、新增接口

### 基础说明

- 资产相关路由已统一为 `:identifier` 口径（虚拟号 / ICCID / IMEI / SN / MSISDN）
- 企业账号调用 `resolve` 接口会返回 403

---

### `GET /api/admin/assets/resolve/:identifier`

通过任意标识符查询设备或卡的完整详情。支持虚拟号、ICCID、IMEI、SN、MSISDN。

**响应字段：**

| 字段 | 类型 | 说明 |
|------|------|------|
| `asset_type` | string | `card` 或 `device` |
| `asset_id` | uint | 数据库 ID |
| `virtual_no` | string | 虚拟号 |
| `status` | int | 资产状态 |
| `batch_no` | string | 批次号 |
| `shop_id` | uint | 所属店铺 ID |
| `shop_name` | string | 所属店铺名称 |
| `series_id` | uint | 套餐系列 ID |
| `series_name` | string | 套餐系列名称 |
| `real_name_status` | int | 实名状态：0 未实名 / 1 实名中 / 2 已实名 |
| `network_status` | int | 网络状态：0 停机 / 1 开机（仅 card） |
| `current_package` | string | 当前套餐名称（无则空） |
| `current_package_usage_id` | uint | 当前主套餐的套餐使用记录 ID（无主套餐时为 null） |
| `real_total_mb` | int64 | 当前主套餐真实总量 MB |
| `real_used_mb` | int64 | 当前主套餐真实已用量 MB |
| `virtual_total_mb` | int64 | 当前主套餐业务停机阈值 MB |
| `virtual_used_mb` | float64 | 当前主套餐展示已用量 MB |
| `reduction_pct` | float64 | 展示增幅比例，公式为 `(real_total_mb / virtual_total_mb) - 1` |
| `enable_virtual_data` | bool | 当前主套餐是否启用虚流量（按套餐使用记录快照返回） |
| `device_protect_status` | string | 保护期状态：`none` / `stop` / `start`（仅 device） |
| `activated_at` | time | 激活时间（未来准备移除，请勿依赖） |
| `created_at` | time | 创建时间 |
| `updated_at` | time | 更新时间 |
| **绑定关系（card 时）** | | |
| `iccid` | string | 卡 ICCID |
| `bound_device_id` | uint | 绑定设备 ID |
| `bound_device_no` | string | 绑定设备虚拟号 |
| `bound_device_name` | string | 绑定设备名称 |
| **绑定关系（device 时）** | | |
| `bound_card_count` | int | 绑定卡数量 |
| `cards[]` | array | 绑定卡列表，每项含：`card_id` / `iccid` / `msisdn` / `network_status` / `real_name_status` / `slot_position` |
| **设备专属字段（card 时为空）** | | |
| `device_name` | string | 设备名称 |
| `imei` | string | IMEI |
| `sn` | string | 序列号 |
| `device_model` | string | 设备型号 |
| `device_type` | string | 设备类型 |
| `max_sim_slots` | int | 最大插槽数 |
| `manufacturer` | string | 制造商 |
| **卡专属字段（device 时为空）** | | |
| `carrier_type` | string | 运营商类型 |
| `carrier_name` | string | 运营商名称 |
| `msisdn` | string | 手机号 |
| `imsi` | string | IMSI |
| `card_category` | string | 卡业务类型 |
| `supplier` | string | 供应商 |
| `activation_status` | int | 激活状态 |
| `enable_polling` | bool | 是否参与轮询 |

---

### `GET /api/admin/assets/:identifier/realtime-status`

读取资产实时状态（直接读 DB/Redis，不调网关）。

**响应字段：**

| 字段 | 类型 | 说明 |
|------|------|------|
| `asset_type` | string | `card` 或 `device` |
| `asset_id` | uint | 资产 ID |
| `network_status` | int | 网络状态（仅 card） |
| `real_name_status` | int | 实名状态（仅 card） |
| `current_month_usage_mb` | float64 | 本月已用流量 MB（仅 card） |
| `last_sync_time` | time | 最后同步时间（仅 card） |
| `device_protect_status` | string | 保护期：`none` / `stop` / `start`（仅 device） |
| `cards[]` | array | 所有绑定卡的状态（仅 device），同 resolve 的 cards 结构 |

---

### `POST /api/admin/assets/:identifier/refresh`

主动调网关拉取最新数据后返回，响应结构与 `realtime-status` 完全相同。

> 设备有 **30 秒冷却期**，冷却中调用返回 429。

---

### `GET /api/admin/assets/:identifier/packages`

查询该资产所有套餐记录，含虚流量换算字段。

**响应为数组，每项字段：**

| 字段 | 类型 | 说明 |
|------|------|------|
| `package_usage_id` | uint | 套餐使用记录 ID |
| `package_id` | uint | 套餐 ID |
| `package_name` | string | 套餐名称 |
| `package_type` | string | `formal`（正式套餐）/ `addon`（加油包） |
| `status` | int | 0 待生效 / 1 生效中 / 2 已用完 / 3 已过期 / 4 已失效 |
| `status_name` | string | 状态中文名 |
| `real_total_mb` | int64 | 套餐真实总量 MB |
| `real_used_mb` | int64 | 套餐真实已用量 MB |
| `virtual_total_mb` | int64 | 套餐业务停机阈值 MB |
| `virtual_used_mb` | float64 | 套餐展示已用量 MB |
| `reduction_pct` | float64 | 展示增幅比例，公式为 `(real_total_mb / virtual_total_mb) - 1` |
| `enable_virtual_data` | bool | 是否启用虚流量（按套餐使用记录快照返回） |
| `activated_at` | time | 激活时间 |
| `expires_at` | time | 到期时间 |
| `master_usage_id` | uint | 主套餐 ID（加油包时有值） |
| `priority` | int | 优先级 |
| `created_at` | time | 创建时间 |

---

### `GET /api/admin/assets/:identifier/current-package`

查询当前生效中的主套餐，响应结构同 `packages` 数组的单项。无生效套餐时返回 `200 + null`。

---

### `POST /api/admin/assets/device/:device_id/stop`

批量停机设备下所有已实名卡，停机成功后设置 **1 小时停机保护期**（保护期内禁止复机）。

**响应字段：**

| 字段 | 类型 | 说明 |
|------|------|------|
| `message` | string | 操作结果描述 |
| `success_count` | int | 成功停机的卡数量 |
| `failed_cards[]` | array | 停机失败列表，每项含 `iccid` 和 `reason` |

---

### `POST /api/admin/assets/device/:device_id/start`

批量复机设备下所有已实名卡，复机成功后设置 **1 小时复机保护期**（保护期内禁止停机）。

无响应 body，HTTP 200 即成功。

---

### `POST /api/admin/assets/card/:iccid/stop`

手动停机单张卡（通过 ICCID）。若卡绑定的设备在**复机保护期**内，返回 403。

无响应 body，HTTP 200 即成功。

---

### `POST /api/admin/assets/card/:iccid/start`

手动复机单张卡（通过 ICCID）。若卡绑定的设备在**停机保护期**内，返回 403。

无响应 body，HTTP 200 即成功。

---

## 三、删除的接口

### IoT 卡

| 删除的接口 | 替代接口 |
|-----------|---------|
| `GET /api/admin/iot-cards/by-iccid/:iccid` | `GET /api/admin/assets/resolve/:iccid` |
| `GET /api/admin/iot-cards/:iccid/gateway-status` | `GET /api/admin/assets/card/:id/realtime-status` |
| `GET /api/admin/iot-cards/:iccid/gateway-flow` | `GET /api/admin/assets/card/:id/realtime-status` |
| `GET /api/admin/iot-cards/:iccid/gateway-realname` | `GET /api/admin/assets/card/:id/realtime-status` |
| `POST /api/admin/iot-cards/:iccid/stop` | `POST /api/admin/assets/card/:iccid/stop` |
| `POST /api/admin/iot-cards/:iccid/start` | `POST /api/admin/assets/card/:iccid/start` |

### 设备

| 删除的接口 | 替代接口 |
|-----------|---------|
| `GET /api/admin/devices/:id` | `GET /api/admin/assets/resolve/:virtual_no` |
| `GET /api/admin/devices/by-identifier/:identifier` | `GET /api/admin/assets/resolve/:identifier` |
| `GET /api/admin/devices/by-identifier/:identifier/gateway-info` | `GET /api/admin/assets/device/:id/realtime-status` |

### 企业卡（Admin）

| 删除的接口 | 替代接口 |
|-----------|---------|
| `POST /api/admin/enterprises/:id/cards/:card_id/suspend` | `POST /api/admin/assets/card/:iccid/stop` |
| `POST /api/admin/enterprises/:id/cards/:card_id/resume` | `POST /api/admin/assets/card/:iccid/start` |

### 企业设备（H5）

| 删除的接口 | 替代接口 |
|-----------|---------|
| `POST /api/h5/enterprise/devices/:device_id/suspend-card` | `POST /api/admin/assets/device/:device_id/stop` |
| `POST /api/h5/enterprise/devices/:device_id/resume-card` | `POST /api/admin/assets/device/:device_id/start` |

---

## 四、新增错误码说明

| HTTP 状态码 | 触发场景 |
|------------|---------|
| 403 | 设备在保护期内（停机 1h 内禁止复机，反之亦然）；企业账号调用 resolve 接口 |
| 404 | 标识符未匹配到任何资产；当前无生效套餐 |
| 429 | 设备刷新冷却中（30 秒内只能主动刷新一次） |
