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 ) // StartAutoAddService 启动自动补号服务(后台检查器) func StartAutoAddService() { autoAddMu.Lock() if autoAddRunning { autoAddMu.Unlock() return } autoAddRunning = true autoAddStopChan = make(chan struct{}) autoAddMu.Unlock() // 注意:这只是启动后台检查器,实际补号需要前端开启开关 logger.Info("自动补号检查器已启动(需在前端开启开关)", "", "auto-add") go func() { ticker := time.NewTicker(60 * time.Second) // 每分钟检查一次 defer ticker.Stop() for { select { case <-autoAddStopChan: logger.Info("自动补号检查器已停止", "", "auto-add") return case <-ticker.C: 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 } } if time.Since(lastAutoAddTime).Seconds() < float64(minInterval) { 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 { return } // 计算需要多少个 Team(每个 Team 产生 4 个账号) teamsNeeded := (deficit + 3) / 4 // 向上取整 // 获取可用的 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") return } // 限制使用的 Owner 数量 actualTeams := teamsNeeded if actualTeams > len(owners) { logger.Warning(fmt.Sprintf("可用 Owner 不足: 需要 %d 个, 仅有 %d 个可用", teamsNeeded, len(owners)), "", "auto-add") 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, } } req := TeamProcessRequest{ Owners: reqOwners, MembersPerTeam: 4, ConcurrentTeams: 2, IncludeOwner: false, Headless: true, BrowserType: "rod", // 默认使用 rod Proxy: "", // 不使用代理 } // 初始化状态 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", actualTeams), "", "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 }