263 lines
20 KiB
Markdown
263 lines
20 KiB
Markdown
# 抢购引擎优化计划(v0.4.0)
|
||
|
||
> 依据:2026-10-16 三台机器(pc-1 / pc-2 / pc-3)同一场活动(sid=2277,17:00 开抢)的真实日志复盘。
|
||
> 参考:`/Users/yml/codes/dygou_go`(斗鱼)的调度设计,**只借鉴结论与参数形态,不照搬链路**(虎牙无锁单/支付,协议也不同)。
|
||
|
||
---
|
||
|
||
## 1. 复盘数据(问题来源)
|
||
|
||
| 机器 | 账号 | 成功 | 成功率 | 兑换请求 | 火爆 | 频率限制 | 售罄 | 版本 |
|
||
|---|---|---|---|---|---|---|---|---|
|
||
| pc-1 | 152 | 85 | 56% | 1421 | 917 | 244 | 19 | 旧版(无汇总 / 无时钟校准 / 售罄即停) |
|
||
| pc-2 | 10 | 7 | 70% | 81 | 51 | 1 | 0 | 新版(轮次窗口不足) |
|
||
| pc-3 | 85 | 83 | 97.6% | 498 | — | 0 | 0 | 新版(目标未售罄) |
|
||
| 合计 | 247 | 175 | 71% | 2000 | — | — | — | — |
|
||
|
||
关键事实:
|
||
|
||
1. 开抢窗口只有 **约 9 秒**(17:00:00.1 放量 → 17:00:08.2~09.4 售罄)。
|
||
2. pc-1 的 **82%** 兑换请求(1161/1421)被服务端限流挡回(「活动太火爆了」+「领取频率限制」);同机同 IP 峰值约 122 req/s。
|
||
3. `Delay` 默认 150ms 时,**账号越少,每秒轮次越多,越容易在开抢前把轮次烧光**:
|
||
|
||
| 实例 | 账号数 | 每轮耗时 | 20 轮覆盖 | 实际打进的开抢窗口 |
|
||
|---|---|---|---|---|
|
||
| pc-3 instance-4 | 1 | 155ms | 3.1s | **0 秒**(提前量 2.5s 吃掉全部预算 → 0 成功) |
|
||
| pc-2 default | 3 | 178ms | 3.6s | ~1.0s |
|
||
| pc-2 instance-3 | 3 | 170ms | 3.4s | ~1.2s |
|
||
| pc-3 instance-3 | 22 | 586ms | 11.7s | 5.7s |
|
||
| pc-1 各实例 | 30 | 830ms | 16.6s | 整窗口(但旧版售罄即停) |
|
||
|
||
4. pc-3 default 的账号 `1199662595042` **连续 13 次**返回 `webActUI.scoreExchangePrize 响应缺少 tRsp/tResp`,而同实例另外 28 个账号全部成功;日志里没有任何服务端线索。
|
||
5. 服务端放行速率是稳定的"每秒 4~6 个"(pc-3 六秒曲线),不是瞬间批量 → **覆盖足够的窗口时长**比**更高瞬时并发**更有效。
|
||
|
||
## 2. 四个优化项(已确认全部实施)
|
||
|
||
| 编号 | 问题 | 现状证据 |
|
||
|---|---|---|
|
||
| P1 | 轮次预算被提前量吃掉,账号少的实例根本没打到开抢瞬间 | pc-3 i4 = 0 成功;pc-2 三实例开抢后 1~1.4s 收工 |
|
||
| P2 | WUP/协议错误不可观测,且被混入普通失败 | 13 次 `缺少 tRsp/tResp` 只给了一句话,`iRet/status/payload` 全丢 |
|
||
| P3 | 限流文案没分类,退避语义缺失 | `火爆`/`领取频率限制` 落到 `failed`,共用同一退避 |
|
||
| P4 | 日志缺关键上下文 | `启动监控` 无 pid/名称;`账号未绑定` 与真失败同桶;无接口耗时统计 |
|
||
|
||
## 3. 参考方案(dygou_go)与适用边界
|
||
|
||
| dygou_go 做法 | 虎牙适用性 | 说明 |
|
||
|---|---|---|
|
||
| 窗口 `deadline` + `expired()`(75s)替代轮次 | ✅ 直接借鉴 | 虎牙实测窗口 ~9s,改为可配 `WindowSeconds`(建议默认 30s) |
|
||
| 每账号独立循环,不做轮间同步 | ⚠️ 改造后借鉴 | 虎牙已有"请求级闸门 + 账号独立 loop",但外层仍按 loop 同步一轮 |
|
||
| `dynamicAdvance = ErrorBound + RTT/2`,下限 50ms | ✅ 直接借鉴 | 替代固定 `LeadMS=2500` |
|
||
| 触发前 3s 复校时钟(`resyncBeforeTrigger`) | ✅ 借鉴 | 虎牙已有 `OffsetHistory` 中位数平滑,缺"临近复校" |
|
||
| 预热早于触发 2s(`warmupBeforeTrigger`) | ✅ 借鉴 | 改为"预热后静默等触发",不再让提前量循环白打 |
|
||
| 首发错峰 `firstWaveSpread=300ms` / `step≤8ms` | ✅ 直接借鉴 | 缓解同机 152 账号同毫秒齐射 |
|
||
| 退避阶梯 `retryIntervalFor(按已过时间分档)` + ×2 递增(≤×4) + 抖动,区间 [100ms,1.5s] | ✅ 直接借鉴 | 替代固定 300ms 退避 |
|
||
| 售罄不终止、退避自动降速到秒级 | ✅ 已有 | huya-go 已在 0294606 实现,保留并加提示日志 |
|
||
| 结局分类 success/rejected/throttled/terminal/expired | ✅ 借鉴 | 用于"窗口结束时"的每账号结局 |
|
||
| 限流关键词表 `exchangeRateLimited`(火爆/频繁/繁忙/排队…) | ✅ 直接借鉴 | 补进 `classifyExchangeText` |
|
||
| 请求统计(count/failed/avg/slowest/lastErr)+ 网络汇总行 | ✅ 借鉴 | 虎牙已有分类计数,补耗时与最后错误 |
|
||
| 目标商品 id 打进日志(`productTargets`) | ✅ 直接借鉴 | 补 `启动监控` 的 pid + 名称 |
|
||
| 抢购后积分核对(基线比对) | ✅ 借鉴 | 现有 `refresh_score` 可升级为"扣分=成功"二次确认 |
|
||
| 锁单→支付、锁单 TTL/CSRF、支付阶梯、和平小店 | ❌ 不适用 | 虎牙是直接兑换、无锁单无支付 |
|
||
| 斗鱼订单错误码表(1644400007 等) | ❌ 不适用 | 虎牙是 TAF/WUP,需按 `iRet / status / 文案` 三类信号自建 |
|
||
|
||
## 4. 现状代码坐标
|
||
|
||
| 位置 | 说明 |
|
||
|---|---|
|
||
| `internal/sniper/sniper.go:198` | `RunTargets` |
|
||
| `internal/sniper/sniper.go:227` | `for loop := 1; loop <= cfg.Loops; loop++`(待替换的轮次模型) |
|
||
| `internal/sniper/sniper.go:308` | `runAccountLoop` |
|
||
| `internal/sniper/sniper.go:444 / 480` | `exchangeTarget` / `applyExchangeDecision`(退避决策) |
|
||
| `internal/sniper/sniper.go:734` | `emitSummary` |
|
||
| `internal/sniper/sniper.go:838` | `normalizeConfig`(默认 Delay 150ms / Backoff 300ms) |
|
||
| `internal/sniper/sniper.go:996` | `classifyExchangeText`(文案分类) |
|
||
| `internal/huya/wss_client.go:704` | `callRPC`(:715 丢诊断) |
|
||
| `internal/huya/http_client.go:140 / 218` | `CallRPC`(:218 同样的丢诊断) |
|
||
| `internal/wup/wup.go:97 / 229` | `Response`(有 `Message/Status/Context/NewData` 未用)、`ReadStruct` |
|
||
| `internal/huya/timecal.go:98 / 145` | `AlignedTriggerTime` / `RunTimeCalibration`(已有中位数平滑) |
|
||
| `internal/gui/app.go:502 / 522-540` | `启动监控` 日志 / 定时触发路径 |
|
||
| ~~`cmd/huya-go/main.go:198 / 220`~~ | CLI `waitForMonitor` —— **CLI 已在 0.4.0 移除**(见文末「实施记录(续)」) |
|
||
|
||
## 5. 设计(四个工作包)
|
||
|
||
### WP1 窗口预算与首发时序(P1)
|
||
|
||
1.1 **终止条件:轮次 → 截止时间**
|
||
- `Config` 新增:`WindowSeconds int`(默认 30)、`DeadlineAt time.Time`(内部)、`FirstWaveSpread / FirstWaveMaxStep`。
|
||
- `RunTargets` 计算 `deadline := openAt + WindowSeconds`;循环条件改为 `!deadlineReached()`;`Loops > 0` 时仍作为上限(**向后兼容**:两者先到先止)。
|
||
|
||
1.2 **起跑阶段不消耗预算(消灭 pc-3 i4 的 0 成功)**
|
||
- 两段式:阶段 A(预打探活)T-advance 起,每账号只发 1 次用于判断是否已开放;阶段 B(正式窗口)等 `openAt` 后才开始计预算,`deadline = openAt + WindowSeconds`。
|
||
- 引擎自持 `openAt`(由 GUI 传入),不再把"等待"放在 `RunTargets` 之外。
|
||
|
||
1.3 **首发错峰**
|
||
- `runState.firstWaveSlot()`:第 0 个账号立即发,其余 `slot × step`;`step = min(spread/(n-1), maxStep)`,`spread=300ms`、`maxStep=8ms`(152 账号 → 2ms/步,总铺开 300ms)。
|
||
|
||
1.4 **触发时序统一(引擎与 GUI 共用一份实现)**
|
||
- 新增 `internal/sniper/timing.go`:`PlanTrigger(target, calibration, fallbackLead) Plan{WarmupAt, ResyncAt, TriggerAt, Advance, WindowEnd}`。
|
||
- GUI(`internal/gui/app.go` 的 `StartMonitor`)改为调用同一函数:预热 T-2s、复校 T-3s、触发 T-advance、窗口结束 openAt+WindowSeconds。
|
||
- `advance = max(ErrorBound + RTT/2, 50ms)`;`LeadMS > 0` 时覆盖动态值(兼容旧配置)。
|
||
|
||
1.5 **退避阶梯**
|
||
- 新增 `retryDelay(elapsed, failStreak)`:基准按"已过时间"分档(默认 0-2s→150ms、2-5s→400ms、>5s→900ms,可配 `RetryStages`),连续失败 ×2(上限 ×4),抖动 0-25%,区间 [100ms, 1500ms]。
|
||
- 退避等待被 deadline 裁剪(`sleepUntil(deadline, d)`);窗口结束立即收尾,不再产生窗口外的无效失败。
|
||
|
||
### WP2 WUP/协议错误可观测性(P2)
|
||
|
||
2.1 `wup.Response` 新增 `Diagnose()`:`iRet=%d servant=%s func=%s status=%v context=%v keys=%v payload=%dB`(value 截断、key 排序,保证日志稳定)。
|
||
2.2 `wss_client.callRPC`(:715)与 `http_client.CallRPC`(:218):tRsp 缺失时错误串带上 `resp.Diagnose()`。
|
||
2.3 新增 debug 级原始帧日志:`cmd=`、帧长度、前 32 字节 HEX(仅在日志等级为 debug 时输出)。
|
||
2.4 新增分类 `CategoryProtocol = "protocol"`(解码失败、缺 tRsp、帧异常)→ 日志等级 error、独立计数。
|
||
2.5 新增分类 `CategorySessionLost = "session_lost"`("账号未绑定"/"登录态"/"token"):瞬时错误照常重试,连续 ≥3 次才升级为账号级终止并打 warn。
|
||
2.6 单测:构造 `iRet≠0 且无 tRsp` 的响应 → 断言错误串含 iRet/status/keys;"账号未绑定" → 断言新分类。
|
||
|
||
### WP3 限流分类与退避(P3)
|
||
|
||
3.1 `classifyExchangeText` 关键词扩充(借 dygou 的关键词表)
|
||
- 归入 `too_frequent`:`火爆`、`频繁`、`稍后重试`、`太频繁`、`限流`、`繁忙`、`人多`、`排队`、`手慢`、`点击太快`、`请求过多`、`行为异常`。
|
||
- 判定顺序固定:`未开放 → 已抢完/库存 → 货币不足/已兑换 → 限流 → 兜底`,避免"稍后重试"吃掉"售罄"。
|
||
|
||
3.2 汇总口径扩充:`RunSummary` 增 `Throttled / SoldOut / SessionLost / Protocol`;汇总行形如
|
||
`任务结束: 窗口=30.0s 账号=29 成功=28 限流=123(67%) 售罄=19 协议错误=13 耗时=9.1s`
|
||
|
||
3.3 退避:`too_frequent` 走 WP1.5 的阶梯,并支持"连续 N 次限流后该账号基础间隔 ×2"(N 默认 3)。
|
||
|
||
3.4 (可选,默认关)**全局自适应降速**:最近 1s 限流占比 > 60% 时,把基础间隔与并发按 1.5 倍衰减,10s 窗口后恢复。开关 `AdaptiveThrottle`,用于缓解 pc-1 那种 122 req/s → 82% 限流的同 IP 风暴。
|
||
|
||
3.5 终态修正:限流/火爆/未绑定一律**不终止**(现状已如此,补测试固化)。
|
||
|
||
### WP4 日志与复盘(P4)
|
||
|
||
4.1 `启动监控` 带目标明细:
|
||
`启动监控: sid=2277 类目=不限 targets=1 目标=13437(冠军之刃) accounts=29 窗口=30s 提前=动态≈120ms 首发错峰=300ms`
|
||
4.2 每账号一行结局(任务结束时):`账号=1234 结局=限流中结束 请求=12 最后=活动太火爆了,请稍后重试`
|
||
4.3 结局分布带原因:`结局 sold_out ×19(窗口结束仍售罄)`
|
||
4.4 接口统计行:`网络: scoreExchangePrize ×183 失败136 平均=412ms 最慢=2.1s 最后错误=…`
|
||
4.5 触发日志补全:`触发=… 复校=… 窗口结束=…`
|
||
|
||
## 6. 兼容性与风险
|
||
|
||
- **向后兼容**:`Loops > 0` 仍生效(作为上限);`LeadMS > 0` 覆盖动态提前量;新字段全部有默认值,GUI 都可关闭新行为。
|
||
- **风险 1**:窗口拉长 → 请求量上升。缓解:退避阶梯 + deadline 裁剪 + 可选自适应降速;`WindowSeconds` 可调。
|
||
- **风险 2**:首发错峰牺牲首波瞬时性。缓解:总铺开仅 300ms,而实测窗口有 9s 余量;第 0 个账号仍立即发。
|
||
- **风险 3**:动态提前量下限 50ms 可能压不住慢网络。缓解:`ErrorBound + RTT/2` 已含往返补偿,且保留"预打探活"。
|
||
- **风险 4**:新增分类影响 GUI 配色/筛选。缓解:`LogLevelForCategory` 补映射(protocol→error,session_lost→warn),前端按 level 上色即可。
|
||
- **风险 5**:`classifyExchangeText` 顺序调整可能改变既有用例结论。缓解:先补"售罄/货币不足优先于限流"的回归测试再改。
|
||
|
||
## 7. 验收标准(DoD)
|
||
|
||
- `go test ./...` 全绿;新增 ≥12 个用例(窗口预算、首发错峰、退避阶梯、分类、诊断串、临近复校)。
|
||
- 关键回归不破:售罄继续探测、同奖品重复兑换去重、成功后异步刷新、`StopAfterSuccess`。
|
||
- `go build ./...` 与 `wails build`(或 `-debug -nopackage`)通过;版本号升到 `0.4.0`。
|
||
- 日志验收(可用假 client 跑模拟):一次运行必须输出「窗口参数行 / 目标明细行 / 每账号结局行 / 汇总行 / 接口统计行」。
|
||
- 反向验收:账号数 = 1、`Loops=1`、窗口 30s 时,日志必须显示"有效请求覆盖到窗口结束",不再出现"开抢前收工"。
|
||
|
||
## 8. 里程碑与改动清单
|
||
|
||
| 里程碑 | 内容 | 主要文件 | 产出 |
|
||
|---|---|---|---|
|
||
| M1 | WP2 可观测性(低风险、独立、立即可用于复盘) | `internal/wup/wup.go`、`internal/huya/wss_client.go`、`internal/huya/http_client.go`、`internal/sniper/sniper.go`(+分类)、测试 | 新分类 + 诊断串 |
|
||
| M2 | WP3 限流分类与退避 | `internal/sniper/sniper.go`、`sniper_test.go`、`internal/gui/app.go` | 退避阶梯 + 汇总口径 |
|
||
| M3 | WP1 窗口预算与首发时序 | `internal/sniper/sniper.go`、新增 `internal/sniper/timing.go`、`internal/gui/app.go`、`internal/huya/timecal.go` | 窗口调度 |
|
||
| M4 | WP4 日志补齐 + 版本/文档 | `internal/gui/app.go`、`internal/logger`、`README.md`、`internal/version` | 可复盘日志 |
|
||
|
||
顺序理由:先把"看不清"变成"看得清"(M1/M2),再动调度核心(M3),最后收尾(M4)。
|
||
|
||
## 9. 待确认参数
|
||
|
||
| 参数 | 建议值 | 说明 |
|
||
|---|---|---|
|
||
| `WindowSeconds` | 30s | 实测售罄 9s;30s 覆盖「库存回流/补货」 |
|
||
| 首发错峰总量 / 单步上限 | 300ms / 8ms | 152 账号 → step 2ms |
|
||
| 退避区间 | [100ms, 1500ms] | 超过 1.5s 的等待可能正好错过回流库存 |
|
||
| 「账号未绑定」升级阈值 | 连续 3 次 | 实测同一账号下一轮即可成功(瞬时) |
|
||
| 自适应降速(WP3.4) | 默认开 | 由用户确认:默认打开,GUI 可关 |
|
||
|
||
---
|
||
|
||
## 10. 实施记录(v0.4.0,2026-10-16)
|
||
|
||
四个工作包已全部落地,`go test ./...`、`go vet`、`wails build`、前端 `npm run typecheck && npm run build` 均通过。
|
||
|
||
### 已实现
|
||
|
||
| 工作包 | 落地位置 |
|
||
|---|---|
|
||
| WP1 窗口预算 | `internal/sniper/sniper.go`(`Config.OpenAt/WindowSeconds/StartAhead`、`runState.configureWindow/windowExpired/windowStart/needsPrefire`、`RunTargets` 重写)、`internal/sniper/timing.go`(`PlanTrigger`)、`internal/gui/app.go` |
|
||
| WP1 首发错峰 | `runState.takeFirstWaveDelay` + `runAccountLoop` 首次尝试等待 |
|
||
| WP1 退避阶梯 | `runState.retryDelay / scheduleRetry / stageMultiplier / streakMultiplier / bumpFailStreak` |
|
||
| WP2 可观测性 | `internal/wup/wup.go`(`Diagnose`、`HexHead`)、`internal/huya/diagnose.go`(`ErrProtocolResponse`、`protocolResponseError`)、`internal/huya/wss_client.go`、`internal/huya/http_client.go` |
|
||
| WP2 新分类 | `CategoryProtocol`、`CategorySessionLost` + `LogLevelForCategory` / `CategoryLabel` |
|
||
| WP3 限流分类 | `classifyExchangeText` 关键词表 + 固定判定顺序 |
|
||
| WP3 汇总口径 | `RunSummary.Throttled`(经 `Outcomes[too_frequent]` 统计)、`RequestStats`、`AccountOutcomes` |
|
||
| WP3 自适应降速 | `throttleGovernor` + `runState.observeThrottle/throttleFactor` |
|
||
| WP4 日志 | `logRunSummary`(GUI)、`sniper.TargetSummary`、`App.logWindowPlan`、`waitFor` |
|
||
| 测试 | `internal/sniper/optimization_test.go`(15 个新用例 + 下文 11.4 再补 4 个)、`internal/wup/wup_test.go`(诊断串 + 原始帧前缀)、`internal/huya/diagnose_test.go`(协议错误判定)、前端 `tsc --noEmit` |
|
||
|
||
### 与原计划的三处偏差(有实测依据)
|
||
|
||
1. **自适应降速的触发信号改为「无进展」而不是「限流占比 > 60%」**。
|
||
原计划按限流占比判定,但实测数据显示 pc-3(成功率 97.6%)也有 67% 请求被限流、pc-1 是 82% —— 用占比阈值会把正常运行的机器一起拖慢。改为「连续 ≥12 个请求且 ≥800ms 拿不到任何业务响应」,命中后按 1.5 倍逐级降速(上限 4 倍),有进展立刻回退。
|
||
2. **退避阶梯不引入 `RetryStages` 配置项**,改为在既有 `ErrorBackoff/TooFrequentBackoff` 基础上乘「时间档位 + 连续失败档位」:既不新增配置面,又避免「连续售罄探测」这类正常等待被放大(售罄/未开放档位更保守)。
|
||
3. **窗口模式下 `Loops` 语义调整**:`OpenAt` 非零时 `Loops<=0` 表示跑到窗口结束(GUI 默认留空),`Loops>0` 仍作为单个账号的轮次上限;非窗口模式(测试与直接调用)保持原来的「默认 1 轮」语义,避免老行为回归。
|
||
|
||
### 顺带修掉的两个小问题
|
||
|
||
- 同一「账号 + 奖品」的「已兑换过,跳过重复兑换」提示改为只报一次:窗口模式下每轮都会命中,不限制会刷满整个窗口(请求数不变)。
|
||
- 会话失效计数在任何非会话失效结局后清零,避免历史计数把偶发抖动累积成「连续失效」。
|
||
|
||
---
|
||
|
||
## 11. 实施记录(续):每账号独立循环 + 共享列表 TTL + 移除 CLI(v0.4.0,2026-10-16)
|
||
|
||
三机复盘后用户追加两条要求:**每个账号独立抢购**(不再一批一批同步)、**移除 CLI 只保留 GUI**。
|
||
|
||
### 11.1 调度:批次同步 → 每账号独立循环
|
||
|
||
**旧实现**:`RunTargets` 外层是 `for loop := 1..Loops`,每轮把所有账号并发跑一遍再 `wg.Wait()` 等齐,然后按 `nextWaveDelay` 等一个共享间隔进入下一轮。慢账号(一次 2s 的请求)会把所有账号钉在这一轮的终点上,账号少的实例尤其吃亏(pc-3 instance-4 = 0 成功)。
|
||
|
||
**新实现**(`internal/sniper/sniper.go`):
|
||
|
||
| 旧 | 新 |
|
||
|---|---|
|
||
| `for loop` + `runWave(loop)` + 每轮 `wg.Wait()` 同步 | 每个账号一个 goroutine 跑 `runAccountLoop`,各自 `for round := 1; ;` 循环 |
|
||
| `nextWaveDelay` 批次间隔 | 账号自己的 `cfg.Delay`(`clipToDeadline` 裁剪到窗口结束) |
|
||
| `runAccountLoop(..., loop, sharedList)` 是一轮的尝试体 | 拆成 `runAccountLoop`(循环)+ `runAccountRound`(一轮尝试体) |
|
||
| 局部 `loopsRun` 记轮次 | `runState.noteRound/maxRound`(取所有账号的最大轮次) |
|
||
| 每轮开头只判断 `allDone()` | `allDone()` + `windowExpired()` + `accountFinished(uid, len(targets))` |
|
||
|
||
退出条件(每个账号独立判断):任务取消 → 显式轮次上限(`Loops > 0`)→ 全局目标终止(售罄/下架,`targetDone`)→ 窗口结束 → 本账号所有目标已完成。
|
||
|
||
**保留的批次语义**:开抢前的「预打一轮」(`runWave(1)`)仍是全账号一波并发,用于探活与验证通道;只有正式窗口改成独立循环。
|
||
|
||
**验证**:新增 `TestRunTargetsPerAccountLoopsAreIndependent` —— 账号 1 的第一个兑换请求被阻塞时,账号 2 必须已经独立跑完 ≥3 轮(旧实现下账号 2 会卡在第二轮起点)。
|
||
|
||
### 11.2 共享列表:每轮一个新快照 → TTL + 单飞缓存
|
||
|
||
`SharePrizeList` 原来的语义是「每一轮所有账号共用同一份快照」,在批次调度下等价于每轮查 1 次列表。改成独立循环后「一轮」不再有全局含义,于是把共享口径改为**按时间的快照缓存**(`runState.sharedListOnce`):
|
||
|
||
- 锁内优先复用 1 秒 TTL 内的成功快照;没有可用快照时**单飞**——只有一个账号真正发请求,其余账号等它完成后复用结果。
|
||
- **只缓存成功结果**:失败不写缓存,否则一次抖动会让所有账号一起拿着错误快照失败。
|
||
- `sharedListTTL = 1000ms`:单次查询约 100ms,单账号节奏是 `cfg.Delay`(默认 150~300ms)。1 秒既能让同一波账号复用一份快照(pc-1 的 152 个账号从每轮 152 次查询降到 1 次),又能及时看到库存变化(实测库存会回流,快照不能太旧)。
|
||
- 关闭 `SharePrizeList` 时保持原样:各查各的,不做任何缓存。
|
||
|
||
**验证**:新增 `TestRunTargetsSharedListTTLSingleFlight` —— 2 账号 × 3 轮,列表请求数必须为 1(只做单飞、无 TTL 时是 3~6 次)。
|
||
|
||
### 11.3 移除 CLI(只保留 GUI)
|
||
|
||
CLI 与 GUI 是两套并行入口(`prepareMonitor` / `logRunSummary` / 参数解析各一份),改动要同步两处,老计划里甚至专门列了「GUI 与 CLI 需保持一致」这一条。既然日常只用 GUI,直接删掉:
|
||
|
||
- 删除 `cmd/huya-go/`;`internal/sniper` 的 `Runner`/`Config` 完全由 GUI 复用,删除 CLI 不影响引擎。
|
||
- `internal/gui/app.go` 的 `StartMonitor` 成为唯一触发路径(时间校准 → 窗口计划 → `RunTargets`)。
|
||
- `README.md`:删掉 CLI 构建/示例/参数表,改为「设置项说明」,并把「GUI 与 CLI 一致」等表述收敛为 GUI。
|
||
|
||
### 11.4 本轮新增测试(4 个,全部通过)
|
||
|
||
| 用例 | 覆盖 |
|
||
|---|---|
|
||
| `TestRunTargetsPerAccountLoopsAreIndependent` | 慢账号不阻塞其他账号 |
|
||
| `TestRunTargetsSharedListTTLSingleFlight` | 共享列表 TTL + 单飞只查 1 次 |
|
||
| `TestRunStateClipToDeadline` | 等待被裁剪到窗口结束(含已过期与非窗口模式) |
|
||
| `TestRunStateAccountFinishedAndRoundTracking` | `accountFinished`(含全局目标终止)与 `maxRound` |
|
||
|