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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user