docs(03.1): capture phase context

This commit is contained in:
2026-03-28 12:38:02 +08:00
parent ff62ea457a
commit 8af5d1a045
2 changed files with 310 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
# Phase 03.1: 设备 sync-info 读取链路修复 - Context
**Gathered:** 2026-03-28
**Status:** Ready for planning
<domain>
## 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保持现状
</domain>
<decisions>
## 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 逻辑自行决定
</decisions>
<canonical_refs>
## 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/15updateDeviceFromSyncInfo 实现细节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 等)
</canonical_refs>
<code_context>
## 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` 字段位置
</code_context>
<specifics>
## Specific Ideas
- 用户明确:设备上游属性(在线/信号/电量/WiFi/当前卡)**不应存 DB**,必须实时查询。现有的 `online_status` 等 DB 字段对于设备管理列表展示仍有价值(缓存上次同步值)。
- 用户明确:关键实时字段优先级 — 在线状态 > 信号强度 > 连接数 > 电量 > WiFi 名称 + WiFi 密码 > 当前使用卡 ICCID
- `DeviceGatewayInfo`B 端)与 `DeviceRealtimeInfo`C 端结构独立为未来管理端展示更多技术字段IMSI、LAN/WAN IP、DL/UL stats 等)预留空间
</specifics>
<deferred>
## Deferred Ideas
- 设备 Refresh 接口的语义重新评估(用户提到"刷新接口对设备而言意义不大")——当前 Refresh 仍保留(写回 DB 供管理列表使用),但未来可能弃用 —— Phase 4+ 评估
- `GetRealtimeStatus` 的防高频调用冷却 Key若 C 端每次进入详情页都触发 Gateway 调用,需评估频率)—— 留待 Phase 4 运营优化
</deferred>
---
*Phase: 03.1-sync-info-phase-3-dto*
*Context gathered: 2026-03-28*

View File

@@ -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 运营阶段评估