Files
dygou_go/internal/seckill/account_store.go
T
yml2213 7c2c5a61a6 重构: 账号管理抽离、强类型返回、消除反射、删除死字段
- 抽离账号管理为独立 AccountStore,Scheduler 只持有引用,
  职责单一化(scheduler.go 减少 ~110 行)
- Wails 绑定方法返回结构体替代 map[string]any(新增 responses.go),
  前端获得强类型,字段名保持兼容无需改前端
- ExchangeRecord 移至 types 包,消除 types↔client 循环依赖
- extractAPIStatus 用 apiResponder 接口替代 reflect 逐字段查找,
  types.APIResponse 与 ExchangeListResponse 实现 APIStatus 方法
- 删除 types.Account.Password 死字段(鉴权仅用 Cookie)
- 重新生成 Wails 绑定,前端 vue-tsc 类型检查通过
2026-07-29 12:47:01 +08:00

162 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package seckill
import (
"bufio"
"bytes"
"os"
"path/filepath"
"strings"
"sync"
"douyu_go/internal/constants"
"douyu_go/internal/logger"
"douyu_go/internal/types"
)
// AccountStore 账号存储,负责从账号文件加载、查询账号信息。
// 账号文件格式:每行 `账号名----密码----Cookie`(密码段保留以兼容历史文件,
// 但程序不读取也不使用密码,鉴权完全基于 Cookie)。
type AccountStore struct {
mu sync.RWMutex
accounts []types.Account
filePath string
}
// NewAccountStore 创建账号存储,filePath 为账号文件路径。
func NewAccountStore(filePath string) *AccountStore {
if filePath == "" {
filePath = constants.DefaultAccountFile
}
return &AccountStore{filePath: filePath}
}
// FilePath 返回账号文件路径。
func (a *AccountStore) FilePath() string {
return a.filePath
}
// Load 从账号文件加载账号。
func (a *AccountStore) Load() {
path := a.filePath
// 文件不存在时自动创建目录和空文件,方便用户直接粘贴 Cookie
if _, err := os.Stat(path); os.IsNotExist(err) {
if dir := filepath.Dir(path); dir != "" && dir != "." {
if mkErr := os.MkdirAll(dir, 0755); mkErr != nil {
logger.LogEvent("账号", "warn", "账号目录创建失败: %s: %v", dir, mkErr)
}
}
// 使用 Windows 换行格式 \r\n
hint := "# 格式: 账号名----密码----Cookie\r\n# 每行一个账号,粘贴后保存即可\r\n"
if wrErr := os.WriteFile(path, []byte(hint), 0644); wrErr != nil {
logger.LogEvent("账号", "warn", "账号文件创建失败: %s: %v", path, wrErr)
} else {
logger.LogEvent("账号", "info", "已创建空账号文件: %s,请粘贴 Cookie 后重新加载", path)
}
}
// 读取文件内容,自动转换换行格式为 Windows 格式
content, err := os.ReadFile(path)
if err != nil {
logger.LogEvent("账号", "error", "读取账号文件失败: %s: %v", path, err)
a.mu.Lock()
a.accounts = nil
a.mu.Unlock()
return
}
// 检测并转换换行格式
normalized, converted := normalizeLineEndings(content)
if converted {
// 保存转换后的内容
if err := os.WriteFile(path, normalized, 0644); err != nil {
logger.LogEvent("账号", "warn", "账号文件换行格式转换失败: %s: %v", path, err)
} else {
logger.LogEvent("账号", "info", "账号文件已自动转换为 Windows 换行格式: %s", path)
}
content = normalized
}
var list []types.Account
scanner := bufio.NewScanner(bytes.NewReader(content))
scanner.Buffer(make([]byte, 1024), 1024*1024)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "----", 3)
if len(parts) != 3 {
logger.LogEvent("账号", "warn", "账号行格式错误,已跳过: %s", line)
continue
}
list = append(list, types.Account{
Name: strings.TrimSpace(parts[0]),
Cookie: strings.TrimSpace(parts[2]),
})
}
if err := scanner.Err(); err != nil {
logger.LogEvent("账号", "error", "读取账号文件中断: %v", err)
}
a.mu.Lock()
a.accounts = list
a.mu.Unlock()
logger.LogEvent("账号", "success", "账号加载完成: %d 个,来源=%s", len(list), path)
}
// Count 返回当前账号数。
func (a *AccountStore) Count() int {
a.mu.RLock()
defer a.mu.RUnlock()
return len(a.accounts)
}
// Snapshot 复制一份账号列表。
func (a *AccountStore) Snapshot() []types.Account {
a.mu.RLock()
defer a.mu.RUnlock()
list := make([]types.Account, len(a.accounts))
copy(list, a.accounts)
return list
}
// CookieByName 按账号名查找 Cookie。
func (a *AccountStore) CookieByName(name string) (string, bool) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, account := range a.accounts {
if account.Name == name {
return account.Cookie, true
}
}
return "", false
}
// normalizeLineEndings 检测并转换换行格式为 Windows 格式(\r\n)。
// 返回转换后的内容和是否进行了转换。
func normalizeLineEndings(content []byte) ([]byte, bool) {
// 检查是否已经是 Windows 格式(包含 \r\n)
if bytes.Contains(content, []byte("\r\n")) {
// 已经是 Windows 格式,检查是否还有单独的 \n
// 先将所有 \r\n 替换为占位符,检查是否还有单独的 \n
temp := bytes.ReplaceAll(content, []byte("\r\n"), []byte("__RN_PLACEHOLDER__"))
if !bytes.Contains(temp, []byte("\n")) && !bytes.Contains(temp, []byte("\r")) {
// 完全是 Windows 格式,不需要转换
return content, false
}
// 混合格式,需要统一转换
}
// 转换逻辑:先统一为 \n,再转换为 \r\n
normalized := content
// 1. 将 \r\n 替换为 \n
normalized = bytes.ReplaceAll(normalized, []byte("\r\n"), []byte("\n"))
// 2. 将单独的 \r 替换为 \n
normalized = bytes.ReplaceAll(normalized, []byte("\r"), []byte("\n"))
// 3. 将 \n 替换为 \r\n
normalized = bytes.ReplaceAll(normalized, []byte("\n"), []byte("\r\n"))
return normalized, true
}