升级版本并改用内部账号存储

This commit is contained in:
yml2213
2026-08-09 13:04:54 +08:00
parent 1cd94c0179
commit 8cf29598a5
13 changed files with 271 additions and 54 deletions
+33 -14
View File
@@ -8,26 +8,32 @@ import (
"strings"
"sync"
"douyu_go/internal/config"
"douyu_go/internal/constants"
"douyu_go/internal/logger"
"douyu_go/internal/types"
)
// AccountStore 账号存储,负责从账号文件加载、查询账号信息。
// AccountStore 账号存储,负责从应用内部账号库加载、查询账号信息。
// 账号文件格式:每行 `账号名----密码----Cookie`(密码段保留以兼容历史文件,
// 但程序不读取也不使用密码,鉴权完全基于 Cookie)。
type AccountStore struct {
mu sync.RWMutex
accounts []types.Account
filePath string
mu sync.RWMutex
accounts []types.Account
filePath string
legacyFilePath string
}
// NewAccountStore 创建账号存储,filePath 为账号文件路径。
func NewAccountStore(filePath string) *AccountStore {
// NewAccountStore 创建账号存储。legacyFilePaths 仅在内部账号库首次创建时用于迁移旧文件。
func NewAccountStore(filePath string, legacyFilePaths ...string) *AccountStore {
if filePath == "" {
filePath = constants.DefaultAccountFile
}
return &AccountStore{filePath: filePath}
legacyFilePath := ""
if len(legacyFilePaths) > 0 {
legacyFilePath = legacyFilePaths[0]
}
return &AccountStore{filePath: filePath, legacyFilePath: legacyFilePath}
}
// FilePath 返回账号文件路径。
@@ -35,23 +41,33 @@ func (a *AccountStore) FilePath() string {
return a.filePath
}
// Load 从账号文件加载账号。
// Load 从应用内部账号库加载账号。
func (a *AccountStore) Load() {
path := a.filePath
// 文件不存在时自动创建目录和空文件,方便用户直接粘贴 Cookie
// 首次启动时迁移旧版本工作目录中的账号文件,保留原文件作为备份。
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, 0755); mkErr != nil {
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), 0644); wrErr != nil {
if wrErr := os.WriteFile(path, []byte(hint), 0600); wrErr != nil {
logger.LogEvent("账号", "warn", "账号文件创建失败: %s: %v", path, wrErr)
} else {
logger.LogEvent("账号", "info", "已创建空账号文件: %s,请粘贴 Cookie 后重新加载", path)
logger.LogEvent("账号", "info", "已创建空账号库: %s,请在 CK 管理中导入账号", path)
}
}
@@ -69,13 +85,16 @@ func (a *AccountStore) Load() {
normalized, converted := normalizeLineEndings(content)
if converted {
// 保存转换后的内容
if err := os.WriteFile(path, normalized, 0644); err != nil {
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))
@@ -102,7 +121,7 @@ func (a *AccountStore) Load() {
a.mu.Lock()
a.accounts = list
a.mu.Unlock()
logger.LogEvent("账号", "success", "账号加载完成: %d 个,来源=%s", len(list), path)
logger.LogEvent("账号", "success", "账号加载完成: %d 个,内部存储=%s", len(list), path)
}
// Count 返回当前账号数。
+38
View File
@@ -0,0 +1,38 @@
package seckill
import (
"os"
"path/filepath"
"testing"
)
func TestAccountStoreMigratesLegacyFile(t *testing.T) {
dir := t.TempDir()
legacy := filepath.Join(dir, "data", "user.txt")
target := filepath.Join(dir, "app-data", "accounts.txt")
content := []byte("# 旧账号\r\nfoo----password----cookie-a\r\n")
if err := os.MkdirAll(filepath.Dir(legacy), 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(legacy, content, 0600); err != nil {
t.Fatal(err)
}
store := NewAccountStore(target, legacy)
store.Load()
accounts := store.Snapshot()
if len(accounts) != 1 || accounts[0].Name != "foo" || accounts[0].Cookie != "cookie-a" {
t.Fatalf("迁移后的账号 = %#v", accounts)
}
if _, err := os.Stat(legacy); err != nil {
t.Fatalf("旧账号文件不应被删除: %v", err)
}
info, err := os.Stat(target)
if err != nil {
t.Fatal(err)
}
if info.Mode().Perm() != 0600 {
t.Fatalf("账号库权限 = %o, want 0600", info.Mode().Perm())
}
}
+3 -3
View File
@@ -129,7 +129,7 @@ func New(cli *client.DouyuClient, paths types.AppPaths, font *truetype.Font, con
client: cli,
paths: paths,
font: font,
accounts: NewAccountStore(paths.AccountFile),
accounts: NewAccountStore(paths.AccountFile, paths.LegacyAccountFile),
successImages: image.NewSuccessImageSaver(
font,
paths.SuccessDir,
@@ -260,7 +260,7 @@ func normalizeSeckillMode(mode types.SeckillMode) types.SeckillMode {
// ==================== 账号转发(AccountStore) ====================
// LoadAccounts 从账号文件加载账号。
// LoadAccounts 从应用内部账号库加载账号。
func (s *Scheduler) LoadAccounts() {
s.accounts.Load()
}
@@ -270,7 +270,7 @@ func (s *Scheduler) AccountCount() int {
return s.accounts.Count()
}
// AccountFilePath 返回账号文件路径。
// AccountFilePath 返回内部账号库路径。
func (s *Scheduler) AccountFilePath() string {
return s.accounts.FilePath()
}