Files
junhong_cmp_fiber/internal/gateway/models.go
邹进 9c3c258f16
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m27s
feat: 卡流量读数先过采集队列再入账,扣减按采集时刻确定套餐
- 每卡 Redis 采集队列(默认 N=3、跨天清空、回退容差 1 MB),攒满且不下降才写最后一个点
- 测试环境全量切换日快照计量,下线灰度名单、切换水位与卡表基线扣费;全部卡置待建起点
- 读数区分没给值与 0;有套餐的卡读数 0 也写快照与日用量 0 行;重置日比较基准为 0 照常防旧值
- 新增采集记录(按月分区保留 90 天)、人工冲正流水与后台接口、采集告警
- 扣减按采集时刻有效套餐查找,只对生效中套餐做状态迁移,查不到套餐按成功跳过
- 换货新卡(新设备下的卡)打待建起点;采集间隔上限只校验启用中的轮询配置

Change: add-card-traffic-queue-and-observed-deduction

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:18:47 +09:00

360 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package gateway 定义 Gateway API 的请求和响应数据传输对象(DTO)
package gateway
import (
"encoding/json"
"strconv"
"strings"
)
// FlexBool 灵活布尔类型
// Gateway 部分设备返回 bool 字段时使用字符串(如 "1"/"0")而非标准 JSON bool,
// 导致 sonic 严格类型检查失败。此类型兼容 bool、string、number 等多种 JSON 值。
type FlexBool bool
// UnmarshalJSON 实现 json.Unmarshaler 接口
// 支持:true/false, "1"/"0", "true"/"false", 1/0, null
func (b *FlexBool) UnmarshalJSON(data []byte) error {
raw := strings.Trim(string(data), "\"")
switch raw {
case "true", "1":
*b = true
default:
*b = false
}
return nil
}
// FlexInt 灵活整数类型
// 与 FlexBool 同理,Gateway 部分设备返回 int 字段时使用字符串(如 "30")而非 JSON number。
type FlexInt int
// UnmarshalJSON 实现 json.Unmarshaler 接口
// 支持:30, "30", null
func (i *FlexInt) UnmarshalJSON(data []byte) error {
raw := strings.Trim(string(data), "\"")
if raw == "" || raw == "null" {
*i = 0
return nil
}
n, err := strconv.Atoi(raw)
if err != nil {
*i = 0
return nil
}
*i = FlexInt(n)
return nil
}
// FlexFloat 灵活浮点类型
// Gateway 部分流量字段会在 number / string 之间漂移,此类型统一解析为 float64。
type FlexFloat float64
// UnmarshalJSON 实现 json.Unmarshaler 接口
// 支持:0.00, "0.00", null
func (f *FlexFloat) UnmarshalJSON(data []byte) error {
raw := strings.TrimSpace(string(data))
if raw == "" || raw == "null" {
*f = 0
return nil
}
if strings.HasPrefix(raw, "\"") && strings.HasSuffix(raw, "\"") {
unquoted, err := strconv.Unquote(raw)
if err != nil {
*f = 0
return nil
}
raw = strings.TrimSpace(unquoted)
}
n, err := strconv.ParseFloat(raw, 64)
if err != nil {
*f = 0
return nil
}
*f = FlexFloat(n)
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 的通用响应结构
type GatewayResponse struct {
Code int `json:"code" description:"业务状态码(200 = 成功)"`
Msg string `json:"msg" description:"业务提示信息"`
Data json.RawMessage `json:"data" description:"业务数据(原始 JSON)"`
TraceID string `json:"trace_id" description:"链路追踪 ID"`
}
// ============ 流量卡相关 DTO ============
// CardStatusReq 是查询流量卡状态的请求
type CardStatusReq struct {
CardNo string `json:"cardNo" validate:"required" required:"true" description:"流量卡号"`
}
// CardStatusResp 是查询流量卡状态的响应
type CardStatusResp struct {
ICCID string `json:"iccid" description:"ICCID"`
CardStatus string `json:"cardStatus" description:"卡状态(准备、正常、停机;仅正常表示开机)"`
// IMEI 为插拔卡业务中网关上报的设备 IMEI,非设备自身 IMEI,仅用于数据同步落库,无业务使用
IMEI string `json:"imei,omitempty" description:"插拔卡业务 IMEI(网关同步,无业务含义)"`
Extend string `json:"extend,omitempty" description:"扩展字段(广电国网特殊参数)"`
}
// FlowQueryReq 是查询流量使用的请求
type FlowQueryReq struct {
CardNo string `json:"cardNo" validate:"required" required:"true" description:"流量卡号"`
}
// FlowUsageResp 是查询流量使用的响应
type FlowUsageResp struct {
ICCID string `json:"iccid" description:"ICCID"`
Used FlexFloat `json:"used" description:"当月已用流量(MB,兼容数字或字符串)"`
Unit string `json:"unit" description:"流量单位(MB)"`
// UsedState 区分运营商"没有给值"与真实读数;零值表示没有给值(响应缺少 used 字段时不会调用解析)。
UsedState FlowReadingState `json:"-"`
}
// FlowReadingState 是流量读数的解析结果。
type FlowReadingState int
const (
// FlowReadingMissing 表示 used 缺失、为 null、为空或无法解析。
FlowReadingMissing FlowReadingState = iota
// FlowReadingPresent 表示 used 解析为数字(可能为负数、NaN、Inf,由调用方判断是否有效)。
FlowReadingPresent
)
// UnmarshalJSON 解析流量响应,并记录 used 是否给出了可解析的数字。
func (r *FlowUsageResp) UnmarshalJSON(data []byte) error {
type plain FlowUsageResp
var raw struct {
plain
Used json.RawMessage `json:"used"`
}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
*r = FlowUsageResp(raw.plain)
r.Used, r.UsedState = 0, FlowReadingMissing
if value, ok := parseFlowReading(raw.Used); ok {
r.Used, r.UsedState = FlexFloat(value), FlowReadingPresent
}
return nil
}
// HasReading 返回运营商是否给出了可解析的读数。
func (r *FlowUsageResp) HasReading() bool {
return r != nil && r.UsedState == FlowReadingPresent
}
// parseFlowReading 按 FlexFloat 的兼容规则解析读数,null、空、无法解析时返回 false。
func parseFlowReading(data json.RawMessage) (float64, bool) {
raw := strings.TrimSpace(string(data))
if raw == "" || raw == "null" {
return 0, false
}
if strings.HasPrefix(raw, "\"") && strings.HasSuffix(raw, "\"") {
unquoted, err := strconv.Unquote(raw)
if err != nil {
return 0, false
}
raw = strings.TrimSpace(unquoted)
}
if raw == "" {
return 0, false
}
value, err := strconv.ParseFloat(raw, 64)
if err != nil {
return 0, false
}
return value, true
}
// CardOperationReq 是停机/复机请求
type CardOperationReq struct {
CardNo string `json:"cardNo" validate:"required" required:"true" description:"流量卡号"`
Extend string `json:"extend,omitempty" description:"扩展字段(广电国网特殊参数)"`
}
// RealnameStatusResp 是实名认证状态的响应
type RealnameStatusResp struct {
ICCID string `json:"iccid" description:"ICCID"`
RealStatus bool `json:"realStatus" description:"实名状态(true=已实名, false=未实名)"`
}
// RealnameLinkResp 是实名认证链接的响应
type RealnameLinkResp struct {
URL string `json:"url" description:"实名认证跳转链接(HTTPS URL)"`
}
// BatchQueryReq 是批量查询的请求
type BatchQueryReq struct {
CardNos []string `json:"cardNos" validate:"required,min=1,max=100" required:"true" description:"流量卡号列表(最多100个)"`
}
// BatchQueryResp 是批量查询的响应
type BatchQueryResp struct {
Results []CardStatusResp `json:"results" description:"查询结果列表"`
}
// ============ 设备相关 DTO ============
// DeviceInfoReq 是查询设备信息的请求
type DeviceInfoReq struct {
CardNo string `json:"cardNo,omitempty" description:"流量卡号(与 DeviceID 二选一)"`
DeviceID string `json:"deviceId,omitempty" description:"设备 ID/IMEI(与 CardNo 二选一)"`
}
// DeviceInfoResp 是查询设备信息的响应
type DeviceInfoResp struct {
IMEI string `json:"imei" description:"设备 IMEI"`
OnlineStatus int `json:"onlineStatus" description:"在线状态(0:离线, 1:在线)"`
SignalLevel int `json:"signalLevel" description:"信号强度(0-31)"`
WiFiSSID string `json:"wifiSsid,omitempty" description:"WiFi 名称"`
WiFiEnabled int `json:"wifiEnabled" description:"WiFi 启用状态(0:禁用, 1:启用)"`
UploadSpeed int `json:"uploadSpeed" description:"上行速率(KB/s)"`
DownloadSpeed int `json:"downloadSpeed" description:"下行速率(KB/s)"`
Extend string `json:"extend,omitempty" description:"扩展字段(广电国网特殊参数)"`
}
// CardSpeedTierReq 是流量卡固定限速档位请求。
type CardSpeedTierReq struct {
CardNo string `json:"cardNo" validate:"required" required:"true" description:"流量卡 ICCID"`
Code string `json:"code" validate:"required" required:"true" description:"限速档位编码 (-1:恢复不限速, 0:0kbps, 1:128Kbps, 2:512Kbps, 3:1Mbps, 4:2Mbps, 5:10Mbps, 6:20Mbps, 7:50Mbps, 8:100Mbps)"`
}
// WiFiParams 是设置设备 WiFi 的内层参数
type WiFiParams struct {
SSIDName string `json:"ssidName" validate:"required,min=1,max=32" required:"true" minLength:"1" maxLength:"32" description:"WiFi 名称"`
SSIDPassword string `json:"ssidPassword,omitempty" description:"WiFi 密码"`
}
// WiFiReq 是设置设备 WiFi 的请求
// Gateway 实际要求的结构为 {"params":{"cardNo":"设备IMEI","params":{"ssidName":"...","ssidPassword":"..."}}}
type WiFiReq struct {
CardNo string `json:"cardNo" validate:"required" required:"true" description:"设备 IMEI"`
Params WiFiParams `json:"params" required:"true" description:"WiFi 配置参数"`
Extend string `json:"extend,omitempty" description:"扩展字段(广电国网特殊参数)"`
}
// SwitchCardReq 是设备切换卡的请求
type SwitchCardReq struct {
CardNo string `json:"cardNo" validate:"required" required:"true" description:"设备编号(IMEI)"`
ICCID string `json:"iccid" validate:"required" required:"true" description:"目标卡 ICCID"`
OperationType int `json:"operationType" description:"操作类型(固定值 2)"`
Extend string `json:"extend,omitempty" description:"扩展字段(广电国网特殊参数)"`
}
// DeviceOperationReq 是设备操作(重启、恢复出厂)的请求
type DeviceOperationReq struct {
DeviceID string `json:"cardNo" validate:"required" required:"true" description:"设备 ID/IMEI"`
Extend string `json:"extend,omitempty" description:"扩展字段(广电国网特殊参数)"`
}
// SwitchModeReq 是设置设备切卡模式的请求
type SwitchModeReq struct {
CardNo string `json:"cardNo" validate:"required" required:"true" description:"设备编号(IMEI)"`
SwitchMode string `json:"switch_mode" description:"切卡模式(0:自动切卡, 1:手动切卡)"`
ICCID string `json:"iccid" validate:"required" required:"true" description:"目标卡 ICCID"`
CallbackURL string `json:"callbackUrl,omitempty" description:"异步回调通知地址"`
}
// SlotInfo 是单个卡槽信息
type SlotInfo struct {
SlotNo int `json:"slotNo" description:"卡槽编号"`
ICCID string `json:"iccid" description:"卡槽中的 ICCID"`
CardStatus string `json:"cardStatus" description:"卡状态(准备、正常、停机)"`
IsActive int `json:"isActive" description:"是否为当前使用的卡槽(0:否, 1:是)"`
Extend string `json:"extend,omitempty" description:"扩展字段(广电国网特殊参数)"`
}
// SlotInfoResp 是查询设备卡槽信息的响应
type SlotInfoResp struct {
IMEI string `json:"imei" description:"设备 IMEI"`
Slots []SlotInfo `json:"slots" description:"卡槽信息列表"`
Extend string `json:"extend,omitempty" description:"扩展字段(广电国网特殊参数)"`
}
// SyncDeviceInfoReq sync-info 同步查询设备信息请求
// cardNo 规则:>15 位用 ICCID,=15 位用 IMEI,=11 位用 SN
type SyncDeviceInfoReq struct {
CardNo string `json:"cardNo" description:"设备标识(ICCID/IMEI/SN)"`
}
// SyncDeviceInfoResp sync-info 同步查询设备信息响应(对应 Gateway data 字段)
// 注意:所有字段均可能为 null/零值,表示暂无数据
type SyncDeviceInfoResp struct {
DeviceID FlexString `json:"device_id" description:"设备ID(IMEI/SN)"`
DeviceName FlexString `json:"device_name" description:"设备名称"`
IMEI FlexString `json:"imei" description:"IMEI号"`
CurrentIccid FlexString `json:"current_iccid" description:"当前使用的ICCID"`
CurrentSlotNo FlexInt `json:"current_slot_number" description:"当前使用卡槽号(逻辑槽位,1开始)"`
DeviceType FlexString `json:"device_type" description:"设备类型:1=诺行,2=玺龙,3=迎势达"`
SoftwareVersion FlexString `json:"software_version" description:"软件版本号"`
MacAddress FlexString `json:"mac_address" description:"MAC地址"`
SSID FlexString `json:"ssid" description:"WiFi热点名称"`
WifiEnabled FlexBool `json:"wifi_enabled" description:"WiFi开关状态"`
SwitchMode FlexString `json:"switch_mode" description:"切卡模式:0=自动,1=手动"`
RSSI FlexString `json:"rssi" description:"接收信号强度"`
BatteryLevel *FlexInt `json:"battery_level" description:"电池电量百分比(无电池时为null)"`
OnlineStatus FlexInt `json:"online_status" description:"在线状态:1=在线,2=离线"`
LastUpdateTime FlexString `json:"last_update_time" description:"设备信息最后更新时间(ISO 8601 或 Unix 时间戳)"`
LastOnlineTime FlexString `json:"last_online_time" description:"设备最后在线时间(ISO 8601 或 Unix 时间戳)"`
RunTime FlexString `json:"run_time" description:"本次开机运行时间(秒)"`
DailyUsage FlexString `json:"daily_usage" description:"日使用流量(字节)"`
// 信号相关(D-10 补全)
Rsrp FlexInt `json:"rsrp" description:"参考信号接收功率(dBm)"`
Rsrq FlexInt `json:"rsrq" description:"参考信号接收质量(dB)"`
Sinr FlexInt `json:"sinr" description:"信噪比(dB)"`
// WiFi 相关(D-10 补全)
WifiPassword FlexString `json:"wifi_password" description:"WiFi密码"`
// 网络相关(D-10 补全)
IPAddress FlexString `json:"ip_address" description:"IP地址"`
WANIP FlexString `json:"wan_ip" description:"基站分配IPv4地址"`
LANIP FlexString `json:"lan_ip" description:"局域网网关IP地址"`
// 连接相关(D-10 补全)
MaxClients FlexInt `json:"max_clients" description:"最大连接客户端数"`
ClientNumber FlexInt `json:"client_number" description:"当前已连接客户端数"`
ConnectTime FlexString `json:"connect_time" description:"设备本次联网时间(秒)"`
// 设备属性(D-10 补全)
Status FlexInt `json:"status" description:"设备状态:1=正常,0=禁用"`
IMSI FlexString `json:"imsi" description:"IMSI用户标识码"`
ULStats FlexString `json:"ul_stats" description:"本次开机上传流量(字节)"`
DLStats FlexString `json:"dl_stats" description:"本次开机下载流量(字节)"`
LimitSpeed FlexInt `json:"limit_speed" description:"限速速率(KB/s)"`
SyncInterval FlexInt `json:"sync_interval" description:"信息上报周期(秒)"`
}