Files
dygou_go/app.go
T
yml2213 5631f579ea 重构: 按领域拆分大文件,清理死代码和空目录
app.go (768行) 拆分为 6 个文件:
- app.go: App struct/辅助方法/NewApp/startup/shutdown/GetVersion
- app_products.go: 商品加载/账号查询
- app_seckill.go: 抢购控制/日志/时间
- app_verify.go: 积分查询/兑换记录复验
- app_export.go: 账号导出/整理
- app_activity.go: 活动切换/配置保存

scheduler.go (1141行) 拆分为 4 个文件:
- scheduler.go: struct/常量/设置/RunNow主循环/executeRound/辅助函数
- attempt.go: runAttempt/runAttemptFast/激进模式缓存
- warmup.go: 预热管理/warmupAttempts
- schedule_timer.go: 定时抢购/时间校准/定时器管理

其他清理:
- CollectFontCandidates 从 config 包移到 image 包(职责归属)
- 删除 logger 死代码: ParseLogLine/ParseLogMeta/DetectLogLevel/
  IsLogLevel/LogMeta/LogDebug/cutBracket 等反解析函数及对应测试
  (日志结构化后不再有生产调用方)
- 删除 static/ 空目录残留
2026-07-29 13:19:43 +08:00

154 lines
4.0 KiB
Go

package main
import (
"context"
"os"
"sync"
"time"
"github.com/golang/freetype/truetype"
"douyu_go/internal/activity"
"douyu_go/internal/client"
"douyu_go/internal/config"
"douyu_go/internal/constants"
imgpkg "douyu_go/internal/image"
"douyu_go/internal/logger"
"douyu_go/internal/seckill"
"douyu_go/internal/trace"
"douyu_go/internal/types"
)
// App 应用主结构体,所有导出方法可被前端通过 Wails 绑定调用。
// 绑定方法按领域拆分到 app_*.go 文件。
type App struct {
ctx context.Context
clientMu sync.RWMutex
client *client.DouyuClient
scheduler *seckill.Scheduler
logStore *logger.MemoryLogStore
trace *trace.Recorder
paths types.AppPaths
font *truetype.Font
cfgMu sync.RWMutex
cfg types.Config
productsMu sync.RWMutex
products []types.Product
productsAt time.Time
closeLog func()
closeTrace func()
}
// getClient 返回当前 client 指针快照(读锁保护)。
func (a *App) getClient() *client.DouyuClient {
a.clientMu.RLock()
defer a.clientMu.RUnlock()
return a.client
}
// getCfg 返回当前配置的深拷贝(读锁保护)。
func (a *App) getCfg() types.Config {
a.cfgMu.RLock()
defer a.cfgMu.RUnlock()
return cloneConfig(a.cfg)
}
// setClient 替换 client(写锁保护)。
func (a *App) setClient(c *client.DouyuClient) {
a.clientMu.Lock()
a.client = c
a.clientMu.Unlock()
}
// setCfg 替换配置(写锁保护)。
func (a *App) setCfg(c types.Config) {
a.cfgMu.Lock()
a.cfg = c
a.cfgMu.Unlock()
}
// cloneConfig 深拷贝配置,避免 RetryStages 切片共享引用。
func cloneConfig(c types.Config) types.Config {
cp := c
if c.RetryStages != nil {
cp.RetryStages = make([]types.RetryStage, len(c.RetryStages))
copy(cp.RetryStages, c.RetryStages)
}
return cp
}
// NewApp 创建 App 实例。
func NewApp() *App {
return &App{}
}
// startup Wails 应用启动时调用。
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
a.paths = config.DefaultAppPaths()
a.logStore = logger.NewMemoryLogStore(800)
a.closeLog = logger.InitLogging(a.logStore, a.paths.LogDir)
// 加载配置并应用运行时设置
a.cfg = config.LoadConfig(a.paths.ConfigFile)
logger.SetMinLevel(a.cfg.LogLevel)
traceCfg := types.TraceConfig{
Enabled: a.cfg.TraceEnabled,
MaxBodyBytes: a.cfg.TraceMaxBody,
}
tr, closeTrace := trace.InitTrace(a.paths.LogDir, traceCfg)
a.trace = tr
a.closeTrace = closeTrace
// 解析当前活动:配置中的 activity ID 决定使用哪套接口参数
act := activity.Get(a.cfg.Activity)
if act == nil {
act = activity.Default()
logger.LogEvent("活动", "warn", "配置中的活动 %q 未识别,已回退到默认活动 %s", a.cfg.Activity, act.DisplayName)
}
a.client = client.New(act, a.cfg.TimeAPIURL, a.trace)
a.font = imgpkg.LoadFont(a.paths.FontFiles)
a.scheduler = seckill.New(a.client, a.paths, a.font, a.cfg.Concurrency, a.cfg.Attempts, a.cfg.RetryStages, a.cfg.SeckillMode)
a.scheduler.LoadAccounts()
if err := os.MkdirAll(a.paths.SuccessDir, 0755); err != nil {
logger.LogEvent("图片", "warn", "成功图片目录创建失败,图片保存功能可能不可用: %v", err)
}
if sample, err := a.client.ServerTimeSample(); err == nil {
logger.SetDouyuOffset(sample.Offset)
logger.LogEvent("时间", "success", "服务端时间获取成功: %s", sample.EstimatedServerNow().In(constants.CST).Format("2006-01-02 15:04:05.000"))
} else {
logger.LogEvent("时间", "error", "服务端时间获取失败: %v", err)
}
logger.LogEvent("系统", "success", "应用启动完成 | 账号: %d", a.scheduler.AccountCount())
}
// shutdown Wails 应用关闭时调用。
func (a *App) shutdown(ctx context.Context) {
if a.scheduler != nil {
a.scheduler.Close()
}
if a.closeTrace != nil {
a.closeTrace()
}
if a.closeLog != nil {
a.closeLog()
}
}
// GetVersion 返回应用版本号。
func (a *App) GetVersion() string {
if constants.BuildTime != "unknown" {
return constants.Version + " (" + constants.BuildTime + ")"
}
return constants.Version
}