Files
dygou_go/internal/app/app_products.go
T

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
}