151 lines
3.7 KiB
Go
151 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// loadAccounts 从账号文件加载账号,格式为“账号----密码----Cookie”。
|
|
func (s *appState) loadAccounts() {
|
|
path := s.paths.AccountFile
|
|
if path == "" {
|
|
path = defaultAccountFile
|
|
}
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
logEvent("账号", "error", "读取账号文件失败: %s: %v", path, err)
|
|
s.accountsMu.Lock()
|
|
s.accounts = nil
|
|
s.accountsMu.Unlock()
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
var list []Account
|
|
scanner := bufio.NewScanner(f)
|
|
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 {
|
|
logEvent("账号", "warn", "账号行格式错误,已跳过: %s", line)
|
|
continue
|
|
}
|
|
list = append(list, Account{
|
|
Name: strings.TrimSpace(parts[0]),
|
|
Password: strings.TrimSpace(parts[1]),
|
|
Cookie: strings.TrimSpace(parts[2]),
|
|
})
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
logEvent("账号", "error", "读取账号文件中断: %v", err)
|
|
}
|
|
|
|
s.accountsMu.Lock()
|
|
s.accounts = list
|
|
s.accountsMu.Unlock()
|
|
logEvent("账号", "success", "账号加载完成: %d 个,来源=%s", len(list), path)
|
|
}
|
|
|
|
// accountCount 返回当前账号数。
|
|
func (s *appState) accountCount() int {
|
|
s.accountsMu.RLock()
|
|
defer s.accountsMu.RUnlock()
|
|
return len(s.accounts)
|
|
}
|
|
|
|
// accountSnapshot 复制一份账号列表,避免执行任务时长时间持锁。
|
|
func (s *appState) accountSnapshot() []Account {
|
|
s.accountsMu.RLock()
|
|
defer s.accountsMu.RUnlock()
|
|
|
|
list := make([]Account, len(s.accounts))
|
|
copy(list, s.accounts)
|
|
return list
|
|
}
|
|
|
|
// accountCookieByName 按账号名查找真实 Cookie,仅在后端内部使用。
|
|
func (s *appState) accountCookieByName(name string) (string, bool) {
|
|
s.accountsMu.RLock()
|
|
defer s.accountsMu.RUnlock()
|
|
|
|
for _, account := range s.accounts {
|
|
if account.Name == name {
|
|
return account.Cookie, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// productSnapshot 复制一份商品列表和对应的刷新时间。
|
|
func (s *appState) productSnapshot() ([]Product, time.Time) {
|
|
s.productsMu.RLock()
|
|
defer s.productsMu.RUnlock()
|
|
|
|
list := make([]Product, len(s.products))
|
|
copy(list, s.products)
|
|
return list, s.productsAt
|
|
}
|
|
|
|
// setProducts 替换商品缓存,并记录这批库存数据的刷新时间。
|
|
func (s *appState) setProducts(products []Product, fetchedAt time.Time) {
|
|
s.productsMu.Lock()
|
|
defer s.productsMu.Unlock()
|
|
s.products = products
|
|
s.productsAt = fetchedAt
|
|
}
|
|
|
|
// productPayload 根据当前缓存生成前端商品响应。
|
|
func (s *appState) productPayload(source string) ProductPayload {
|
|
products, fetchedAt := s.productSnapshot()
|
|
payload := ProductPayload{
|
|
Success: true,
|
|
Source: source,
|
|
Products: products,
|
|
Count: len(products),
|
|
}
|
|
if !fetchedAt.IsZero() {
|
|
age := time.Since(fetchedAt)
|
|
if age < 0 {
|
|
age = 0
|
|
}
|
|
payload.FetchedAt = fetchedAt.In(cst).Format("2006-01-02 15:04:05.000")
|
|
payload.AgeSecond = int(age.Seconds())
|
|
payload.Stale = age > productCacheTTL
|
|
} else {
|
|
payload.Stale = true
|
|
}
|
|
return payload
|
|
}
|
|
|
|
// clearResults 清空抢购结果。
|
|
func (s *appState) clearResults() {
|
|
s.resultsMu.Lock()
|
|
defer s.resultsMu.Unlock()
|
|
s.results = make(map[string]string)
|
|
}
|
|
|
|
// setResult 写入单次兑换结果。
|
|
func (s *appState) setResult(key, value string) {
|
|
s.resultsMu.Lock()
|
|
defer s.resultsMu.Unlock()
|
|
s.results[key] = value
|
|
}
|
|
|
|
// resultsSnapshot 复制当前结果。
|
|
func (s *appState) resultsSnapshot() map[string]string {
|
|
s.resultsMu.RLock()
|
|
defer s.resultsMu.RUnlock()
|
|
|
|
out := make(map[string]string, len(s.results))
|
|
for k, v := range s.results {
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|