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 列注释标记为已废弃,不删列不改数据
129 lines
5.6 KiB
Go
129 lines
5.6 KiB
Go
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)
|
||
}
|