Files
junhong_cmp_fiber/internal/task/polling_package_handler.go
邹进 79b7580554
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m30s
feat: 下线设备保护期
- 后台设备停复机不再设置 1 小时保护期,也不再因保护期拒绝相反操作;单卡手动停复机去掉保护期检查
- 删除保护期一致性轮询(polling:protect)及其强制停复机方法;调度、初始化、监控、匹配预览不再包含保护期;已入队的旧任务由空处理函数直接确认,下一次发布移除
- 轮询配置接口不再接受与返回 protect_check_interval;并发配置不再返回 protect,查询、更新、重置该类型时拒绝
- 资产详情 device_protect_status 固定返回 none 并标记废弃,兼容老版本客户端
- 删除保护期 Redis key 与时长常量;停机原因 protect_period 保留供历史数据与开放接口映射
- 迁移 000249 只把 protect_check_interval、last_protect_check_at 列注释标记为已废弃,不删列不改数据
2026-10-25 17:19:10 +09:00

129 lines
5.6 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"
priorityapp "github.com/break/junhong_cmp_fiber/internal/application/prioritypolling"
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"
)
// PollingPackageHandler 套餐检查任务处理器
// 职责:触发 EvaluateAndAct 检查套餐状态并判断停复机
// 不做:直接停复机决策,委托给 StopResumeService.EvaluateAndAct
// 注:套餐状态由 PackageActivationHandler/DataResetHandler/UsageService 维护,
// 本 Handler 无需查 Gateway,只负责周期性触发再评估。
type PollingPackageHandler struct {
base *PollingBase
iotCardStore *postgres.IotCardStore
stopResumeSvc iot_card_svc.StopResumeServiceInterface
}
// NewPollingPackageHandler 创建套餐检查任务处理器
func NewPollingPackageHandler(
base *PollingBase,
iotCardStore *postgres.IotCardStore,
stopResumeSvc iot_card_svc.StopResumeServiceInterface,
) *PollingPackageHandler {
return &PollingPackageHandler{
base: base,
iotCardStore: iotCardStore,
stopResumeSvc: stopResumeSvc,
}
}
// Handle 处理套餐检查任务
func (h *PollingPackageHandler) Handle(ctx context.Context, t *asynq.Task) error {
startTime := time.Now()
cardID, ok := parseTaskPayload(t.Payload(), h.base.logger)
if !ok {
return nil
}
ctx = withPollingWorkerAuditContext(ctx, constants.TaskTypePollingPackage, "套餐状态轮询任务", t.ResultWriter().TaskID())
if !h.base.acquireConcurrency(ctx, constants.TaskTypePollingPackage) {
h.base.logger.Debug("并发已满,重新入队", zap.Uint("card_id", cardID))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)
}
defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingPackage)
// 优先轮询互斥接缝:存在活动优先项时本轮即优先执行或被跳过;无活动项时行为与既有实现一致。
priority, skip, priorityErr := h.base.beginPollingPriority(ctx, cardID, constants.TaskTypePollingPackage)
if priorityErr != nil {
h.base.logger.Error("查询活动优先轮询项失败", zap.Uint("card_id", cardID), zap.Error(priorityErr))
h.base.updateStats(ctx, constants.TaskTypePollingPackage, false, time.Since(startTime))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)
}
if skip {
h.base.logger.Debug("卡轮询优先项已被其它执行持有,本轮跳过并延后",
zap.Uint("card_id", cardID), zap.String("task_type", constants.TaskTypePollingPackage))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)
}
card, cacheErr := h.base.getCardWithCache(ctx, cardID)
if cacheErr != nil {
if isNotFound(cacheErr) {
priority.FailFinal(ctx, "卡不存在")
return nil
}
h.base.logger.Error("获取卡信息失败", zap.Uint("card_id", cardID), zap.Error(cacheErr))
// 本地读卡失败属可恢复失败:与其余三个轮询 handler 统一按尝试上限收敛,避免加急事实被静默丢弃。
priority.MarkExecution(ctx)
priority.FailRetryable(ctx, "获取卡信息失败")
h.base.updateStats(ctx, constants.TaskTypePollingPackage, false, time.Since(startTime))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)
}
// 执行前轮询范围校验:优先通道不得重启已停止轮询的卡。
if !h.base.priorityInPollingScope(ctx, priority, card) {
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)
}
if h.stopResumeSvc == nil {
priority.FailFinal(ctx, "套餐评估能力未配置")
h.base.updateStats(ctx, constants.TaskTypePollingPackage, true, time.Since(startTime))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)
}
// 停复机评估就是本任务的上游执行动作:先累加尝试次数再发起。
priority.MarkExecution(ctx)
freshCard, loadErr := h.iotCardStore.GetByID(ctx, cardID)
if loadErr != nil {
h.base.logger.Warn("套餐检查:加载卡信息失败",
zap.Uint("card_id", cardID), zap.Error(loadErr))
priority.FailRetryable(ctx, "获取卡信息失败")
h.base.updateStats(ctx, constants.TaskTypePollingPackage, true, time.Since(startTime))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)
}
h.base.logger.Info("套餐检查:执行停复机评估",
zap.Uint("card_id", cardID),
zap.Int("network_status", freshCard.NetworkStatus),
zap.String("stop_reason", freshCard.StopReason))
if h.base.verboseLog {
h.base.logger.Info("套餐检查详情",
zap.Uint("card_id", cardID),
zap.String("iccid", freshCard.ICCID),
zap.Int("network_status", freshCard.NetworkStatus),
zap.String("stop_reason", freshCard.StopReason))
}
// 标记本次评估来自普通套餐轮询:无有效套餐场景据此才允许追加优先轮询请求,
// 其余调用链(观测消费者、手动实名等)不打标即不写。
evaluationCtx := priorityapp.WithPollingPriorityTrigger(ctx)
if evalErr := h.stopResumeSvc.EvaluateAndAct(evaluationCtx, freshCard); evalErr != nil {
h.base.logger.Warn("套餐检查后停复机评估失败",
zap.Uint("card_id", cardID), zap.Error(evalErr))
priority.FailRetryable(ctx, "停复机评估失败")
h.base.updateStats(ctx, constants.TaskTypePollingPackage, true, time.Since(startTime))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)
}
priority.Complete(ctx)
h.base.updateStats(ctx, constants.TaskTypePollingPackage, true, time.Since(startTime))
return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingPackage)
}