主要变更: - 新增 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>
3.6 KiB
Context
设备:tb_device.virtual_no 已是 NOT NULL + UNIQUE(数据库层已强制)。但 pkg/utils/excel.go 中设备行解析遇到空 VirtualNo 会 continue(跳过该行),导入任务报告跳过数量但不报告失败原因,用户无法知道为什么某些行没被导入。
IoT 卡:tb_iot_card.virtual_no 当前为 nullable,唯一索引条件是 WHERE deleted_at IS NULL AND virtual_no IS NOT NULL AND virtual_no <> ''(允许多条 NULL 值)。导入时空 VirtualNo 的卡直接进库,不做任何处理。
两者均需要把"安静跳过/允许空值"改为"明确报错",并在数据库层为 IoT 卡补上 NOT NULL 约束。
Goals / Non-Goals
Goals:
- 导入时 VirtualNo 为空 → 报告为失败行,包含行号和原因
tb_iot_card.virtual_no加 NOT NULL 约束- 更新 Excel 解析层、任务处理层、模型层
Non-Goals:
- 为现有 NULL 记录自动生成 VirtualNo(测试阶段直接清库)
- IoT 卡导入的其他字段校验(不在本提案范围内)
- VirtualNo 格式校验(长度/字符集约束,留给未来)
Decisions
决策 1:在 Excel 解析层(pkg/utils/excel.go)拦截空值,不在任务层
选择:在 parseCardRows() 和 parseDeviceRows() 中,当 VirtualNo 为空时,将该行加入解析错误列表(ParseError),而非 continue 跳过
备选方案:在任务处理层(iot_card_import.go / device_import.go)拦截。问题:任务层已有处理批次逻辑,较复杂;解析层更早发现问题,更符合"fail fast"原则
理由:越早发现越好;解析层返回 ParseErrors 与当前批量验证逻辑(ICCID 格式校验已在此处)风格一致
决策 2:IoT 卡数据库层迁移分两步
- 先清库(测试阶段手动执行):
DELETE FROM tb_iot_card WHERE virtual_no IS NULL OR virtual_no = '' - 再加约束(迁移文件):
ALTER TABLE tb_iot_card ALTER COLUMN virtual_no SET NOT NULL- 重建唯一索引,去掉
WHERE virtual_no IS NOT NULL AND virtual_no <> ''条件,改为无条件唯一
理由:先清数据再加约束,迁移不会失败;两步操作在同一迁移文件中完成,原子执行(PostgreSQL 支持事务内 DDL)
决策 3:设备导入不需要数据库迁移,只改导入行为
设备的 VirtualNo 数据库层已是 NOT NULL,不需要迁移。只需把 excel.go 中 if row.VirtualNo == "" { continue } 改为加入失败列表即可。
Risks / Trade-offs
| 风险 | 缓解措施 |
|---|---|
| 现有使用方的 Excel 模板无 VirtualNo 列 | 属于 BREAKING 变更,提前通知;导入失败信息明确说明"VirtualNo 为必填列" |
| 迁移前 IoT 卡存在 NULL 记录导致 ALTER 失败 | 迁移文件中先执行 DELETE 清理(适用测试环境),再 ALTER;生产环境需手动确认数据干净后执行 |
| IoT 卡在注册表(提案一)中未注册 VirtualNo | 本提案独立于提案一;若提案一先完成,IoT 卡导入时需同步注册 VirtualNo 到注册表(在提案一 task 6.2 中已覆盖) |
Migration Plan
- 清理测试库中
virtual_no IS NULL的 IoT 卡记录 - 执行数据库迁移:
tb_iot_card.virtual_no加 NOT NULL,重建唯一索引 - 更新
pkg/utils/excel.go(解析层) - 更新
internal/task/iot_card_import.go(任务层,去除残余跳过逻辑) - 更新
internal/task/device_import.go(任务层,同上) - 更新
internal/model/iot_card.go(GORM tag) - 更新文档
docs/excel-import-frontend-guide.md
Open Questions
- 无