package api import ( "fmt" "net/http" "strconv" "sync" "time" "codex-pool/internal/config" "codex-pool/internal/database" "codex-pool/internal/logger" ) var ( autoAddRunning bool autoAddMu sync.Mutex autoAddStopChan chan struct{} lastAutoAddTime time.Time lastAutoRegTime time.Time // 上次自动注册时间 autoRegisterRunning bool // 自动注册是否正在运行 ) // StartAutoAddService 启动自动补号服务(后台检查器) func StartAutoAddService() { autoAddMu.Lock() if autoAddRunning { autoAddMu.Unlock() return } autoAddRunning = true autoAddStopChan = make(chan struct{}) autoAddMu.Unlock() // 注意:这只是启动后台检查器,实际补号需要前端开启开关 logger.Info("自动补号检查器已启动(需在前端开启开关)", "", "auto-add") go func() { // 默认检查间隔 60 秒 checkInterval := 60 for { // 动态读取检查间隔配置 if database.Instance != nil { if val, _ := database.Instance.GetConfig("monitor_check_interval"); val != "" { if v, err := strconv.Atoi(val); err == nil && v >= 10 { checkInterval = v } } } select { case <-autoAddStopChan: logger.Info("自动补号检查器已停止", "", "auto-add") return case <-time.After(time.Duration(checkInterval) * time.Second): checkAndAutoAdd() } } }() } // StopAutoAddService 停止自动补号服务 func StopAutoAddService() { autoAddMu.Lock() defer autoAddMu.Unlock() if autoAddRunning && autoAddStopChan != nil { close(autoAddStopChan) autoAddRunning = false } } // checkAndAutoAdd 检查并自动补号 func checkAndAutoAdd() { if database.Instance == nil { return } // 读取配置 autoAddEnabled := false if val, _ := database.Instance.GetConfig("monitor_auto_add"); val == "true" { autoAddEnabled = true } if !autoAddEnabled { // 自动补号未开启,静默返回 return } // 检查最小间隔 minInterval := 300 if val, _ := database.Instance.GetConfig("monitor_min_interval"); val != "" { if v, err := strconv.Atoi(val); err == nil { minInterval = v } } elapsed := time.Since(lastAutoAddTime).Seconds() if elapsed < float64(minInterval) { // 距离上次补号时间不足,显示等待信息 remaining := float64(minInterval) - elapsed logger.Info(fmt.Sprintf("自动补号: 等待中 (还需 %.0f 秒)", remaining), "", "auto-add") return } // 检查是否有任务在运行 if teamProcessState.Running { logger.Info("已有任务在运行,跳过自动补号", "", "auto-add") return } // 获取目标数量 target := 50 if val, _ := database.Instance.GetConfig("monitor_target"); val != "" { if v, err := strconv.Atoi(val); err == nil { target = v } } // 获取当前 S2A 账号数量 current, err := getS2AAccountCount() if err != nil { logger.Error(fmt.Sprintf("获取 S2A 账号数量失败: %v", err), "", "auto-add") return } deficit := target - current if deficit <= 0 { logger.Info(fmt.Sprintf("自动补号: 当前 %d >= 目标 %d, 无需补号", current, target), "", "auto-add") return } // 读取每 Team 成员数配置 membersPerTeam := 4 if val, _ := database.Instance.GetConfig("monitor_members_per_team"); val != "" { if v, err := strconv.Atoi(val); err == nil && v >= 1 && v <= 10 { membersPerTeam = v } } // 计算需要多少个 Team teamsNeeded := (deficit + membersPerTeam - 1) / membersPerTeam // 向上取整 // 获取可用的 Owner owners, err := database.Instance.GetPendingOwners() if err != nil { logger.Error(fmt.Sprintf("获取可用 Owner 失败: %v", err), "", "auto-add") return } if len(owners) == 0 { logger.Warning("没有可用的 Owner 账号,无法自动补号", "", "auto-add") // 检查是否开启自动注册 tryAutoRegisterOwners(teamsNeeded, 0) return } // 限制使用的 Owner 数量 actualTeams := teamsNeeded if actualTeams > len(owners) { logger.Warning(fmt.Sprintf("可用 Owner 不足: 需要 %d 个, 仅有 %d 个可用", teamsNeeded, len(owners)), "", "auto-add") // 检查是否开启自动注册,补充不足的母号 shortfall := teamsNeeded - len(owners) tryAutoRegisterOwners(shortfall, len(owners)) actualTeams = len(owners) } logger.Info(fmt.Sprintf("自动补号: 当前 %d, 目标 %d, 缺少 %d, 需要 %d 个 Team, 将处理 %d 个", current, target, deficit, teamsNeeded, actualTeams), "", "auto-add") // 构建请求 - 直接使用 database.TeamOwner 转换 reqOwners := make([]TeamOwner, actualTeams) for i := 0; i < actualTeams; i++ { reqOwners[i] = TeamOwner{ Email: owners[i].Email, Password: owners[i].Password, Token: owners[i].Token, AccountID: owners[i].AccountID, } } // 读取代理配置 - 支持代理池模式 proxyURL := "" replenishUseProxy := false if val, _ := database.Instance.GetConfig("monitor_replenish_use_proxy"); val == "true" { replenishUseProxy = true } if replenishUseProxy { // 使用全局代理配置(支持 pool:random, pool:id:N 等格式) proxyURL = config.Global.DefaultProxy } // 读取浏览器类型配置 browserType := "rod" if val, _ := database.Instance.GetConfig("monitor_browser_type"); val != "" { browserType = val } // 读取并发 Team 数配置 concurrentTeams := 2 if val, _ := database.Instance.GetConfig("monitor_concurrent_teams"); val != "" { if v, err := strconv.Atoi(val); err == nil && v >= 1 && v <= 10 { concurrentTeams = v } } // 读取入库并发数配置 s2aConcurrency := 2 if val, _ := database.Instance.GetConfig("monitor_s2a_concurrency"); val != "" { if v, err := strconv.Atoi(val); err == nil && v >= 1 && v <= 4 { s2aConcurrency = v } } req := TeamProcessRequest{ Owners: reqOwners, MembersPerTeam: membersPerTeam, ConcurrentTeams: concurrentTeams, ConcurrentS2A: s2aConcurrency, IncludeOwner: false, Headless: true, BrowserType: browserType, Proxy: proxyURL, } // 输出代理使用状态日志 if proxyURL != "" { displayProxy := proxyURL if proxyURL == "pool:random" { displayProxy = "代理池轮询模式" } else if len(proxyURL) > 8 && proxyURL[:8] == "pool:id:" { displayProxy = fmt.Sprintf("代理池固定项 (ID: %s)", proxyURL[8:]) } logger.Info(fmt.Sprintf("自动补号: 使用代理 %s", displayProxy), "", "auto-add") } else { logger.Info("自动补号: 未使用代理", "", "auto-add") } // 初始化状态 teamProcessState.Running = true teamProcessState.StartedAt = time.Now() teamProcessState.TotalTeams = len(reqOwners) teamProcessState.Completed = 0 teamProcessState.Results = make([]TeamProcessResult, 0, len(reqOwners)) lastAutoAddTime = time.Now() // 异步执行 go runTeamProcess(req) logger.Success(fmt.Sprintf("自动补号任务已启动: %d 个 Team, 每 Team %d 成员, 并发 %d (浏览器: %s)", actualTeams, membersPerTeam, concurrentTeams, browserType), "", "auto-add") } // getS2AAccountCount 获取 S2A 当前账号数量 func getS2AAccountCount() (int, error) { if config.Global == nil || config.Global.S2AApiBase == "" { return 0, fmt.Errorf("S2A 配置未设置") } url := config.Global.S2AApiBase + "/api/v1/admin/dashboard/stats" req, err := http.NewRequest("GET", url, nil) if err != nil { return 0, err } // 设置认证头(与代理保持一致) adminKey := config.Global.S2AAdminKey req.Header.Set("Authorization", "Bearer "+adminKey) req.Header.Set("X-API-Key", adminKey) req.Header.Set("X-Admin-Key", adminKey) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return 0, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return 0, fmt.Errorf("S2A 返回状态: %d", resp.StatusCode) } var result struct { Code int `json:"code"` Message string `json:"message"` Data struct { NormalAccounts int `json:"normal_accounts"` } `json:"data"` } if err := decodeJSON(resp.Body, &result); err != nil { return 0, err } // S2A 返回 code=0 表示成功 if result.Code != 0 { return 0, fmt.Errorf("S2A 返回错误: %s", result.Message) } return result.Data.NormalAccounts, nil } // tryAutoRegisterOwners 尝试自动注册母号 // count: 需要注册的数量 // currentOwners: 当前可用的母号数量(用于日志) func tryAutoRegisterOwners(count int, currentOwners int) { if database.Instance == nil { return } // 检查是否开启自动注册 autoRegEnabled := false if val, _ := database.Instance.GetConfig("monitor_auto_register"); val == "true" { autoRegEnabled = true } if !autoRegEnabled { logger.Info("自动注册未开启,跳过母号注册", "", "auto-add") return } // 检查是否有注册任务在运行 autoAddMu.Lock() if autoRegisterRunning { autoAddMu.Unlock() logger.Info("已有自动注册任务在运行,跳过", "", "auto-add") return } // 检查 Team 注册是否在运行 teamRegState.mu.Lock() teamRegRunning := teamRegState.Running teamRegState.mu.Unlock() if teamRegRunning { autoAddMu.Unlock() logger.Info("Team 注册任务正在运行,跳过自动注册", "", "auto-add") return } // 检查距离上次自动注册的间隔(至少 5 分钟) if time.Since(lastAutoRegTime) < 5*time.Minute { autoAddMu.Unlock() remaining := 5*time.Minute - time.Since(lastAutoRegTime) logger.Info(fmt.Sprintf("自动注册: 冷却中 (还需 %.0f 秒)", remaining.Seconds()), "", "auto-add") return } autoRegisterRunning = true lastAutoRegTime = time.Now() autoAddMu.Unlock() // 读取自动注册配置 concurrency := 2 if val, _ := database.Instance.GetConfig("monitor_auto_reg_concurrency"); val != "" { if v, err := strconv.Atoi(val); err == nil && v >= 1 && v <= 10 { concurrency = v } } useProxy := false if val, _ := database.Instance.GetConfig("monitor_auto_reg_use_proxy"); val == "true" { useProxy = true } // 获取代理地址 proxyURL := "" if useProxy && config.Global != nil { proxyURL = config.Global.DefaultProxy } // 限制单次注册数量(最多 20 个) if count > 20 { count = 20 } if count < 1 { count = 1 } logger.Info(fmt.Sprintf("自动注册: 当前母号 %d 个不足,将注册 %d 个新账号 (并发: %d, 代理: %v)", currentOwners, count, concurrency, useProxy), "", "auto-add") // 构建注册配置 regConfig := TeamRegConfig{ Count: count, Concurrency: concurrency, Proxy: proxyURL, AutoImport: true, // 自动导入到数据库 } // 异步执行注册 go func() { defer func() { autoAddMu.Lock() autoRegisterRunning = false autoAddMu.Unlock() }() // 调用 Team 注册流程 runTeamRegProcess(regConfig) logger.Info("自动注册任务已完成", "", "auto-add") }() }