Files
junhong_cmp_fiber/internal/task/polling_protect_handler.go
huang 71559547b6 feat: 为四种现有轮询 Handler 添加 verbose log 输出
realname: verbose log 移入 gateway 块内,避免 gateway=nil 时输出零值
carddata: 添加 && h.gateway != nil 门卫,确保仅 gateway 查询成功时输出
package: verbose log 在 freshCard 加载后、EvaluateAndAct 之前输出
protect: 引入 actionTaken 变量,字段名 network_status → network_status_at_check,新增 action_taken

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

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

167 lines
6.5 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"
"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
gateway *gateway.Client
iotCardStore *postgres.IotCardStore
deviceSimBindingStore *postgres.DeviceSimBindingStore
stopResumeSvc iot_card_svc.StopResumeServiceInterface
}
// NewPollingProtectHandler 创建保护期一致性检查任务处理器
func NewPollingProtectHandler(
base *PollingBase,
gw *gateway.Client,
iotCardStore *postgres.IotCardStore,
deviceSimBindingStore *postgres.DeviceSimBindingStore,
stopResumeSvc iot_card_svc.StopResumeServiceInterface,
) *PollingProtectHandler {
return &PollingProtectHandler{
base: base,
gateway: gw,
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
stopProtect := h.base.redis.Exists(ctx, constants.RedisDeviceProtectKey(deviceID, "stop")).Val() > 0
startProtect := h.base.redis.Exists(ctx, constants.RedisDeviceProtectKey(deviceID, "start")).Val() > 0
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.gateway == nil {
break
}
if err := h.gateway.StopCard(ctx, &gateway.CardOperationReq{CardNo: card.ICCID}); 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)
}
if updateErr := h.iotCardStore.UpdateFields(ctx, cardID, map[string]any{
"network_status": constants.NetworkStatusOffline,
"stopped_at": time.Now(),
"stop_reason": constants.StopReasonProtectPeriod,
}); updateErr != nil {
h.base.logger.Warn("保护期停机 DB 更新失败", zap.Uint("card_id", cardID), zap.Error(updateErr))
}
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.gateway == nil {
break
}
if err := h.gateway.StartCard(ctx, &gateway.CardOperationReq{CardNo: card.ICCID}); 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)
}
if updateErr := h.iotCardStore.UpdateFields(ctx, cardID, map[string]any{
"network_status": constants.NetworkStatusOnline,
"resumed_at": time.Now(),
"stop_reason": "",
}); updateErr != nil {
h.base.logger.Warn("保护期复机 DB 更新失败", zap.Uint("card_id", cardID), zap.Error(updateErr))
}
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)
}