Files
junhong_cmp_fiber/openspec/specs/asset-resolve/spec.md
huang b9c3875c08
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s
feat: 新增数据库迁移,重命名 device_no 为 virtual_no,新增 iot_card.virtual_no 和 package.virtual_ratio 字段
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-14 18:27:28 +08:00

139 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# asset-resolve Specification
## Purpose
提供统一的资产解析入口,通过任意标识符(虚拟号/ICCID/IMEI/SN/MSISDN定位卡或设备并返回该资产的中等聚合信息包含套餐流量、保护期状态和绑定关系。
## Requirements
### Requirement: 统一资产解析入口
系统 SHALL 提供统一的资产查找接口,通过任意标识符定位卡或设备,并返回该资产的中等聚合信息。
**API 端点**: `GET /api/admin/assets/resolve/:identifier`
**查找顺序**:
1. 先在 `tb_device` 表查找(匹配 `virtual_no = ? OR imei = ? OR sn = ?`
2. 未命中则在 `tb_iot_card` 表查找(匹配 `virtual_no = ? OR iccid = ? OR msisdn = ?`
3. 两表均未命中 → 返回 HTTP 404
4. 找到后应用数据权限过滤,无权限 → 返回 HTTP 403
**数据权限规则**:
- 代理用户:只能查看 `shop_id` 在自己及下级店铺范围内的资产
- 平台用户SuperAdmin/Platform可查看所有资产
- 企业账号:暂不支持此接口,调用时返回 HTTP 403
**响应结构AssetResolveResponse**:
*通用字段device 和 card 均有)*:
- `asset_type`: 资产类型(`"device"``"card"`
- `asset_id`: 资产主键 ID
- `virtual_no`: 虚拟号(设备/卡均使用此字段)
- `status`: 资产状态(整型)
- `batch_no`: 批次号
- `shop_id`: 所属店铺 ID平台库存时为空
- `shop_name`: 所属店铺名称
- `series_id`: 套餐系列 ID未绑定时为空
- `series_name`: 套餐系列名称
- `first_commission_paid`: 一次性佣金是否已发放
- `accumulated_recharge`: 累计充值金额(分)
- `activated_at`: 激活时间(未激活时为空)
- `created_at`: 创建时间
- `updated_at`: 更新时间
*状态与套餐字段device 和 card 均有)*:
- `real_name_status`: 实名状态(整型)
- `current_package`: 当前套餐名称(无套餐时返回空字符串)
- `package_total_mb`: 真总流量,即 RealDataMB无套餐时返回 0
- `package_virtual_mb`: 虚总流量/停机阈值,即 VirtualDataMB无套餐时返回 0
- `package_used_mb`: 客户端展示已使用流量(经虚流量换算,见流量计算规则)
- `package_remain_mb`: 客户端展示剩余流量
- `device_protect_status`: 保护期状态(`"none"` / `"stop"` / `"start"`card 类型时若绑定的设备有保护期也返回该设备的保护期状态
*绑定关系字段*:
- `iccid`: 仅 card 类型时有值,供前端调用停复机接口使用
- `bound_device_id`: 仅 card 类型且卡绑定了设备时有值
- `bound_device_no`: 绑定设备的虚拟号
- `bound_device_name`: 绑定设备的名称
- `bound_card_count`: 仅 device 类型时有值,绑定卡的总数量
- `cards`: 仅 device 类型时有值,所有绑定卡列表(含未实名、已停用)
*设备专属档案字段asset_type=device 时有值card 类型时为空/零值)*:
- `device_name`: 设备名称
- `imei`: IMEI
- `sn`: 序列号
- `device_model`: 设备型号
- `device_type`: 设备类型
- `max_sim_slots`: 最大插槽数
- `manufacturer`: 制造商
*卡专属档案字段asset_type=card 时有值device 类型时为空/零值)*:
- `carrier_id`: 运营商 ID
- `carrier_type`: 运营商类型CMCC/CUCC/CTCC/CBN
- `carrier_name`: 运营商名称
- `msisdn`: 卡接入号
- `imsi`: IMSI
- `card_category`: 卡业务类型normal/industry
- `supplier`: 供应商
- `activation_status`: 激活状态0-未激活 1-已激活)
- `enable_polling`: 是否参与轮询
**DeviceCardInfo 结构**:
- `iot_card_id`: 卡 ID
- `iccid`: ICCID
- `virtual_no`: 卡的虚拟号
- `real_name_status`: 实名状态
- `network_status`: 网络状态
- `current_month_usage_mb`: 本月已用流量(来自持久化缓存字段)
- `last_sync_at`: 最后与 Gateway 同步时间
**流量展示计算规则**:
- `package_used_mb = current_month_usage_mb × virtual_ratio`
- `package_remain_mb = package_total_mb - package_used_mb`
-`enable_virtual_data = false` 时,`virtual_ratio = 1.0`(无换算)
- 设备级套餐:`current_month_usage_mb` 为所有绑定卡本月用量之和
**特殊情况处理**:
- 卡绑定的设备已被软删除:视为独立卡,不填充绑定信息
- `cards` 列表包含所有状态的绑定卡,不过滤未实名或已停用的卡
#### Scenario: 通过 ICCID 找到卡
- **WHEN** 管理员调用 `GET /api/admin/assets/resolve/89860123456789012345`ICCID 匹配到一张独立卡
- **THEN** 系统返回 `asset_type="card"`,包含该卡的虚拟号、状态、套餐流量信息,`bound_device_id` 为空
#### Scenario: 通过虚拟号找到设备
- **WHEN** 管理员调用 `GET /api/admin/assets/resolve/GPS-001`,设备表中 `virtual_no = "GPS-001"` 存在
- **THEN** 系统返回 `asset_type="device"`包含该设备的绑定卡列表DeviceCardInfo 数组),`bound_card_count` 为绑定卡总数
#### Scenario: 标识符同时命中设备和卡(设备优先)
- **WHEN** `GPS-001` 在 device 表和 iot_card 表均有匹配virtual_no 相同)
- **THEN** 系统返回设备信息device 优先),不返回卡信息
#### Scenario: 标识符未命中任何资产
- **WHEN** 管理员查询不存在的标识符 `UNKNOWN-999`
- **THEN** 系统返回 HTTP 404
#### Scenario: 代理用户查询无权限的资产
- **WHEN** 代理用户shop_id=10查询属于 shop_id=99非下级的设备
- **THEN** 系统返回 HTTP 403明确提示无权限
#### Scenario: 企业账号调用 resolve
- **WHEN** 企业账号调用 `GET /api/admin/assets/resolve/:identifier`
- **THEN** 系统返回 HTTP 403提示企业账号暂不支持此接口
#### Scenario: 卡绑定了有停机保护期的设备
- **WHEN** 管理员通过 ICCID 查询某张卡,该卡绑定的设备当前有 stop 保护期
- **THEN** 响应中 `device_protect_status = "stop"`,反映所属设备的保护期状态
#### Scenario: 设备无当前生效套餐
- **WHEN** 管理员查询一台没有购买任何套餐的设备
- **THEN** `current_package = ""``package_total_mb = 0``package_used_mb = 0``package_remain_mb = 0`