修复抢购流程多项问题
- executeRound 四次加锁合并为单次,减少锁竞争 - shouldFallbackStandard token 匹配从宽泛改为精确匹配 cvl_csrf_token - retryCooldownDuration 新增操作频繁/请求过多/请求太频繁三种冷却匹配 - behaviorCooldown 从 8s 降为 5s - BestServerTimeCalibration 部分失败时不再返回 error,仅全败才报错 - warmupSchedule 增加 gen 计数器+warmupRunMu 互斥锁,防止 resync 时新旧预热并发 - ScheduleRun 中 clearWarmups 移入锁内 stopSchedulesLocked 之后执行
This commit is contained in:
@@ -138,6 +138,10 @@ func (c *DouyuClient) BestServerTimeCalibration(rounds int, maxWait time.Duratio
|
||||
Fallback: true,
|
||||
}, fmt.Errorf("斗鱼时间校准失败: %w", lastErr)
|
||||
}
|
||||
var calErr error
|
||||
if lastErr != nil {
|
||||
calErr = fmt.Errorf("斗鱼时间校准部分失败(已使用最优结果): %w", lastErr)
|
||||
}
|
||||
return types.TimeCalibration{
|
||||
Offset: best.Offset,
|
||||
ErrorBound: best.ErrorBound,
|
||||
@@ -146,7 +150,7 @@ func (c *DouyuClient) BestServerTimeCalibration(rounds int, maxWait time.Duratio
|
||||
CreatedAt: time.Now(),
|
||||
Source: best.Source,
|
||||
Fallback: best.Fallback,
|
||||
}, lastErr
|
||||
}, calErr
|
||||
}
|
||||
|
||||
func betterTimeSample(candidate, current types.TimeSample) bool {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
@@ -30,7 +31,7 @@ const (
|
||||
timeCalibrateWait = 1200 * time.Millisecond
|
||||
resyncBeforeTrigger = 10 * time.Second
|
||||
seckillMaxDuration = 75 * time.Second
|
||||
behaviorCooldown = 8 * time.Second
|
||||
behaviorCooldown = 5 * time.Second
|
||||
clickFastCooldown = 3 * time.Second
|
||||
)
|
||||
|
||||
@@ -77,9 +78,11 @@ type Scheduler struct {
|
||||
schedule *time.Timer
|
||||
warmupSchedule *time.Timer
|
||||
resyncSchedule *time.Timer
|
||||
warmupGen int64
|
||||
|
||||
warmupsMu sync.RWMutex
|
||||
warmups map[string]types.WarmupData
|
||||
warmupRunMu sync.Mutex
|
||||
|
||||
attemptCacheMu sync.RWMutex
|
||||
attemptCache map[string]*types.AttemptCache // 激进模式缓存
|
||||
@@ -580,36 +583,30 @@ func (s *Scheduler) executeRound(attempts []types.ExchangeAttempt, settings runS
|
||||
key := fmt.Sprintf("%s_第%d包", result.Account, result.Attempt)
|
||||
s.setResult(key, result.Message)
|
||||
|
||||
mu.Lock()
|
||||
outcome.attempts++
|
||||
mu.Unlock()
|
||||
|
||||
if result.OK {
|
||||
logger.LogAccountEvent("抢购", "success", result.Account, "成功: 账号=%s 商品=%s 第%d包 %s", result.Account, result.Product, result.Attempt, result.Message)
|
||||
s.enqueueSuccessImage(attempt.Account, attempt.Product, result.RoleName)
|
||||
|
||||
mu.Lock()
|
||||
outcome.successAccounts[attempt.Account.Name] = true
|
||||
mu.Unlock()
|
||||
} else {
|
||||
logger.LogAccountEvent("抢购", "info", result.Account, "尝试: 账号=%s 商品=%s 第%d包 %s", result.Account, result.Product, result.Attempt, result.Message)
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
outcome.attempts++
|
||||
if result.OK {
|
||||
outcome.successAccounts[attempt.Account.Name] = true
|
||||
} else {
|
||||
if cooldown := retryCooldownDuration(result.Message); cooldown > 0 {
|
||||
mu.Lock()
|
||||
outcome.cooldownAccounts[attempt.Account.Name] = time.Now().Add(jitterDuration(cooldown))
|
||||
mu.Unlock()
|
||||
}
|
||||
terminalAccount, terminalProduct, reason := classifyRetryStop(result.Message)
|
||||
if terminalAccount || terminalProduct {
|
||||
mu.Lock()
|
||||
if terminalAccount {
|
||||
outcome.stoppedAccounts[attempt.Account.Name] = reason
|
||||
}
|
||||
if terminalProduct {
|
||||
outcome.stoppedProducts[attempt.Product.CommodityID] = reason
|
||||
}
|
||||
mu.Unlock()
|
||||
if terminalAccount {
|
||||
outcome.stoppedAccounts[attempt.Account.Name] = reason
|
||||
}
|
||||
if terminalProduct {
|
||||
outcome.stoppedProducts[attempt.Product.CommodityID] = reason
|
||||
}
|
||||
}
|
||||
mu.Unlock()
|
||||
}(attempt)
|
||||
}
|
||||
|
||||
@@ -654,6 +651,12 @@ func retryCooldownDuration(message string) time.Duration {
|
||||
return behaviorCooldown
|
||||
case strings.Contains(message, "点击太快"):
|
||||
return clickFastCooldown
|
||||
case strings.Contains(message, "操作频繁"):
|
||||
return behaviorCooldown
|
||||
case strings.Contains(message, "请求过多"):
|
||||
return behaviorCooldown
|
||||
case strings.Contains(message, "请求太频繁"):
|
||||
return behaviorCooldown
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
@@ -795,6 +798,9 @@ func (s *Scheduler) clearAttemptCache() {
|
||||
}
|
||||
|
||||
func (s *Scheduler) warmupAttempts(attempts []types.ExchangeAttempt) {
|
||||
s.warmupRunMu.Lock()
|
||||
defer s.warmupRunMu.Unlock()
|
||||
|
||||
attempts = uniqueWarmupAttempts(attempts)
|
||||
if len(attempts) == 0 {
|
||||
return
|
||||
@@ -1053,7 +1059,7 @@ func shouldFallbackStandard(errMsg string) bool {
|
||||
switch {
|
||||
case strings.Contains(lower, "csrf"):
|
||||
return true
|
||||
case strings.Contains(lower, "token"):
|
||||
case strings.Contains(lower, "cvl_csrf_token"):
|
||||
return true
|
||||
case strings.Contains(lower, "preexchange"):
|
||||
return true
|
||||
@@ -1094,9 +1100,9 @@ func (s *Scheduler) ScheduleRun(req types.SeckillRequest) (map[string]any, error
|
||||
}
|
||||
|
||||
ap := utils.CloneStringMap(req.AccountProducts)
|
||||
s.clearWarmups()
|
||||
s.scheduleMu.Lock()
|
||||
s.stopSchedulesLocked()
|
||||
s.clearWarmups()
|
||||
s.scheduleMu.Unlock()
|
||||
|
||||
s.scheduleTimers(ap, targetTime, triggerTime, calibration, false)
|
||||
@@ -1156,7 +1162,11 @@ func (s *Scheduler) scheduleTimers(accountProducts map[string]string, targetTime
|
||||
|
||||
s.scheduleMu.Lock()
|
||||
defer s.scheduleMu.Unlock()
|
||||
gen := atomic.AddInt64(&s.warmupGen, 1)
|
||||
s.replaceTimerLocked(&s.warmupSchedule, warmupRemain, func() {
|
||||
if atomic.LoadInt64(&s.warmupGen) != gen {
|
||||
return
|
||||
}
|
||||
logger.LogEvent("预热", "success", "预热触发: 本地=%s", time.Now().In(constants.CST).Format("15:04:05.000"))
|
||||
settings := s.settingsSnapshot()
|
||||
attempts := s.buildAttempts(accountProducts, settings.attempts)
|
||||
|
||||
Reference in New Issue
Block a user