Files
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

6.6 KiB
Raw Permalink Blame History

asset-queries Specification

Purpose

提供基于已知资产 ID 的轻量查询接口,包括实时状态查询、手动刷新、套餐历史列表和当前主套餐详情。供前端在 resolve 之后进行快速轮询和详情展示。

Requirements

Requirement: 轻量实时状态查询

系统 SHALL 提供基于持久化数据的轻量状态查询接口,供前端在已知资产 ID 后进行快速轮询。

API 端点: GET /api/admin/assets/:asset_type/:id/realtime-status

约束:

  • :asset_type 取值为 devicecard
  • 此接口不调用网关,仅读取 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: 套餐使用记录 ID
  • package_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