feat(03.1-01): 建立设备实时 Gateway 调用链路,替换 mock 数据

- GetRealtimeStatus() device case 新增 Gateway sync-info 实时调用(IMEI/SN 优先,gatewayClient nil 安全)
- 新增 mapSyncRespToDeviceGatewayInfo() 将 SyncDeviceInfoResp 映射为 B 端 DeviceGatewayInfo DTO
- 新增 strPtr() 辅助函数(空字符串→nil,防止 JSON 出现大量空字符串字段)
- client_asset.go 删除 buildMockDeviceRealtime(),改为调用 assetService.GetRealtimeStatus() 获取真实数据
- 新增 mapDeviceGatewayInfoToClientInfo() 将 B 端 DeviceGatewayInfo 映射为 C 端 DeviceRealtimeInfo
- Gateway 调用失败时 DeviceRealtime 返回 null,不阻断主流程
This commit is contained in:
2026-03-28 13:21:02 +08:00
parent 3b54850f1b
commit a202b2da8a
2 changed files with 154 additions and 41 deletions

View File

@@ -300,6 +300,30 @@ func (s *Service) GetRealtimeStatus(ctx context.Context, assetType string, id ui
}
resp.DeviceProtectStatus = s.getDeviceProtectStatus(ctx, id)
// 实时查询 Gateway 设备状态不缓存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))
}
}
}
}
default:
return nil, errors.New(errors.CodeInvalidParam, "不支持的资产类型,仅支持 card 或 device")
}
@@ -307,6 +331,57 @@ func (s *Service) GetRealtimeStatus(ctx context.Context, assetType string, id ui
return resp, nil
}
// 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 响应中出现大量空字符串字段omitempty 对空字符串无效)
func strPtr(s string) *string {
if s == "" {
return nil
}
return &s
}
// Refresh 刷新资产数据(调网关同步)
func (s *Service) Refresh(ctx context.Context, assetType string, id uint) (*dto.AssetRealtimeStatusResponse, error) {
switch assetType {