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

82 lines
6.4 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.
## 1. 数据库迁移:全局标识符注册表
- [x] 1.1 创建迁移文件:`tb_asset_identifier`identifier UNIQUE、asset_type、asset_id、created_at建立 `idx_asset_identifier_asset(asset_type, asset_id)` 索引
- [x] 1.2 执行迁移,验证表结构和唯一约束正确
## 2. 数据层AssetIdentifier 模型与 Store
- [x] 2.1 创建 `internal/model/asset_identifier.go`:定义 `AssetIdentifier` 结构体(含 GORM 标签、TableName
- [x] 2.2 创建 `internal/store/postgres/asset_identifier_store.go`:实现 `Register(ctx, identifier, assetType, assetID)`(写入注册表)、`FindByIdentifier(ctx, identifier)`(精确查询)、`DeleteByAsset(ctx, assetType, assetID)`(删除资产时清理)
- [x] 2.3 在 `internal/bootstrap/stores.go` 注入 `AssetIdentifierStore`
## 3. 数据层Order 模型新增 asset_identifier 字段
- [x] 3.1 创建迁移文件:`tb_order` 表增加 `asset_identifier VARCHAR(100)` 字段
- [x] 3.2 更新 `internal/model/order.go`:新增 `AssetIdentifier string` 字段(含 GORM 标签和注释)
- [x] 3.3 更新 `internal/store/postgres/order_store.go`:列表查询新增 `identifier` 过滤条件(精确匹配 `asset_identifier`
## 4. 服务层Resolve 改走注册表
- [x] 4.1 更新 `internal/service/asset/service.go``Resolve` 方法优先查 `AssetIdentifierStore.FindByIdentifier()`,命中则按 asset_type 查对应表;未命中 fallback 原有逻辑
- [x] 4.2 在 `AssetService` 中注入 `AssetIdentifierStore` 依赖
## 5. 服务层purchase_validation 增加 is_standalone 校验
- [x] 5.1 更新 `internal/service/purchase_validation/service.go`:在 `ValidateCardPurchase()` 中增加 `if !card.IsStandalone { return error "该卡已绑定设备,请前往设备页面购买套餐" }` 校验(在套餐校验之前执行)
## 6. 服务层:资产创建/删除时同步注册表
- [x] 6.1 更新设备导入任务 `internal/task/device_import.go`:创建设备成功后在同一事务内调用 `AssetIdentifierStore.Register()` 注册 VirtualNo若冲突则拒绝该行
- [x] 6.2 更新 IoT 卡导入任务 `internal/task/iot_card_import.go`:创建卡成功后注册 ICCID必填和 VirtualNo如有到注册表冲突则拒绝该行
- [x] 6.3 更新设备/IoT 卡的软删除逻辑:删除时调用 `AssetIdentifierStore.DeleteByAsset()` 清理注册表
## 7. 服务层:订单创建改用 identifier
- [x] 7.1 更新 `internal/service/order/service.go`B 端):`Create()` 方法接收 `identifier` 字符串,内部调用 `AssetService.Resolve()` 解析资产,不再接受 `iot_card_id`/`device_id`;写入订单时填入 `asset_identifier` 快照字段
- [x] 7.2 更新 `internal/service/client_order/service.go`C 端):同上,接收 `identifier`,解析后写入 `asset_identifier` 快照
## 8. DTO 层:更新请求/响应结构
- [x] 8.1 更新 `internal/model/dto/order_dto.go``CreateAdminOrderRequest` 去掉 `IotCardID`/`DeviceID`/`OrderType`,新增 `Identifier string``OrderResponse` 新增 `AssetIdentifier string``AssetType string``OrderListRequest` 新增 `Identifier string`
- [x] 8.2 更新 `internal/model/dto/client_order_dto.go``CreateOrderRequest`):同上,去掉 `IotCardID`/`DeviceID`/`OrderType`,新增 `Identifier string`
- [x] 8.3 更新 `internal/model/dto/asset_dto.go``AssetTypeIDRequest` 废弃,`AssetResolveRequest` 确认 `Identifier` 字段已有;新增 `AssetIdentifierRequest`(路径参数,供操作接口使用)
- [x] 8.4 确认 `AssetResolveResponse` 新增 `Identifier` 字段(回传请求中使用的标识符)
## 9. Handler 层B 端资产操作接口更新
- [x] 9.1 更新 `internal/handler/admin/asset.go``RealtimeStatus`/`Refresh`/`Packages`/`CurrentPackage`/`UpdatePollingStatus`/`StopDevice`/`StartDevice`/`StopCard`/`StartCard` 均改为从路径参数 `:identifier` 读取标识符,调用 Resolve 解析后操作;合并 Stop/Start 卡和设备为统一的 `Stop`/`Start` handler内部按资产类型分支
- [x] 9.2 更新 `internal/handler/admin/asset.go`:新增 `Deactivate` handler合并 IoT 卡和设备停用,原 `/iot-cards/:id/deactivate``/devices/:id/deactivate`
- [x] 9.3 更新 `internal/handler/admin/device.go``Delete`/`ListCards`/`BindCard`/`UnbindCard` 改为从路径参数 `:virtual_no` 读取设备标识;`UnbindCard` 第二段路径参数改为 `:iccid`
## 10. Handler 层B 端订单 Handler 更新
- [x] 10.1 更新 `internal/handler/admin/order.go``Create` handler 解析 `CreateAdminOrderRequest.Identifier` 字段,移除 `IotCardID`/`DeviceID` 解析逻辑;`List` handler 支持 `identifier` query 参数传入 Store 过滤
## 11. Handler 层C 端订单 Handler 更新
- [x] 11.1 更新 `internal/handler/app/client_order.go``CreateOrder` handler 解析 `CreateOrderRequest.Identifier`,移除旧字段解析
## 12. 路由层:重注册所有受影响路由
- [x] 12.1 更新 `internal/routes/asset.go`:将所有 `:asset_type/:id` 路由改为 `:identifier`;合并卡/设备的 stop/start/deactivate 路由
- [x] 12.2 更新 `internal/routes/device.go``/:id` 改为 `/:virtual_no`;解绑路由 `/:id/cards/:cardId` 改为 `/:virtual_no/cards/:iccid`
- [x] 12.3 更新 `internal/routes/iot_card.go`:移除 `/:id/deactivate`(已并入 asset.go 的统一路由)
- [x] 12.4 更新 `internal/routes/order.go`:无路由变更,仅确认 Handler 已更新
## 13. 文档生成器更新
- [x] 13.1 更新 `cmd/api/docs.go``cmd/gendocs/main.go`:确保新 Handler 方法已注册Deactivate 等新合并 handler 需加入)
- [x] 13.2 执行 `go run cmd/gendocs/main.go` 重新生成 OpenAPI 文档,验证新路由和 DTO 正确体现
## 14. 验证
- [x] 14.1 构建验证:`go build ./...` 无编译错误
- [x] 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` 返回该资产的订单