diff --git a/.planning/STATE.md b/.planning/STATE.md index 498d36f..8f44285 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,12 +3,12 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: unknown -last_updated: "2026-03-28T03:45:16.290Z" +last_updated: "2026-03-28T05:22:43.190Z" progress: - total_phases: 6 - completed_phases: 3 - total_plans: 9 - completed_plans: 9 + total_phases: 7 + completed_phases: 4 + total_plans: 10 + completed_plans: 10 --- # Project State — 君鸿卡管系统 @@ -73,6 +73,7 @@ Progress: [ 1 ]──[ 2 ]──[ 3 ]──[ 4 ]──[ 5 ]──[ 6 ] --- | Phase 03-device-system P01 | 8min | 2 tasks | 10 files | | Phase 03-device-system P02 | 10min | 2 tasks | 3 files | +| Phase 03.1-sync-info-phase-3-dto P01 | 5min | 3 tasks | 6 files | ## Execution Log @@ -85,6 +86,7 @@ Progress: [ 1 ]──[ 2 ]──[ 3 ]──[ 4 ]──[ 5 ]──[ 6 ] | Phase 01 P05 | 8min | 2 tasks | 3 files | | Phase 02 P01 | 10min | 2 tasks | 14 files | | Phase 02 P02 | 25min | 2 tasks | 8 files | +| Phase 03.1 P01 | 5min | 3 tasks | 6 files | --- @@ -100,6 +102,7 @@ Progress: [ 1 ]──[ 2 ]──[ 3 ]──[ 4 ]──[ 5 ]──[ 6 ] - REFUND-02/03 依赖 REFUND-01(数据模型先行) - DEVICE-04: gatewayClient nil guard 可选注入,sync-info 失败仅记 Warn 不阻断 Refresh 主流程 - UpdateIsCurrentByDeviceID 事务两步原子更新 is_current,保证同一设备唯一 is_current=true +- DeviceGatewayInfo(B端)与 DeviceRealtimeInfo(C端)独立结构体,SwitchMode 字符串→整型转换(Phase 03.1 决策) ### Architecture Notes diff --git a/.planning/phases/03.1-sync-info-phase-3-dto/03.1-01-SUMMARY.md b/.planning/phases/03.1-sync-info-phase-3-dto/03.1-01-SUMMARY.md new file mode 100644 index 0000000..aee7154 --- /dev/null +++ b/.planning/phases/03.1-sync-info-phase-3-dto/03.1-01-SUMMARY.md @@ -0,0 +1,185 @@ +--- +phase: 03.1-sync-info-phase-3-dto +plan: "01" +subsystem: asset/device +tags: + - gateway + - dto + - device + - realtime +dependency_graph: + requires: + - Phase 3 DEVICE-01~04(DB 字段写入已完成) + provides: + - 设备管理列表/详情返回 5 个 DB 缓存字段 + - 设备绑卡列表返回 is_current 字段 + - 资产 Resolve/Refresh BoundCardInfo 含 is_current + - admin 资产解析返回 5 个 DB 缓存字段 + - GetRealtimeStatus device 类型实时调用 Gateway + - C 端 GetAssetInfo device_realtime 为真实 Gateway 数据 + affects: + - GET /api/admin/devices(新增 5 字段) + - GET /api/admin/devices/:id/cards(新增 is_current) + - GET /api/admin/assets/resolve/:identifier(新增 5 字段 + is_current) + - GET /api/admin/assets/:type/:id/realtime(新增 device_realtime) + - GET /api/c/v1/asset/info(device_realtime 从 mock 改为真实数据) +tech_stack: + added: [] + patterns: + - isCurrentMap 与 slotMap 同构模式(asset/service.go) + - strPtr() 辅助函数(空字符串→nil,防止 JSON 冗余字段) + - mapSyncRespToDeviceGatewayInfo() Gateway DTO → B端 DTO 映射 + - mapDeviceGatewayInfoToClientInfo() B端 DTO → C端 DTO 映射 +key_files: + created: [] + modified: + - internal/gateway/models.go(+16 行:SyncDeviceInfoResp 补全 15 个字段) + - internal/model/dto/asset_dto.go(+77 行:DeviceGatewayInfo 新增;AssetRealtimeStatusResponse.DeviceRealtime 新增;AssetResolveResponse 5 字段新增) + - internal/service/device/service.go(+5 行:toDeviceResponse 补全 5 字段) + - internal/service/device/binding.go(+1 行:ListBindings IsCurrent 补全) + - internal/service/asset/service.go(+84 行:isCurrentMap 两处补全;Gateway 实时调用;mapSyncRespToDeviceGatewayInfo;strPtr) + - internal/handler/app/client_asset.go(+113/-41 行:mock 替换为真实 Gateway 调用;mapDeviceGatewayInfoToClientInfo) +decisions: + - DeviceGatewayInfo(B端)与 DeviceRealtimeInfo(C端)独立结构,通过映射函数转换(per D-08) + - SwitchMode 在 B端为 *string(Gateway 原始),C端为 *int,映射时使用 strconv.Atoi 转换 + - strPtr() 在 asset/service.go 包内独立定义(order/service.go 已有同名函数但不同包) +metrics: + duration: 5min + completed_date: "2026-03-28" + tasks: 3 + files_modified: 6 +--- + +# Phase 03.1 Plan 01: 设备读取链路修复 + DTO 映射补全 Summary + +**一句话总结:** 补全 Phase 3 设备数据写入链路的读取侧缺口——6 个字段映射缺口修复 + Gateway sync-info 实时调用链路建立 + C端 mock 数据替换。 + +--- + +## 完成的任务 + +### Task 1:补全 Gateway 模型和 B 端 DTO 契约 + +**`internal/gateway/models.go`(第 162-183 行):** + +`SyncDeviceInfoResp` 新增 15 个缺失字段: +- 信号:`Rsrp`、`Rsrq`、`Sinr` +- WiFi:`WifiPassword` +- 网络:`IPAddress`、`WANIP`、`LANIP` +- 连接:`MaxClients`、`ConnectTime` +- 设备:`Status`、`IMSI`、`ULStats`、`DLStats`、`LimitSpeed`、`SyncInterval` + +**`internal/model/dto/asset_dto.go`:** + +1. 新增 `DeviceGatewayInfo` 结构体(第 138-188 行),49 字段,全部含 `description` tag(per D-08/D-09) +2. `AssetRealtimeStatusResponse` 新增 `DeviceRealtime *DeviceGatewayInfo` 字段(第 77 行)(per D-07/D-08) +3. `AssetResolveResponse` 新增 5 个设备 DB 缓存字段(第 44-49 行)(per D-12): + - `OnlineStatus`、`LastOnlineTime`、`SoftwareVersion`、`SwitchMode`、`LastGatewaySyncAt` + +--- + +### Task 2:修复静态 DTO 映射缺口 + +**`internal/service/device/service.go`(`toDeviceResponse()`,第 559-563 行):** + +补全 5 个 DB 缓存字段赋值: +```go +OnlineStatus: device.OnlineStatus, +LastOnlineTime: device.LastOnlineTime, +SoftwareVersion: device.SoftwareVersion, +SwitchMode: device.SwitchMode, +LastGatewaySyncAt: device.LastGatewaySyncAt, +``` + +**`internal/service/device/binding.go`(`ListBindings()`,第 60 行):** + +```go +IsCurrent: binding.IsCurrent, +``` + +**`internal/service/asset/service.go`(两处修复):** + +1. `buildDeviceResolveResponse()`(第 124-128 行):新增 `isCurrentMap` + `IsCurrent` + 5 个 DB 缓存字段 +2. `GetRealtimeStatus()` device case(第 274-279 行):新增 `isCurrentMap` + `IsCurrent` + +两处均采用与现有 `slotMap` 同构的 `isCurrentMap` 模式: +```go +isCurrentMap := make(map[uint]bool, len(bindings)) +// ... +isCurrentMap[b.IotCardID] = b.IsCurrent +// ... +IsCurrent: isCurrentMap[c.ID], +``` + +--- + +### Task 3:建立设备实时 Gateway 调用链路 + +**`internal/service/asset/service.go`:** + +1. `GetRealtimeStatus()` device case 末尾(第 291-312 行)新增 Gateway 实时调用: + - `gatewayClient != nil` 安全检查 + - IMEI 优先,无则用 SN(与 Refresh 逻辑一致) + - 调用 `s.gatewayClient.SyncDeviceInfo(ctx, &gateway.SyncDeviceInfoReq{CardNo: cardNo})` + - 成功时调用 `mapSyncRespToDeviceGatewayInfo(syncResp)` 填充 `resp.DeviceRealtime` + - 失败时仅记录 Warn 日志,`DeviceRealtime` 保持 nil(per D-06) + +2. 新增 `mapSyncRespToDeviceGatewayInfo()`(第 330-369 行):Gateway resp → B端 DTO 映射 + +3. 新增 `strPtr()`(第 371-378 行):辅助函数,将空字符串转为 nil 指针 + +**`internal/handler/app/client_asset.go`:** + +1. `GetAssetInfo()`(第 188-197 行):删除 `buildMockDeviceRealtime()` 调用,替换为: + ```go + realtimeResp, realtimeErr := h.assetService.GetRealtimeStatus( + c.UserContext(), "device", resp.AssetID, + ) + if realtimeErr == nil && realtimeResp.DeviceRealtime != nil { + resp.DeviceRealtime = mapDeviceGatewayInfoToClientInfo(realtimeResp.DeviceRealtime) + } + ``` + +2. 删除 `buildMockDeviceRealtime()` 函数(原第 595-635 行) + +3. 新增 `mapDeviceGatewayInfoToClientInfo()`(第 595-660 行):B端 DeviceGatewayInfo → C端 DeviceRealtimeInfo 映射 + +--- + +## 编译验证 + +``` +go build ./... → 全量编译通过 +go vet ./internal/gateway/... ./internal/model/dto/... ./internal/service/asset/... ./internal/service/device/... ./internal/handler/app/... → 无新增错误 +``` + +--- + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] SwitchMode 类型不匹配** + +- **Found during:** Task 3 +- **Issue:** 计划中 `mapDeviceGatewayInfoToClientInfo` 的 `SwitchMode` 转换代码为 `v := int(*g.SwitchMode)`,但 `g.SwitchMode` 是 `*string`(B端 Gateway DTO 定义),`DeviceRealtimeInfo.SwitchMode` 是 `*int`(C端 DTO),直接转换会编译报错 +- **Fix:** 使用 `strconv.Atoi(*g.SwitchMode)` 进行字符串→整型安全转换,解析失败时忽略(`SwitchMode` 保持 nil) +- **Files modified:** `internal/handler/app/client_asset.go` +- **Commit:** `a202b2d` + +--- + +## Self-Check: PASSED + +| 检查项 | 结果 | +|--------|------| +| `internal/gateway/models.go` 存在 | ✅ FOUND | +| `internal/model/dto/asset_dto.go` 存在 | ✅ FOUND | +| `internal/service/device/service.go` 存在 | ✅ FOUND | +| `internal/service/device/binding.go` 存在 | ✅ FOUND | +| `internal/service/asset/service.go` 存在 | ✅ FOUND | +| `internal/handler/app/client_asset.go` 存在 | ✅ FOUND | +| 提交 `00dba42` 存在 | ✅ FOUND | +| 提交 `3b54850` 存在 | ✅ FOUND | +| 提交 `a202b2d` 存在 | ✅ FOUND | +| `go build ./...` 编译通过 | ✅ 通过 |