Files
junhong_cmp_fiber/.planning/phases/03.1-sync-info-phase-3-dto/03.1-01-PLAN.md

638 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 03.1-sync-info-phase-3-dto
plan: "01"
type: execute
wave: 1
depends_on: []
files_modified:
- internal/gateway/models.go
- internal/model/dto/asset_dto.go
- internal/service/device/service.go
- internal/service/device/binding.go
- internal/service/asset/service.go
- internal/handler/app/client_asset.go
autonomous: true
requirements:
- DEVICE-02
- DEVICE-03
- DEVICE-04
must_haves:
truths:
- "设备管理列表/详情接口返回 online_status、last_online_time、software_version、switch_mode、last_gateway_sync_at 字段Phase 3 已写入 DB现在需要映射到 DTO"
- "设备绑卡列表接口ListBindings返回 is_current 字段,标识当前使用的卡"
- "资产 Resolve/Refresh 接口的 BoundCardInfo 包含 is_current 字段"
- "admin 资产解析接口AssetResolveResponse返回 5 个 DB 缓存字段online_status/last_online_time/software_version/switch_mode/last_gateway_sync_at"
- "资产实时状态接口GetRealtimeStatus对 device 类型实时调用 Gateway sync-info返回 DeviceRealtime 字段"
- "C 端 GetAsset 接口的 device_realtime 字段从真实 Gateway 数据填充,不再是 mock 数据"
- "Gateway 调用失败时IMEI 为空/超时/离线device_realtime 返回 null资产基础信息正常返回"
artifacts:
- path: "internal/gateway/models.go"
provides: "SyncDeviceInfoResp 补全缺失字段Rsrp/Rsrq/Sinr/WifiPassword/IMSI/LANIP/WANIP/MaxClients/ConnectTime/ULStats/DLStats/LimitSpeed/SyncInterval/MACAddress/Status"
- path: "internal/model/dto/asset_dto.go"
provides: "DeviceGatewayInfo 新 B 端实时状态结构体AssetRealtimeStatusResponse.DeviceRealtime 字段AssetResolveResponse 5个 DB 缓存字段"
- path: "internal/service/device/service.go"
provides: "toDeviceResponse() 补全 OnlineStatus/LastOnlineTime/SoftwareVersion/SwitchMode/LastGatewaySyncAt 5个字段映射"
- path: "internal/service/device/binding.go"
provides: "ListBindings() 补全 IsCurrent 字段映射"
- path: "internal/service/asset/service.go"
provides: "buildDeviceResolveResponse() 补全 IsCurrent + 5个DB缓存字段GetRealtimeStatus() device case 追加 Gateway 实时调用"
- path: "internal/handler/app/client_asset.go"
provides: "GetAsset() 删除 buildMockDeviceRealtime(),改为调用 assetService.GetRealtimeStatus() 并映射到 DeviceRealtimeInfo"
key_links:
- from: "internal/service/asset/service.go:GetRealtimeStatus()"
to: "internal/gateway/device.go:SyncDeviceInfo()"
via: "s.gatewayClient.SyncDeviceInfo(ctx, &gateway.SyncDeviceInfoReq{CardNo: device.IMEI})"
pattern: "gatewayClient.*SyncDeviceInfo"
- from: "internal/handler/app/client_asset.go:GetAsset()"
to: "internal/service/asset/service.go:GetRealtimeStatus()"
via: "h.assetService.GetRealtimeStatus(\"device\", deviceID)"
pattern: "assetService.GetRealtimeStatus"
- from: "internal/service/asset/service.go:buildDeviceResolveResponse()"
to: "dto.BoundCardInfo.IsCurrent"
via: "isCurrentMap[b.IotCardID] = b.IsCurrent"
pattern: "isCurrentMap"
---
<objective>
补全 Phase 3 完成的设备数据写入链路在读取侧的缺口:修复 DTO 字段映射遗漏(静态缺口)+ 建立设备实时状态的 Gateway 查询链路(架构对齐)。
Purpose: Phase 3 已实现 sync-info 调用和 DB 字段写入,但 API 接口未返回这些字段,且 C 端设备实时状态仍是 mock 数据。本 Phase 完成"读取侧"闭环。
Output: 6 个文件修改,设备管理/资产解析/C 端资产接口全部返回真实数据。
</objective>
<execution_context>
@$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md
@$HOME/.config/opencode/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/03.1-sync-info-phase-3-dto/03.1-CONTEXT.md
@internal/gateway/models.go
@internal/model/dto/asset_dto.go
@internal/model/dto/device_dto.go
@internal/model/dto/client_asset_dto.go
@internal/service/asset/service.go
@internal/service/device/service.go
@internal/service/device/binding.go
@internal/handler/app/client_asset.go
<interfaces>
<!-- 执行本 Plan 前必须读取以下接口定义,无需探索代码库 -->
<!-- gateway/models.go 当前 SyncDeviceInfoResp缺失字段需补全-->
```go
type SyncDeviceInfoResp struct {
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
IMEI string `json:"imei"`
CurrentIccid string `json:"current_iccid"`
DeviceType string `json:"device_type"`
SoftwareVersion string `json:"software_version"`
MacAddress string `json:"mac_address"`
SSID string `json:"ssid"`
WifiEnabled bool `json:"wifi_enabled"`
SwitchMode string `json:"switch_mode"`
RSSI string `json:"rssi"`
BatteryLevel *int `json:"battery_level"`
OnlineStatus int `json:"online_status"`
LastUpdateTime string `json:"last_update_time"`
LastOnlineTime string `json:"last_online_time"`
RunTime string `json:"run_time"`
DailyUsage string `json:"daily_usage"`
// 以下字段 D-10 要求补全:
// 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已有 MacAddress需确认 json tag
}
```
<!-- asset_dto.go 当前 BoundCardInfo已有 IsCurrent但 buildDeviceResolveResponse 未填充)-->
```go
type BoundCardInfo struct {
CardID uint `json:"card_id"`
ICCID string `json:"iccid"`
MSISDN string `json:"msisdn,omitempty"`
NetworkStatus int `json:"network_status"`
RealNameStatus int `json:"real_name_status"`
SlotPosition int `json:"slot_position,omitempty"`
IsCurrent bool `json:"is_current"` // ← 已定义,但 buildDeviceResolveResponse 未赋值
}
```
<!-- asset_dto.go 当前 AssetRealtimeStatusResponse需新增 DeviceRealtime 字段)-->
```go
type AssetRealtimeStatusResponse struct {
AssetType string `json:"asset_type"`
AssetID uint `json:"asset_id"`
NetworkStatus int `json:"network_status,omitempty"`
RealNameStatus int `json:"real_name_status,omitempty"`
CurrentMonthUsageMB float64 `json:"current_month_usage_mb,omitempty"`
LastSyncTime *time.Time `json:"last_sync_time,omitempty"`
DeviceProtectStatus string `json:"device_protect_status,omitempty"`
Cards []BoundCardInfo `json:"cards,omitempty"`
// 需新增DeviceRealtime *DeviceGatewayInfo `json:"device_realtime,omitempty"`
}
```
<!-- device_dto.go 当前 DeviceResponse已有字段但 toDeviceResponse 未映射)-->
```go
type DeviceResponse struct {
// ... 其他字段已有
OnlineStatus int `json:"online_status"`
LastOnlineTime *time.Time `json:"last_online_time,omitempty"`
SoftwareVersion string `json:"software_version"`
SwitchMode string `json:"switch_mode"`
LastGatewaySyncAt *time.Time `json:"last_gateway_sync_at,omitempty"`
// ↑ 以上字段已在 device_dto.go 中定义Phase 3 完成),但 toDeviceResponse() 未赋值
}
```
<!-- device/binding.go DeviceCardBindingResponse已有 IsCurrent但 ListBindings 未填充)-->
```go
type DeviceCardBindingResponse struct {
ID uint
SlotPosition int
IotCardID uint
ICCID string
MSISDN string
CarrierName string
Status int
BindTime *time.Time
IsCurrent bool // ← 已定义,但 ListBindings() 未赋值
}
```
<!-- asset/service.go 关键模式isCurrentMap 构建方式)-->
```go
// buildDeviceResolveResponse 中现有 slotMap 构建模式isCurrentMap 同构:
slotMap := make(map[uint]int, len(bindings))
for _, b := range bindings {
cardIDs = append(cardIDs, b.IotCardID)
slotMap[b.IotCardID] = b.SlotPosition
}
// 新增同构的 isCurrentMap
// isCurrentMap := make(map[uint]bool, len(bindings))
// isCurrentMap[b.IotCardID] = b.IsCurrent
// GetRealtimeStatus device case 中现有 slotMap 构建模式isCurrentMap 同构补充
```
<!-- client_asset_dto.go DeviceRealtimeInfo 完整结构B 端 DeviceGatewayInfo 的字段参考)-->
```go
type DeviceRealtimeInfo struct {
OnlineStatus *int64; BatteryLevel *int64; Status *int64
RunTime *string; ConnectTime *string; LastOnlineTime *string; LastUpdateTime *string
Rsrp *int64; Rsrq *int64; Rssi *string; Sinr *int64
SSID *string; WifiEnabled *bool; WifiPassword *string
IPAddress *string; WANIP *string; LANIP *string; MACAddress *string
DailyUsage *string; DLStats *string; ULStats *string; LimitSpeed *int64
CurrentIccid *string; MaxClients *int64; SoftwareVersion *string
SwitchMode *int; SyncInterval *int64
DeviceID *string; DeviceName *string; DeviceType *string
Imei *string; Imsi *string; CreatedAt *int64; UpdatedAt *int64
}
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: 补全 Gateway 模型和 B 端 DTO 契约</name>
<files>internal/gateway/models.go, internal/model/dto/asset_dto.go</files>
<action>
**A. 补全 `internal/gateway/models.go` 的 `SyncDeviceInfoResp`per D-10**
在现有 `DailyUsage string` 字段之后,追加以下缺失字段(参考 Gateway 接口文档 `docs/第三方文档/gateway设备详情同步接口.md`
```go
// 信号相关(补全)
Rsrp int `json:"rsrp" description:"参考信号接收功率(dBm)"`
Rsrq int `json:"rsrq" description:"参考信号接收质量(dB)"`
Sinr int `json:"sinr" description:"信噪比(dB)"`
// WiFi 相关(补全)
WifiPassword string `json:"wifi_password" description:"WiFi密码"`
// 网络相关(补全)
IPAddress string `json:"ip_address" description:"IP地址"`
WANIP string `json:"wan_ip" description:"基站分配IPv4地址"`
LANIP string `json:"lan_ip" description:"局域网网关IP地址"`
// 连接相关(补全)
MaxClients int `json:"max_clients" description:"最大连接客户端数"`
ConnectTime string `json:"connect_time" description:"设备本次联网时间(秒)"`
// 设备属性(补全)
Status int `json:"status" description:"设备状态1=正常0=禁用"`
IMSI string `json:"imsi" description:"IMSI用户标识码"`
ULStats string `json:"ul_stats" description:"本次开机上传流量(字节)"`
DLStats string `json:"dl_stats" description:"本次开机下载流量(字节)"`
LimitSpeed int `json:"limit_speed" description:"限速速率(KB/s)"`
SyncInterval int `json:"sync_interval" description:"信息上报周期(秒)"`
```
注意:`MacAddress` 字段已存在json tag 为 `mac_address`**不要重复添加**。
**B. 在 `internal/model/dto/asset_dto.go` 中新增 `DeviceGatewayInfo` 结构体per D-08/D-09**
在文件末尾追加(在 `CardICCIDRequest` 之后B 端 DTO 独立结构,全字段带 `description` tag
```go
// DeviceGatewayInfo B端设备实时状态信息来自 Gateway sync-info
// 与 C 端 DeviceRealtimeInfo 独立,未来可展示更多技术字段
// 所有字段均为可选Gateway 调用失败时整体为 null
type DeviceGatewayInfo struct {
// 设备状态
OnlineStatus *int `json:"online_status,omitempty" description:"在线状态1=在线2=离线"`
BatteryLevel *int `json:"battery_level,omitempty" description:"电池电量百分比无电池时为null"`
Status *int `json:"status,omitempty" description:"设备状态1=正常0=禁用"`
RunTime *string `json:"run_time,omitempty" description:"本次开机运行时间(秒)"`
ConnectTime *string `json:"connect_time,omitempty" description:"本次联网时间(秒)"`
LastOnlineTime *string `json:"last_online_time,omitempty" description:"设备最后在线时间"`
LastUpdateTime *string `json:"last_update_time,omitempty" description:"设备信息最后更新时间"`
// 信号相关
Rsrp *int `json:"rsrp,omitempty" description:"参考信号接收功率(dBm)"`
Rsrq *int `json:"rsrq,omitempty" description:"参考信号接收质量(dB)"`
Rssi *string `json:"rssi,omitempty" description:"接收信号强度"`
Sinr *int `json:"sinr,omitempty" description:"信噪比(dB)"`
// WiFi 相关
SSID *string `json:"ssid,omitempty" description:"WiFi热点名称"`
WifiEnabled *bool `json:"wifi_enabled,omitempty" description:"WiFi开关状态"`
WifiPassword *string `json:"wifi_password,omitempty" description:"WiFi密码"`
// 网络相关
IPAddress *string `json:"ip_address,omitempty" description:"IP地址"`
WANIP *string `json:"wan_ip,omitempty" description:"基站分配IPv4地址"`
LANIP *string `json:"lan_ip,omitempty" description:"局域网网关IP地址"`
MACAddress *string `json:"mac_address,omitempty" description:"MAC地址"`
// 流量与速率
DailyUsage *string `json:"daily_usage,omitempty" description:"日使用流量(字节)"`
DLStats *string `json:"dl_stats,omitempty" description:"本次开机下载流量(字节)"`
ULStats *string `json:"ul_stats,omitempty" description:"本次开机上传流量(字节)"`
LimitSpeed *int `json:"limit_speed,omitempty" description:"限速速率(KB/s)"`
// 设备属性
CurrentIccid *string `json:"current_iccid,omitempty" description:"当前使用的ICCID"`
MaxClients *int `json:"max_clients,omitempty" description:"最大连接客户端数"`
SoftwareVersion *string `json:"software_version,omitempty" description:"软件版本号"`
SwitchMode *string `json:"switch_mode,omitempty" description:"切卡模式0=自动1=手动"`
SyncInterval *int `json:"sync_interval,omitempty" description:"信息上报周期(秒)"`
// Gateway 标识字段
DeviceID *string `json:"device_id,omitempty" description:"Gateway设备ID(IMEI/SN)"`
DeviceName *string `json:"device_name,omitempty" description:"Gateway返回的设备名称"`
DeviceType *string `json:"device_type,omitempty" description:"Gateway返回的设备类型"`
IMEI *string `json:"imei,omitempty" description:"IMEI号"`
IMSI *string `json:"imsi,omitempty" description:"IMSI用户标识码"`
}
```
**C. 修改 `AssetRealtimeStatusResponse`per D-07/D-08**
`Cards []BoundCardInfo` 字段之后追加:
```go
DeviceRealtime *DeviceGatewayInfo `json:"device_realtime,omitempty" description:"设备实时状态(来自 Gateway sync-infoGateway 失败时为 null"`
```
**D. 修改 `AssetResolveResponse`per D-12**
`Manufacturer string` 字段之后、`// 卡专属字段` 注释之前,追加 5 个 DB 缓存字段:
```go
// 设备 DB 缓存字段Phase 3 sync-info 写入,展示上次同步快照)
OnlineStatus int `json:"online_status,omitempty" description:"在线状态0未知 1在线 2离线设备类型时有效"`
LastOnlineTime *time.Time `json:"last_online_time,omitempty" description:"最后在线时间(设备类型时有效)"`
SoftwareVersion string `json:"software_version,omitempty" description:"固件版本号(设备类型时有效)"`
SwitchMode string `json:"switch_mode,omitempty" description:"切卡模式0=自动1=手动(设备类型时有效)"`
LastGatewaySyncAt *time.Time `json:"last_gateway_sync_at,omitempty" description:"最后 sync-info 同步时间(设备类型时有效)"`
```
</action>
<verify>go build ./internal/gateway/... && go build ./internal/model/dto/...</verify>
<done>
- `SyncDeviceInfoResp` 包含所有 Gateway 文档字段(包括 Rsrp/Rsrq/Sinr/WifiPassword/IMSI/LANIP/WANIP/MaxClients/ConnectTime/Status/ULStats/DLStats/LimitSpeed/SyncInterval
- `asset_dto.go``DeviceGatewayInfo` 结构体存在,字段带 `description` tag
- `AssetRealtimeStatusResponse``DeviceRealtime *DeviceGatewayInfo`
- `AssetResolveResponse` 含 5 个 DB 缓存设备字段
- `go build` 编译通过
</done>
</task>
<task type="auto">
<name>Task 2: 修复静态 DTO 映射缺口5 个文件映射补全)</name>
<files>internal/service/device/service.go, internal/service/device/binding.go, internal/service/asset/service.go</files>
<action>
**A. `internal/service/device/service.go:toDeviceResponse()`per D-02**
在现有 `resp := &dto.DeviceResponse{...}` 初始化块中,追加 5 个 DB 缓存字段映射:
```go
OnlineStatus: device.OnlineStatus,
LastOnlineTime: device.LastOnlineTime,
SoftwareVersion: device.SoftwareVersion,
SwitchMode: device.SwitchMode,
LastGatewaySyncAt: device.LastGatewaySyncAt,
```
**B. `internal/service/device/binding.go:ListBindings()`per D-03**
在构建 `resp := &dto.DeviceCardBindingResponse{...}` 时,追加:
```go
IsCurrent: binding.IsCurrent,
```
**C. `internal/service/asset/service.go:buildDeviceResolveResponse()`per D-04 + D-12**
1. **补全 `BoundCardInfo.IsCurrent`per D-04**
在现有 `slotMap` 构建循环中,同构添加 `isCurrentMap`
```go
slotMap := make(map[uint]int, len(bindings))
isCurrentMap := make(map[uint]bool, len(bindings)) // 新增
for _, b := range bindings {
cardIDs = append(cardIDs, b.IotCardID)
slotMap[b.IotCardID] = b.SlotPosition
isCurrentMap[b.IotCardID] = b.IsCurrent // 新增
}
```
在构建 `dto.BoundCardInfo{...}` 时追加:
```go
IsCurrent: isCurrentMap[c.ID], // 新增
```
2. **补全 AssetResolveResponse 的 5 个 DB 缓存字段per D-12**
在 `resp := &dto.AssetResolveResponse{...}` 初始化块中追加:
```go
OnlineStatus: device.OnlineStatus,
LastOnlineTime: device.LastOnlineTime,
SoftwareVersion: device.SoftwareVersion,
SwitchMode: device.SwitchMode,
LastGatewaySyncAt: device.LastGatewaySyncAt,
```
**D. `internal/service/asset/service.go:GetRealtimeStatus()` device case 中 `BoundCardInfo`per D-04**
同 C 步骤,在 `GetRealtimeStatus` device case 中也需补全 `isCurrentMap`
在 `slotMap` 构建循环中追加 `isCurrentMap`,在 `dto.BoundCardInfo{...}` 中追加 `IsCurrent: isCurrentMap[c.ID]`。
注意:`Refresh()` 内部会调用 `GetRealtimeStatus()``buildDeviceResolveResponse()` 是独立的两处代码路径,都需要修复。
</action>
<verify>go build ./internal/service/device/... && go build ./internal/service/asset/...</verify>
<done>
- `toDeviceResponse()` 初始化块中含 5 个新字段赋值
- `ListBindings()` 的 `DeviceCardBindingResponse` 含 `IsCurrent: binding.IsCurrent`
- `buildDeviceResolveResponse()` 中含 `isCurrentMap` + `BoundCardInfo.IsCurrent` + 5 个 DB 缓存字段
- `GetRealtimeStatus()` device case 中含 `isCurrentMap` + `BoundCardInfo.IsCurrent`
- `go build` 编译通过
</done>
</task>
<task type="auto">
<name>Task 3: 建立设备实时 Gateway 调用链路GetRealtimeStatus + C 端 stub 替换)</name>
<files>internal/service/asset/service.go, internal/handler/app/client_asset.go</files>
<action>
**A. `internal/service/asset/service.go:GetRealtimeStatus()` device case 追加 Gateway 实时调用per D-07**
在现有 `resp.DeviceProtectStatus = s.getDeviceProtectStatus(ctx, id)` 之后追加:
```go
// 实时查询 Gateway 设备状态in-line不缓存per D-05
// Gateway 失败不阻断主流程DeviceRealtime 返回 nullper D-06
if s.gatewayClient != nil {
device, devErr := s.deviceStore.GetByID(ctx, id)
if devErr == nil {
// IMEI 优先,无则用 SN与 Refresh 中 updateDeviceFromSyncInfo 保持一致)
cardNo := device.IMEI
if cardNo == "" {
cardNo = device.SN
}
if cardNo != "" {
if syncResp, syncErr := s.gatewayClient.SyncDeviceInfo(ctx, &gateway.SyncDeviceInfoReq{
CardNo: cardNo,
}); syncErr == nil {
resp.DeviceRealtime = mapSyncRespToDeviceGatewayInfo(syncResp)
} else {
logger.GetAppLogger().Warn("GetRealtimeStatus: sync-info 调用失败DeviceRealtime 返回 null",
zap.Uint("device_id", id),
zap.Error(syncErr))
}
}
}
}
```
在 `GetRealtimeStatus()` 函数之后(同一文件,`service.go` 中),新增私有转换函数:
```go
// mapSyncRespToDeviceGatewayInfo 将 Gateway SyncDeviceInfoResp 映射为 B 端 DeviceGatewayInfo DTO
// 使用指针字段,未赋值的字段保持 nilomitempty
func mapSyncRespToDeviceGatewayInfo(r *gateway.SyncDeviceInfoResp) *dto.DeviceGatewayInfo {
info := &dto.DeviceGatewayInfo{
OnlineStatus: &r.OnlineStatus,
RunTime: strPtr(r.RunTime),
ConnectTime: strPtr(r.ConnectTime),
LastOnlineTime: strPtr(r.LastOnlineTime),
LastUpdateTime: strPtr(r.LastUpdateTime),
Rsrp: &r.Rsrp,
Rsrq: &r.Rsrq,
Rssi: strPtr(r.RSSI),
Sinr: &r.Sinr,
SSID: strPtr(r.SSID),
WifiEnabled: &r.WifiEnabled,
WifiPassword: strPtr(r.WifiPassword),
IPAddress: strPtr(r.IPAddress),
WANIP: strPtr(r.WANIP),
LANIP: strPtr(r.LANIP),
MACAddress: strPtr(r.MacAddress),
DailyUsage: strPtr(r.DailyUsage),
DLStats: strPtr(r.DLStats),
ULStats: strPtr(r.ULStats),
LimitSpeed: &r.LimitSpeed,
CurrentIccid: strPtr(r.CurrentIccid),
MaxClients: &r.MaxClients,
SoftwareVersion: strPtr(r.SoftwareVersion),
SwitchMode: strPtr(r.SwitchMode),
SyncInterval: &r.SyncInterval,
DeviceID: strPtr(r.DeviceID),
DeviceName: strPtr(r.DeviceName),
DeviceType: strPtr(r.DeviceType),
IMEI: strPtr(r.IMEI),
IMSI: strPtr(r.IMSI),
Status: &r.Status,
}
if r.BatteryLevel != nil {
info.BatteryLevel = r.BatteryLevel
}
return info
}
// strPtr 将空字符串转为 nil非空字符串返回指针
// 避免 JSON 响应中出现大量空字符串字段
func strPtr(s string) *string {
if s == "" {
return nil
}
return &s
}
```
注意:若 `strPtr` 函数已在其他地方定义,复用现有实现,不要重复定义。
**B. `internal/handler/app/client_asset.go:GetAsset()` 替换 mockper D-11**
定位 `handler/app/client_asset.go` 约 187-191 行的 mock 调用:
```go
// 旧代码(删除):
if resp.AssetType == "device" {
resp.DeviceRealtime = buildMockDeviceRealtime()
}
```
替换为:
```go
// 调用服务层获取设备实时 Gateway 数据per D-11
// Gateway 失败时 DeviceRealtime 为 null不阻断主流程per D-06
if resp.AssetType == "device" {
realtimeResp, realtimeErr := h.assetService.GetRealtimeStatus(
c.UserContext(), "device", resp.AssetID,
)
if realtimeErr == nil && realtimeResp.DeviceRealtime != nil {
// 将 B 端 DeviceGatewayInfo 映射到 C 端 DeviceRealtimeInfo
resp.DeviceRealtime = mapDeviceGatewayInfoToClientInfo(realtimeResp.DeviceRealtime)
}
}
```
在同一文件中,新增私有映射函数(替换被删除的 `buildMockDeviceRealtime()`
```go
// mapDeviceGatewayInfoToClientInfo 将 B 端 DeviceGatewayInfo 映射为 C 端 DeviceRealtimeInfo
// B 端和 C 端结构独立通过映射函数转换per D-08
func mapDeviceGatewayInfoToClientInfo(g *dto.DeviceGatewayInfo) *dto.DeviceRealtimeInfo {
if g == nil {
return nil
}
info := &dto.DeviceRealtimeInfo{}
if g.OnlineStatus != nil {
v := int64(*g.OnlineStatus)
info.OnlineStatus = &v
}
if g.BatteryLevel != nil {
v := int64(*g.BatteryLevel)
info.BatteryLevel = &v
}
if g.Status != nil {
v := int64(*g.Status)
info.Status = &v
}
info.RunTime = g.RunTime
info.ConnectTime = g.ConnectTime
info.LastOnlineTime = g.LastOnlineTime
info.LastUpdateTime = g.LastUpdateTime
if g.Rsrp != nil {
v := int64(*g.Rsrp)
info.Rsrp = &v
}
if g.Rsrq != nil {
v := int64(*g.Rsrq)
info.Rsrq = &v
}
info.Rssi = g.Rssi
if g.Sinr != nil {
v := int64(*g.Sinr)
info.Sinr = &v
}
info.SSID = g.SSID
info.WifiEnabled = g.WifiEnabled
info.WifiPassword = g.WifiPassword
info.IPAddress = g.IPAddress
info.WANIP = g.WANIP
info.LANIP = g.LANIP
info.MACAddress = g.MACAddress
info.DailyUsage = g.DailyUsage
info.DLStats = g.DLStats
info.ULStats = g.ULStats
if g.LimitSpeed != nil {
v := int64(*g.LimitSpeed)
info.LimitSpeed = &v
}
info.CurrentIccid = g.CurrentIccid
if g.MaxClients != nil {
v := int64(*g.MaxClients)
info.MaxClients = &v
}
info.SoftwareVersion = g.SoftwareVersion
if g.SwitchMode != nil {
v := int(*g.SwitchMode) // C端 SwitchMode 是 *int
info.SwitchMode = &v
}
if g.SyncInterval != nil {
v := int64(*g.SyncInterval)
info.SyncInterval = &v
}
info.DeviceID = g.DeviceID
info.DeviceName = g.DeviceName
info.DeviceType = g.DeviceType
info.Imei = g.IMEI
info.Imsi = g.IMSI
return info
}
```
最后,**删除** `buildMockDeviceRealtime()` 函数(约 595-640 行),该函数不再需要。
</action>
<verify>go build ./internal/service/asset/... && go build ./internal/handler/app/...</verify>
<done>
- `GetRealtimeStatus()` device case 末尾有 Gateway 调用 + `mapSyncRespToDeviceGatewayInfo` 函数存在
- `GetAsset()` 中 `buildMockDeviceRealtime()` 调用已删除,改为调用 `h.assetService.GetRealtimeStatus()` + `mapDeviceGatewayInfoToClientInfo()`
- `buildMockDeviceRealtime()` 函数已删除
- `go build ./...` 全量编译通过(无编译错误)
</done>
</task>
</tasks>
<verification>
```bash
# 全量编译验证(必须通过)
go build ./...
# 静态检查(无新增错误)
go vet ./internal/gateway/... ./internal/model/dto/... ./internal/service/asset/... ./internal/service/device/... ./internal/handler/app/...
```
**手动验证清单(通过 DB 和 API 确认):**
1. 设备管理列表接口GET /api/admin/devices— 响应中含 `online_status`、`software_version` 等字段
2. 设备绑卡列表接口GET /api/admin/devices/:id/cards— 响应中 `bindings[].is_current` 有值
3. 资产解析接口GET /api/admin/assets/resolve/:identifier标识符为设备号— 响应中含 `online_status`、`last_gateway_sync_at` 等字段,`cards[].is_current` 有值
4. 资产实时状态接口GET /api/admin/assets/:type/:id/realtimetype=device— 响应中含 `device_realtime`(若 Gateway 可达则有值,不可达则为 null`cards[].is_current` 有值
5. C 端资产信息接口GET /api/c/v1/assetidentifier=设备IMEI/SN— 响应中 `device_realtime` 非 mock 数据(含真实 `online_status`/`battery_level` 等字段)
</verification>
<success_criteria>
- `go build ./...` 编译通过,无错误无 warning
- 所有 DTO 静态映射缺口已补全5 处字段 + 2 处 IsCurrent + 5 处 DB 缓存字段)
- `GetRealtimeStatus("device", id)` 调用 Gateway sync-info失败时 `DeviceRealtime` 为 null
- C 端 `GetAsset` device 类型不再返回 mock 数据
- 代码符合项目规范:中文注释、分层架构、错误只记 Warn 不阻断
</success_criteria>
<output>
完成后创建 `.planning/phases/03.1-sync-info-phase-3-dto/03.1-01-SUMMARY.md`,记录:
- 实际修改的文件和行号
- `mapSyncRespToDeviceGatewayInfo` 和 `mapDeviceGatewayInfoToClientInfo` 的实现位置
- 编译通过的确认
- 任何与计划不符的实现调整(并说明原因)
</output>