feat: 下线设备保护期
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m30s

- 后台设备停复机不再设置 1 小时保护期,也不再因保护期拒绝相反操作;单卡手动停复机去掉保护期检查
- 删除保护期一致性轮询(polling:protect)及其强制停复机方法;调度、初始化、监控、匹配预览不再包含保护期;已入队的旧任务由空处理函数直接确认,下一次发布移除
- 轮询配置接口不再接受与返回 protect_check_interval;并发配置不再返回 protect,查询、更新、重置该类型时拒绝
- 资产详情 device_protect_status 固定返回 none 并标记废弃,兼容老版本客户端
- 删除保护期 Redis key 与时长常量;停机原因 protect_period 保留供历史数据与开放接口映射
- 迁移 000249 只把 protect_check_interval、last_protect_check_at 列注释标记为已废弃,不删列不改数据
This commit is contained in:
邹进
2026-10-25 17:19:10 +09:00
parent 4de67f6dc3
commit 79b7580554
32 changed files with 88 additions and 411 deletions

View File

@@ -64,7 +64,7 @@ const (
TaskTypePollingRealname = "polling:realname" // 实名状态检查
TaskTypePollingCarddata = "polling:carddata" // 卡流量检查
TaskTypePollingPackage = "polling:package" // 套餐流量检查
TaskTypePollingProtect = "polling:protect" // 保护期一致性检查
TaskTypePollingProtect = "polling:protect" // 已下线:保护期一致性检查,仅保留空处理消化存量任务,下一次发布移除
TaskTypePollingCardStatus = "polling:card_status" // 卡开停机状态检查
// 套餐激活任务类型
@@ -235,7 +235,7 @@ const (
QueuePollingRealname = TaskTypePollingRealname // 实名轮询任务队列
QueuePollingCarddata = TaskTypePollingCarddata // 流量轮询任务队列
QueuePollingPackage = TaskTypePollingPackage // 套餐轮询任务队列
QueuePollingProtect = TaskTypePollingProtect // 保护期轮询任务队列
QueuePollingProtect = TaskTypePollingProtect // 已下线:保护期轮询任务队列,仅消化存量任务
QueuePollingCardStatus = TaskTypePollingCardStatus // 卡状态轮询任务队列
QueuePackageFirstActivation = TaskTypePackageFirstActivation // 首次实名激活任务队列
QueuePackageQueueActivation = TaskTypePackageQueueActivation // 主套餐排队激活任务队列
@@ -499,9 +499,11 @@ const (
AutoPurchaseStatusFailed = "failed" // 失败
)
// 设备保护期相关时长常量
// DeviceProtectStatusNone 是资产详情 device_protect_status 的固定返回值:设备保护期已下线,字段仅为兼容老版本客户端保留。
const DeviceProtectStatusNone = "none"
// 设备相关时长常量
const (
DeviceProtectPeriodDuration = 1 * time.Hour // 设备停/复机保护期时长(1小时)
DeviceRefreshCooldownDuration = 30 * time.Second // 设备网关刷新冷却时长(30秒)
)

View File

@@ -72,7 +72,7 @@ const (
StopReasonTrafficExhausted = "traffic_exhausted" // 流量耗尽
StopReasonManual = "manual" // 手动停机
StopReasonArrears = "arrears" // 欠费
StopReasonProtectPeriod = "protect_period" // 保护期一致性检查自动停机
StopReasonProtectPeriod = "protect_period" // 保护期一致性检查自动停机(保护期已下线,不再产生新值;保留供历史数据与开放接口映射)
StopReasonNoPackage = "no_package" // 停机原因:无有效套餐
StopReasonNotRealname = "not_realname" // 停机原因:非行业卡且未完成实名认证
StopReasonCarrierStopped = "carrier_stopped" // 停机原因:运营商/Gateway 侧主动停机

View File

@@ -146,7 +146,7 @@ const (
)
// PollingPriorityTaskTypes 返回纳入优先轮询的既有轮询任务类型。
// 一次优先需求对应这些任务类型各执行一次;保护期一致性检查不在集合内。
// 一次优先需求对应这些任务类型各执行一次。
// 返回新切片,调用方修改不会影响其它调用方。
func PollingPriorityTaskTypes() []string {
return []string{

View File

@@ -417,16 +417,9 @@ func RedisClientPurchaseLockKey(assetType string, assetID uint) string {
}
// ========================================
// 设备保护期相关 Redis Key
// 设备刷新相关 Redis Key
// ========================================
// RedisDeviceProtectKey 生成设备保护期 Redis 键
// action: "stop"(停机保护期)或 "start"(复机保护期),两者互斥
// 过期时间:DeviceProtectPeriodDuration(1 小时)
func RedisDeviceProtectKey(deviceID uint, action string) string {
return fmt.Sprintf("protect:device:%d:%s", deviceID, action)
}
// RedisDeviceRefreshCooldownKey 生成设备刷新冷却期 Redis 键
// 用途:防止同一设备短时间内多次调网关刷新(限频)
// 过期时间:DeviceRefreshCooldownDuration(30 秒)
@@ -434,16 +427,6 @@ func RedisDeviceRefreshCooldownKey(deviceID uint) string {
return fmt.Sprintf("refresh:cooldown:device:%d", deviceID)
}
// ========================================
// 轮询保护期队列 Redis Key
// ========================================
// RedisPollingQueueProtectKey 保护期一致性检查轮询队列键
// 用途:存储需要进行保护期一致性检查的卡 ID(ZSet,按时间排序)
func RedisPollingQueueProtectKey() string {
return "polling:queue:protect"
}
// ========================================
// 微信配置相关 Redis Key
// ========================================

View File

@@ -1,6 +1,8 @@
package queue
import (
"context"
"github.com/hibiken/asynq"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
@@ -287,19 +289,16 @@ func (h *Handler) registerPollingHandlers() {
packageHandler := task.NewPollingPackageHandler(
h.pollingBase, h.workerResult.Stores.IotCard,
h.pollingStopResumeSvc)
protectHandler := task.NewPollingProtectHandler(
h.db, h.workerResult.Services.ObservationSeriesEvents,
h.pollingBase, h.gatewayClient, h.workerResult.Stores.IotCard,
h.workerResult.Stores.DeviceSimBinding, h.pollingStopResumeSvc)
cardStatusHandler := task.NewPollingCardStatusHandler(
h.pollingBase, h.gatewayClient, h.workerResult.Services.CardObservation, integrationRepository)
h.mux.HandleFunc(constants.TaskTypePollingRealname, realnameHandler.Handle)
h.mux.HandleFunc(constants.TaskTypePollingCarddata, carddataHandler.Handle)
h.mux.HandleFunc(constants.TaskTypePollingPackage, packageHandler.Handle)
h.mux.HandleFunc(constants.TaskTypePollingProtect, protectHandler.Handle)
// 设备保护期已下线:保留空处理函数消化发布前已入队的 polling:protect 任务,下一次发布再移除。
h.mux.HandleFunc(constants.TaskTypePollingProtect, func(context.Context, *asynq.Task) error { return nil })
h.mux.HandleFunc(constants.TaskTypePollingCardStatus, cardStatusHandler.Handle)
h.logger.Info("已注册轮询任务处理器(realname/carddata/package/protect/card_status)")
h.logger.Info("已注册轮询任务处理器(realname/carddata/package/card_status)")
}
func (h *Handler) registerPackageActivationHandlers() {