Files
junhong_cmp_fiber/internal/model/dto/asset_dto.go
huang 434a8b0349
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m46s
feat: 轮询系统重构(分片队列 + 停复机统一 + Handler 拆分)
【核心变更】

1. 停复机逻辑统一(StopResumeService)
   - 新增 EvaluateAndAct 统一入口,封装三条件停复机判断
   - 停机条件:无套餐(no_package) / 流量耗尽(traffic_exhausted) / 未实名(not_realname)
   - 复机条件:stop_reason 合规 + 有套餐且未耗尽 + 已实名或行业卡
   - 修复设备套餐 Bug:hasValidPackage 按 device_id 查套餐,而非仅 iot_card_id
   - 设备维度停复机加幂等锁(Redis SetNX,TTL 30s),防止多卡并发重复调 Gateway

2. Redis 分片队列(PollingQueueManager)
   - 新建 queue_manager.go,封装所有轮询 Redis 操作
   - 16 分片 Sorted Set,Key 格式:polling:shard:{shardID}:queue:{taskType}
   - Lua 脚本原子出队(ZRANGEBYSCORE + 分批 ZREM),消除竞态窗口
   - 新增背压检测:队列深度超 50 万时 Scheduler 跳过该分片
   - RemoveFromAllQueues 覆盖 4 种任务类型(含 protect)

3. Handler 拆分(polling_handler.go 1360行 → 5个专注文件)
   - polling_base.go:共享基类(并发控制/卡缓存/重入队)
   - polling_realname_handler.go:实名采集,实名 0→1 时立即触发复机
   - polling_carddata_handler.go:流量采集,保留跨月边界检测逻辑
   - polling_package_handler.go:套餐采集,委托 EvaluateAndAct 决策
   - polling_protect_handler.go:保护期一致性检查,保护期内强制修正

4. 配置管理(PollingConfigManager)
   - 新建 config_manager.go,从 scheduler.go 提取配置职责
   - 内存缓存 + 5 分钟定时刷新,刷新失败保留原缓存
   - 修复 getCardCondition:停机卡返回 suspended,不再错配 activated 配置

5. 渐进式初始化(CardInitializer)
   - 新建 initializer.go,分批加载(每批 10 万),批次间 sleep 500ms
   - 过滤 enable_polling=false 的卡,初始化完成前 Scheduler 不出队

6. 卡生命周期服务(PollingLifecycleService)
   - 新建 lifecycle_service.go,替代已删除的 callbacks.go 和 api_callback.go
   - OnCardCreated/OnCardEnabled/OnCardStatusChanged 入队前检查 enable_polling

7. Scheduler 精简(1000+行 → 227行)
   - 保留纯调度循环:scheduleLoop + processShardSchedule + enqueueBatch
   - 保留每 10 秒触发套餐过期检测和流量重置
   - 移除所有 DB 操作、配置加载、卡初始化逻辑

8. 轮询管控 API(enable_polling)
   - 新增 PUT /api/admin/assets/:id/polling-status 接口
   - 支持对设备/卡维度开关轮询,关闭后从所有分片队列移除

9. 数据库迁移
   - 000103:tb_device 新增 enable_polling 字段(boolean, NOT NULL, DEFAULT true)
   - 000104:新增 suspended 轮询配置,为 activated 配置补全 protect_check_interval

【文件统计】
- 新增:19 个文件(handler × 5、polling 组件 × 4、迁移 × 3 等)
- 修改:20 个文件(bootstrap 注入、store 接口、monitoring 适配分片等)
- 删除:3 个文件(polling_handler.go、callbacks.go、api_callback.go)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-07 12:27:04 +08:00

214 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 dto
import "time"
// AssetResolveResponse 统一资产解析响应
type AssetResolveResponse struct {
AssetType string `json:"asset_type" description:"资产类型card 或 device"`
AssetID uint `json:"asset_id" description:"资产数据库ID"`
VirtualNo string `json:"virtual_no" description:"虚拟号"`
Status int `json:"status" description:"资产状态"`
BatchNo string `json:"batch_no" description:"批次号"`
ShopID *uint `json:"shop_id,omitempty" description:"所属店铺ID"`
ShopName string `json:"shop_name,omitempty" description:"所属店铺名称"`
SeriesID *uint `json:"series_id,omitempty" description:"套餐系列ID"`
SeriesName string `json:"series_name,omitempty" description:"套餐系列名称"`
FirstCommissionPaid bool `json:"first_commission_paid" description:"一次性佣金是否已发放"`
AccumulatedRecharge int64 `json:"accumulated_recharge" description:"累计充值金额(分)"`
ActivatedAt *time.Time `json:"activated_at,omitempty" description:"激活时间"`
CreatedAt time.Time `json:"created_at" description:"创建时间"`
UpdatedAt time.Time `json:"updated_at" description:"更新时间"`
// 状态聚合字段
RealNameStatus int `json:"real_name_status" description:"实名状态0未实名 1已实名"`
CurrentPackage string `json:"current_package" description:"当前套餐名称(无套餐时为空)"`
PackageTotalMB int64 `json:"package_total_mb" description:"当前套餐总虚流量(MB)已按virtual_ratio换算"`
PackageUsedMB float64 `json:"package_used_mb" description:"当前已用虚流量(MB)已按virtual_ratio换算"`
PackageRemainMB float64 `json:"package_remain_mb" description:"当前套餐剩余虚流量(MB)已按virtual_ratio换算"`
DeviceProtectStatus string `json:"device_protect_status,omitempty" description:"设备保护期状态none/stop/start仅asset_type=device时有效"`
// 绑定关系字段
ICCID string `json:"iccid,omitempty" description:"卡ICCIDasset_type=card时有效"`
BoundDeviceID *uint `json:"bound_device_id,omitempty" description:"绑定的设备IDasset_type=card时有效"`
BoundDeviceNo string `json:"bound_device_no,omitempty" description:"绑定的设备虚拟号asset_type=card时有效"`
BoundDeviceName string `json:"bound_device_name,omitempty" description:"绑定的设备名称asset_type=card时有效"`
BoundCardCount int `json:"bound_card_count,omitempty" description:"绑定的卡数量asset_type=device时有效"`
Cards []BoundCardInfo `json:"cards,omitempty" description:"绑定的卡列表asset_type=device时有效"`
// 设备专属字段card类型时为零值
DeviceName string `json:"device_name,omitempty" description:"设备名称"`
IMEI string `json:"imei,omitempty" description:"设备IMEI"`
SN string `json:"sn,omitempty" description:"设备序列号"`
DeviceModel string `json:"device_model,omitempty" description:"设备型号"`
DeviceType string `json:"device_type,omitempty" description:"设备类型"`
MaxSimSlots int `json:"max_sim_slots,omitempty" description:"最大插槽数"`
Manufacturer string `json:"manufacturer,omitempty" description:"制造商"`
// 设备 DB 缓存字段Phase 3 sync-info 写入,展示上次同步快照)
OnlineStatus int `json:"online_status,omitempty" description:"在线状态0未知 1在线 2离线设备类型时有效"`
LastOnlineTime *time.Time `json:"last_online_time,omitempty" description:"最后在线时间(设备类型时有效)"`
SoftwareVersion string `json:"software_version,omitempty" description:"固件版本号(设备类型时有效)"`
SwitchMode string `json:"switch_mode,omitempty" description:"切卡模式0=自动1=手动(设备类型时有效)"`
LastGatewaySyncAt *time.Time `json:"last_gateway_sync_at,omitempty" description:"最后 sync-info 同步时间(设备类型时有效)"`
// 卡专属字段device类型时为零值
CarrierID uint `json:"carrier_id,omitempty" description:"运营商ID"`
CarrierType string `json:"carrier_type,omitempty" description:"运营商类型"`
CarrierName string `json:"carrier_name,omitempty" description:"运营商名称"`
MSISDN string `json:"msisdn,omitempty" description:"手机号"`
IMSI string `json:"imsi,omitempty" description:"IMSI"`
CardCategory string `json:"card_category,omitempty" description:"卡业务类型"`
Supplier string `json:"supplier,omitempty" description:"供应商"`
ActivationStatus int `json:"activation_status,omitempty" description:"激活状态"`
EnablePolling bool `json:"enable_polling,omitempty" description:"是否参与轮询"`
NetworkStatus int `json:"network_status,omitempty" description:"网络状态0停机 1开机asset_type=card时有效"`
}
// BoundCardInfo 设备绑定的卡信息
type BoundCardInfo struct {
CardID uint `json:"card_id" description:"卡ID"`
ICCID string `json:"iccid" description:"ICCID"`
MSISDN string `json:"msisdn,omitempty" description:"手机号"`
NetworkStatus int `json:"network_status" description:"网络状态0停机 1开机"`
RealNameStatus int `json:"real_name_status" description:"实名状态0未实名 1已实名"`
SlotPosition int `json:"slot_position,omitempty" description:"插槽位置"`
IsCurrent bool `json:"is_current" description:"是否为当前使用的卡"`
}
// AssetRealtimeStatusResponse 资产实时状态只读DB/Redis
type AssetRealtimeStatusResponse struct {
AssetType string `json:"asset_type" description:"资产类型card 或 device"`
AssetID uint `json:"asset_id" description:"资产ID"`
NetworkStatus int `json:"network_status,omitempty" description:"网络状态asset_type=card时有效0停机 1开机"`
RealNameStatus int `json:"real_name_status,omitempty" description:"实名状态asset_type=card时有效0未实名 1已实名"`
CurrentMonthUsageMB float64 `json:"current_month_usage_mb,omitempty" description:"系统累计的自然月流量MBasset_type=card时有效"`
LastSyncTime *time.Time `json:"last_sync_time,omitempty" description:"最后同步时间asset_type=card时有效"`
DeviceProtectStatus string `json:"device_protect_status,omitempty" description:"保护期状态asset_type=device时有效none/stop/start"`
Cards []BoundCardInfo `json:"cards,omitempty" description:"绑定卡状态列表asset_type=device时有效"`
DeviceRealtime *DeviceGatewayInfo `json:"device_realtime" description:"设备实时状态(来自 Gateway sync-info始终返回失败时仅含 gateway_msg"`
}
// AssetPackageResponse 资产套餐信息
type AssetPackageResponse struct {
PackageUsageID uint `json:"package_usage_id" description:"套餐使用记录ID"`
PackageID uint `json:"package_id" description:"套餐ID"`
PackageName string `json:"package_name" description:"套餐名称"`
PackageType string `json:"package_type" description:"套餐类型formal/addon"`
UsageType string `json:"usage_type" description:"使用类型single_card/device"`
Status int `json:"status" description:"状态0待生效 1生效中 2已用完 3已过期 4已失效"`
StatusName string `json:"status_name" description:"状态名称"`
DataLimitMB int64 `json:"data_limit_mb" description:"套餐真流量总量(MB)"`
VirtualLimitMB int64 `json:"virtual_limit_mb" description:"套餐虚流量总量(MB)按virtual_ratio换算"`
DataUsageMB int64 `json:"data_usage_mb" description:"已用真流量(MB)"`
VirtualUsedMB float64 `json:"virtual_used_mb" description:"已用虚流量(MB)按virtual_ratio换算"`
VirtualRemainMB float64 `json:"virtual_remain_mb" description:"剩余虚流量(MB)按virtual_ratio换算"`
VirtualRatio float64 `json:"virtual_ratio" description:"虚流量比例(real/virtual)"`
ActivatedAt *time.Time `json:"activated_at,omitempty" description:"激活时间(待生效套餐为空)"`
ExpiresAt *time.Time `json:"expires_at,omitempty" description:"到期时间(待生效套餐为空)"`
MasterUsageID *uint `json:"master_usage_id,omitempty" description:"主套餐ID加油包时有值"`
Priority int `json:"priority" description:"优先级"`
CreatedAt time.Time `json:"created_at" description:"创建时间"`
}
// AssetPackagesResult 套餐列表分页结果
type AssetPackagesResult struct {
Total int64 `json:"total" description:"总条数"`
Page int `json:"page" description:"当前页码"`
PageSize int `json:"page_size" description:"每页条数"`
Items []*AssetPackageResponse `json:"items" description:"套餐列表"`
}
// AssetResolveRequest 资产解析请求(路径参数)
type AssetResolveRequest struct {
Identifier string `path:"identifier" description:"资产标识符(虚拟号/ICCID/IMEI/SN/MSISDN" required:"true"`
}
// AssetTypeIDRequest 资产类型+ID请求路径参数
type AssetTypeIDRequest struct {
AssetType string `path:"asset_type" description:"资产类型card 或 device" required:"true"`
ID uint `path:"id" description:"资产ID" required:"true"`
}
// AssetPackagesRequest 资产套餐列表请求(路径参数 + 分页)
type AssetPackagesRequest struct {
AssetType string `path:"asset_type" description:"资产类型card 或 device" required:"true"`
ID uint `path:"id" description:"资产ID" required:"true"`
Page int `query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码默认1"`
PageSize int `query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页条数默认50最大100"`
}
// DeviceIDRequest 设备ID请求路径参数
type DeviceIDRequest struct {
DeviceID uint `path:"device_id" description:"设备ID" required:"true"`
}
// CardICCIDRequest 卡ICCID请求路径参数
type CardICCIDRequest struct {
ICCID string `path:"iccid" description:"卡ICCID" required:"true"`
}
// DeviceGatewayInfo B端设备实时状态信息来自 Gateway sync-info
// 与 C 端 DeviceRealtimeInfo 独立,未来可展示更多技术字段
// 所有字段均为可选Gateway 调用失败时仅填充 GatewayMsg
type DeviceGatewayInfo struct {
// Gateway 上游消息(成功时为 null失败时为错误信息
GatewayMsg *string `json:"gateway_msg" description:"Gateway 上游消息(成功时为 null失败时为错误信息"`
// 设备状态
OnlineStatus *int `json:"online_status,omitempty" description:"在线状态1=在线2=离线"`
BatteryLevel *int `json:"battery_level,omitempty" description:"电池电量百分比无电池时为null"`
Status *int `json:"status,omitempty" description:"设备状态1=正常0=禁用"`
RunTime *string `json:"run_time,omitempty" description:"本次开机运行时间(秒)"`
ConnectTime *string `json:"connect_time,omitempty" description:"本次联网时间(秒)"`
LastOnlineTime *string `json:"last_online_time,omitempty" description:"设备最后在线时间"`
LastUpdateTime *string `json:"last_update_time,omitempty" description:"设备信息最后更新时间"`
// 信号相关
Rsrp *int `json:"rsrp,omitempty" description:"参考信号接收功率(dBm)"`
Rsrq *int `json:"rsrq,omitempty" description:"参考信号接收质量(dB)"`
Rssi *string `json:"rssi,omitempty" description:"接收信号强度"`
Sinr *int `json:"sinr,omitempty" description:"信噪比(dB)"`
// WiFi 相关
SSID *string `json:"ssid,omitempty" description:"WiFi热点名称"`
WifiEnabled *bool `json:"wifi_enabled,omitempty" description:"WiFi开关状态"`
WifiPassword *string `json:"wifi_password,omitempty" description:"WiFi密码"`
// 网络相关
IPAddress *string `json:"ip_address,omitempty" description:"IP地址"`
WANIP *string `json:"wan_ip,omitempty" description:"基站分配IPv4地址"`
LANIP *string `json:"lan_ip,omitempty" description:"局域网网关IP地址"`
MACAddress *string `json:"mac_address,omitempty" description:"MAC地址"`
// 流量与速率
DailyUsage *string `json:"daily_usage,omitempty" description:"日使用流量(字节)"`
DLStats *string `json:"dl_stats,omitempty" description:"本次开机下载流量(字节)"`
ULStats *string `json:"ul_stats,omitempty" description:"本次开机上传流量(字节)"`
LimitSpeed *int `json:"limit_speed,omitempty" description:"限速速率(KB/s)"`
// 设备属性
CurrentIccid *string `json:"current_iccid,omitempty" description:"当前使用的ICCID"`
MaxClients *int `json:"max_clients,omitempty" description:"最大连接客户端数"`
ClientNumber *int `json:"client_number,omitempty" description:"当前已连接客户端数"`
SoftwareVersion *string `json:"software_version,omitempty" description:"软件版本号"`
SwitchMode *string `json:"switch_mode,omitempty" description:"切卡模式0=自动1=手动"`
SyncInterval *int `json:"sync_interval,omitempty" description:"信息上报周期(秒)"`
// Gateway 标识字段
DeviceID *string `json:"device_id,omitempty" description:"Gateway设备ID(IMEI/SN)"`
DeviceName *string `json:"device_name,omitempty" description:"Gateway返回的设备名称"`
DeviceType *string `json:"device_type,omitempty" description:"Gateway返回的设备类型"`
IMEI *string `json:"imei,omitempty" description:"IMEI号"`
IMSI *string `json:"imsi,omitempty" description:"IMSI用户标识码"`
}
// UpdateAssetPollingStatusRequest 更新资产轮询状态请求
type UpdateAssetPollingStatusRequest struct {
AssetType string `path:"asset_type" description:"资产类型 (iot_card:IoT卡, device:设备)" required:"true"`
ID uint `path:"id" description:"资产ID" required:"true"`
// EnablePolling 是否启用轮询
EnablePolling bool `json:"enable_polling" description:"是否启用轮询 (true:启用, false:禁用)"`
}
// UpdateAssetPollingStatusResponse 更新资产轮询状态响应
type UpdateAssetPollingStatusResponse struct {
AssetType string `json:"asset_type" description:"资产类型 (card:IoT卡, device:设备)"`
AssetID uint `json:"asset_id" description:"资产ID"`
EnablePolling bool `json:"enable_polling" description:"更新后的轮询状态 (true:已启用, false:已禁用)"`
}