171 lines
10 KiB
Markdown
171 lines
10 KiB
Markdown
# 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 响应
|
||
- `AssetResolveResponse` 缺 5 个 DB 缓存字段 → 影响 admin 资产解析接口
|
||
|
||
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-12:** `asset_dto.go:AssetResolveResponse` 新增 5 个 DB 缓存字段,同步更新 `asset/service.go:buildDeviceResolveResponse()` 映射
|
||
- 新增字段:`OnlineStatus int`、`LastOnlineTime *time.Time`、`SoftwareVersion string`、`SwitchMode string`、`LastGatewaySyncAt *time.Time`
|
||
- 这些是 DB 存储值(Phase 3 写入,上次 sync-info 的快照),不触发 Gateway 调用
|
||
- 适用场景:admin 资产 Resolve 接口(`/api/admin/assets/resolve/:identifier`)快速展示设备上次已知状态
|
||
- 与 D-07(GetRealtimeStatus 实时 Gateway 调用)互补:Resolve = 快速快照,Realtime = 精确实时
|
||
|
||
### 设备实时数据架构原则
|
||
|
||
- **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/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` 字段;`AssetResolveResponse` 新增 5 个 DB 缓存字段(D-12)
|
||
- `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 (updated: 2026-03-28, added D-12)*
|