This commit is contained in:
@@ -46,6 +46,33 @@ func (i *FlexInt) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlexString 灵活字符串类型
|
||||||
|
// Gateway 部分字段会在 string / number / bool 之间漂移,此类型统一转为字符串避免解析失败。
|
||||||
|
type FlexString string
|
||||||
|
|
||||||
|
// UnmarshalJSON 实现 json.Unmarshaler 接口
|
||||||
|
// 支持:"text", 123, true, null
|
||||||
|
func (s *FlexString) UnmarshalJSON(data []byte) error {
|
||||||
|
raw := strings.TrimSpace(string(data))
|
||||||
|
if raw == "" || raw == "null" {
|
||||||
|
*s = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标准 JSON 字符串,优先做反转义,保持原始内容语义
|
||||||
|
if strings.HasPrefix(raw, "\"") && strings.HasSuffix(raw, "\"") {
|
||||||
|
unquoted, err := strconv.Unquote(raw)
|
||||||
|
if err == nil {
|
||||||
|
*s = FlexString(unquoted)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// number/bool/object/array 等类型直接保留原始文本
|
||||||
|
*s = FlexString(raw)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GatewayResponse 是 Gateway API 的通用响应结构
|
// GatewayResponse 是 Gateway API 的通用响应结构
|
||||||
type GatewayResponse struct {
|
type GatewayResponse struct {
|
||||||
Code int `json:"code" description:"业务状态码(200 = 成功)"`
|
Code int `json:"code" description:"业务状态码(200 = 成功)"`
|
||||||
@@ -191,23 +218,23 @@ type SyncDeviceInfoReq struct {
|
|||||||
// SyncDeviceInfoResp sync-info 同步查询设备信息响应(对应 Gateway data 字段)
|
// SyncDeviceInfoResp sync-info 同步查询设备信息响应(对应 Gateway data 字段)
|
||||||
// 注意:所有字段均可能为 null/零值,表示暂无数据
|
// 注意:所有字段均可能为 null/零值,表示暂无数据
|
||||||
type SyncDeviceInfoResp struct {
|
type SyncDeviceInfoResp struct {
|
||||||
DeviceID string `json:"device_id" description:"设备ID(IMEI/SN)"`
|
DeviceID FlexString `json:"device_id" description:"设备ID(IMEI/SN)"`
|
||||||
DeviceName string `json:"device_name" description:"设备名称"`
|
DeviceName FlexString `json:"device_name" description:"设备名称"`
|
||||||
IMEI string `json:"imei" description:"IMEI号"`
|
IMEI FlexString `json:"imei" description:"IMEI号"`
|
||||||
CurrentIccid string `json:"current_iccid" description:"当前使用的ICCID"`
|
CurrentIccid FlexString `json:"current_iccid" description:"当前使用的ICCID"`
|
||||||
DeviceType string `json:"device_type" description:"设备类型:1=诺行,2=玺龙,3=迎势达"`
|
DeviceType FlexString `json:"device_type" description:"设备类型:1=诺行,2=玺龙,3=迎势达"`
|
||||||
SoftwareVersion string `json:"software_version" description:"软件版本号"`
|
SoftwareVersion FlexString `json:"software_version" description:"软件版本号"`
|
||||||
MacAddress string `json:"mac_address" description:"MAC地址"`
|
MacAddress FlexString `json:"mac_address" description:"MAC地址"`
|
||||||
SSID string `json:"ssid" description:"WiFi热点名称"`
|
SSID FlexString `json:"ssid" description:"WiFi热点名称"`
|
||||||
WifiEnabled FlexBool `json:"wifi_enabled" description:"WiFi开关状态"`
|
WifiEnabled FlexBool `json:"wifi_enabled" description:"WiFi开关状态"`
|
||||||
SwitchMode string `json:"switch_mode" description:"切卡模式:0=自动,1=手动"`
|
SwitchMode FlexString `json:"switch_mode" description:"切卡模式:0=自动,1=手动"`
|
||||||
RSSI string `json:"rssi" description:"接收信号强度"`
|
RSSI FlexString `json:"rssi" description:"接收信号强度"`
|
||||||
BatteryLevel *FlexInt `json:"battery_level" description:"电池电量百分比(无电池时为null)"`
|
BatteryLevel *FlexInt `json:"battery_level" description:"电池电量百分比(无电池时为null)"`
|
||||||
OnlineStatus FlexInt `json:"online_status" description:"在线状态:1=在线,2=离线"`
|
OnlineStatus FlexInt `json:"online_status" description:"在线状态:1=在线,2=离线"`
|
||||||
LastUpdateTime string `json:"last_update_time" description:"设备信息最后更新时间(ISO 8601)"`
|
LastUpdateTime FlexString `json:"last_update_time" description:"设备信息最后更新时间(ISO 8601 或 Unix 时间戳)"`
|
||||||
LastOnlineTime string `json:"last_online_time" description:"设备最后在线时间(ISO 8601)"`
|
LastOnlineTime FlexString `json:"last_online_time" description:"设备最后在线时间(ISO 8601 或 Unix 时间戳)"`
|
||||||
RunTime string `json:"run_time" description:"本次开机运行时间(秒)"`
|
RunTime FlexString `json:"run_time" description:"本次开机运行时间(秒)"`
|
||||||
DailyUsage string `json:"daily_usage" description:"日使用流量(字节)"`
|
DailyUsage FlexString `json:"daily_usage" description:"日使用流量(字节)"`
|
||||||
|
|
||||||
// 信号相关(D-10 补全)
|
// 信号相关(D-10 补全)
|
||||||
Rsrp FlexInt `json:"rsrp" description:"参考信号接收功率(dBm)"`
|
Rsrp FlexInt `json:"rsrp" description:"参考信号接收功率(dBm)"`
|
||||||
@@ -215,23 +242,23 @@ type SyncDeviceInfoResp struct {
|
|||||||
Sinr FlexInt `json:"sinr" description:"信噪比(dB)"`
|
Sinr FlexInt `json:"sinr" description:"信噪比(dB)"`
|
||||||
|
|
||||||
// WiFi 相关(D-10 补全)
|
// WiFi 相关(D-10 补全)
|
||||||
WifiPassword string `json:"wifi_password" description:"WiFi密码"`
|
WifiPassword FlexString `json:"wifi_password" description:"WiFi密码"`
|
||||||
|
|
||||||
// 网络相关(D-10 补全)
|
// 网络相关(D-10 补全)
|
||||||
IPAddress string `json:"ip_address" description:"IP地址"`
|
IPAddress FlexString `json:"ip_address" description:"IP地址"`
|
||||||
WANIP string `json:"wan_ip" description:"基站分配IPv4地址"`
|
WANIP FlexString `json:"wan_ip" description:"基站分配IPv4地址"`
|
||||||
LANIP string `json:"lan_ip" description:"局域网网关IP地址"`
|
LANIP FlexString `json:"lan_ip" description:"局域网网关IP地址"`
|
||||||
|
|
||||||
// 连接相关(D-10 补全)
|
// 连接相关(D-10 补全)
|
||||||
MaxClients FlexInt `json:"max_clients" description:"最大连接客户端数"`
|
MaxClients FlexInt `json:"max_clients" description:"最大连接客户端数"`
|
||||||
ClientNumber FlexInt `json:"client_number" description:"当前已连接客户端数"`
|
ClientNumber FlexInt `json:"client_number" description:"当前已连接客户端数"`
|
||||||
ConnectTime string `json:"connect_time" description:"设备本次联网时间(秒)"`
|
ConnectTime FlexString `json:"connect_time" description:"设备本次联网时间(秒)"`
|
||||||
|
|
||||||
// 设备属性(D-10 补全)
|
// 设备属性(D-10 补全)
|
||||||
Status FlexInt `json:"status" description:"设备状态:1=正常,0=禁用"`
|
Status FlexInt `json:"status" description:"设备状态:1=正常,0=禁用"`
|
||||||
IMSI string `json:"imsi" description:"IMSI用户标识码"`
|
IMSI FlexString `json:"imsi" description:"IMSI用户标识码"`
|
||||||
ULStats string `json:"ul_stats" description:"本次开机上传流量(字节)"`
|
ULStats FlexString `json:"ul_stats" description:"本次开机上传流量(字节)"`
|
||||||
DLStats string `json:"dl_stats" description:"本次开机下载流量(字节)"`
|
DLStats FlexString `json:"dl_stats" description:"本次开机下载流量(字节)"`
|
||||||
LimitSpeed FlexInt `json:"limit_speed" description:"限速速率(KB/s)"`
|
LimitSpeed FlexInt `json:"limit_speed" description:"限速速率(KB/s)"`
|
||||||
SyncInterval FlexInt `json:"sync_interval" description:"信息上报周期(秒)"`
|
SyncInterval FlexInt `json:"sync_interval" description:"信息上报周期(秒)"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
stderrors "errors"
|
stderrors "errors"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||||
@@ -442,36 +443,36 @@ func (s *Service) fetchDeviceGatewayInfo(ctx context.Context, deviceID uint) *dt
|
|||||||
func mapSyncRespToDeviceGatewayInfo(r *gateway.SyncDeviceInfoResp) *dto.DeviceGatewayInfo {
|
func mapSyncRespToDeviceGatewayInfo(r *gateway.SyncDeviceInfoResp) *dto.DeviceGatewayInfo {
|
||||||
info := &dto.DeviceGatewayInfo{
|
info := &dto.DeviceGatewayInfo{
|
||||||
OnlineStatus: flexIntPtr(r.OnlineStatus),
|
OnlineStatus: flexIntPtr(r.OnlineStatus),
|
||||||
RunTime: strPtr(r.RunTime),
|
RunTime: flexStrPtr(r.RunTime),
|
||||||
ConnectTime: strPtr(r.ConnectTime),
|
ConnectTime: flexStrPtr(r.ConnectTime),
|
||||||
LastOnlineTime: strPtr(r.LastOnlineTime),
|
LastOnlineTime: flexStrPtr(r.LastOnlineTime),
|
||||||
LastUpdateTime: strPtr(r.LastUpdateTime),
|
LastUpdateTime: flexStrPtr(r.LastUpdateTime),
|
||||||
Rsrp: flexIntPtr(r.Rsrp),
|
Rsrp: flexIntPtr(r.Rsrp),
|
||||||
Rsrq: flexIntPtr(r.Rsrq),
|
Rsrq: flexIntPtr(r.Rsrq),
|
||||||
Rssi: strPtr(r.RSSI),
|
Rssi: flexStrPtr(r.RSSI),
|
||||||
Sinr: flexIntPtr(r.Sinr),
|
Sinr: flexIntPtr(r.Sinr),
|
||||||
SSID: strPtr(r.SSID),
|
SSID: flexStrPtr(r.SSID),
|
||||||
WifiEnabled: flexBoolPtr(r.WifiEnabled),
|
WifiEnabled: flexBoolPtr(r.WifiEnabled),
|
||||||
WifiPassword: strPtr(r.WifiPassword),
|
WifiPassword: flexStrPtr(r.WifiPassword),
|
||||||
IPAddress: strPtr(r.IPAddress),
|
IPAddress: flexStrPtr(r.IPAddress),
|
||||||
WANIP: strPtr(r.WANIP),
|
WANIP: flexStrPtr(r.WANIP),
|
||||||
LANIP: strPtr(r.LANIP),
|
LANIP: flexStrPtr(r.LANIP),
|
||||||
MACAddress: strPtr(r.MacAddress),
|
MACAddress: flexStrPtr(r.MacAddress),
|
||||||
DailyUsage: strPtr(r.DailyUsage),
|
DailyUsage: flexStrPtr(r.DailyUsage),
|
||||||
DLStats: strPtr(r.DLStats),
|
DLStats: flexStrPtr(r.DLStats),
|
||||||
ULStats: strPtr(r.ULStats),
|
ULStats: flexStrPtr(r.ULStats),
|
||||||
LimitSpeed: flexIntPtr(r.LimitSpeed),
|
LimitSpeed: flexIntPtr(r.LimitSpeed),
|
||||||
CurrentIccid: strPtr(r.CurrentIccid),
|
CurrentIccid: flexStrPtr(r.CurrentIccid),
|
||||||
MaxClients: flexIntPtr(r.MaxClients),
|
MaxClients: flexIntPtr(r.MaxClients),
|
||||||
ClientNumber: flexIntPtr(r.ClientNumber),
|
ClientNumber: flexIntPtr(r.ClientNumber),
|
||||||
SoftwareVersion: strPtr(r.SoftwareVersion),
|
SoftwareVersion: flexStrPtr(r.SoftwareVersion),
|
||||||
SwitchMode: strPtr(r.SwitchMode),
|
SwitchMode: flexStrPtr(r.SwitchMode),
|
||||||
SyncInterval: flexIntPtr(r.SyncInterval),
|
SyncInterval: flexIntPtr(r.SyncInterval),
|
||||||
DeviceID: strPtr(r.DeviceID),
|
DeviceID: flexStrPtr(r.DeviceID),
|
||||||
DeviceName: strPtr(r.DeviceName),
|
DeviceName: flexStrPtr(r.DeviceName),
|
||||||
DeviceType: strPtr(r.DeviceType),
|
DeviceType: flexStrPtr(r.DeviceType),
|
||||||
IMEI: strPtr(r.IMEI),
|
IMEI: flexStrPtr(r.IMEI),
|
||||||
IMSI: strPtr(r.IMSI),
|
IMSI: flexStrPtr(r.IMSI),
|
||||||
Status: flexIntPtr(r.Status),
|
Status: flexIntPtr(r.Status),
|
||||||
}
|
}
|
||||||
if r.BatteryLevel != nil {
|
if r.BatteryLevel != nil {
|
||||||
@@ -490,6 +491,11 @@ func strPtr(s string) *string {
|
|||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flexStrPtr 将 FlexString 转为字符串指针
|
||||||
|
func flexStrPtr(s gateway.FlexString) *string {
|
||||||
|
return strPtr(string(s))
|
||||||
|
}
|
||||||
|
|
||||||
func flexBoolPtr(fb gateway.FlexBool) *bool {
|
func flexBoolPtr(fb gateway.FlexBool) *bool {
|
||||||
b := bool(fb)
|
b := bool(fb)
|
||||||
return &b
|
return &b
|
||||||
@@ -580,19 +586,14 @@ func (s *Service) Refresh(ctx context.Context, assetType string, id uint) (*dto.
|
|||||||
func (s *Service) updateDeviceFromSyncInfo(ctx context.Context, deviceID uint, resp *gateway.SyncDeviceInfoResp) {
|
func (s *Service) updateDeviceFromSyncInfo(ctx context.Context, deviceID uint, resp *gateway.SyncDeviceInfoResp) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
// 解析 LastOnlineTime(ISO 8601 字符串 → *time.Time)
|
// Gateway 时间字段可能返回 RFC3339 或 Unix 时间戳,统一解析后再回写数据库
|
||||||
var lastOnlineTime *time.Time
|
lastOnlineTime := parseGatewayTime(resp.LastOnlineTime)
|
||||||
if resp.LastOnlineTime != "" {
|
|
||||||
if t, err := time.Parse(time.RFC3339, resp.LastOnlineTime); err == nil {
|
|
||||||
lastOnlineTime = &t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新设备 5 个存储字段
|
// 更新设备 5 个存储字段
|
||||||
updates := map[string]any{
|
updates := map[string]any{
|
||||||
"online_status": int(resp.OnlineStatus),
|
"online_status": int(resp.OnlineStatus),
|
||||||
"software_version": resp.SoftwareVersion,
|
"software_version": string(resp.SoftwareVersion),
|
||||||
"switch_mode": resp.SwitchMode,
|
"switch_mode": string(resp.SwitchMode),
|
||||||
"last_gateway_sync_at": now,
|
"last_gateway_sync_at": now,
|
||||||
}
|
}
|
||||||
if lastOnlineTime != nil {
|
if lastOnlineTime != nil {
|
||||||
@@ -609,9 +610,9 @@ func (s *Service) updateDeviceFromSyncInfo(ctx context.Context, deviceID uint, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.CurrentIccid != "" {
|
if string(resp.CurrentIccid) != "" {
|
||||||
// 更新 is_current 标识(事务原子操作,per D-14 决策)
|
// 更新 is_current 标识(事务原子操作,per D-14 决策)
|
||||||
if err := s.deviceSimBindingStore.UpdateIsCurrentByDeviceID(ctx, deviceID, resp.CurrentIccid); err != nil {
|
if err := s.deviceSimBindingStore.UpdateIsCurrentByDeviceID(ctx, deviceID, string(resp.CurrentIccid)); err != nil {
|
||||||
logger.GetAppLogger().Warn("sync-info 更新 is_current 失败",
|
logger.GetAppLogger().Warn("sync-info 更新 is_current 失败",
|
||||||
zap.Uint("device_id", deviceID),
|
zap.Uint("device_id", deviceID),
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
@@ -619,6 +620,33 @@ func (s *Service) updateDeviceFromSyncInfo(ctx context.Context, deviceID uint, r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseGatewayTime 兼容解析 Gateway 时间字段
|
||||||
|
// 支持 RFC3339 字符串、Unix 秒时间戳、Unix 毫秒时间戳。
|
||||||
|
func parseGatewayTime(raw gateway.FlexString) *time.Time {
|
||||||
|
value := string(raw)
|
||||||
|
if value == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, err := time.Parse(time.RFC3339, value); err == nil {
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容网关返回的纯数字时间戳(秒或毫秒)
|
||||||
|
ts, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var t time.Time
|
||||||
|
if len(value) >= 13 {
|
||||||
|
t = time.UnixMilli(ts)
|
||||||
|
} else {
|
||||||
|
t = time.Unix(ts, 0)
|
||||||
|
}
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
// GetPackages 获取资产的所有套餐列表(支持分页)
|
// GetPackages 获取资产的所有套餐列表(支持分页)
|
||||||
// page 默认 1,pageSize 默认 50,pageSize 最大 100
|
// page 默认 1,pageSize 默认 50,pageSize 最大 100
|
||||||
func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, page, pageSize int) (*dto.AssetPackagesResult, error) {
|
func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, page, pageSize int) (*dto.AssetPackagesResult, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user