438 lines
13 KiB
Go
438 lines
13 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"douyu_go/internal/logger"
|
|
"douyu_go/internal/types"
|
|
)
|
|
|
|
const exchangeLookupConcurrency = 8
|
|
const xiaodianExchangeLookupConcurrency = 4
|
|
|
|
type exchangeLookupResult struct {
|
|
Account types.Account
|
|
Records []types.ExchangeRecord
|
|
Err error
|
|
EmptyCookie bool
|
|
}
|
|
|
|
// QueryScore 查询单个账号的积分。
|
|
func (a *App) QueryScore(accountName string) types.ScoreResult {
|
|
if accountName == "" {
|
|
return types.ScoreResult{Score: "缺少账号名"}
|
|
}
|
|
cookie, ok := a.scheduler.AccountCookieByName(accountName)
|
|
if !ok || cookie == "" {
|
|
return types.ScoreResult{Score: fmt.Sprintf("账号不存在或 Cookie 为空: %s", accountName)}
|
|
}
|
|
score, err := a.getClient().Score(cookie)
|
|
if err != nil {
|
|
return types.ScoreResult{Score: "查询失败: " + err.Error()}
|
|
}
|
|
return types.ScoreResult{Score: strconv.Itoa(score)}
|
|
}
|
|
|
|
// QueryXiaodianAssets 查询勾选账号的和平小店点券和扭蛋碎片。
|
|
func (a *App) QueryXiaodianAssets(accountNames []string) types.XiaodianAssetsResult {
|
|
results := make(map[string]types.XiaodianAssets)
|
|
if !a.getClient().Activity.IsXiaodian {
|
|
return types.XiaodianAssetsResult{Results: results}
|
|
}
|
|
selected := make(map[string]struct{}, len(accountNames))
|
|
for _, name := range accountNames {
|
|
if name = strings.TrimSpace(name); name != "" {
|
|
selected[name] = struct{}{}
|
|
}
|
|
}
|
|
for _, account := range a.scheduler.AccountSnapshot() {
|
|
if _, ok := selected[account.Name]; !ok {
|
|
continue
|
|
}
|
|
if strings.TrimSpace(account.Cookie) == "" {
|
|
results[account.Name] = types.XiaodianAssets{Error: "Cookie 为空"}
|
|
continue
|
|
}
|
|
assets, err := a.getClient().XiaodianAssets(account.Cookie)
|
|
if err != nil {
|
|
results[account.Name] = types.XiaodianAssets{Error: "查询失败: " + err.Error()}
|
|
continue
|
|
}
|
|
results[account.Name] = assets
|
|
}
|
|
return types.XiaodianAssetsResult{Results: results}
|
|
}
|
|
|
|
// QueryXiaodianRoles 查询勾选账号当前绑定的和平小店角色。
|
|
func (a *App) QueryXiaodianRoles(accountNames []string) types.XiaodianRoleResult {
|
|
results := make(map[string]types.XiaodianRole)
|
|
if !a.getClient().Activity.IsXiaodian {
|
|
return types.XiaodianRoleResult{Results: results}
|
|
}
|
|
selected := make(map[string]struct{}, len(accountNames))
|
|
for _, name := range accountNames {
|
|
if name = strings.TrimSpace(name); name != "" {
|
|
selected[name] = struct{}{}
|
|
}
|
|
}
|
|
for _, account := range a.scheduler.AccountSnapshot() {
|
|
if _, ok := selected[account.Name]; !ok {
|
|
continue
|
|
}
|
|
if strings.TrimSpace(account.Cookie) == "" {
|
|
results[account.Name] = types.XiaodianRole{Error: "Cookie 为空"}
|
|
continue
|
|
}
|
|
role, err := a.getClient().XiaodianRole(account.Cookie)
|
|
if err != nil {
|
|
results[account.Name] = types.XiaodianRole{Error: "查询失败: " + err.Error()}
|
|
continue
|
|
}
|
|
results[account.Name] = role
|
|
}
|
|
return types.XiaodianRoleResult{Results: results}
|
|
}
|
|
|
|
// GetXiaodianBindQRCode 为指定账号生成和平小店角色绑定二维码。
|
|
func (a *App) GetXiaodianBindQRCode(accountName string) types.XiaodianBindQRCode {
|
|
accountName = strings.TrimSpace(accountName)
|
|
if !a.getClient().Activity.IsXiaodian {
|
|
return types.XiaodianBindQRCode{Error: "当前活动不是和平小店"}
|
|
}
|
|
cookie, ok := a.scheduler.AccountCookieByName(accountName)
|
|
if !ok || strings.TrimSpace(cookie) == "" {
|
|
return types.XiaodianBindQRCode{Error: "账号不存在或 Cookie 为空"}
|
|
}
|
|
urlValue, bindURL, err := a.getClient().XiaodianBindQRCode(cookie)
|
|
if err != nil {
|
|
return types.XiaodianBindQRCode{Error: "获取绑定二维码失败: " + err.Error()}
|
|
}
|
|
return types.XiaodianBindQRCode{
|
|
Success: true,
|
|
AccountName: accountName,
|
|
URL: urlValue,
|
|
GameRoleBindURL: bindURL,
|
|
}
|
|
}
|
|
|
|
// BindXiaodianRole 确认扫码后的绑定,并返回最新角色信息。
|
|
func (a *App) BindXiaodianRole(accountName string) types.XiaodianRole {
|
|
accountName = strings.TrimSpace(accountName)
|
|
if !a.getClient().Activity.IsXiaodian {
|
|
return types.XiaodianRole{Error: "当前活动不是和平小店"}
|
|
}
|
|
cookie, ok := a.scheduler.AccountCookieByName(accountName)
|
|
if !ok || strings.TrimSpace(cookie) == "" {
|
|
return types.XiaodianRole{Error: "账号不存在或 Cookie 为空"}
|
|
}
|
|
role, err := a.getClient().XiaodianBindRole(cookie)
|
|
if err != nil {
|
|
return types.XiaodianRole{Error: "绑定失败: " + err.Error()}
|
|
}
|
|
return role
|
|
}
|
|
|
|
// VerifyExchange 查询兑换记录,用于复验抢购结果。
|
|
func (a *App) VerifyExchange(accountName string) types.ExchangeRecordsResult {
|
|
if accountName == "" {
|
|
return types.ExchangeRecordsResult{Error: "缺少账号名"}
|
|
}
|
|
cookie, ok := a.scheduler.AccountCookieByName(accountName)
|
|
if !ok || cookie == "" {
|
|
return types.ExchangeRecordsResult{Error: fmt.Sprintf("账号不存在或 Cookie 为空: %s", accountName)}
|
|
}
|
|
|
|
records, err := a.getClient().ExchangeList(cookie)
|
|
if err != nil {
|
|
return types.ExchangeRecordsResult{Error: "查询失败: " + err.Error()}
|
|
}
|
|
matched := filterExchangeRecords(records)
|
|
|
|
return types.ExchangeRecordsResult{
|
|
Success: true,
|
|
Records: matched,
|
|
Count: len(matched),
|
|
}
|
|
}
|
|
|
|
// VerifyAllExchange 批量查询兑换记录,使用有限并发减少等待时间。
|
|
func (a *App) VerifyAllExchange() types.VerifyAllExchangeResult {
|
|
accounts := a.scheduler.AccountSnapshot()
|
|
results := make(map[string]types.AccountExchangeResult)
|
|
lookups := a.lookupExchangeRecords("复验", accounts)
|
|
|
|
for _, lookup := range lookups {
|
|
if lookup.EmptyCookie {
|
|
results[lookup.Account.Name] = types.AccountExchangeResult{Error: "Cookie 为空"}
|
|
continue
|
|
}
|
|
|
|
if lookup.Err != nil {
|
|
results[lookup.Account.Name] = types.AccountExchangeResult{Error: "查询失败: " + lookup.Err.Error()}
|
|
continue
|
|
}
|
|
|
|
matched := filterExchangeRecords(lookup.Records)
|
|
results[lookup.Account.Name] = types.AccountExchangeResult{
|
|
Success: true,
|
|
Records: matched,
|
|
Count: len(matched),
|
|
}
|
|
}
|
|
|
|
return types.VerifyAllExchangeResult{Results: results}
|
|
}
|
|
|
|
// QueryXiaodianExchangeRecords 查询所有账号的和平小店真实订单,并标记当前商品是否已兑换。
|
|
func (a *App) QueryXiaodianExchangeRecords(commodityID, commodityName string) types.XiaodianExchangeRecordsResult {
|
|
result := types.XiaodianExchangeRecordsResult{
|
|
ProductID: strings.TrimSpace(commodityID),
|
|
ProductName: strings.TrimSpace(commodityName),
|
|
Results: make(map[string]types.XiaodianExchangeAccountResult),
|
|
}
|
|
if !a.getClient().Activity.IsXiaodian {
|
|
result.Error = "当前活动不是和平小店"
|
|
return result
|
|
}
|
|
if result.ProductID == "" {
|
|
result.Error = "请先选择商品"
|
|
return result
|
|
}
|
|
|
|
accounts := a.scheduler.AccountSnapshot()
|
|
if len(accounts) == 0 {
|
|
result.Success = true
|
|
return result
|
|
}
|
|
start := time.Now()
|
|
logger.LogEvent("兑换记录", "info", "和平小店兑换记录批量查询开始: 账号=%d 并发=%d", len(accounts), xiaodianExchangeLookupConcurrency)
|
|
|
|
type lookup struct {
|
|
account types.Account
|
|
records []types.XiaodianExchangeRecord
|
|
err error
|
|
}
|
|
lookups := make([]lookup, len(accounts))
|
|
jobs := make(chan int)
|
|
workerCount := xiaodianExchangeLookupConcurrency
|
|
if len(accounts) < workerCount {
|
|
workerCount = len(accounts)
|
|
}
|
|
var wg sync.WaitGroup
|
|
for worker := 0; worker < workerCount; worker++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for index := range jobs {
|
|
account := accounts[index]
|
|
item := lookup{account: account}
|
|
if strings.TrimSpace(account.Cookie) == "" {
|
|
item.err = fmt.Errorf("Cookie 为空")
|
|
} else {
|
|
item.records, item.err = a.getClient().XiaodianExchangeList(account.Cookie)
|
|
}
|
|
lookups[index] = item
|
|
}
|
|
}()
|
|
}
|
|
for index := range accounts {
|
|
jobs <- index
|
|
}
|
|
close(jobs)
|
|
wg.Wait()
|
|
|
|
failed := 0
|
|
matched := 0
|
|
for _, lookup := range lookups {
|
|
if lookup.err != nil {
|
|
failed++
|
|
result.Results[lookup.account.Name] = types.XiaodianExchangeAccountResult{Error: lookup.err.Error()}
|
|
continue
|
|
}
|
|
isMatched := false
|
|
for _, record := range lookup.records {
|
|
if record.CommodityID == result.ProductID {
|
|
isMatched = true
|
|
break
|
|
}
|
|
}
|
|
if isMatched {
|
|
matched++
|
|
}
|
|
result.Results[lookup.account.Name] = types.XiaodianExchangeAccountResult{
|
|
Success: true,
|
|
Records: lookup.records,
|
|
Matched: isMatched,
|
|
}
|
|
}
|
|
result.Success = true
|
|
level := "success"
|
|
if failed > 0 {
|
|
level = "warn"
|
|
}
|
|
logger.LogEvent("兑换记录", level, "和平小店兑换记录批量查询完成: 账号=%d 命中=%d 失败=%d 耗时=%s",
|
|
len(accounts), matched, failed, time.Since(start).Round(time.Millisecond))
|
|
return result
|
|
}
|
|
|
|
// QueryXiaodianAccountExchangeRecords 查询一个账号的和平小店真实订单,供账号表按需展开使用。
|
|
func (a *App) QueryXiaodianAccountExchangeRecords(accountName string) types.XiaodianExchangeAccountResult {
|
|
accountName = strings.TrimSpace(accountName)
|
|
if !a.getClient().Activity.IsXiaodian {
|
|
return types.XiaodianExchangeAccountResult{Error: "当前活动不是和平小店"}
|
|
}
|
|
if accountName == "" {
|
|
return types.XiaodianExchangeAccountResult{Error: "缺少账号名"}
|
|
}
|
|
cookie, ok := a.scheduler.AccountCookieByName(accountName)
|
|
if !ok || strings.TrimSpace(cookie) == "" {
|
|
return types.XiaodianExchangeAccountResult{Error: "账号不存在或 Cookie 为空"}
|
|
}
|
|
records, err := a.getClient().XiaodianExchangeList(cookie)
|
|
if err != nil {
|
|
return types.XiaodianExchangeAccountResult{Error: "查询失败: " + err.Error()}
|
|
}
|
|
return types.XiaodianExchangeAccountResult{Success: true, Records: records}
|
|
}
|
|
|
|
// VerifyAndExport 复验并导出结果。
|
|
func (a *App) VerifyAndExport(commodityID string) types.VerifyAndExportResult {
|
|
accounts := a.scheduler.AccountSnapshot()
|
|
results := make(map[string]types.AccountHitResult)
|
|
hitAccounts := make([]string, 0)
|
|
missAccounts := make([]string, 0)
|
|
lookups := a.lookupExchangeRecords("复验", accounts)
|
|
|
|
for _, lookup := range lookups {
|
|
if lookup.EmptyCookie {
|
|
missAccounts = append(missAccounts, lookup.Account.Name)
|
|
results[lookup.Account.Name] = types.AccountHitResult{Error: "Cookie 为空"}
|
|
continue
|
|
}
|
|
|
|
if lookup.Err != nil {
|
|
missAccounts = append(missAccounts, lookup.Account.Name)
|
|
results[lookup.Account.Name] = types.AccountHitResult{Error: "查询失败: " + lookup.Err.Error()}
|
|
continue
|
|
}
|
|
|
|
// 检查是否抢到了指定商品
|
|
hit := hasCommodityRecord(lookup.Records, commodityID, 0, 0)
|
|
if hit {
|
|
hitAccounts = append(hitAccounts, lookup.Account.Name)
|
|
results[lookup.Account.Name] = types.AccountHitResult{Success: true, Hit: true}
|
|
} else {
|
|
missAccounts = append(missAccounts, lookup.Account.Name)
|
|
results[lookup.Account.Name] = types.AccountHitResult{Success: true}
|
|
}
|
|
}
|
|
|
|
return types.VerifyAndExportResult{
|
|
Success: true,
|
|
HitCount: len(hitAccounts),
|
|
MissCount: len(missAccounts),
|
|
HitAccounts: hitAccounts,
|
|
MissAccounts: missAccounts,
|
|
Details: results,
|
|
}
|
|
}
|
|
|
|
func (a *App) lookupExchangeRecords(category string, accounts []types.Account) []exchangeLookupResult {
|
|
results := make([]exchangeLookupResult, len(accounts))
|
|
if len(accounts) == 0 {
|
|
return results
|
|
}
|
|
|
|
workerCount := exchangeLookupConcurrency
|
|
if len(accounts) < workerCount {
|
|
workerCount = len(accounts)
|
|
}
|
|
start := time.Now()
|
|
logger.LogEvent(category, "info", "兑换记录批量查询开始: 账号=%d 并发=%d", len(accounts), workerCount)
|
|
|
|
jobs := make(chan int)
|
|
var wg sync.WaitGroup
|
|
for worker := 0; worker < workerCount; worker++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for idx := range jobs {
|
|
account := accounts[idx]
|
|
lookup := exchangeLookupResult{Account: account}
|
|
if strings.TrimSpace(account.Cookie) == "" {
|
|
lookup.EmptyCookie = true
|
|
results[idx] = lookup
|
|
continue
|
|
}
|
|
records, err := a.getClient().ExchangeList(account.Cookie)
|
|
lookup.Records = records
|
|
lookup.Err = err
|
|
results[idx] = lookup
|
|
}
|
|
}()
|
|
}
|
|
for idx := range accounts {
|
|
jobs <- idx
|
|
}
|
|
close(jobs)
|
|
wg.Wait()
|
|
|
|
errors := 0
|
|
empty := 0
|
|
for _, result := range results {
|
|
if result.EmptyCookie {
|
|
empty++
|
|
}
|
|
if result.Err != nil {
|
|
errors++
|
|
}
|
|
}
|
|
level := "success"
|
|
if errors > 0 {
|
|
level = "warn"
|
|
}
|
|
logger.LogEvent(category, level, "兑换记录批量查询完成: 账号=%d 空CK=%d 失败=%d 耗时=%s",
|
|
len(accounts), empty, errors, time.Since(start).Round(time.Millisecond))
|
|
return results
|
|
}
|
|
|
|
func filterExchangeRecords(records []types.ExchangeRecord) []types.ExchangeRecord {
|
|
matched := make([]types.ExchangeRecord, 0, len(records))
|
|
for _, r := range records {
|
|
if r.CommodityID != "" {
|
|
matched = append(matched, r)
|
|
}
|
|
}
|
|
return matched
|
|
}
|
|
|
|
func hasCommodityRecord(records []types.ExchangeRecord, commodityID string, startTime, endTime int64) bool {
|
|
if commodityID == "" {
|
|
return false
|
|
}
|
|
for _, r := range records {
|
|
if r.CommodityID != commodityID {
|
|
continue
|
|
}
|
|
if startTime == 0 && endTime == 0 {
|
|
return true
|
|
}
|
|
recordTime, err := strconv.ParseInt(r.Time, 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if startTime > 0 && recordTime < startTime {
|
|
continue
|
|
}
|
|
if endTime > 0 && recordTime > endTime {
|
|
continue
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|