Files
junhong_cmp_fiber/openspec/changes/archive/2026-04-07-asset-identifier-standardization/tasks.md
huang 80c6f6c756
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m21s
feat: 资产标识符标准化、资产历史订单查询及导入虚拟号强制验证
主要变更:
- 新增 AssetIdentifier 模型及 Store,统一管理资产标识符(ICCID/IMEI/SN 等)
- 新增迁移:asset_identifier 表、order 表新增 asset_identifier 字段、iot_card.virtual_no NOT NULL 约束
- 资产 Handler/Service/Route 全面重构,支持标识符路由查询与解析
- 新增资产历史订单查询接口,支持跨设备/卡/钱包维度的订单聚合
- 设备与物联卡导入任务强制校验虚拟号,缺失时直接拒绝
- Excel 工具函数优化,前端导入指引文档同步更新
- 归档三个 OpenSpec 提案:asset-identifier-standardization、asset-historical-orders、import-mandatory-virtual-no
- 更新 OpenAPI 文档及相关 DTO

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-07 17:39:36 +08:00

6.4 KiB
Raw Blame History

1. 数据库迁移:全局标识符注册表

  • 1.1 创建迁移文件:tb_asset_identifieridentifier UNIQUE、asset_type、asset_id、created_at建立 idx_asset_identifier_asset(asset_type, asset_id) 索引
  • 1.2 执行迁移,验证表结构和唯一约束正确

2. 数据层AssetIdentifier 模型与 Store

  • 2.1 创建 internal/model/asset_identifier.go:定义 AssetIdentifier 结构体(含 GORM 标签、TableName
  • 2.2 创建 internal/store/postgres/asset_identifier_store.go:实现 Register(ctx, identifier, assetType, assetID)(写入注册表)、FindByIdentifier(ctx, identifier)(精确查询)、DeleteByAsset(ctx, assetType, assetID)(删除资产时清理)
  • 2.3 在 internal/bootstrap/stores.go 注入 AssetIdentifierStore

3. 数据层Order 模型新增 asset_identifier 字段

  • 3.1 创建迁移文件:tb_order 表增加 asset_identifier VARCHAR(100) 字段
  • 3.2 更新 internal/model/order.go:新增 AssetIdentifier string 字段(含 GORM 标签和注释)
  • 3.3 更新 internal/store/postgres/order_store.go:列表查询新增 identifier 过滤条件(精确匹配 asset_identifier

4. 服务层Resolve 改走注册表

  • 4.1 更新 internal/service/asset/service.goResolve 方法优先查 AssetIdentifierStore.FindByIdentifier(),命中则按 asset_type 查对应表;未命中 fallback 原有逻辑
  • 4.2 在 AssetService 中注入 AssetIdentifierStore 依赖

5. 服务层purchase_validation 增加 is_standalone 校验

  • 5.1 更新 internal/service/purchase_validation/service.go:在 ValidateCardPurchase() 中增加 if !card.IsStandalone { return error "该卡已绑定设备,请前往设备页面购买套餐" } 校验(在套餐校验之前执行)

6. 服务层:资产创建/删除时同步注册表

  • 6.1 更新设备导入任务 internal/task/device_import.go:创建设备成功后在同一事务内调用 AssetIdentifierStore.Register() 注册 VirtualNo若冲突则拒绝该行
  • 6.2 更新 IoT 卡导入任务 internal/task/iot_card_import.go:创建卡成功后注册 ICCID必填和 VirtualNo如有到注册表冲突则拒绝该行
  • 6.3 更新设备/IoT 卡的软删除逻辑:删除时调用 AssetIdentifierStore.DeleteByAsset() 清理注册表

7. 服务层:订单创建改用 identifier

  • 7.1 更新 internal/service/order/service.goB 端):Create() 方法接收 identifier 字符串,内部调用 AssetService.Resolve() 解析资产,不再接受 iot_card_id/device_id;写入订单时填入 asset_identifier 快照字段
  • 7.2 更新 internal/service/client_order/service.goC 端):同上,接收 identifier,解析后写入 asset_identifier 快照

8. DTO 层:更新请求/响应结构

  • 8.1 更新 internal/model/dto/order_dto.goCreateAdminOrderRequest 去掉 IotCardID/DeviceID/OrderType,新增 Identifier stringOrderResponse 新增 AssetIdentifier stringAssetType stringOrderListRequest 新增 Identifier string
  • 8.2 更新 internal/model/dto/client_order_dto.goCreateOrderRequest):同上,去掉 IotCardID/DeviceID/OrderType,新增 Identifier string
  • 8.3 更新 internal/model/dto/asset_dto.goAssetTypeIDRequest 废弃,AssetResolveRequest 确认 Identifier 字段已有;新增 AssetIdentifierRequest(路径参数,供操作接口使用)
  • 8.4 确认 AssetResolveResponse 新增 Identifier 字段(回传请求中使用的标识符)

9. Handler 层B 端资产操作接口更新

  • 9.1 更新 internal/handler/admin/asset.goRealtimeStatus/Refresh/Packages/CurrentPackage/UpdatePollingStatus/StopDevice/StartDevice/StopCard/StartCard 均改为从路径参数 :identifier 读取标识符,调用 Resolve 解析后操作;合并 Stop/Start 卡和设备为统一的 Stop/Start handler内部按资产类型分支
  • 9.2 更新 internal/handler/admin/asset.go:新增 Deactivate handler合并 IoT 卡和设备停用,原 /iot-cards/:id/deactivate/devices/:id/deactivate
  • 9.3 更新 internal/handler/admin/device.goDelete/ListCards/BindCard/UnbindCard 改为从路径参数 :virtual_no 读取设备标识;UnbindCard 第二段路径参数改为 :iccid

10. Handler 层B 端订单 Handler 更新

  • 10.1 更新 internal/handler/admin/order.goCreate handler 解析 CreateAdminOrderRequest.Identifier 字段,移除 IotCardID/DeviceID 解析逻辑;List handler 支持 identifier query 参数传入 Store 过滤

11. Handler 层C 端订单 Handler 更新

  • 11.1 更新 internal/handler/app/client_order.goCreateOrder handler 解析 CreateOrderRequest.Identifier,移除旧字段解析

12. 路由层:重注册所有受影响路由

  • 12.1 更新 internal/routes/asset.go:将所有 :asset_type/:id 路由改为 :identifier;合并卡/设备的 stop/start/deactivate 路由
  • 12.2 更新 internal/routes/device.go/:id 改为 /:virtual_no;解绑路由 /:id/cards/:cardId 改为 /:virtual_no/cards/:iccid
  • 12.3 更新 internal/routes/iot_card.go:移除 /:id/deactivate(已并入 asset.go 的统一路由)
  • 12.4 更新 internal/routes/order.go:无路由变更,仅确认 Handler 已更新

13. 文档生成器更新

  • 13.1 更新 cmd/api/docs.gocmd/gendocs/main.go:确保新 Handler 方法已注册Deactivate 等新合并 handler 需加入)
  • 13.2 执行 go run cmd/gendocs/main.go 重新生成 OpenAPI 文档,验证新路由和 DTO 正确体现

14. 验证

  • 14.1 构建验证:go build ./... 无编译错误
  • 14.2 LSP 诊断:对所有修改文件运行 lsp_diagnostics无错误/警告
  • 14.3 接口验证(使用 PostgreSQL MCP 和 curl
    • 通过 ICCID 调用 GET /api/admin/assets/:identifier/packages 返回正确套餐列表
    • 通过 VirtualNo 调用 POST /api/admin/assets/:identifier/stop 执行停机
    • 使用绑定设备的卡 identifier 创建订单,验证返回"该卡已绑定设备"错误
    • 使用独立卡 identifier 创建订单成功OrderResponse 包含 asset_identifier 字段
    • 并发写入同一 VirtualNo 到注册表,验证只有一条成功
  • 14.4 订单列表验证:GET /api/admin/orders?identifier=CARD-001 返回该资产的订单