---
phase: 03.1-sync-info-phase-3-dto
verified: 2026-03-28T06:30:00Z
status: passed
score: 7/7 must-haves verified
re_verification: false
human_verification:
  - test: "设备实时状态接口（GET /api/admin/assets/device/:id/realtime）——在 Gateway 可达时验证 device_realtime 字段含真实数据"
    expected: "device_realtime 字段非 null，包含 online_status/battery_level/software_version 等真实 Gateway 返回值"
    why_human: "需要运行中的服务和真实 Gateway 连接才能验证数据链路完整性"
  - test: "C 端资产信息接口（GET /api/c/v1/asset/info?identifier=<IMEI>）——验证 device_realtime 为真实 Gateway 数据而非 mock"
    expected: "device_realtime 字段含真实设备状态，online_status 与设备实际在线状态一致"
    why_human: "需要 C 端 JWT 认证 + 真实设备 IMEI + Gateway 连接"
  - test: "Gateway 失败场景——当 IMEI 为空或 Gateway 不可达时 device_realtime 为 null"
    expected: "资产基础信息正常返回，device_realtime 字段为 null（非 500 错误）"
    why_human: "需要模拟 Gateway 超时/离线场景"
---

# Phase 03.1: 设备读取链路修复 + DTO 映射补全 Verification Report

**Phase Goal:** 补全 Phase 3 完成的设备数据写入链路在读取侧的缺口：修复 DTO 字段映射遗漏（5个 DB 缓存字段 + 2处 IsCurrent）+ 建立设备实时状态的 Gateway 查询链路（替换 mock → 真实 Gateway 数据）
**Verified:** 2026-03-28T06:30:00Z
**Status:** passed
**Re-verification:** No — initial verification

---

## Goal Achievement

### Observable Truths

| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | 设备管理列表/详情接口返回 online_status、last_online_time、software_version、switch_mode、last_gateway_sync_at 字段 | ✓ VERIFIED | `service.go:559-563` 中 `toDeviceResponse()` 含 5 个字段赋值；`device_dto.go:44-48` 含字段定义 |
| 2 | 设备绑卡列表接口（ListBindings）返回 is_current 字段 | ✓ VERIFIED | `binding.go:60` 含 `IsCurrent: binding.IsCurrent`；`device_dto.go:85` 含 IsCurrent 字段定义 |
| 3 | 资产 Resolve/Refresh 接口的 BoundCardInfo 包含 is_current 字段 | ✓ VERIFIED | `asset/service.go:130-145`（buildDeviceResolveResponse）和 `282-297`（GetRealtimeStatus）均含 `isCurrentMap` + `IsCurrent: isCurrentMap[c.ID]` |
| 4 | admin 资产解析接口（AssetResolveResponse）返回 5 个 DB 缓存字段 | ✓ VERIFIED | `asset_dto.go:44-48` 含 5 字段定义；`asset/service.go:117-121` 含映射赋值 |
| 5 | 资产实时状态接口（GetRealtimeStatus）对 device 类型实时调用 Gateway sync-info | ✓ VERIFIED | `asset/service.go:303-325` 含完整 Gateway 调用链路，`gatewayClient != nil` 安全检查，IMEI 优先/SN 备用 |
| 6 | C 端 GetAsset 接口的 device_realtime 字段从真实 Gateway 数据填充，不再是 mock 数据 | ✓ VERIFIED | `client_asset.go:188-196` 删除了 `buildMockDeviceRealtime()` 调用，替换为 `h.assetService.GetRealtimeStatus()` + `mapDeviceGatewayInfoToClientInfo()` |
| 7 | Gateway 调用失败时（IMEI 为空/超时/离线）device_realtime 返回 null，资产基础信息正常返回 | ✓ VERIFIED | `asset/service.go:318-322` 失败时仅记录 Warn 日志，`resp.DeviceRealtime` 保持 nil；`cardNo == ""` 分支不发起调用 |

**Score:** 7/7 truths verified

---

### Required Artifacts

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `internal/gateway/models.go` | SyncDeviceInfoResp 补全 15 个缺失字段 | ✓ VERIFIED | 行 162-185 含 Rsrp/Rsrq/Sinr/WifiPassword/IPAddress/WANIP/LANIP/MaxClients/ConnectTime/Status/IMSI/ULStats/DLStats/LimitSpeed/SyncInterval |
| `internal/model/dto/asset_dto.go` | DeviceGatewayInfo 新结构体；AssetRealtimeStatusResponse.DeviceRealtime；AssetResolveResponse 5 字段 | ✓ VERIFIED | 行 145-193 含 DeviceGatewayInfo（49 字段，全部含 description tag）；行 83 含 DeviceRealtime；行 43-48 含 5 个 DB 缓存字段 |
| `internal/service/device/service.go` | toDeviceResponse() 补全 5 字段映射 | ✓ VERIFIED | 行 559-563 含 OnlineStatus/LastOnlineTime/SoftwareVersion/SwitchMode/LastGatewaySyncAt 赋值 |
| `internal/service/device/binding.go` | ListBindings() 补全 IsCurrent 字段映射 | ✓ VERIFIED | 行 60 含 `IsCurrent: binding.IsCurrent` |
| `internal/service/asset/service.go` | buildDeviceResolveResponse() 补全 IsCurrent + 5个DB缓存字段；GetRealtimeStatus() device case 追加 Gateway 实时调用 | ✓ VERIFIED | 行 130-145 含 isCurrentMap + IsCurrent（buildDeviceResolveResponse）；行 282-297 含 isCurrentMap + IsCurrent（GetRealtimeStatus）；行 303-325 含 Gateway 调用；行 334-383 含 mapSyncRespToDeviceGatewayInfo + strPtr |
| `internal/handler/app/client_asset.go` | GetAsset() 删除 buildMockDeviceRealtime()，改为调用 assetService.GetRealtimeStatus() | ✓ VERIFIED | 行 188-196 含真实 Gateway 调用逻辑；行 601-673 含 mapDeviceGatewayInfoToClientInfo；buildMockDeviceRealtime 已删除（全文搜索无匹配） |

---

### Key Link Verification

| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `internal/service/asset/service.go:GetRealtimeStatus()` | `internal/gateway/device.go:SyncDeviceInfo()` | `s.gatewayClient.SyncDeviceInfo(ctx, &gateway.SyncDeviceInfoReq{CardNo: cardNo})` | ✓ WIRED | 行 314 确认调用；gatewayClient nil guard 在行 305 |
| `internal/handler/app/client_asset.go:GetAsset()` | `internal/service/asset/service.go:GetRealtimeStatus()` | `h.assetService.GetRealtimeStatus("device", resp.AssetID)` | ✓ WIRED | 行 191-196 确认调用及响应处理 |
| `internal/service/asset/service.go:buildDeviceResolveResponse()` | `dto.BoundCardInfo.IsCurrent` | `isCurrentMap[b.IotCardID] = b.IsCurrent` | ✓ WIRED | 行 130-145：isCurrentMap 构建 + IsCurrent 赋值均在函数内 |

---

### Data-Flow Trace (Level 4)

| Artifact | Data Variable | Source | Produces Real Data | Status |
|----------|---------------|--------|-------------------|--------|
| `client_asset.go:GetAsset()` | `resp.DeviceRealtime` | `h.assetService.GetRealtimeStatus()` → `s.gatewayClient.SyncDeviceInfo()` → Gateway HTTP 调用 | ✓ 调用真实 Gateway `/device/sync-info` 接口 | ✓ FLOWING |
| `asset/service.go:GetRealtimeStatus()` | `resp.DeviceRealtime` | `s.gatewayClient.SyncDeviceInfo()` 返回 `*SyncDeviceInfoResp` | ✓ 从真实外部 Gateway 获取，mapSyncRespToDeviceGatewayInfo 映射全部字段 | ✓ FLOWING |
| `device/service.go:toDeviceResponse()` | `OnlineStatus` 等 5 字段 | `device.OnlineStatus` 等来自 DB（Phase 3 写入的 sync-info 快照） | ✓ 直接来自 GORM model 字段 | ✓ FLOWING |
| `asset/service.go:buildDeviceResolveResponse()` | `BoundCardInfo.IsCurrent` | `isCurrentMap[c.ID]`，来自 `deviceSimBindingStore.ListByDeviceID` 查询 | ✓ 来自 DB 绑定记录中的 `is_current` 字段 | ✓ FLOWING |

---

### Behavioral Spot-Checks

| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
| 全量编译通过 | `go build ./...` | 无错误 | ✓ PASS |
| go vet 无新增警告 | `go vet ./internal/gateway/... ./internal/model/dto/... ./internal/service/asset/... ./internal/service/device/... ./internal/handler/app/...` | 无警告 | ✓ PASS |
| buildMockDeviceRealtime 已删除 | `grep -n "buildMockDeviceRealtime" client_asset.go` | 无匹配 | ✓ PASS |
| Gateway 调用 nil guard 存在 | `grep -n "gatewayClient != nil"` in asset/service.go | 行 305 存在 | ✓ PASS |
| 3 个提交均已存在 | `git show --stat 00dba42 3b54850 a202b2d` | 3 个提交均已找到 | ✓ PASS |
| C 端 device_realtime 调用真实服务 | `grep -n "assetService.GetRealtimeStatus"` in client_asset.go | 行 191 存在 | ✓ PASS |

---

### Requirements Coverage

| Requirement | Source Plan | Description | Status | Evidence |
|-------------|------------|-------------|--------|----------|
| DEVICE-02 | 03.1-01-PLAN.md | Gateway sync-info 同步接口对接 — `internal/gateway/device.go` 新增 `SyncDeviceInfo()` 方法 | ✓ SATISFIED | `gateway/device.go:72-79` 含 SyncDeviceInfo 方法；Phase 03.1 扩展了其 Gateway 调用用途（AssetService 集成）；`gateway/models.go` 补全了 SyncDeviceInfoResp 的 15 个缺失字段 |
| DEVICE-03 | 03.1-01-PLAN.md | `tb_device_sim_binding` 新增 `is_current` 字段 — DB 迁移 + Model + 更新逻辑 | ✓ SATISFIED | Phase 3 已完成 DB 迁移；Phase 03.1 补全了读取侧的 `IsCurrent` 映射（binding.go:60，asset/service.go:145,297） |
| DEVICE-04 | 03.1-01-PLAN.md | 设备 Refresh + 详情接入 sync-info — `RefreshDevice()` 调用后更新设备状态 | ✓ SATISFIED | Phase 3 完成写入侧；Phase 03.1 补全读取侧：5 个 DB 缓存字段映射到 DTO，GetRealtimeStatus 实时 Gateway 调用链路 |

**注意：** DEVICE-02/03/04 在 REQUIREMENTS.md Traceability 表中映射到 Phase 3，标记为 Complete。Phase 03.1 是这些需求的读取侧补全（写入侧在 Phase 3，读取侧在 Phase 03.1），需求已完全交付。

---

### Anti-Patterns Found

| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| `internal/model/dto/client_asset_dto.go` | 62, 67 | 残留过期注释："当前为 null" / "尚未对接" / "待后续填充" | ℹ️ Info | 代码功能正确，仅 description tag 和注释描述未更新；不影响运行时行为；由于 description 是标签值非代码逻辑，不影响 API 正确性 |

**说明：** `client_asset_dto.go` 中的 `description:"设备实时状态（Gateway 同步接口对接后填充，当前为 null）"` 是 DTO 结构体中的文档标签，已过期（实际已对接）。这属于文档/注释层面的 Info 级别问题，不是代码功能缺陷，不阻断 Phase 目标达成。

---

### Human Verification Required

#### 1. 设备实时状态接口真实数据验证

**Test:** 启动服务，调用 `GET /api/admin/assets/device/:id/realtime`（其中 `:id` 为有效 IMEI 的设备 ID），使用管理员 Token
**Expected:** 响应中 `device_realtime` 字段非 null，包含 `online_status`、`software_version`、`rssi` 等真实 Gateway 返回值
**Why human:** 需要运行中的服务、有效的管理员 Token 和真实 Gateway 连接

#### 2. C 端资产接口 device_realtime 真实数据

**Test:** 以 C 端用户身份，调用 `GET /api/c/v1/asset/info?identifier=<IMEI>`
**Expected:** 响应中 `device_realtime` 含真实设备状态（`online_status` 与设备实际在线状态一致），而非 mock 数据
**Why human:** 需要 C 端 JWT 认证 + 有效 IMEI + Gateway 连接

#### 3. Gateway 失败降级行为

**Test:** 断开 Gateway 连接或使用无 IMEI 的设备 ID，调用 `GET /api/admin/assets/device/:id/realtime`
**Expected:** 接口返回 200，`device_realtime` 为 null，其他字段（`cards`、`device_protect_status`）正常返回；服务日志中出现 Warn 级别的 "sync-info 调用失败" 日志
**Why human:** 需要模拟 Gateway 故障场景

---

### Gaps Summary

无 gaps。所有 7 个 must-have truths 均已验证通过。

---

## Summary

Phase 03.1 完整实现了计划目标：

1. **DTO 契约层**：`gateway/models.go` 补全 15 个 SyncDeviceInfoResp 缺失字段；`asset_dto.go` 新增 `DeviceGatewayInfo` B 端结构体、`AssetRealtimeStatusResponse.DeviceRealtime` 字段、`AssetResolveResponse` 5 个 DB 缓存字段。

2. **静态映射缺口修复**：`device/service.go:toDeviceResponse()` 补全 5 字段；`device/binding.go:ListBindings()` 补全 `IsCurrent`；`asset/service.go` 在 `buildDeviceResolveResponse()` 和 `GetRealtimeStatus()` 两处都补全了 `isCurrentMap` + `IsCurrent`。

3. **Gateway 实时调用链路**：`asset/service.go:GetRealtimeStatus()` device case 新增完整 Gateway 调用链路（nil guard + IMEI/SN 优先 + 失败只记 Warn）；`client_asset.go:GetAsset()` 删除 `buildMockDeviceRealtime()`，改用真实服务调用。

4. **编译验证**：`go build ./...` 和 `go vet` 全量通过，3 个提交均已确认存在。

唯一的 Info 级别发现是 `client_asset_dto.go` 中两处过期注释（"当前为 null"/"尚未对接"），不影响功能。

---

_Verified: 2026-03-28T06:30:00Z_  
_Verifier: the agent (gsd-verifier)_
