优化和平小店抢购重试策略
This commit is contained in:
@@ -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, "商品不存在"),
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user