Files
junhong_cmp_fiber/internal/task/polling_protect_handler.go
break 5e552d99bc 收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展
Confidence: medium
Scope-risk: broad
Directive: 后续修改需保持审计事件与业务事务边界一致
Tested: git diff --cached --check
Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
2026-08-05 14:30:54 +08:00

157 lines
6.1 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 task
import (
"context"
"time"
"github.com/hibiken/asynq"
"go.uber.org/zap"
"gorm.io/gorm"
cardObservationApp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
"github.com/break/junhong_cmp_fiber/internal/gateway"
iot_card_svc "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// PollingProtectHandler 保护期一致性检查任务处理器
// 保护期内:直接调 Gateway 强制停/复机(绕过 EvaluateAndAct 三条件判断)
// 保护期结束:调 EvaluateAndAct 重新评估正常停复机条件
// 两种路径不可混淆:保护期内=强制修正;保护期结束=重新评估
type PollingProtectHandler struct {
base *PollingBase
iotCardStore *postgres.IotCardStore
deviceSimBindingStore *postgres.DeviceSimBindingStore
stopResumeSvc iot_card_svc.StopResumeServiceInterface
}
// NewPollingProtectHandler 创建保护期一致性检查任务处理器
func NewPollingProtectHandler(
db *gorm.DB,
observationSeriesEvents cardObservationApp.SeriesEventWriter,
base *PollingBase,
gw *gateway.Client,
iotCardStore *postgres.IotCardStore,
deviceSimBindingStore *postgres.DeviceSimBindingStore,
stopResumeSvc iot_card_svc.StopResumeServiceInterface,
) *PollingProtectHandler {
return &PollingProtectHandler{
base: base,
iotCardStore: iotCardStore,
deviceSimBindingStore: deviceSimBindingStore,
stopResumeSvc: stopResumeSvc,
}
}
// Handle 处理保护期一致性检查任务
func (h *PollingProtectHandler) Handle(ctx context.Context, t *asynq.Task) error {
startTime := time.Now()
cardID, ok := parseTaskPayload(t.Payload(), h.base.logger)
if !ok {
return nil
}
if !h.base.acquireConcurrency(ctx, constants.TaskTypePollingProtect) {
h.base.logger.Debug("并发已满,重新入队", zap.Uint("card_id", cardID))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
}
defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingProtect)
card, err := h.iotCardStore.GetByID(ctx, cardID)
if err != nil {
if isNotFound(err) {
return nil
}
h.base.logger.Error("查询卡信息失败", zap.Uint("card_id", cardID), zap.Error(err))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
}
// 独立卡(未绑设备)跳过保护期检查
if card.IsStandalone {
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
}
// 未实名卡跳过保护期检查
if card.RealNameStatus != constants.RealNameStatusVerified {
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
}
binding, err := h.deviceSimBindingStore.GetActiveBindingByCardID(ctx, card.ID)
if err != nil || binding == nil {
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
}
deviceID := binding.DeviceID
stopProtectGeneration := h.base.redis.Get(ctx, constants.RedisDeviceProtectKey(deviceID, "stop")).Val()
startProtectGeneration := h.base.redis.Get(ctx, constants.RedisDeviceProtectKey(deviceID, "start")).Val()
stopProtect := stopProtectGeneration != ""
startProtect := startProtectGeneration != ""
actionTaken := "no_action"
switch {
case stopProtect && card.NetworkStatus == constants.NetworkStatusOnline:
// 保护期内:停机保护期发现开机卡 → 强制停机(绕过 EvaluateAndAct
h.base.logger.Info("保护期一致性:停机保护期内发现开机卡,强制停机",
zap.Uint("card_id", card.ID), zap.Uint("device_id", deviceID))
if h.stopResumeSvc == nil {
break
}
if err := h.stopResumeSvc.ForceStopCard(ctx, card, constants.StopReasonProtectPeriod); err != nil {
h.base.logger.Error("保护期强制停机失败",
zap.Uint("card_id", card.ID), zap.Error(err))
h.base.updateStats(ctx, constants.TaskTypePollingProtect, false, time.Since(startTime))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
}
h.base.updateCardCache(ctx, cardID, map[string]any{"network_status": constants.NetworkStatusOffline})
actionTaken = "forced_stop"
case startProtect && card.NetworkStatus == constants.NetworkStatusOffline:
// 保护期内:复机保护期发现停机卡 → 强制复机(绕过 EvaluateAndAct
h.base.logger.Info("保护期一致性:复机保护期内发现停机卡,强制复机",
zap.Uint("card_id", card.ID), zap.Uint("device_id", deviceID))
if h.stopResumeSvc == nil {
break
}
if err := h.stopResumeSvc.ForceStartCard(ctx, card); err != nil {
h.base.logger.Error("保护期强制复机失败",
zap.Uint("card_id", card.ID), zap.Error(err))
h.base.updateStats(ctx, constants.TaskTypePollingProtect, false, time.Since(startTime))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
}
h.base.updateCardCache(ctx, cardID, map[string]any{"network_status": constants.NetworkStatusOnline})
actionTaken = "forced_resume"
case !stopProtect && !startProtect:
// 保护期结束:调 EvaluateAndAct 重新评估正常停复机条件
if h.stopResumeSvc != nil {
if evalErr := h.stopResumeSvc.EvaluateAndAct(ctx, card); evalErr != nil {
h.base.logger.Warn("保护期结束后停复机评估失败",
zap.Uint("card_id", cardID), zap.Error(evalErr))
}
}
actionTaken = "evaluate"
}
if h.base.verboseLog {
h.base.logger.Info("保护期检查详情",
zap.Uint("card_id", cardID),
zap.String("iccid", card.ICCID),
zap.Bool("stop_protect", stopProtect),
zap.Bool("start_protect", startProtect),
zap.Int("network_status_at_check", card.NetworkStatus),
zap.String("action_taken", actionTaken))
}
if updateErr := h.iotCardStore.UpdateFields(ctx, cardID, map[string]any{
"last_protect_check_at": time.Now(),
}); updateErr != nil {
h.base.logger.Warn("更新保护期检查时间失败", zap.Uint("card_id", cardID), zap.Error(updateErr))
}
h.base.updateStats(ctx, constants.TaskTypePollingProtect, true, time.Since(startTime))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
}