优化和平小店抢购重试策略

This commit is contained in:
yml2213
2026-08-09 14:48:20 +08:00
parent 87cb1dc3f0
commit 754e3f1ca8
2 changed files with 72 additions and 15 deletions
+48 -15
View File
@@ -629,24 +629,27 @@ func (s *Scheduler) RunXiaodian(accountProducts map[string]string, attempts, del
return
}
stopped := make(map[string]bool)
stoppedAccounts := make(map[string]string)
cooldownAccounts := make(map[string]time.Time)
cooldownCount := make(map[string]int)
start := time.Now()
logger.LogEvent("抢购", "info", "和平小店抢购开始: %d 个账号,次数=%d,间隔=%dms",
len(accounts), attempts, delay.Milliseconds())
for round := 1; round <= attempts; round++ {
for round, waitBeforeRound := 1, false; round <= attempts; {
if s.runStopped() {
logger.LogEvent("抢购", "warn", "收到停止指令,和平小店抢购正在结束")
break
}
if round > 1 && s.interruptibleSleep(delay) {
if waitBeforeRound && s.interruptibleSleep(delay) {
logger.LogEvent("抢购", "warn", "收到停止指令,和平小店抢购正在结束")
break
}
waitBeforeRound = false
roundAttempts := make([]types.ExchangeAttempt, 0, len(accounts))
for _, account := range accounts {
if stopped[account.Name] {
if stoppedAccounts[account.Name] != "" {
continue
}
productID := accountProducts[account.Name]
@@ -663,24 +666,55 @@ func (s *Scheduler) RunXiaodian(accountProducts map[string]string, attempts, del
if len(roundAttempts) == 0 {
break
}
now := time.Now()
roundAttempts, nextReady := filterCooldownAttempts(roundAttempts, cooldownAccounts, now)
if len(roundAttempts) == 0 {
waitFor := nextReady.Sub(now)
if waitFor > 0 {
logger.LogEvent("抢购", "info", "和平小店第%d/%d轮账号冷却中,等待=%v", round, attempts, utils.RoundDuration(waitFor))
if s.interruptibleSleep(waitFor) {
logger.LogEvent("抢购", "warn", "收到停止指令,和平小店抢购正在结束")
break
}
}
continue
}
outcome := s.executeXiaodianRound(roundAttempts, settings)
for name := range outcome.successAccounts {
stopped[name] = true
stoppedAccounts[name] = "已抢到"
}
for name := range outcome.stoppedAccounts {
stopped[name] = true
for name, reason := range outcome.stoppedAccounts {
if stoppedAccounts[name] == "" {
stoppedAccounts[name] = reason
logger.LogAccountEvent("抢购", "warn", name, "账号停止重试: 账号=%s 原因=%s", name, reason)
}
}
logger.LogEvent("抢购", "info", "和平小店第%d/%d轮完成: 成功=%d,停止=%d,剩余账号=%d",
round, attempts, len(outcome.successAccounts), len(outcome.stoppedAccounts), len(accounts)-len(stopped))
for name, until := range outcome.cooldownAccounts {
if stoppedAccounts[name] != "" || !until.After(cooldownAccounts[name]) {
continue
}
cooldownAccounts[name] = until
cooldownCount[name]++
logger.LogAccountEvent("抢购", "warn", name, "账号临时降频: 账号=%s 冷却至=%s (第%d次)",
name, until.In(constants.CST).Format("15:04:05.000"), cooldownCount[name])
if cooldownCount[name] >= maxCooldownCount {
stoppedAccounts[name] = "冷却次数过多"
logger.LogAccountEvent("抢购", "warn", name, "账号停止重试: 账号=%s 原因=冷却次数过多(%d次)", name, cooldownCount[name])
}
}
logger.LogEvent("抢购", "info", "和平小店第%d/%d轮完成: 成功=%d,账号停止=%d,冷却=%d,剩余账号=%d",
round, attempts, len(outcome.successAccounts), len(outcome.stoppedAccounts), len(outcome.cooldownAccounts), len(accounts)-len(stoppedAccounts))
if len(stopped) >= len(accounts) {
if len(stoppedAccounts) >= len(accounts) {
break
}
round++
waitBeforeRound = true
}
logger.LogEvent("抢购", "info", "和平小店抢购结束: 成功或停止=%d/%d,耗时=%v",
len(stopped), len(accounts), utils.RoundDuration(time.Since(start)))
len(stoppedAccounts), len(accounts), utils.RoundDuration(time.Since(start)))
}
func normalizeXiaodianRun(attempts, delayMs, fallbackAttempts int) (int, time.Duration) {
@@ -926,14 +960,13 @@ func classifyRetryStop(message string) (account bool, product bool, reason strin
func classifyXiaodianRetryStop(message string) (account bool, product bool, reason string) {
switch {
case strings.Contains(message, "余额不足"),
strings.Contains(message, "点券不足"):
return true, false, "点券余额不足"
case strings.Contains(message, "已拥有该道具"),
strings.Contains(message, "已经拥有该道具"),
strings.Contains(message, "已拥有该商品"):
return true, false, "账号已拥有该道具"
case strings.Contains(message, "已达限量"),
strings.Contains(message, "已达本次活动兑换上限"),
strings.Contains(message, "兑换上限"):
return true, false, "已达兑换限量"
case strings.Contains(message, "未绑定账号"):
return true, false, "账号未绑定角色"
case strings.Contains(message, "商品不存在"),
+24
View File
@@ -121,6 +121,30 @@ func TestClassifyXiaodianRetryStopOwnedItem(t *testing.T) {
}
}
func TestClassifyXiaodianRetryStop(t *testing.T) {
cases := []struct {
message string
account bool
product bool
reason string
}{
{"不好意思,您的余额不足~", true, false, "点券余额不足"},
{"点券不足,请充值后重试", true, false, "点券余额不足"},
{"未绑定账号", true, false, "账号未绑定角色"},
{"商品不存在", false, true, "商品不可兑换"},
{"已达限量", false, false, ""},
{"已达本次活动兑换上限", false, false, ""},
{"库存不足", false, false, ""},
}
for _, tc := range cases {
account, product, reason := classifyXiaodianRetryStop(tc.message)
if account != tc.account || product != tc.product || reason != tc.reason {
t.Errorf("classifyXiaodianRetryStop(%q) = (%v,%v,%q), want (%v,%v,%q)",
tc.message, account, product, reason, tc.account, tc.product, tc.reason)
}
}
}
func TestFilterCooldownAttempts(t *testing.T) {
attempts := makeAttempts("a", "b", "c")
now := time.Now()