131 lines
3.8 KiB
Go
131 lines
3.8 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand/v2"
|
|
"strings"
|
|
"time"
|
|
|
|
"douyu_go/internal/constants"
|
|
"douyu_go/internal/logger"
|
|
"douyu_go/internal/types"
|
|
"douyu_go/internal/utils"
|
|
)
|
|
|
|
// LoadProducts 刷新商品列表。refresh=true 时从斗鱼实时拉取。
|
|
func (a *App) LoadProducts(refresh bool) types.ProductPayload {
|
|
return a.loadProducts(refresh, nil)
|
|
}
|
|
|
|
// LoadProductsForAccounts 使用指定账号随机刷新和平小店商品。
|
|
// 库存监控仅使用参与监控的账号,避免无关账号承担角色鉴权请求。
|
|
func (a *App) LoadProductsForAccounts(accountNames []string) types.ProductPayload {
|
|
return a.loadProducts(true, accountNames)
|
|
}
|
|
|
|
func (a *App) loadProducts(refresh bool, accountNames []string) types.ProductPayload {
|
|
if refresh {
|
|
cfg := a.getCfg()
|
|
options := types.XiaodianProductListOptions{PageSize: cfg.XiaodianPageSize}
|
|
var products []types.Product
|
|
err := fmt.Errorf("没有可用账号 Cookie")
|
|
accounts := productRefreshAccounts(a.scheduler.AccountSnapshot(), accountNames)
|
|
for _, account := range accounts {
|
|
products, err = a.getClient().Products(account.Cookie, options)
|
|
if err == nil {
|
|
break
|
|
}
|
|
logger.LogEvent("商品", "warn", "账号 %s 获取和平小店角色失败,尝试下一个账号: %v", account.Name, err)
|
|
}
|
|
if err != nil {
|
|
logger.LogEvent("商品", "error", "商品刷新失败: %v", err)
|
|
return types.ProductPayload{Success: false}
|
|
}
|
|
fetchedAt := time.Now()
|
|
a.setProducts(products, fetchedAt)
|
|
logger.LogEvent("商品", "success", "商品刷新完成: %d 个,刷新时间=%s", len(products), fetchedAt.In(constants.CST).Format("15:04:05.000"))
|
|
return a.productPayload("live")
|
|
}
|
|
return a.productPayload("cache")
|
|
}
|
|
|
|
func productRefreshAccounts(accounts []types.Account, preferredNames []string) []types.Account {
|
|
preferred := make(map[string]struct{}, len(preferredNames))
|
|
for _, name := range preferredNames {
|
|
preferred[strings.TrimSpace(name)] = struct{}{}
|
|
}
|
|
|
|
filtered := make([]types.Account, 0, len(accounts))
|
|
for _, account := range accounts {
|
|
if strings.TrimSpace(account.Cookie) == "" {
|
|
continue
|
|
}
|
|
if len(preferred) > 0 {
|
|
if _, ok := preferred[account.Name]; !ok {
|
|
continue
|
|
}
|
|
}
|
|
filtered = append(filtered, account)
|
|
}
|
|
rand.Shuffle(len(filtered), func(i, j int) {
|
|
filtered[i], filtered[j] = filtered[j], filtered[i]
|
|
})
|
|
return filtered
|
|
}
|
|
|
|
// GetAccounts 返回账号列表。
|
|
func (a *App) GetAccounts() []types.AccountView {
|
|
accounts := a.scheduler.AccountSnapshot()
|
|
list := make([]types.AccountView, 0, len(accounts))
|
|
for i, account := range accounts {
|
|
list = append(list, types.AccountView{
|
|
Index: i,
|
|
Name: account.Name,
|
|
Cookie: utils.MaskCookie(account.Cookie),
|
|
})
|
|
}
|
|
return list
|
|
}
|
|
|
|
// ReloadAccounts 重新加载内部账号库。
|
|
func (a *App) ReloadAccounts() types.ReloadAccountsResult {
|
|
a.scheduler.LoadAccounts()
|
|
return types.ReloadAccountsResult{Success: true, Count: a.scheduler.AccountCount()}
|
|
}
|
|
|
|
// ==================== 内部辅助方法 ====================
|
|
|
|
func (a *App) setProducts(products []types.Product, fetchedAt time.Time) {
|
|
a.productsMu.Lock()
|
|
a.products = products
|
|
a.productsAt = fetchedAt
|
|
a.productsMu.Unlock()
|
|
if a.scheduler != nil {
|
|
a.scheduler.UpdateProducts(products)
|
|
}
|
|
}
|
|
|
|
func (a *App) productPayload(source string) types.ProductPayload {
|
|
a.productsMu.RLock()
|
|
defer a.productsMu.RUnlock()
|
|
|
|
payload := types.ProductPayload{
|
|
Success: true,
|
|
Source: source,
|
|
Products: a.products,
|
|
Count: len(a.products),
|
|
}
|
|
if !a.productsAt.IsZero() {
|
|
age := time.Since(a.productsAt)
|
|
if age < 0 {
|
|
age = 0
|
|
}
|
|
payload.FetchedAt = a.productsAt.In(constants.CST).Format("2006-01-02 15:04:05.000")
|
|
payload.AgeSecond = int(age.Seconds())
|
|
payload.Stale = age > constants.ProductCacheTTL
|
|
} else {
|
|
payload.Stale = true
|
|
}
|
|
return payload
|
|
}
|