From c8aa9252053715f72d1e967a3c7403a56e823936 Mon Sep 17 00:00:00 2001 From: kyx236 Date: Sun, 1 Feb 2026 07:13:34 +0800 Subject: [PATCH] feat: Add backend API for team registration execution. --- backend/internal/api/team_reg_exec.go | 78 +++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/backend/internal/api/team_reg_exec.go b/backend/internal/api/team_reg_exec.go index 7a955c6..e5b5367 100644 --- a/backend/internal/api/team_reg_exec.go +++ b/backend/internal/api/team_reg_exec.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "runtime" "sort" "strings" @@ -20,6 +21,9 @@ import ( "codex-pool/internal/logger" ) +// ansiRegex 匹配 ANSI 转义序列(颜色码等) +var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`) + // TeamRegConfig 注册配置 type TeamRegConfig struct { Count int `json:"count"` // 注册数量 @@ -40,8 +44,17 @@ type TeamRegState struct { cmd *exec.Cmd cancel context.CancelFunc stdin io.WriteCloser + // 403 错误检测 + error403Count int // 403 错误计数 + error403Start time.Time // 计数开始时间 } +// 403 自动停止配置 +const ( + max403Errors = 10 // 最大 403 错误数 + error403Window = 60 * time.Second // 时间窗口(60秒内) +) + var teamRegState = &TeamRegState{ Logs: make([]string, 0), } @@ -91,6 +104,8 @@ func HandleTeamRegStart(w http.ResponseWriter, r *http.Request) { teamRegState.Logs = make([]string, 0) teamRegState.OutputFile = "" teamRegState.Imported = 0 + teamRegState.error403Count = 0 + teamRegState.error403Start = time.Now() teamRegState.mu.Unlock() // 启动进程 @@ -110,9 +125,9 @@ func HandleTeamRegStop(w http.ResponseWriter, r *http.Request) { } teamRegState.mu.Lock() - defer teamRegState.mu.Unlock() if !teamRegState.Running { + teamRegState.mu.Unlock() json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "没有正在运行的任务", @@ -120,7 +135,7 @@ func HandleTeamRegStop(w http.ResponseWriter, r *http.Request) { return } - // 发送 Ctrl+C 信号 + // 发送取消信号 if teamRegState.cancel != nil { teamRegState.cancel() } @@ -131,6 +146,9 @@ func HandleTeamRegStop(w http.ResponseWriter, r *http.Request) { } teamRegState.Running = false + teamRegState.mu.Unlock() + + // 在释放锁之后再添加日志 addTeamRegLog("[系统] 任务已被用户停止") json.NewEncoder(w).Encode(map[string]interface{}{ @@ -396,17 +414,69 @@ func readOutput(reader io.Reader, workDir string, config TeamRegConfig) { trimmed := strings.TrimSpace(line) if trimmed != "" { addTeamRegLog(trimmed) + + // 检测 403 错误 + if strings.Contains(trimmed, "403") { + if check403AndStop() { + return // 已触发自动停止 + } + } } } } +// check403AndStop 检查 403 错误并决定是否自动停止 +// 返回 true 表示已触发停止 +func check403AndStop() bool { + teamRegState.mu.Lock() + defer teamRegState.mu.Unlock() + + now := time.Now() + + // 如果超过时间窗口,重置计数 + if now.Sub(teamRegState.error403Start) > error403Window { + teamRegState.error403Count = 0 + teamRegState.error403Start = now + } + + teamRegState.error403Count++ + + // 如果超过阈值,自动停止 + if teamRegState.error403Count >= max403Errors { + // 触发停止 + if teamRegState.cancel != nil { + teamRegState.cancel() + } + if teamRegState.cmd != nil && teamRegState.cmd.Process != nil { + teamRegState.cmd.Process.Kill() + } + teamRegState.Running = false + + // 不在锁内调用 addTeamRegLog,先解锁 + go func() { + addTeamRegLog(fmt.Sprintf("[警告] 检测到 %d 次 403 错误,自动停止注册", max403Errors)) + addTeamRegLog("[提示] 可能是代理 IP 被限制,请更换代理后重试") + }() + + return true + } + + return false +} + // addTeamRegLog 添加日志 func addTeamRegLog(log string) { teamRegState.mu.Lock() defer teamRegState.mu.Unlock() + // 过滤 ANSI 颜色码 + cleanLog := ansiRegex.ReplaceAllString(log, "") + if cleanLog == "" { + return + } + timestamp := time.Now().Format("15:04:05") - fullLog := fmt.Sprintf("[%s] %s", timestamp, log) + fullLog := fmt.Sprintf("[%s] %s", timestamp, cleanLog) teamRegState.Logs = append(teamRegState.Logs, fullLog) // 限制日志数量 @@ -415,7 +485,7 @@ func addTeamRegLog(log string) { } // 同时输出到系统日志 - logger.Info(fmt.Sprintf("[TeamReg] %s", log), "", "team-reg") + logger.Info(fmt.Sprintf("[TeamReg] %s", cleanLog), "", "team-reg") } // findTeamRegExecutable 查找 team-reg 可执行文件