Files
dygou_go/internal/app/app_verify.go
T

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
}