Files
dygou_go/app_export.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

176 lines
5.1 KiB
Go

package main
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"douyu_go/internal/logger"
"douyu_go/internal/types"
"douyu_go/internal/utils"
)
// accountLine 账号文件的一行解析结果。
type accountLine struct {
Raw string // 原始行文本
Name string // 解析出的账号名(注释/空行为空)
Comment bool // 是否为注释或空行
}
// parseAccountFile 读取账号文件并按行解析账号名。
func parseAccountFile(path string) ([]accountLine, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var lines []accountLine
scanner := bufio.NewScanner(strings.NewReader(string(data)))
for scanner.Scan() {
raw := scanner.Text()
trimmed := strings.TrimSpace(raw)
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
lines = append(lines, accountLine{Raw: raw, Comment: true})
continue
}
name := ""
if parts := strings.SplitN(trimmed, "----", 3); len(parts) >= 1 {
name = strings.TrimSpace(parts[0])
}
lines = append(lines, accountLine{Raw: raw, Name: name})
}
return lines, scanner.Err()
}
// ExportAccounts 导出指定账号到文件。
func (a *App) ExportAccounts(accountNames []string, filename string) types.ExportAccountsResult {
if len(accountNames) == 0 {
return types.ExportAccountsResult{Error: "没有要导出的账号"}
}
accountFile := a.scheduler.AccountFilePath()
lines, err := parseAccountFile(accountFile)
if err != nil {
return types.ExportAccountsResult{Error: "读取账号文件失败: " + err.Error()}
}
nameSet := make(map[string]bool)
for _, name := range accountNames {
nameSet[name] = true
}
var matchedLines []string
for _, line := range lines {
if line.Comment {
continue
}
if nameSet[line.Name] {
matchedLines = append(matchedLines, line.Raw)
}
}
if len(matchedLines) == 0 {
return types.ExportAccountsResult{Error: "没有找到匹配的账号行"}
}
// 生成导出文件路径
exportPath := filepath.Join(filepath.Dir(accountFile), filename)
content := strings.Join(matchedLines, "\n") + "\n"
if err := os.WriteFile(exportPath, []byte(content), 0644); err != nil {
return types.ExportAccountsResult{Error: "写入文件失败: " + err.Error()}
}
logger.LogEvent("导出", "success", "已导出 %d 个账号到: %s", len(matchedLines), exportPath)
return types.ExportAccountsResult{
Success: true,
Count: len(matchedLines),
FilePath: exportPath,
}
}
// ExportAccountsWithProduct 导出指定账号到文件,自动使用中文商品名和时间戳命名。
func (a *App) ExportAccountsWithProduct(accountNames []string, commodityID, commodityName string) types.ExportAccountsResult {
// 生成文件名: user_已抢到_商品中文名_20260626_150405.txt
now := time.Now()
timestamp := now.Format("20060102_150405")
// 清理商品名中的特殊字符(复用 utils.SafeFilename,避免与 utils 包重复实现)
safeName := utils.SafeFilename(commodityName)
filename := fmt.Sprintf("user_已抢到_%s_%s.txt", safeName, timestamp)
return a.ExportAccounts(accountNames, filename)
}
// OrganizeAccounts 整理账号,把已抢到指定商品的账号分离出来。
func (a *App) OrganizeAccounts(commodityID, commodityName string, startTime, endTime int64) types.OrganizeAccountsResult {
accounts := a.scheduler.AccountSnapshot()
hitAccounts := make([]string, 0)
missAccounts := make([]string, 0)
lookups := a.lookupExchangeRecords("整理", accounts)
for _, lookup := range lookups {
if lookup.EmptyCookie || lookup.Err != nil {
missAccounts = append(missAccounts, lookup.Account.Name)
continue
}
if hasCommodityRecord(lookup.Records, commodityID, startTime, endTime) {
hitAccounts = append(hitAccounts, lookup.Account.Name)
} else {
missAccounts = append(missAccounts, lookup.Account.Name)
}
}
// 导出已抢到的账号
var exportResult types.ExportAccountsResult
if len(hitAccounts) > 0 {
exportResult = a.ExportAccountsWithProduct(hitAccounts, commodityID, commodityName)
}
// 更新原文件,只保留未抢到的
accountFile := a.scheduler.AccountFilePath()
lines, err := parseAccountFile(accountFile)
if err != nil {
return types.OrganizeAccountsResult{Error: "读取账号文件失败: " + err.Error()}
}
missSet := make(map[string]bool)
for _, name := range missAccounts {
missSet[name] = true
}
var keepLines []string
for _, line := range lines {
if line.Comment {
keepLines = append(keepLines, line.Raw)
continue
}
if missSet[line.Name] {
keepLines = append(keepLines, line.Raw)
}
}
// 写回原文件
newContent := strings.Join(keepLines, "\n") + "\n"
if err := os.WriteFile(accountFile, []byte(newContent), 0644); err != nil {
return types.OrganizeAccountsResult{Error: "更新账号文件失败: " + err.Error()}
}
// 重新加载账号
a.scheduler.LoadAccounts()
logger.LogEvent("整理", "success", "整理完成: 已抢到=%d, 未抢到=%d", len(hitAccounts), len(missAccounts))
return types.OrganizeAccountsResult{
Success: true,
HitCount: len(hitAccounts),
MissCount: len(missAccounts),
HitAccounts: hitAccounts,
MissAccounts: missAccounts,
ExportResult: exportResult,
}
}