package handler import ( "context" "log" "time" "github.com/Wei-Shaw/sub2api/internal/service" ) // TempUnscheduler 用于 HandleFailoverError 中同账号重试耗尽后的临时封禁。 // GatewayService 隐式实现此接口。 type TempUnscheduler interface { TempUnscheduleRetryableError(ctx context.Context, accountID int64, failoverErr *service.UpstreamFailoverError) } // FailoverAction 表示 failover 错误处理后的下一步动作 type FailoverAction int const ( // FailoverRetry 同账号重试(调用方应 continue 重新进入循环,不更换账号) FailoverRetry FailoverAction = iota // FailoverSwitch 切换账号(调用方应 continue 重新选择账号) FailoverSwitch // FailoverExhausted 切换次数耗尽(调用方应返回错误响应) FailoverExhausted // FailoverCanceled context 已取消(调用方应直接 return) FailoverCanceled ) const ( // maxSameAccountRetries 同账号重试次数上限(针对 RetryableOnSameAccount 错误) maxSameAccountRetries = 2 // sameAccountRetryDelay 同账号重试间隔 sameAccountRetryDelay = 500 * time.Millisecond ) // FailoverState 跨循环迭代共享的 failover 状态 type FailoverState struct { SwitchCount int MaxSwitches int FailedAccountIDs map[int64]struct{} SameAccountRetryCount map[int64]int LastFailoverErr *service.UpstreamFailoverError ForceCacheBilling bool hasBoundSession bool } // NewFailoverState 创建 failover 状态 func NewFailoverState(maxSwitches int, hasBoundSession bool) *FailoverState { return &FailoverState{ MaxSwitches: maxSwitches, FailedAccountIDs: make(map[int64]struct{}), SameAccountRetryCount: make(map[int64]int), hasBoundSession: hasBoundSession, } } // HandleFailoverError 处理 UpstreamFailoverError,返回下一步动作。 // 包含:缓存计费判断、同账号重试、临时封禁、切换计数、Antigravity 延时。 func (s *FailoverState) HandleFailoverError( ctx context.Context, gatewayService TempUnscheduler, accountID int64, platform string, failoverErr *service.UpstreamFailoverError, ) FailoverAction { s.LastFailoverErr = failoverErr // 缓存计费判断 if needForceCacheBilling(s.hasBoundSession, failoverErr) { s.ForceCacheBilling = true } // 同账号重试:对 RetryableOnSameAccount 的临时性错误,先在同一账号上重试 if failoverErr.RetryableOnSameAccount && s.SameAccountRetryCount[accountID] < maxSameAccountRetries { s.SameAccountRetryCount[accountID]++ log.Printf("Account %d: retryable error %d, same-account retry %d/%d", accountID, failoverErr.StatusCode, s.SameAccountRetryCount[accountID], maxSameAccountRetries) if !sleepWithContext(ctx, sameAccountRetryDelay) { return FailoverCanceled } return FailoverRetry } // 同账号重试用尽,执行临时封禁 if failoverErr.RetryableOnSameAccount { gatewayService.TempUnscheduleRetryableError(ctx, accountID, failoverErr) } // 加入失败列表 s.FailedAccountIDs[accountID] = struct{}{} // 检查是否耗尽 if s.SwitchCount >= s.MaxSwitches { return FailoverExhausted } // 递增切换计数 s.SwitchCount++ log.Printf("Account %d: upstream error %d, switching account %d/%d", accountID, failoverErr.StatusCode, s.SwitchCount, s.MaxSwitches) // Antigravity 平台换号线性递增延时 if platform == service.PlatformAntigravity { if !sleepFailoverDelay(ctx, s.SwitchCount) { return FailoverCanceled } } return FailoverSwitch } // sleepWithContext 等待指定时长,返回 false 表示 context 已取消。 func sleepWithContext(ctx context.Context, d time.Duration) bool { if d <= 0 { return true } select { case <-ctx.Done(): return false case <-time.After(d): return true } }