- 抢购调度重构:参考 dygou_go 将 sniper 拆分为独立子模块(runner/run_control/retry/exchange/classify/config/summary/category/helpers);移除全局自适应降速与轮次上限,采用账号独立循环、统一账号级退避、单请求并发闸门与首发错峰 - 预热与建连优化:默认关闭预热(避免单账号超时连坐整机开抢),开启时转为后台非阻塞协程;WebSocket 握手超时从 10s 降至 1500ms - 双时间日志:logger 增加 SetServerOffset,校准后日志前缀自动渲染「本地时间(H虎牙估算时间)」,实现跨机器绝对毫秒对齐 - 默认配置调优:版本提升至 v0.5.0,默认线程调为 2(缓解多实例并发限流),前端自动平滑迁移 - 验证方式:go test ./... -count=1 全通,npm run build 构建正常,单测覆盖双时间与预热配置
88 lines
2.3 KiB
Go
88 lines
2.3 KiB
Go
package sniper
|
||
|
||
import (
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
// TargetSummary 把目标列表渲染成「13437(冠军之刃)」这样的单行摘要,供启动日志使用。
|
||
//
|
||
// 2026-10-16 的复盘正卡在「日志里没有目标 pid」:三台机器都只记了 sid 与 targets=1,
|
||
// 看不出各自打的是哪个奖品,也就无法解释为什么一台售罄、另一台库存完好。
|
||
func TargetSummary(targets []Target) string {
|
||
parts := make([]string, 0, len(targets))
|
||
for _, target := range targets {
|
||
name := strings.TrimSpace(target.Name)
|
||
switch {
|
||
case target.PID > 0 && name != "":
|
||
parts = append(parts, fmt.Sprintf("%d(%s)", target.PID, name))
|
||
case target.PID > 0:
|
||
parts = append(parts, strconv.FormatInt(int64(target.PID), 10))
|
||
case name != "":
|
||
parts = append(parts, name)
|
||
case target.Auto:
|
||
parts = append(parts, "自动选择")
|
||
}
|
||
}
|
||
if len(parts) == 0 {
|
||
return "未指定"
|
||
}
|
||
return strings.Join(parts, ",")
|
||
}
|
||
|
||
// CategoryLabel 返回结局分类的中文短标签,供任务结束时的结局日志使用。
|
||
func CategoryLabel(category string) string {
|
||
switch category {
|
||
case CategorySuccess:
|
||
return "已抢到"
|
||
case CategorySoldOut:
|
||
return "售罄"
|
||
case CategoryTooFrequent:
|
||
return "被限流"
|
||
case CategoryNotOpen:
|
||
return "未开放"
|
||
case CategoryNotEnough:
|
||
return "积分不足"
|
||
case CategoryAlreadyDone:
|
||
return "已兑换过"
|
||
case CategoryOffShelf:
|
||
return "已下架"
|
||
case CategorySessionLost:
|
||
return "会话失效"
|
||
case CategoryProtocol:
|
||
return "协议异常"
|
||
case CategoryCondition:
|
||
return "条件未达成"
|
||
case CategoryNotFound:
|
||
return "未匹配到奖品"
|
||
case CategoryError:
|
||
return "请求异常"
|
||
default:
|
||
return "失败"
|
||
}
|
||
}
|
||
|
||
// CategoryText 把类目限制渲染成日志文案(空串表示不限类目)。
|
||
func CategoryText(category string) string {
|
||
key := strings.TrimSpace(category)
|
||
if key == "" {
|
||
return "不限"
|
||
}
|
||
return key
|
||
}
|
||
|
||
// LogLevelForCategory 把结果分类映射成日志等级,供界面按等级高亮与筛选。
|
||
func LogLevelForCategory(category string) string {
|
||
switch category {
|
||
case CategorySuccess:
|
||
return "success"
|
||
case CategoryError, CategoryProtocol:
|
||
return "error"
|
||
case CategoryFailed, CategoryTooFrequent, CategoryCondition, CategorySessionLost:
|
||
return "warn"
|
||
default:
|
||
return "info"
|
||
}
|
||
}
|