diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 68eabb5..42785a0 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -160,6 +160,7 @@ func startServer(cfg *config.Config) { mux.HandleFunc("/api/team-reg/status", api.CORS(api.HandleTeamRegStatus)) mux.HandleFunc("/api/team-reg/logs", api.HandleTeamRegLogs) // SSE mux.HandleFunc("/api/team-reg/import", api.CORS(api.HandleTeamRegImport)) + mux.HandleFunc("/api/team-reg/clear-logs", api.CORS(api.HandleTeamRegClearLogs)) // 嵌入的前端静态文件 if web.IsEmbedded() { @@ -216,21 +217,23 @@ func handleConfig(w http.ResponseWriter, r *http.Request) { return } api.Success(w, map[string]interface{}{ - "port": config.Global.Port, - "s2a_api_base": config.Global.S2AApiBase, - "s2a_admin_key": config.Global.S2AAdminKey, - "has_admin_key": config.Global.S2AAdminKey != "", - "concurrency": config.Global.Concurrency, - "priority": config.Global.Priority, - "group_ids": config.Global.GroupIDs, - "proxy_enabled": config.Global.ProxyEnabled, - "default_proxy": config.Global.DefaultProxy, - "team_reg_proxy": config.Global.TeamRegProxy, - "proxy_test_status": getProxyTestStatus(), - "proxy_test_ip": getProxyTestIP(), - "site_name": config.Global.SiteName, - "mail_services_count": len(config.Global.MailServices), - "mail_services": config.Global.MailServices, + "port": config.Global.Port, + "s2a_api_base": config.Global.S2AApiBase, + "s2a_admin_key": config.Global.S2AAdminKey, + "has_admin_key": config.Global.S2AAdminKey != "", + "concurrency": config.Global.Concurrency, + "priority": config.Global.Priority, + "group_ids": config.Global.GroupIDs, + "proxy_enabled": config.Global.ProxyEnabled, + "default_proxy": config.Global.DefaultProxy, + "team_reg_proxy": config.Global.TeamRegProxy, + "proxy_test_status": getProxyTestStatus(), + "proxy_test_ip": getProxyTestIP(), + "team_reg_proxy_test_status": getTeamRegProxyTestStatus(), + "team_reg_proxy_test_ip": getTeamRegProxyTestIP(), + "site_name": config.Global.SiteName, + "mail_services_count": len(config.Global.MailServices), + "mail_services": config.Global.MailServices, }) case http.MethodPut: @@ -1007,7 +1010,8 @@ func handleProxyTest(w http.ResponseWriter, r *http.Request) { } var req struct { - ProxyURL string `json:"proxy_url"` + ProxyURL string `json:"proxy_url"` + ProxyType string `json:"proxy_type"` // "default" 或 "team_reg" } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { api.Error(w, http.StatusBadRequest, "请求格式错误") @@ -1020,6 +1024,14 @@ func handleProxyTest(w http.ResponseWriter, r *http.Request) { return } + // 确定保存状态的 key 前缀 + statusKey := "proxy_test_status" + ipKey := "proxy_test_ip" + if req.ProxyType == "team_reg" { + statusKey = "team_reg_proxy_test_status" + ipKey = "team_reg_proxy_test_ip" + } + logger.Info(fmt.Sprintf("测试代理连接: %s", proxyURL), "", "proxy") // 解析代理 URL @@ -1052,8 +1064,8 @@ func handleProxyTest(w http.ResponseWriter, r *http.Request) { logger.Error(fmt.Sprintf("代理测试失败: HTTP %d", resp.StatusCode), "", "proxy") // 保存失败状态 if database.Instance != nil { - database.Instance.SetConfig("proxy_test_status", "error") - database.Instance.SetConfig("proxy_test_ip", "") + database.Instance.SetConfig(statusKey, "error") + database.Instance.SetConfig(ipKey, "") } api.Error(w, http.StatusBadGateway, fmt.Sprintf("代理测试失败: HTTP %d", resp.StatusCode)) return @@ -1071,8 +1083,8 @@ func handleProxyTest(w http.ResponseWriter, r *http.Request) { // 保存成功状态到数据库 if database.Instance != nil { - database.Instance.SetConfig("proxy_test_status", "success") - database.Instance.SetConfig("proxy_test_ip", ipResp.Origin) + database.Instance.SetConfig(statusKey, "success") + database.Instance.SetConfig(ipKey, ipResp.Origin) } api.Success(w, map[string]interface{}{ @@ -1102,6 +1114,26 @@ func getProxyTestIP() string { return val } +// getTeamRegProxyTestStatus 获取注册代理测试状态 +func getTeamRegProxyTestStatus() string { + if database.Instance == nil { + return "unknown" + } + if val, _ := database.Instance.GetConfig("team_reg_proxy_test_status"); val != "" { + return val + } + return "unknown" +} + +// getTeamRegProxyTestIP 获取注册代理测试出口IP +func getTeamRegProxyTestIP() string { + if database.Instance == nil { + return "" + } + val, _ := database.Instance.GetConfig("team_reg_proxy_test_ip") + return val +} + // parseProxyURL 解析代理 URL func parseProxyURL(proxyURL string) (*url.URL, error) { // 如果没有协议前缀,默认添加 http:// diff --git a/backend/internal/api/team_reg_exec.go b/backend/internal/api/team_reg_exec.go index 39c1d6c..7acd6ef 100644 --- a/backend/internal/api/team_reg_exec.go +++ b/backend/internal/api/team_reg_exec.go @@ -375,26 +375,26 @@ func runTeamRegProcess(config TeamRegConfig) { addTeamRegLog("[系统] 进程正常完成") } - // 查找输出文件 - outputFile := findLatestOutputFile(workDir) - if outputFile != "" { - teamRegState.mu.Lock() - teamRegState.OutputFile = outputFile - teamRegState.mu.Unlock() - addTeamRegLog(fmt.Sprintf("[系统] 输出文件: %s", filepath.Base(outputFile))) + // 查找输出文件 + outputFile := findLatestOutputFile(workDir) + if outputFile != "" { + teamRegState.mu.Lock() + teamRegState.OutputFile = outputFile + teamRegState.mu.Unlock() + addTeamRegLog(fmt.Sprintf("[系统] 输出文件: %s", filepath.Base(outputFile))) - // 自动导入 - if config.AutoImport { - addTeamRegLog("[系统] 自动导入账号到数据库...") - tryAutoImport(outputFile, config) - } + // 自动导入 + if config.AutoImport { + addTeamRegLog("[系统] 自动导入账号到数据库...") + tryAutoImport(outputFile, config) } - - // 发送回车退出程序(如果还在运行) - time.Sleep(500 * time.Millisecond) - signalTeamRegExit() } + // 发送回车退出程序(如果还在运行) + time.Sleep(500 * time.Millisecond) + signalTeamRegExit() +} + // readOutput 读取进程输出 func readOutput(reader io.Reader, workDir string, config TeamRegConfig) { scanner := bufio.NewScanner(reader) @@ -405,26 +405,26 @@ func readOutput(reader io.Reader, workDir string, config TeamRegConfig) { if trimmed != "" { addTeamRegLog(trimmed) - // 检测输出文件名(例如:结果已保存到: accounts-2-20260201-071558.json) - if strings.Contains(trimmed, "结果已保存到") || strings.Contains(trimmed, "accounts-") && strings.Contains(trimmed, ".json") { - // 尝试提取文件名 - if idx := strings.Index(trimmed, "accounts-"); idx >= 0 { - endIdx := strings.Index(trimmed[idx:], ".json") - if endIdx > 0 { - fileName := trimmed[idx : idx+endIdx+5] // 包含 .json - // 构建完整路径 - fullPath := filepath.Join(workDir, fileName) - teamRegState.mu.Lock() - teamRegState.OutputFile = fullPath - teamRegState.mu.Unlock() - if config.AutoImport { - go tryAutoImport(fullPath, config) - } - // 发送回车提示退出(有些程序会在完成后等待回车) - signalTeamRegExit() + // 检测输出文件名(例如:结果已保存到: accounts-2-20260201-071558.json) + if strings.Contains(trimmed, "结果已保存到") || strings.Contains(trimmed, "accounts-") && strings.Contains(trimmed, ".json") { + // 尝试提取文件名 + if idx := strings.Index(trimmed, "accounts-"); idx >= 0 { + endIdx := strings.Index(trimmed[idx:], ".json") + if endIdx > 0 { + fileName := trimmed[idx : idx+endIdx+5] // 包含 .json + // 构建完整路径 + fullPath := filepath.Join(workDir, fileName) + teamRegState.mu.Lock() + teamRegState.OutputFile = fullPath + teamRegState.mu.Unlock() + if config.AutoImport { + go tryAutoImport(fullPath, config) } + // 发送回车提示退出(有些程序会在完成后等待回车) + signalTeamRegExit() } } + } // 检测 403 错误 if strings.Contains(trimmed, "403") { @@ -573,6 +573,21 @@ func addTeamRegLog(log string) { logger.Info(fmt.Sprintf("[TeamReg] %s", cleanLog), "", "team-reg") } +// HandleTeamRegClearLogs POST /api/team-reg/clear-logs - 清除日志 +func HandleTeamRegClearLogs(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + Error(w, http.StatusMethodNotAllowed, "仅支持 POST") + return + } + + teamRegState.mu.Lock() + teamRegState.Logs = make([]string, 0) + teamRegState.mu.Unlock() + + logger.Info("[TeamReg] 日志已清除", "", "team-reg") + Success(w, map[string]string{"message": "日志已清除"}) +} + // findTeamRegExecutable 查找 team-reg 可执行文件 func findTeamRegExecutable() string { // 可能的文件名 diff --git a/frontend/src/pages/Config.tsx b/frontend/src/pages/Config.tsx index 40ab1ab..926aeec 100644 --- a/frontend/src/pages/Config.tsx +++ b/frontend/src/pages/Config.tsx @@ -45,7 +45,7 @@ export default function Config() { setSiteName(data.data.site_name || 'Codex Pool') setDefaultProxy(data.data.default_proxy || '') setTeamRegProxy(data.data.team_reg_proxy || '') - // 恢复代理测试状态 + // 恢复全局代理测试状态 const testStatus = data.data.proxy_test_status if (testStatus === 'success' || testStatus === 'error') { setProxyStatus(testStatus) @@ -53,6 +53,14 @@ export default function Config() { if (data.data.proxy_test_ip) { setProxyOriginIP(data.data.proxy_test_ip) } + // 恢复注册代理测试状态 + const teamRegTestStatus = data.data.team_reg_proxy_test_status + if (teamRegTestStatus === 'success' || teamRegTestStatus === 'error') { + setTeamRegProxyStatus(teamRegTestStatus) + } + if (data.data.team_reg_proxy_test_ip) { + setTeamRegProxyIP(data.data.team_reg_proxy_test_ip) + } } } catch (error) { console.error('Failed to fetch config:', error) @@ -122,7 +130,7 @@ export default function Config() { const res = await fetch('/api/proxy/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ proxy_url: defaultProxy }), + body: JSON.stringify({ proxy_url: defaultProxy, proxy_type: 'default' }), }) const data = await res.json() if (data.code === 0 && data.data?.connected) { @@ -177,7 +185,7 @@ export default function Config() { const res = await fetch('/api/proxy/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ proxy_url: teamRegProxy }), + body: JSON.stringify({ proxy_url: teamRegProxy, proxy_type: 'team_reg' }), }) const data = await res.json() if (data.code === 0 && data.data?.connected) { diff --git a/frontend/src/pages/TeamReg.tsx b/frontend/src/pages/TeamReg.tsx index 021e821..38df0eb 100644 --- a/frontend/src/pages/TeamReg.tsx +++ b/frontend/src/pages/TeamReg.tsx @@ -68,9 +68,9 @@ export default function TeamReg() { loadProxyConfig() }, [loadProxyConfig]) - // 获取状态 - const fetchStatus = useCallback(async () => { - setLoading(true) + // 获取状态(silent=true 时不显示加载状态) + const fetchStatus = useCallback(async (silent = false) => { + if (!silent) setLoading(true) try { const res = await fetch('/api/team-reg/status') if (res.ok) { @@ -80,16 +80,16 @@ export default function TeamReg() { } catch (e) { console.error('获取状态失败:', e) } - setLoading(false) + if (!silent) setLoading(false) }, []) // 初始化和轮询 useEffect(() => { fetchStatus() - // 如果正在运行,每秒刷新状态 + // 如果正在运行,每秒刷新状态(静默模式) const interval = setInterval(() => { - fetchStatus() + fetchStatus(true) }, 1000) return () => clearInterval(interval) @@ -202,7 +202,7 @@ export default function TeamReg() { - +
{logs.length === 0 ? (