Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
6.6 KiB
asset-queries Specification
Purpose
提供基于已知资产 ID 的轻量查询接口,包括实时状态查询、手动刷新、套餐历史列表和当前主套餐详情。供前端在 resolve 之后进行快速轮询和详情展示。
Requirements
Requirement: 轻量实时状态查询
系统 SHALL 提供基于持久化数据的轻量状态查询接口,供前端在已知资产 ID 后进行快速轮询。
API 端点: GET /api/admin/assets/:asset_type/:id/realtime-status
约束:
:asset_type取值为device或card- 此接口不调用网关,仅读取 DB/Redis 中持久化的最新数据
- 不包含套餐流量计算(与 resolve 的区别)
- "实时性"依赖轮询系统定期刷新(实名状态约 5 分钟,流量约 10 分钟)
card 类型响应字段:
network_status: 网络状态(0-停机 1-开机)real_name_status: 实名状态(0-未实名 1-已实名)current_month_usage_mb: 本月已用流量(持久化缓存值)last_sync_at: 最后与 Gateway 同步时间
device 类型响应字段:
device_protect_status: 保护期状态("none"/"stop"/"start")cards: 所有绑定卡的状态列表(同 DeviceCardInfo 结构)
Scenario: 查询单卡实时状态
- WHEN 管理员调用
GET /api/admin/assets/card/123/realtime-status - THEN 系统返回该卡的 network_status、real_name_status、current_month_usage_mb、last_sync_at
Scenario: 查询设备实时状态
- WHEN 管理员调用
GET /api/admin/assets/device/456/realtime-status - THEN 系统返回设备的保护期状态及所有绑定卡的当前状态列表
Scenario: asset_type 参数非法
- WHEN 管理员调用
GET /api/admin/assets/unknown-type/123/realtime-status - THEN 系统返回 HTTP 400 参数错误
Requirement: 手动刷新接口
系统 SHALL 提供手动触发网关同步的接口,用于客服主动刷新资产最新状态。
API 端点: POST /api/admin/assets/:asset_type/:id/refresh
行为规则:
- card 类型:直接调用
RefreshCardDataFromGateway(iccid)同步网络状态、实名状态、本月流量、最后同步时间 - device 类型:对该设备所有绑定卡遍历调用
RefreshCardDataFromGateway
设备类型频率限制:
- 使用 Redis Key
RedisDeviceRefreshCooldownKey(deviceID)限频 - 同一设备 30 秒冷却期内不允许重复触发
- 冷却期内调用返回 HTTP 429
响应:
- 刷新完成后返回刷新后的最新状态(与 realtime-status 响应结构相同)
Scenario: 刷新单卡状态
- WHEN 客服调用
POST /api/admin/assets/card/123/refresh - THEN 系统调用 RefreshCardDataFromGateway,更新 DB 中的卡状态字段,返回刷新后的最新状态
Scenario: 刷新设备状态(首次)
- WHEN 管理员调用
POST /api/admin/assets/device/456/refresh,该设备有 3 张绑定卡 - THEN 系统依次刷新 3 张卡,设置 30 秒冷却期,返回最新状态
Scenario: 设备刷新冷却期内重复触发
- WHEN 管理员在 30 秒冷却期内第二次调用
POST /api/admin/assets/device/456/refresh - THEN 系统返回 HTTP 429,提示"刷新过于频繁,请稍后再试"
Requirement: 套餐历史列表查询
系统 SHALL 提供资产的全量套餐记录查询接口,包含历史和当前生效套餐。
API 端点: GET /api/admin/assets/:asset_type/:id/packages
排序: 按 created_at 倒序(最新套餐在前)
分页: 不分页,全量返回
范围: 包含所有状态(含 status=4 已失效的历史套餐)
按 asset_type 区分查询:
- card:查询
PackageUsage.iot_card_id = :id - device:查询
PackageUsage.device_id = :id
每条记录响应字段:
package_usage_id: 套餐使用记录 IDpackage_name: 套餐名称package_type: 套餐类型(formal/addon)master_usage_id: 主套餐 ID(加油包时有值,主套餐时为 null)real_data_mb: 真总流量(MB)virtual_data_mb: 虚总流量/停机阈值(MB)package_used_mb: 展示已使用流量(经虚流量换算)package_remain_mb: 展示剩余流量activated_at: 生效时间expires_at: 过期时间status: 套餐状态(0-待生效 1-生效中 2-已用完 3-已过期 4-已失效)
Scenario: 查询卡的套餐历史
- WHEN 管理员调用
GET /api/admin/assets/card/123/packages,该卡有 3 条套餐记录(含 1 条已失效) - THEN 系统返回全部 3 条记录,按创建时间倒序排列
Scenario: 查询设备的套餐历史
- WHEN 管理员调用
GET /api/admin/assets/device/456/packages - THEN 系统返回该设备 device_id 下的所有套餐记录
Scenario: 资产无套餐记录
- WHEN 管理员查询一张从未购买过套餐的卡
- THEN 系统返回空数组,不报错
Requirement: 当前主套餐详情查询
系统 SHALL 提供查询资产当前生效主套餐的接口,用于展示套餐详细信息。
API 端点: GET /api/admin/assets/:asset_type/:id/current-package
查询条件: status = 1(生效中)AND master_usage_id IS NULL
多套餐同时生效时:只返回主套餐(master_usage_id IS NULL),不返回加油包
响应字段:
- 完整套餐信息(同套餐历史列表中的单条记录字段)
- 当无生效主套餐时,返回 HTTP 404
Scenario: 返回当前主套餐
- WHEN 管理员调用
GET /api/admin/assets/card/123/current-package,该卡有 1 个生效主套餐和 1 个加油包 - THEN 系统只返回主套餐信息,不包含加油包
Scenario: 无当前生效主套餐
- WHEN 管理员查询没有生效中主套餐的资产
- THEN 系统返回 HTTP 404
Requirement: RefreshCardDataFromGateway 完整同步
系统 SHALL 提供从 Gateway 完整同步卡数据的方法,替代原 SyncCardStatusFromGateway(仅为示例实现)。
方法签名: RefreshCardDataFromGateway(ctx context.Context, iccid string) error
同步字段:
network_status: 网络状态(从网关卡状态映射)real_name_status: 实名状态(从网关实名接口获取)current_month_usage_mb: 本月已用流量(从网关流量接口获取)last_sync_time: 更新为当前时间
错误处理: 网关调用失败时记录 Error 日志并返回错误,不更新 DB
Scenario: 完整同步卡数据
- WHEN 调用
RefreshCardDataFromGateway(ctx, "89860123456789012345") - THEN 系统调用网关接口,将 network_status、real_name_status、current_month_usage_mb、last_sync_time 写回 DB