Files
dygou_go/internal/seckill/account_store.go
T

181 lines
5.8 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/config"
"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
legacyFilePath string
}
// NewAccountStore 创建账号存储。legacyFilePaths 仅在内部账号库首次创建时用于迁移旧文件。
func NewAccountStore(filePath string, legacyFilePaths ...string) *AccountStore {
if filePath == "" {
filePath = constants.DefaultAccountFile
}
legacyFilePath := ""
if len(legacyFilePaths) > 0 {
legacyFilePath = legacyFilePaths[0]
}
return &AccountStore{filePath: filePath, legacyFilePath: legacyFilePath}
}
// FilePath 返回账号文件路径。
func (a *AccountStore) FilePath() string {
return a.filePath
}
// Load 从应用内部账号库加载账号。
func (a *AccountStore) Load() {
path := a.filePath
// 首次启动时迁移旧版本工作目录中的账号文件,保留原文件作为备份。
if _, err := os.Stat(path); os.IsNotExist(err) {
migrated, migrateErr := config.MigrateLegacyFile(path, a.legacyFilePath)
if migrateErr != nil {
logger.LogEvent("账号", "warn", "旧账号文件迁移失败: %s: %v", a.legacyFilePath, migrateErr)
} else if migrated {
logger.LogEvent("账号", "success", "已迁移旧账号文件到应用内部目录: %s", path)
}
}
// 应用内部账号库不存在时自动创建目录和空文件,方便用户直接在界面导入。
if _, err := os.Stat(path); os.IsNotExist(err) {
if dir := filepath.Dir(path); dir != "" && dir != "." {
if mkErr := os.MkdirAll(dir, 0700); 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), 0600); wrErr != nil {
logger.LogEvent("账号", "warn", "账号文件创建失败: %s: %v", path, wrErr)
} else {
logger.LogEvent("账号", "info", "已创建空账号库: %s,请在 CK 管理中导入账号", 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, 0600); err != nil {
logger.LogEvent("账号", "warn", "账号文件换行格式转换失败: %s: %v", path, err)
} else {
logger.LogEvent("账号", "info", "账号文件已自动转换为 Windows 换行格式: %s", path)
}
content = normalized
}
if err := os.Chmod(path, 0600); err != nil {
logger.LogEvent("账号", "warn", "账号库权限收紧失败: %s: %v", path, err)
}
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
}