diff --git a/backend/internal/api/owner_ban_check.go b/backend/internal/api/owner_ban_check.go index 1ca94cc..f12be55 100644 --- a/backend/internal/api/owner_ban_check.go +++ b/backend/internal/api/owner_ban_check.go @@ -134,8 +134,8 @@ func runScheduledBanCheck() { logger.Info(fmt.Sprintf("定期封禁检查: 发现 %d 个需要检查的母号", len(owners)), "", "ban-check") - // 执行检查 - go runBanCheckTask(owners, 2) + // 执行检查(并发数 20) + go runBanCheckTask(owners, 20) } // HandleBanCheckSettings 获取/设置封禁检查配置 @@ -231,14 +231,11 @@ func HandleManualBanCheck(w http.ResponseWriter, r *http.Request) { } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { // 允许空 body - req.Concurrency = 2 + req.Concurrency = 20 } if req.Concurrency <= 0 { - req.Concurrency = 2 - } - if req.Concurrency > 5 { - req.Concurrency = 5 + req.Concurrency = 20 } var owners []database.TeamOwner diff --git a/backend/internal/api/upload.go b/backend/internal/api/upload.go index 569f7dd..50b3715 100644 --- a/backend/internal/api/upload.go +++ b/backend/internal/api/upload.go @@ -82,49 +82,34 @@ func HandleUploadValidate(w http.ResponseWriter, r *http.Request) { return } - // 统计需要获取 account_id 的数量 - needFetch := 0 - for _, o := range owners { - if o.AccountID == "" { - needFetch++ - } + // 并发验证账号并获取 account_id(只保留 team 账号) + logger.Info(fmt.Sprintf("开始验证账号: 共 %d 个,只导入 plan 为 team 的账号", len(owners)), "", "upload") + validOwners, teamCount, nonTeamCount, failCount := validateAndFetchAccountIDs(owners, 20) + logger.Info(fmt.Sprintf("验证完成: team=%d, 非team=%d, 失败=%d", teamCount, nonTeamCount, failCount), "", "upload") + + if len(validOwners) == 0 { + Error(w, http.StatusBadRequest, fmt.Sprintf("没有有效的 team 账号(共 %d 个,非team: %d,失败: %d)", len(owners), nonTeamCount, failCount)) + return } - // 并发获取 account_id (使用20并发) - if needFetch > 0 { - logger.Info(fmt.Sprintf("开始获取 account_id: 需要获取 %d 个", needFetch), "", "upload") - fetchAccountIDsConcurrent(owners, 20) - logger.Info("account_id 获取完成", "", "upload") - } - - inserted, err := database.Instance.AddTeamOwners(owners) + inserted, err := database.Instance.AddTeamOwners(validOwners) if err != nil { Error(w, http.StatusInternalServerError, fmt.Sprintf("写入数据库失败: %v", err)) return } - // 统计获取结果 - successCount := 0 - failCount := 0 - for _, o := range owners { - if o.AccountID != "" { - successCount++ - } else { - failCount++ - } - } - // 输出导入日志 - logger.Info(fmt.Sprintf("母号导入成功: 成功=%d, 总数=%d, account_id获取成功=%d, 失败=%d", - inserted, len(owners), successCount, failCount), "", "upload") + logger.Info(fmt.Sprintf("母号导入成功: 导入=%d, 总数=%d, team=%d, 非team跳过=%d, 失败=%d", + inserted, len(owners), teamCount, nonTeamCount, failCount), "", "upload") stats := database.Instance.GetOwnerStats() Success(w, map[string]interface{}{ - "imported": inserted, - "total": len(owners), - "stats": stats, - "account_id_ok": successCount, - "account_id_fail": failCount, + "imported": inserted, + "total": len(owners), + "stats": stats, + "team_count": teamCount, + "non_team_count": nonTeamCount, + "fail_count": failCount, }) } @@ -206,7 +191,61 @@ func HandleRefetchAccountIDs(w http.ResponseWriter, r *http.Request) { }) } -// fetchAccountIDsConcurrent 并发获取 account_id +// validateAndFetchAccountIDs 并发验证账号并获取 account_id(只保留 team 账号) +// 返回: 有效的 owners 列表, team 数量, 非 team 数量, 失败数量 +func validateAndFetchAccountIDs(owners []database.TeamOwner, concurrency int) ([]database.TeamOwner, int, int, int) { + var wg sync.WaitGroup + sem := make(chan struct{}, concurrency) + var mu sync.Mutex + completed := 0 + total := len(owners) + + validOwners := make([]database.TeamOwner, 0, total) + teamCount := 0 + nonTeamCount := 0 + failCount := 0 + + for i := range owners { + wg.Add(1) + sem <- struct{}{} + + go func(idx int) { + defer wg.Done() + defer func() { <-sem }() + + email := owners[idx].Email + token := owners[idx].Token + + accountID, err := fetchAccountID(token) + + mu.Lock() + completed++ + progress := completed + + if err != nil { + errStr := err.Error() + if strings.Contains(errStr, "非 team 账户") { + nonTeamCount++ + logger.Warning(fmt.Sprintf("[%d/%d] 跳过非 team 账号: %s", progress, total, email), "", "upload") + } else { + failCount++ + logger.Warning(fmt.Sprintf("[%d/%d] 验证失败 (%s): %v", progress, total, email, err), "", "upload") + } + } else { + owners[idx].AccountID = accountID + validOwners = append(validOwners, owners[idx]) + teamCount++ + logger.Info(fmt.Sprintf("[%d/%d] team 账号验证通过: %s -> %s", progress, total, email, accountID), "", "upload") + } + mu.Unlock() + }(i) + } + + wg.Wait() + return validOwners, teamCount, nonTeamCount, failCount +} + +// fetchAccountIDsConcurrent 并发获取 account_id(用于重新获取) func fetchAccountIDsConcurrent(owners []database.TeamOwner, concurrency int) { var wg sync.WaitGroup sem := make(chan struct{}, concurrency) @@ -332,7 +371,7 @@ func normalizeOwner(rec accountRecord, index int) (database.TeamOwner, error) { }, nil } -// fetchAccountID 通过 token 获取 account_id +// fetchAccountID 通过 token 获取 account_id(只接受 plan_type 为 team 的账号) func fetchAccountID(token string) (string, error) { // 使用配置中的代理(如果启用) proxy := "" @@ -382,22 +421,17 @@ func fetchAccountID(token string) (string, error) { return "", fmt.Errorf("解析响应失败: %v", err) } - // 优先查找 plan_type 为 "team" 的账户 - // 注意:account_id 是 map 的 key,而不是 Account.ID 字段 + // 只接受 plan_type 为 "team" 的账户 for accountID, info := range result.Accounts { if accountID == "default" { continue } - if strings.Contains(strings.ToLower(info.Account.PlanType), "team") { - return accountID, nil - } - } - - // 否则取第一个非 "default" 的账户 - for accountID := range result.Accounts { - if accountID != "default" { + planType := strings.ToLower(info.Account.PlanType) + if strings.Contains(planType, "team") { return accountID, nil } + // 如果找到非 team 的账户,返回错误 + return "", fmt.Errorf("账户 plan 为 %s,非 team 账户", info.Account.PlanType) } return "", fmt.Errorf("未找到有效的 account_id")