From a202b2da8a5e5e47bb12305bd84145a984ca8f8e Mon Sep 17 00:00:00 2001 From: huang Date: Sat, 28 Mar 2026 13:21:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(03.1-01):=20=E5=BB=BA=E7=AB=8B=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E5=AE=9E=E6=97=B6=20Gateway=20=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E9=93=BE=E8=B7=AF=EF=BC=8C=E6=9B=BF=E6=8D=A2=20mock=20?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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,不阻断主流程 --- internal/handler/app/client_asset.go | 120 ++++++++++++++++++--------- internal/service/asset/service.go | 75 +++++++++++++++++ 2 files changed, 154 insertions(+), 41 deletions(-) diff --git a/internal/handler/app/client_asset.go b/internal/handler/app/client_asset.go index 0fa26a7..8b2c2a4 100644 --- a/internal/handler/app/client_asset.go +++ b/internal/handler/app/client_asset.go @@ -185,9 +185,15 @@ func (h *ClientAssetHandler) GetAssetInfo(c *fiber.Ctx) error { BoundDeviceName: resolved.Asset.BoundDeviceName, } - // TODO: Gateway 同步接口对接后,替换为真实设备实时数据 + // 调用服务层获取设备实时 Gateway 数据(per D-11) + // Gateway 失败时 DeviceRealtime 为 null,不阻断主流程(per D-06) if resp.AssetType == "device" { - resp.DeviceRealtime = buildMockDeviceRealtime() + realtimeResp, realtimeErr := h.assetService.GetRealtimeStatus( + c.UserContext(), "device", resp.AssetID, + ) + if realtimeErr == nil && realtimeResp.DeviceRealtime != nil { + resp.DeviceRealtime = mapDeviceGatewayInfoToClientInfo(realtimeResp.DeviceRealtime) + } } return response.Success(c, resp) @@ -592,44 +598,76 @@ func packageStatusName(status int) string { } } -// buildMockDeviceRealtime 构建设备实时状态假数据 -// TODO: Gateway 同步接口对接后移除此函数,改为调用 Gateway 接口获取真实数据 -func buildMockDeviceRealtime() *dto.DeviceRealtimeInfo { - onlineStatus := int64(1) - batteryLevel := int64(85) - deviceStatus := int64(1) - runTime := "3600" - connectTime := "3500" - rsrp := int64(-80) - rsrq := int64(-10) - rssi := "-65" - sinr := int64(15) - ssid := "JunHong-WiFi" - wifiEnabled := true - wifiPassword := "12345678" - ipAddress := "192.168.1.1" - lanIP := "192.168.1.1" - dailyUsage := "0" - maxClients := int64(32) - switchMode := 0 - - return &dto.DeviceRealtimeInfo{ - OnlineStatus: &onlineStatus, - BatteryLevel: &batteryLevel, - Status: &deviceStatus, - RunTime: &runTime, - ConnectTime: &connectTime, - Rsrp: &rsrp, - Rsrq: &rsrq, - Rssi: &rssi, - Sinr: &sinr, - SSID: &ssid, - WifiEnabled: &wifiEnabled, - WifiPassword: &wifiPassword, - IPAddress: &ipAddress, - LANIP: &lanIP, - DailyUsage: &dailyUsage, - MaxClients: &maxClients, - SwitchMode: &switchMode, +// 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 { + // SwitchMode 在 B 端为字符串("0"/"1"),C 端为整型(0/1),需解析转换 + if n, convErr := strconv.Atoi(*g.SwitchMode); convErr == nil { + info.SwitchMode = &n + } + } + 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 } diff --git a/internal/service/asset/service.go b/internal/service/asset/service.go index ff2e58d..9bdc977 100644 --- a/internal/service/asset/service.go +++ b/internal/service/asset/service.go @@ -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 返回 null(per 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 +// 使用指针字段,未赋值的字段保持 nil(omitempty) +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 {