Files
huya-go/internal/sniper/config.go
T
yml2213 43b3cb351e feat(sniper): 增加1.5s快速超时熔断,重构角色未绑定分类解绑会话失效
- 兑换请求增加独立的 RequestTimeout 上下文超时熔断(默认 1500ms),防止微服务卡死吃掉 5 秒
- 剥离 CategoryRoleUnbound(角色未绑定/抖动),不再计入 sessionStreak 判死阈值,杜绝误杀有效账号
- 将 CategoryRoleUnbound 与 CategoryTooFrequent 纳入 sessionAliveCategory,正常回包可重置会话计数
- 补充单测 TestRunTargetsRoleUnboundDoesNotStopAccount 与 TestExchangeTargetFastTimeout
2026-10-18 21:23:49 +08:00

92 lines
2.7 KiB
Go

package sniper
import (
"strings"
"time"
)
func normalizeConfig(cfg Config) Config {
cfg.Category = strings.TrimSpace(cfg.Category)
if cfg.maxAttempts <= 0 && cfg.OpenAt.IsZero() {
// 非窗口模式必须有个有限轮次,否则会变成死循环。
cfg.maxAttempts = 1
}
// 窗口模式由 deadline 兜底,maxAttempts<=0 表示一直尝试到窗口结束。
if cfg.Delay <= 0 {
cfg.Delay = 120 * time.Millisecond
}
if cfg.Concurrency <= 0 {
cfg.Concurrency = 30
}
if cfg.TooFrequentBackoff <= 0 {
cfg.TooFrequentBackoff = maxDuration(cfg.Delay*2, defaultRetryBackoff)
}
if cfg.ErrorBackoff <= 0 {
cfg.ErrorBackoff = maxDuration(cfg.Delay, defaultRetryBackoff)
}
// 首发错峰给了 spread 就至少要有 1ms 步长,否则等于没启用。
if cfg.FirstWaveSpread > 0 && cfg.FirstWaveMaxStep <= 0 {
cfg.FirstWaveMaxStep = DefaultFirstWaveMaxStep
}
if cfg.RequestTimeout <= 0 {
cfg.RequestTimeout = DefaultRequestTimeout
}
// 积分不足是终态:积分不会自己变多,继续打只是浪费请求,默认停手。
if !cfg.ContinueOnNotEnough {
cfg.StopOnNotEnough = true
}
// 售罄刻意不给默认值:库存可能因别人取消 / 锁单过期而回流,默认继续探测到
// 窗口结束;需要「售罄即停」时显式设置 StopOnSoldOut。
if !cfg.ListBeforeExchange {
cfg.DirectExchange = true
}
return cfg
}
// normalizeTargets 去掉空目标与重复目标。
//
// 除了完全相同的 target,还按 pid / 名称去重:界面选品会同时提交 pid、名称,
// 以及 targetText 里的同一个 pid,不去重就会对同一奖品重复兑换(实测踩过)。
func normalizeTargets(targets []Target) []Target {
out := make([]Target, 0, len(targets))
seenKey := map[string]bool{}
seenPID := map[int32]bool{}
seenName := map[string]bool{}
for _, target := range targets {
target.Name = strings.TrimSpace(target.Name)
if target.PID <= 0 && target.Name == "" && !target.Auto {
continue
}
if target.PID > 0 && seenPID[target.PID] {
continue
}
if target.Name != "" && seenName[target.Name] {
continue
}
key := targetKey(target)
if seenKey[key] {
continue
}
seenKey[key] = true
if target.PID > 0 {
seenPID[target.PID] = true
}
if target.Name != "" {
seenName[target.Name] = true
}
out = append(out, target)
}
return out
}
func shouldDirectExchange(target Target, cfg Config) bool {
// 设置了价格/库存门槛时必须先查列表,否则直兑会绕过过滤条件,
// 商品临时涨价或库存不足时仍会直接下单。
return cfg.DirectExchange &&
!cfg.ListBeforeExchange &&
target.PID > 0 &&
!target.Auto &&
target.MaxPrice <= 0 &&
target.MinLeft <= 0
}