package task import ( "context" "time" "github.com/break/junhong_cmp_fiber/internal/gateway" "github.com/break/junhong_cmp_fiber/pkg/constants" ) // StepResult 是一个检查步骤的执行结果分类。 type StepResult int const ( // StepSucceeded 检查成功(含观测入账、评估完成)。 StepSucceeded StepResult = iota // StepFailed 检查失败,计入连续失败(是否计入还要看失败类别,见 countsAsFailure)。 StepFailed // StepRateLimited 本账户限流拒绝或等待超时,请求未发出:短延迟重排,不计失败。 StepRateLimited // StepDeferred 暂不可执行(流量同步锁被占、优先项由其他执行持有):短延迟或按间隔重排,不计失败。 StepDeferred // StepSkipped 本轮无需执行(能力未配置、不在轮询范围、所属账户未开启实名管控):按正常间隔重排。 StepSkipped // StepCardGone 卡不存在:不再重排。 StepCardGone // StepStopPolling 观测后该卡已关闭轮询(风险停机、已销户):不再重排。 StepStopPolling ) // StepOutcome 是一个检查步骤的结果。旧分片调度把它映射为原有的重入队动作;按运营商账户调度据此写回到期与失败计数。 type StepOutcome struct { Result StepResult // DeferDelay 为 StepDeferred 时的建议短延迟;0 表示按正常间隔。 DeferDelay time.Duration // Category 失败类别:网关错误类别(network / timeout / http / invalid_response),上游业务错误时为错误分类类别。 Category string // Message 失败信息(已脱敏截断)。 Message string // Observed 卡状态步骤观测到的网络状态(online / offline),用于延迟确认是否已满足预期。 Observed string // TriggeredEvaluation 流量步骤本轮观测产生了流量变化(会触发停复机评估),可据此跳过套餐步骤。 TriggeredEvaluation bool } // 卡状态步骤的观测结果取值。 const ( ObservedOnline = "online" ObservedOffline = "offline" ) func stepOK() StepOutcome { return StepOutcome{Result: StepSucceeded} } func stepSkip() StepOutcome { return StepOutcome{Result: StepSkipped} } func stepDefer(delay time.Duration) StepOutcome { return StepOutcome{Result: StepDeferred, DeferDelay: delay} } // stepFail 按错误构造失败结果:网关结构化错误取其类别,上游业务错误再按错误分类规则细分(用于判断是否计入连续失败与告警)。 func (b *PollingBase) stepFail(ctx context.Context, op, message string, err error) StepOutcome { outcome := StepOutcome{Result: StepFailed, Message: message, Category: "local"} if gwErr, ok := gateway.AsGatewayError(err); ok { outcome.Category = gwErr.Category if gwErr.Category == constants.GatewayErrorUpstream { outcome.Message = message + ":" + gwErr.ProviderMessage if b.errorClassifier != nil { outcome.Category = b.errorClassifier(ctx, op, gwErr.ProviderMessage) } } } return outcome } // CountsAsFailure 判断失败结果是否计入连续失败:上游频率限制与已是目标状态不计入。 func (o StepOutcome) CountsAsFailure() bool { if o.Result != StepFailed { return false } switch o.Category { case constants.GatewayClassRateLimitedUpstream, constants.GatewayClassAlreadyTarget: return false } return true } // ErrorClassifier 对上游业务错误信息分类,返回错误分类类别。 type ErrorClassifier func(ctx context.Context, op, message string) string // SetErrorClassifier 注入上游错误分类器(按运营商账户调度的失败退避与告警使用)。 func (b *PollingBase) SetErrorClassifier(classifier ErrorClassifier) { b.errorClassifier = classifier } // SchedulerModeReader 返回当前卡轮询调度模式(受控系统配置 polling.scheduler.mode)。 type SchedulerModeReader func(ctx context.Context) string // SetSchedulerModeReader 注入调度模式读取;未注入时视为旧分片调度。 func (b *PollingBase) SetSchedulerModeReader(reader SchedulerModeReader) { b.schedulerMode = reader } // carrierSchedulerActive 判断当前是否已切到按运营商账户调度:是则旧 Asynq 轮询任务直接结束,不调网关、不重入旧队列。 func (b *PollingBase) carrierSchedulerActive(ctx context.Context) bool { return b != nil && b.schedulerMode != nil && b.schedulerMode(ctx) == constants.PollingSchedulerModeCarrier } // applyLegacyOutcome 把步骤结果映射为旧分片调度原有的重入队动作。 func (b *PollingBase) applyLegacyOutcome(ctx context.Context, cardID uint, taskType string, outcome StepOutcome) error { switch outcome.Result { case StepCardGone, StepStopPolling: return nil case StepRateLimited: return b.requeueRateLimited(cardID, taskType) case StepDeferred: if outcome.DeferDelay > 0 { return b.queueMgr.Requeue(ctx, cardID, taskType, time.Now().Add(outcome.DeferDelay)) } return b.requeueCard(ctx, cardID, taskType) default: return b.requeueCard(ctx, cardID, taskType) } }