From 8af5d1a045d07ac6c4a51d67c4323de95882c37b Mon Sep 17 00:00:00 2001 From: huang Date: Sat, 28 Mar 2026 12:38:02 +0800 Subject: [PATCH] docs(03.1): capture phase context --- .../03.1-CONTEXT.md | 163 ++++++++++++++++++ .../03.1-DISCUSSION-LOG.md | 147 ++++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 .planning/phases/03.1-sync-info-phase-3-dto/03.1-CONTEXT.md create mode 100644 .planning/phases/03.1-sync-info-phase-3-dto/03.1-DISCUSSION-LOG.md diff --git a/.planning/phases/03.1-sync-info-phase-3-dto/03.1-CONTEXT.md b/.planning/phases/03.1-sync-info-phase-3-dto/03.1-CONTEXT.md new file mode 100644 index 0000000..09921e2 --- /dev/null +++ b/.planning/phases/03.1-sync-info-phase-3-dto/03.1-CONTEXT.md @@ -0,0 +1,163 @@ +# Phase 03.1: 设备 sync-info 读取链路修复 - Context + +**Gathered:** 2026-03-28 +**Status:** Ready for planning + + +## Phase Boundary + +Phase 3 完成了设备数据的**写入侧**(sync-info 调用 + DB 字段写入 + is_current 更新),但遗漏了**读取侧**的 DTO 映射和设备实时数据的架构对齐。 + +本 Phase 修复两类缺口: + +1. **静态 DTO 映射缺口**(DB 字段已存储但未通过 API 返回) + - `toDeviceResponse` 5 个字段未映射 → 影响设备管理列表/详情 + - `ListBindings` IsCurrent 未映射 → 影响设备绑卡列表接口 + - `BoundCardInfo` IsCurrent 未映射(2 处)→ 影响资产 Resolve/Refresh 响应 + +2. **设备实时数据架构对齐**(统一视图的上游属性必须实时查询) + - 设备上游属性(在线状态/信号/电量/WiFi/当前卡)具有实时特性,存 DB 无意义,必须实时从 Gateway 拉取 + - `GetRealtimeStatus` 对 device 类型实时调用 Gateway sync-info + - C 端 `GetAsset` 的 `DeviceRealtimeInfo` stub 替换为真实 Gateway 数据 + +**不在本 Phase 范围:** +- 卡资产的上游属性(走轮询系统,已由 DB 存储,无需改动) +- 设备管理列表(展示 DB 缓存字段即可,不需要实时 Gateway 调用) +- 设备 Refresh 流程(已有 updateDeviceFromSyncInfo,保持现状) + + + + +## Implementation Decisions + +### Plan 分组 +- **D-01:** 全部修复合并为 **1 个 Plan**。所有变更同属"DTO 映射缺口 + 实时数据架构对齐",无依赖关系需拆分。 + +### 静态 DTO 缺口修复 + +- **D-02:** `device/service.go:toDeviceResponse()` 补全 5 个 DB 缓存字段: + - `OnlineStatus`、`LastOnlineTime`、`SoftwareVersion`、`SwitchMode`、`LastGatewaySyncAt` + - 这些字段对设备管理列表(List)仍有意义(展示 DB 缓存值),保留此映射 + +- **D-03:** `device/binding.go:ListBindings()` 补全 `IsCurrent` 字段映射 + - `DeviceCardBindingResponse.IsCurrent` 直接从 `binding.IsCurrent` 取值 + +- **D-04:** `asset/service.go` 中 2 处 `BoundCardInfo` 构建(Resolve path + Refresh path)补全 `IsCurrent` + - 实现方式:在 `bindings` 遍历中同时构建 `isCurrentMap[b.IotCardID] = b.IsCurrent`(与 `slotMap` 同构) + - 构建 `BoundCardInfo` 时追加 `IsCurrent: isCurrentMap[c.ID]` + +### 设备实时数据架构原则 + +- **D-05:** 设备上游属性(在线/信号/电量/WiFi/当前卡)**不缓存,必须实时查询 Gateway**。原因:设备具有在线/离线实时特性,存 DB 的值在设备离线后立即过时,无法代表真实状态。 + - 对比:卡资产的上游属性通过轮询系统有规律地同步到 DB,查询 DB 有意义。 + +- **D-06:** Gateway 调用失败时(设备离线/超时/IMEI 为空)**返回部分数据**:`DeviceRealtime` 字段为 null,资产基础信息正常返回。不抛错误,与卡资产处理保持一致。 + +### GetRealtimeStatus 改造 + +- **D-07:** `asset/service.go:GetRealtimeStatus()` 对 device 类型新增 Gateway 实时调用: + - 调用 `s.gatewayClient.SyncDeviceInfo(ctx, &gateway.SyncDeviceInfoReq{IMEI: device.IMEI})`(gatewayClient 已注入) + - 成功:将响应映射到新增的 `AssetRealtimeStatusResponse.DeviceRealtime *DeviceGatewayInfo` 字段 + - 失败:仅记录 Warn 日志,`DeviceRealtime` 返回 null + +### DTO 结构 + +- **D-08:** B 端和 C 端使用**独立结构体**,不共用。原因:未来管理端可能展示 C 端不展示的字段(如 IMSI、DL/UL stats、LAN/WAN IP 等技术字段)。 + - **B 端**:新建 `DeviceGatewayInfo` struct,位于 `internal/model/dto/asset_dto.go` + - **C 端**:保留现有 `DeviceRealtimeInfo` struct,位于 `internal/model/dto/client_asset_dto.go` + - `AssetRealtimeStatusResponse` 新增 `DeviceRealtime *DeviceGatewayInfo` 字段 + +- **D-09:** `DeviceGatewayInfo`(B 端)至少包含以下字段(参考 Gateway sync-info 文档完整映射): + - 关键:`OnlineStatus`、`BatteryLevel`、`Rsrp/Rsrq/Rssi/Sinr`(信号)、`MaxClients`(连接数) + - WiFi:`SSID`、`WifiPassword` + - 当前卡:`CurrentIccid` + - 附加:`SoftwareVersion`、`SwitchMode`、`RunTime`、`DailyUsage`、`LastOnlineTime` + +- **D-10:** `gateway.SyncDeviceInfoResp` 当前缺少字段,需补全: + - 补充:`Rsrp int`、`Rsrq int`、`Sinr int`、`WifiPassword string`、`IPAddress string` + - 补充:`WANIP string`、`LANIP string`、`MaxClients int`、`ConnectTime string` + - 补充:`Status int`(设备状态)、`IMSI string`、`ULStats string`、`DLStats string` + - 补充:`LimitSpeed int`、`SyncInterval int`、`MACAddress string` + +### C 端 DeviceRealtime stub 替换 + +- **D-11:** C 端 `client_asset.go:GetAsset()` 中对 device 类型的处理: + - 删除 `buildMockDeviceRealtime()` 调用 + - 改为调用 `h.assetService.GetRealtimeStatus("device", deviceID)` 获取实时数据 + - 将返回的 `AssetRealtimeStatusResponse.DeviceRealtime (*DeviceGatewayInfo)` 映射到 `ClientAssetResponse.DeviceRealtime (*DeviceRealtimeInfo)` + - Gateway 失败时 `resp.DeviceRealtime = nil`(已处理,不额外报错) + +### Agent's Discretion + +- `DeviceGatewayInfo` B 端结构体的字段是否包含所有 Gateway 字段或只包含关键字段,agent 可参考 `DeviceRealtimeInfo`(C 端)的完整字段集合决定 +- `toDeviceResponse` 中 `GetRealtimeStatus` 调用的冷却 Key 策略(是否需要防止高频调用)由 agent 参考现有 Refresh 冷却 Key 逻辑自行决定 + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Gateway 接口文档 +- `docs/第三方文档/gateway设备详情同步接口.md` — sync-info 完整字段说明(data 字段映射表,包含 rsrp/rsrq/sinr/wifi_password 等当前 SyncDeviceInfoResp 缺失的字段) + +### Phase 3 上下文(继承决策) +- `.planning/phases/03-device-system/03-CONTEXT.md` — Phase 3 所有决策(D-06:结构体位置;D-13/14/15:updateDeviceFromSyncInfo 实现细节;D-08/09:存储字段范围) + +### 核心修改文件 +- `internal/service/device/service.go` — `toDeviceResponse()`:补全 5 个字段 +- `internal/service/device/binding.go` — `ListBindings()`:补全 IsCurrent +- `internal/service/asset/service.go` — `GetRealtimeStatus()`:新增 Gateway 调用;Resolve/Refresh 两处 BoundCardInfo:补全 IsCurrent +- `internal/model/dto/asset_dto.go` — 新增 `DeviceGatewayInfo`;`AssetRealtimeStatusResponse` 新增 `DeviceRealtime` 字段 +- `internal/model/dto/device_dto.go` — 已有字段,无需新增(确认映射即可) +- `internal/gateway/models.go` — `SyncDeviceInfoResp` 补全缺失字段 +- `internal/handler/app/client_asset.go` — 删除 `buildMockDeviceRealtime()` stub,改为调用服务 + 映射 + +### 当前 SyncDeviceInfoResp 位置 +- `internal/gateway/models.go:147` — 当前结构(缺 Rsrp/Rsrq/Sinr/WifiPassword/LANIP/MaxClients 等) + + + + +## Existing Code Insights + +### Reusable Assets +- `internal/service/asset/service.go:gatewayClient` — 已注入,`GetRealtimeStatus` 可直接使用 +- `internal/service/asset/service.go:updateDeviceFromSyncInfo()` — Phase 3 已实现,展示了 SyncDeviceInfoResp → DB 的映射模式,可参考作为 Gateway 响应 → DTO 映射的参考 +- `internal/gateway/device.go:SyncDeviceInfo()` — 已实现(Phase 3),直接复用 +- `internal/model/dto/client_asset_dto.go:DeviceRealtimeInfo` — 完整的 C 端实时信息结构(46 字段),对应所有 Gateway 字段,作为 `DeviceGatewayInfo` 的参考 + +### Established Patterns +- isCurrentMap 模式:参考 `slotMap` 在 `asset/service.go` 的构建方式,`isCurrentMap[b.IotCardID] = b.IsCurrent` +- Gateway 失败不阻断主流程:`logger.Warn("...")`,继续返回部分数据 +- B 端 DTO 字段均需 `description` tag(参考 `asset_dto.go` 现有字段) + +### Integration Points +- `asset/service.go:GetRealtimeStatus()` — device case 追加 Gateway 调用入口(现只有 `getDeviceProtectStatus` 和 bindings 查询) +- `handler/app/client_asset.go:GetAsset()` — 约第 187-191 行,device 类型的 `resp.DeviceRealtime = buildMockDeviceRealtime()` 替换入口 +- `internal/model/dto/asset_dto.go:AssetRealtimeStatusResponse` — 新增 `DeviceRealtime *DeviceGatewayInfo` 字段位置 + + + + +## Specific Ideas + +- 用户明确:设备上游属性(在线/信号/电量/WiFi/当前卡)**不应存 DB**,必须实时查询。现有的 `online_status` 等 DB 字段对于设备管理列表展示仍有价值(缓存上次同步值)。 +- 用户明确:关键实时字段优先级 — 在线状态 > 信号强度 > 连接数 > 电量 > WiFi 名称 + WiFi 密码 > 当前使用卡 ICCID +- `DeviceGatewayInfo`(B 端)与 `DeviceRealtimeInfo`(C 端)结构独立,为未来管理端展示更多技术字段(IMSI、LAN/WAN IP、DL/UL stats 等)预留空间 + + + + +## Deferred Ideas + +- 设备 Refresh 接口的语义重新评估(用户提到"刷新接口对设备而言意义不大")——当前 Refresh 仍保留(写回 DB 供管理列表使用),但未来可能弃用 —— Phase 4+ 评估 +- `GetRealtimeStatus` 的防高频调用冷却 Key(若 C 端每次进入详情页都触发 Gateway 调用,需评估频率)—— 留待 Phase 4 运营优化 + + + +--- + +*Phase: 03.1-sync-info-phase-3-dto* +*Context gathered: 2026-03-28* diff --git a/.planning/phases/03.1-sync-info-phase-3-dto/03.1-DISCUSSION-LOG.md b/.planning/phases/03.1-sync-info-phase-3-dto/03.1-DISCUSSION-LOG.md new file mode 100644 index 0000000..534c8f5 --- /dev/null +++ b/.planning/phases/03.1-sync-info-phase-3-dto/03.1-DISCUSSION-LOG.md @@ -0,0 +1,147 @@ +# Phase 03.1: 设备 sync-info 读取链路修复 - Discussion Log + +> **仅供审计参考。** 不用于规划、研究或执行 Agent 的输入。 +> 决策结论见 CONTEXT.md — 本日志保留了讨论过程中的备选项。 + +**Date:** 2026-03-28 +**Phase:** 03.1-sync-info-phase-3-dto +**Areas discussed:** Plan 分组策略、IsCurrent 实现细节、作用域完整性确认(C 端 stub + 实时数据架构) + +--- + +## Plan 分组策略 + +| 选项 | 说明 | 选中 | +|------|------|------| +| 1 Plan 合并 | 3 修复点同属 DTO 映射缺口,统一处理 | ✓ | +| 2 Plans 分离 | device/service 系 + asset/service 系拆分 | | + +**用户选择:** 1 Plan 合并 +**备注:** 修复性质单一(DTO 映射缺落),无依赖分拆理由 + +--- + +## IsCurrent 实现方式 + +| 选项 | 说明 | 选中 | +|------|------|------| +| isCurrentMap 标准扩展 | 与 slotMap 同构,在 bindings 遍历时同时构建 | ✓ | +| 直接引用 binding.IsCurrent | 无 map,在 BoundCardInfo 构建时直接访问外层 binding | | + +**用户选择:** isCurrentMap 标准扩展 +**备注:** 与 asset/service.go 的 slotMap 保持风格一致 + +--- + +## 作用域完整性:C 端 stub + +| 选项 | 说明 | 选中 | +|------|------|------| +| 一并修复 C 端 stub | 替换 buildMockDeviceRealtime() 为真实 Gateway 数据 | ✓ | +| 仅修复管理端 | C 端 stub 留待后续 Phase | | + +**用户选择:** 一并修复 + +--- + +## C 端 DeviceRealtimeInfo 修复粒度 + +| 选项 | 说明 | 选中 | +|------|------|------| +| 只补 DB 存储字段 (3 个) | OnlineStatus/SwitchMode/SoftwareVersion,其余 nil | | +| 完整替换:调用 Gateway | 实时调用 sync-info,返回所有可用字段 | ✓ | +| 推迟到独立 Phase | C 端 stub 修复是独立需求 | | + +**用户选择:** 完整替换,调用 Gateway + +--- + +## 设备实时数据架构(新增议题) + +**用户补充说明(原文):** +> "不管是C端还是后台管理或者哪里,查询设备资产的时候其实都是需要实时数据的,这部分实时数据是很关键的...设备不一样,设备他具有离线,在线实时的特性,所以我们直接存没有什么意义,必然只能实时查询" + +**架构原则确认:** +- 单卡资产上游属性 → 轮询系统同步到 DB → 查询 DB +- 设备资产上游属性 → 实时从 Gateway 查询(不缓存) +- 设备管理列表 → DB 缓存字段(不需要实时) +- 设备资产详情(B 端 + C 端)→ 实时 Gateway + +--- + +## 关键实时字段优先级 + +**用户原文:** +> "实时状态要的东西也不多,主要是在线状态,连接设备数,网络信号强度,设备电量" +> "哦还有Wi-Fi密码跟Wi-Fi名称" + +**关键字段列表:** +1. 在线状态 (OnlineStatus) +2. 网络信号强度 (Rsrp/Rsrq/Rssi/Sinr) +3. 连接设备数 (MaxClients) +4. 设备电量 (BatteryLevel) +5. WiFi 名称 (SSID) +6. WiFi 密码 (WifiPassword) +7. 当前使用卡 ICCID (CurrentIccid) + +--- + +## Gateway 失败处理 + +| 选项 | 说明 | 选中 | +|------|------|------| +| 返回部分数据 | DeviceRealtime = null,其他资产信息正常返回 | ✓ | +| 返回错误 | 设备离线/Gateway 超时直接报错 | | + +**用户选择:** 返回部分数据 +**备注:** 与现有卡资产处理逻辑一致 + +--- + +## 查询范围(设备列表 vs 详情) + +**用户原文:** +> "除了列表,因为列表要实时状态没有什么意义,但是像详情跟C端我就很需要实时数据了" + +**决策:** +- 设备管理列表 → DB 缓存字段(toDeviceResponse 补全 5 字段即可) +- 设备资产详情(GetRealtimeStatus)→ 实时 Gateway + +--- + +## 统一资产视图架构 + +**用户原文(核心说明):** +> "我们有设备资产跟单卡资产两种,按照之前的做法其实是大家各有各的详情,我嫌弃麻烦而且不符合业务的要求,所以我们做成统一的口子,这个口子可以看到任意资产的完整生态信息" + +**影响:** +- 统一口子 = `asset/service.GetRealtimeStatus()`(管理端)+ `assetService.Resolve()`(C 端) +- 设备详情不走 `device/service.Get()`,走资产视图 + +--- + +## B 端 vs C 端 DTO 结构 + +| 选项 | 说明 | 选中 | +|------|------|------| +| 共享同一结构体 | B/C 端复用 DeviceRealtimeInfo | | +| 独立结构体 | B 端 DeviceGatewayInfo + C 端 DeviceRealtimeInfo | ✓ | + +**用户原文:** +> "不能是同一个结构体,要考虑到未来后台管理可能展示的信息在C端不展示的情况" + +**决策:** 独立结构体 +- B 端:`DeviceGatewayInfo`(asset_dto.go) +- C 端:`DeviceRealtimeInfo`(client_asset_dto.go,已存在) + +--- + +## Agent's Discretion + +- `DeviceGatewayInfo` B 端字段集合的最终范围(包含全量还是仅关键字段) +- 防高频调用冷却 Key 策略(C 端每次进详情页都调 Gateway) + +## Deferred Ideas + +- 设备 Refresh 接口语义重评("刷新接口对设备意义不大")→ Phase 4+ 评估 +- 防高频调用优化 → Phase 4 运营阶段评估