## 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` 返回该资产的订单