Files
junhong_cmp_fiber/openspec/changes/archive/2026-03-14-asset-detail-refactor/tasks.md
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

9.4 KiB
Raw Blame History

1. 数据库迁移(先行)

  • 1.1 创建迁移文件:tb_device.device_novirtual_notb_personal_customer_device.device_novirtual_no(两张表在同一个迁移文件中完成)
  • 1.2 创建迁移文件:tb_iot_card 新增 virtual_no VARCHAR(50) 字段,创建部分唯一索引 idx_iot_card_virtual_noWHERE deleted_at IS NULL
  • 1.3 创建迁移文件:tb_package 新增 virtual_ratio DECIMAL(10,6) DEFAULT 1.0 字段,回填现有数据(根据 enable_virtual_datareal_data_mb / virtual_data_mb 计算)
  • 1.4 执行全部迁移验证三张表结构变更成功PostgreSQL MCP 确认)

2. 数据模型更新device_no 全量改名)

  • 2.1 更新 internal/model/device.goDeviceNoVirtualNoGORM column tag 从 device_no 改为 virtual_no,更新注释
  • 2.2 更新 internal/model/personal_customer_device.goDeviceNoVirtualNocolumn tag 同步更新
  • 2.3 更新 internal/model/dto/device_dto.goDeviceResponse.DeviceNoVirtualNoJSON tag 从 "device_no" 改为 "virtual_no"ListDeviceRequest.DeviceNoVirtualNoquery tag 同步更新;AllocationDeviceFailedItem.DeviceNoVirtualNoDeviceSeriesBindngFailedItem.DeviceNoVirtualNo
  • 2.4 全量搜索代码中所有 .DeviceNo 引用Store/Service/Handler 层),逐一替换为 .VirtualNo(使用 lsp_rename 保证全量覆盖)
  • 2.5 运行 go build ./... 确认无编译错误,运行 lsp_diagnostics 确认无类型错误

3. IotCard 模型更新(新增 virtual_no 字段)

  • 3.1 更新 internal/model/iot_card.go:新增 VirtualNo string 字段GORM tag 包含 column:virtual_no; type:varchar(50); uniqueIndex:idx_iot_card_virtual_no,where:deleted_at IS NULL 注释
  • 3.2 运行 lsp_diagnostics 确认模型字段无错误

4. Package 模型更新(新增 virtual_ratio 字段)

  • 4.1 更新 internal/model/package.goPackage 结构体新增 VirtualRatio float64 字段GORM tag column:virtual_ratio; type:decimal(10,6); default:1.0
  • 4.2 更新 internal/service/package/service.go:在套餐创建和更新逻辑中自动计算并存储 virtual_ratioenable_virtual_data=truevirtual_data_mb>0 时 = real_data_mb/virtual_data_mb,否则 = 1.0
  • 4.3 运行 lsp_diagnostics 确认无错误

5. Redis 常量新增

  • 5.1 在 pkg/constants/redis.go 新增 RedisDeviceProtectKey(deviceID uint, action string) string(格式:protect:device:{id}:{action}TTL 注释1 小时)
  • 5.2 在 pkg/constants/redis.go 新增 RedisDeviceRefreshCooldownKey(deviceID uint) string(格式:refresh:cooldown:device:{id}TTL 注释:冷却时长,建议 30 秒)
  • 5.3 在 pkg/constants/redis.go 新增 RedisPollingQueueProtectKey() string(格式:polling:queue:protect
  • 5.4 在 pkg/constants/ 新增设备保护期时长常量 DeviceProtectPeriodDuration = 1 * time.Hour,设备刷新冷却时长常量 DeviceRefreshCooldownDuration = 30 * time.Second

6. RefreshCardDataFromGateway 方法增强

  • 6.1 在 internal/service/iot_card/service.go 中将 SyncCardStatusFromGateway 改名为 RefreshCardDataFromGateway
  • 6.2 增强方法实现:调用网关查询卡状态(网络状态)、实名状态、本月流量用量,将结果写回 DB更新 network_statusreal_name_statuscurrent_month_usage_mblast_sync_time(参考 polling_handler.go 中的完整同步逻辑)
  • 6.3 更新所有调用 SyncCardStatusFromGateway 的地方改为 RefreshCardDataFromGateway(全局搜索替换)
  • 6.4 运行 go build ./... 确认无编译错误

7. 新建 AssetService

  • 7.1 创建 internal/service/asset/service.go,定义 Service 结构体依赖注入DeviceStore、IotCardStore、PackageUsageStore、PackageStore、Redis通过现有 bootstrap 体系接入)
  • 7.2 实现 Resolve(ctx, identifier string) (*dto.AssetResolveResponse, error)先查设备virtual_no/imei/sn再查卡virtual_no/iccid/msisdn应用数据权限过滤聚合套餐流量含 virtual_ratio 换算)、保护期状态、绑定信息
  • 7.3 实现 GetRealtimeStatus(ctx, assetType string, id uint) (*dto.AssetRealtimeStatusResponse, error):仅读 DB/Redis 持久化数据不调网关card 返回网络状态/实名/流量/最后同步device 返回保护期状态+所有绑定卡状态
  • 7.4 实现 Refresh(ctx, assetType string, id uint) (*dto.AssetRealtimeStatusResponse, error)card 调 RefreshCardDataFromGatewaydevice 检查 Redis 冷却期429可刷新则遍历绑定卡调 RefreshCardDataFromGateway,设置冷却 Key
  • 7.5 实现 GetPackages(ctx, assetType string, id uint) ([]*dto.AssetPackageResponse, error):按 asset_type 查 PackageUsagecard→iot_card_iddevice→device_id全量返回按创建时间倒序含 virtual_ratio 展示换算
  • 7.6 实现 GetCurrentPackage(ctx, assetType string, id uint) (*dto.AssetPackageResponse, error):查 status=1 且 master_usage_id IS NULL 的主套餐,无则返回 ErrNotFound

8. 设备停复机 Service

  • 8.1 在 internal/service/device/service.go 实现 StopDevice(ctx, deviceID uint) (*dto.DeviceSuspendResponse, error):验证设备存在、检查保护期、获取已实名绑定卡、批量调网关停机、更新卡状态、设置 Redis stop 保护期(部分失败时仍设置)
  • 8.2 实现 StartDevice(ctx, deviceID uint) error:验证设备存在、检查保护期、获取已实名绑定卡、批量调网关复机、更新卡状态、设置 Redis start 保护期

9. 卡停复机 Service 扩展

  • 9.1 在 internal/service/iot_card/stop_resume_service.go 实现 ManualStopCard(ctx, iccid string) error:通过 ICCID 查卡、验证已实名、检查绑定设备的保护期stop 保护期允许、start 保护期允许)、调网关停机、更新卡状态
  • 9.2 实现 ManualStartCard(ctx, iccid string) error:通过 ICCID 查卡、验证已实名、检查绑定设备的保护期stop 保护期→拒绝 403、start 保护期→允许)、调网关复机、更新卡状态

10. DTO 新增

  • 10.1 在 internal/model/dto/ 新增 asset_dto.go,定义以下 DTO含所有字段及 description tag
    • AssetResolveResponseBoundCardInfoAssetRealtimeStatusResponseAssetPackageResponse
  • 10.2 DeviceSuspendResponse(成功信息 + 失败卡列表,已在 device_dto.go 中)

11. 新建 AssetHandler 和路由注册

  • 11.1 创建 internal/handler/admin/asset.go,定义 AssetHandler 结构体和 9 个 Handler 方法Resolve、RealtimeStatus、Refresh、Packages、CurrentPackage、StopDevice、StartDevice、StopCard、StartCard
  • 11.2 创建 internal/routes/asset.go 注册 /api/admin/assets/* 路由9 个端点);企业账号访问 resolve 时在 Handler 层检查 user_type 返回 403
  • 11.3 更新 internal/bootstrap/types.goservices.gohandlers.go,将 Asset、StopResumeService 加入 bootstrap 体系;更新 docs 文件

12. 轮询系统新增保护期一致性检查任务

  • 12.1 RedisPollingQueueProtectKey() 已存在Task 5.3
  • 12.2 在 internal/task/polling_handler.go 新增 HandleProtectConsistencyCheck:检查 is_standalone、real_name_status、设备保护期 Redis Key按规则强制同步网络状态
  • 12.3 在 pkg/constants/constants.go 添加 TaskTypePollingProtect,在 pkg/queue/handler.go 注册处理器

13. 卡 ICCID 导入支持 virtual_no 列

  • 13.1 pkg/utils/excel.go 新增 virtual_no 列识别关键字virtual_no/VirtualNo/虚拟号/设备号)
  • 13.2 internal/task/iot_card_import.go 实现 virtual_no 唯一性校验和写入逻辑;IotCardStore 新增 ExistsByVirtualNoBatch

14. 删除废弃接口

14a. 废弃停复机接口

  • 14.1 删除 internal/handler/admin/enterprise_card.go 中的 SuspendCardResumeCard Handler
  • 14.2 删除 internal/handler/h5/enterprise_device.go 中的 SuspendCardResumeCard Handler
  • 14.3 删除 internal/handler/admin/iot_card.go 中的 StopCardStartCard Handler
  • 14.4 清理对应路由注册,go build ./... 通过

14b. 废弃详情查询和网关直查接口

  • 14.5 删除 internal/handler/admin/iot_card.go 中的 GetByICCID Handler
  • 14.6 删除 internal/handler/admin/iot_card.go 中的 GetGatewayStatusGetGatewayFlowGetGatewayRealname 三个 Handler
  • 14.7 删除 internal/handler/admin/device.go 中的 GetByID Handler
  • 14.8 删除 internal/handler/admin/device.go 中的 GetByIdentifier Handler
  • 14.9 删除 internal/handler/admin/device.go 中的 GetGatewayInfo Handler
  • 14.10 清理对应路由注册,go build ./... 通过

15. 文档和最终验收

  • 15.1 更新 API 文档生成器,运行 go run cmd/gendocs/main.go 确认 9 个新接口出现在文档中
  • 15.2 使用 PostgreSQL MCP 验证三张表结构变更正确tb_device.virtual_no 唯一索引、tb_iot_card.virtual_no 条件唯一索引、tb_package.virtual_ratio 字段 NOT NULL DEFAULT 1.0
  • 15.3 使用 PostgreSQL MCP 验证 Package 数据回填正确enable_virtual_data=true 的套餐 virtual_ratio=930.9,非 1.0
  • 15.4 运行 go build ./... 全量检查无编译错误
  • 15.5 tasks.md 全部任务标记完成