feat: Introduce API for owner account import, validation, account ID management, and ban checks.
This commit is contained in:
@@ -134,8 +134,8 @@ func runScheduledBanCheck() {
|
|||||||
|
|
||||||
logger.Info(fmt.Sprintf("定期封禁检查: 发现 %d 个需要检查的母号", len(owners)), "", "ban-check")
|
logger.Info(fmt.Sprintf("定期封禁检查: 发现 %d 个需要检查的母号", len(owners)), "", "ban-check")
|
||||||
|
|
||||||
// 执行检查
|
// 执行检查(并发数 20)
|
||||||
go runBanCheckTask(owners, 2)
|
go runBanCheckTask(owners, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleBanCheckSettings 获取/设置封禁检查配置
|
// HandleBanCheckSettings 获取/设置封禁检查配置
|
||||||
@@ -231,14 +231,11 @@ func HandleManualBanCheck(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
// 允许空 body
|
// 允许空 body
|
||||||
req.Concurrency = 2
|
req.Concurrency = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Concurrency <= 0 {
|
if req.Concurrency <= 0 {
|
||||||
req.Concurrency = 2
|
req.Concurrency = 20
|
||||||
}
|
|
||||||
if req.Concurrency > 5 {
|
|
||||||
req.Concurrency = 5
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var owners []database.TeamOwner
|
var owners []database.TeamOwner
|
||||||
|
|||||||
@@ -82,49 +82,34 @@ func HandleUploadValidate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统计需要获取 account_id 的数量
|
// 并发验证账号并获取 account_id(只保留 team 账号)
|
||||||
needFetch := 0
|
logger.Info(fmt.Sprintf("开始验证账号: 共 %d 个,只导入 plan 为 team 的账号", len(owners)), "", "upload")
|
||||||
for _, o := range owners {
|
validOwners, teamCount, nonTeamCount, failCount := validateAndFetchAccountIDs(owners, 20)
|
||||||
if o.AccountID == "" {
|
logger.Info(fmt.Sprintf("验证完成: team=%d, 非team=%d, 失败=%d", teamCount, nonTeamCount, failCount), "", "upload")
|
||||||
needFetch++
|
|
||||||
}
|
if len(validOwners) == 0 {
|
||||||
|
Error(w, http.StatusBadRequest, fmt.Sprintf("没有有效的 team 账号(共 %d 个,非team: %d,失败: %d)", len(owners), nonTeamCount, failCount))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 并发获取 account_id (使用20并发)
|
inserted, err := database.Instance.AddTeamOwners(validOwners)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error(w, http.StatusInternalServerError, fmt.Sprintf("写入数据库失败: %v", err))
|
Error(w, http.StatusInternalServerError, fmt.Sprintf("写入数据库失败: %v", err))
|
||||||
return
|
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",
|
logger.Info(fmt.Sprintf("母号导入成功: 导入=%d, 总数=%d, team=%d, 非team跳过=%d, 失败=%d",
|
||||||
inserted, len(owners), successCount, failCount), "", "upload")
|
inserted, len(owners), teamCount, nonTeamCount, failCount), "", "upload")
|
||||||
|
|
||||||
stats := database.Instance.GetOwnerStats()
|
stats := database.Instance.GetOwnerStats()
|
||||||
Success(w, map[string]interface{}{
|
Success(w, map[string]interface{}{
|
||||||
"imported": inserted,
|
"imported": inserted,
|
||||||
"total": len(owners),
|
"total": len(owners),
|
||||||
"stats": stats,
|
"stats": stats,
|
||||||
"account_id_ok": successCount,
|
"team_count": teamCount,
|
||||||
"account_id_fail": failCount,
|
"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) {
|
func fetchAccountIDsConcurrent(owners []database.TeamOwner, concurrency int) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
sem := make(chan struct{}, concurrency)
|
sem := make(chan struct{}, concurrency)
|
||||||
@@ -332,7 +371,7 @@ func normalizeOwner(rec accountRecord, index int) (database.TeamOwner, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchAccountID 通过 token 获取 account_id
|
// fetchAccountID 通过 token 获取 account_id(只接受 plan_type 为 team 的账号)
|
||||||
func fetchAccountID(token string) (string, error) {
|
func fetchAccountID(token string) (string, error) {
|
||||||
// 使用配置中的代理(如果启用)
|
// 使用配置中的代理(如果启用)
|
||||||
proxy := ""
|
proxy := ""
|
||||||
@@ -382,22 +421,17 @@ func fetchAccountID(token string) (string, error) {
|
|||||||
return "", fmt.Errorf("解析响应失败: %v", err)
|
return "", fmt.Errorf("解析响应失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先查找 plan_type 为 "team" 的账户
|
// 只接受 plan_type 为 "team" 的账户
|
||||||
// 注意:account_id 是 map 的 key,而不是 Account.ID 字段
|
|
||||||
for accountID, info := range result.Accounts {
|
for accountID, info := range result.Accounts {
|
||||||
if accountID == "default" {
|
if accountID == "default" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(strings.ToLower(info.Account.PlanType), "team") {
|
planType := strings.ToLower(info.Account.PlanType)
|
||||||
return accountID, nil
|
if strings.Contains(planType, "team") {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 否则取第一个非 "default" 的账户
|
|
||||||
for accountID := range result.Accounts {
|
|
||||||
if accountID != "default" {
|
|
||||||
return accountID, nil
|
return accountID, nil
|
||||||
}
|
}
|
||||||
|
// 如果找到非 team 的账户,返回错误
|
||||||
|
return "", fmt.Errorf("账户 plan 为 %s,非 team 账户", info.Account.PlanType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("未找到有效的 account_id")
|
return "", fmt.Errorf("未找到有效的 account_id")
|
||||||
|
|||||||
Reference in New Issue
Block a user