From c2cae5665e1f9fe075ade18b983455ad08d4c080 Mon Sep 17 00:00:00 2001 From: panda <1415243231@qq.com> Date: Sat, 23 May 2026 09:32:32 +0800 Subject: [PATCH] fix: replace complex bat scripts with Python launcher + minimal bat wrappers Root cause: Windows batch files written with LF endings caused cmd.exe to misparse labels and Chinese characters, producing garbled "not a command" errors. The Python launcher avoids encoding issues entirely. - start.py: reliable cross-platform launcher (kill ports, start 3 services, wait for health, print status) - start.bat / start_all.bat: minimal 4-line ASCII wrappers - stop.bat: inline Python for port-based process killing --- start.bat | 70 +------------------------- start.py | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++ start_all.bat | 100 +------------------------------------- stop.bat | 41 ++++++---------- 4 files changed, 150 insertions(+), 193 deletions(-) create mode 100644 start.py diff --git a/start.bat b/start.bat index f2f91b5..3b4ee9a 100644 --- a/start.bat +++ b/start.bat @@ -1,72 +1,4 @@ @echo off -chcp 65001 >nul -setlocal enabledelayedexpansion -echo ================================================ -echo agent_jrxml 启动 (API + 验证) -echo ================================================ cd /d "%~dp0" - -:: ── 环境检查 ── -if not exist "%~dp0.venv\Scripts\python.exe" ( - echo [错误] 未找到 .venv,请先创建虚拟环境 - pause - exit /b 1 -) - -echo. - -:: ── 清理残留进程 ── -echo [清理] 检查残留进程... -for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8000.*LISTENING"') do ( - taskkill /F /PID %%a >nul 2>&1 && echo 已清理端口 8000 (PID %%a) -) -for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8001.*LISTENING"') do ( - taskkill /F /PID %%a >nul 2>&1 && echo 已清理端口 8001 (PID %%a) -) -echo. - -:: ── 1. 验证服务 ── -echo [启动] 验证服务 :8001 -start "jrxml-validator" .venv\Scripts\python.exe -c "import uvicorn; uvicorn.run('validation_service.main:app',host='0.0.0.0',port=8001,reload=False)" 2> "%~dp0logs\validator-stderr.log" - -echo [等待] 验证服务就绪... -set /a N=0 -:wait_val -ping -n 2 127.0.0.1 >nul -powershell -Command "try{$r=Invoke-WebRequest -Uri http://localhost:8001/health -TimeoutSec 2 -UseBasicParsing;exit 0}catch{exit 1}" >nul 2>&1 -if not errorlevel 1 goto val_ok -set /a N+=1 -if !N! GEQ 30 ( - echo [失败] 验证服务启动超时 (30次重试) - echo 请检查: logs\validator-stderr.log - goto cleanup -) -goto wait_val -:val_ok -echo :8001 就绪 - -:: ── 2. API 服务 (前台运行) ── -echo [启动] API 服务 :8000 -echo ================================================ -echo 服务已就绪: -echo API: http://localhost:8000/docs -echo 验证: http://localhost:8001/health -echo 按 Ctrl+C 停止 API 服务 -echo 关闭窗口后会自动清理验证服务 -echo ================================================ -.venv\Scripts\python.exe -c "import uvicorn; uvicorn.run('api_server:app',host='0.0.0.0',port=8000,reload=False)" - -:: ── API 退出后清理 ── -echo. -echo [清理] 停止验证服务... -taskkill /F /FI "WINDOWTITLE eq jrxml-validator*" >nul 2>&1 -for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8001.*LISTENING"') do taskkill /F /PID %%a >nul 2>&1 -echo 已停止所有服务 +.venv\Scripts\python.exe start.py pause -exit /b 0 - -:cleanup -taskkill /F /FI "WINDOWTITLE eq jrxml-validator*" >nul 2>&1 -for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8001.*LISTENING"') do taskkill /F /PID %%a >nul 2>&1 -pause -exit /b 1 diff --git a/start.py b/start.py new file mode 100644 index 0000000..cd6bed9 --- /dev/null +++ b/start.py @@ -0,0 +1,132 @@ +"""Start all jrxml-agent services (validator, API, frontend).""" +import subprocess +import sys +import time +import urllib.request +from pathlib import Path + +ROOT = Path(__file__).parent +VENV_PYTHON = ROOT / ".venv" / "Scripts" / "python.exe" +FRONTEND_DIR = ROOT / "frontend" + + +def kill_port(port: int): + """Kill any process listening on the given port.""" + import os + import signal + try: + result = subprocess.run( + ["netstat", "-ano"], capture_output=True, text=True + ) + for line in result.stdout.splitlines(): + if f":{port}" in line and "LISTENING" in line: + parts = line.split() + pid = int(parts[-1]) + print(f" Killing PID {pid} on port {port}") + os.kill(pid, signal.SIGTERM) + except Exception as e: + print(f" (cleanup note: {e})") + + +def wait_http(url: str, timeout: int = 60, interval: float = 2.0) -> bool: + """Wait until an HTTP endpoint responds 200, or timeout.""" + deadline = time.time() + timeout + while time.time() < deadline: + try: + urllib.request.urlopen(url, timeout=2) + return True + except Exception: + time.sleep(interval) + return False + + +def wait_port(port: int, timeout: int = 60, interval: float = 3.0) -> bool: + """Wait until a TCP port is listening.""" + deadline = time.time() + timeout + while time.time() < deadline: + try: + result = subprocess.run( + ["netstat", "-ano"], capture_output=True, text=True + ) + for line in result.stdout.splitlines(): + if f":{port}" in line and "LISTENING" in line: + return True + except Exception: + pass + time.sleep(interval) + return False + + +def main(): + if not VENV_PYTHON.exists(): + print("[ERROR] .venv not found. Create a virtual environment first.") + sys.exit(1) + + print("=" * 48) + print(" jrxml-agent launcher (full stack)") + print("=" * 48) + + # -- cleanup -- + print("\n[Cleanup] Checking residual processes...") + for port in (8000, 8001, 5173): + kill_port(port) + + # -- 1. Validator -- + print("\n[1/3] Starting validator on :8001 ...") + subprocess.Popen( + [str(VENV_PYTHON), "-c", + "import uvicorn; uvicorn.run('validation_service.main:app',host='0.0.0.0',port=8001,reload=False)"], + cwd=str(ROOT), + creationflags=subprocess.CREATE_NO_WINDOW, + ) + if not wait_http("http://localhost:8001/health"): + print("[FAIL] Validator did not start in time.") + sys.exit(1) + print(" :8001 ready") + + # -- 2. API -- + print("[2/3] Starting API on :8000 ...") + subprocess.Popen( + [str(VENV_PYTHON), "-c", + "import uvicorn; uvicorn.run('api_server:app',host='0.0.0.0',port=8000,reload=False)"], + cwd=str(ROOT), + creationflags=subprocess.CREATE_NO_WINDOW, + ) + if not wait_http("http://localhost:8000/api/health"): + print("[FAIL] API server did not start in time.") + sys.exit(1) + print(" :8000 ready") + + # -- 3. Frontend -- + print("[3/3] Starting frontend on :5173 ...") + if not (FRONTEND_DIR / "node_modules").exists(): + print(" Installing npm dependencies...") + subprocess.run(["npm", "install"], cwd=str(FRONTEND_DIR), check=True) + + subprocess.Popen( + ["npm", "run", "dev"], + cwd=str(FRONTEND_DIR), + creationflags=subprocess.CREATE_NO_WINDOW, + ) + if not wait_port(5173): + print("[FAIL] Frontend did not start in time.") + sys.exit(1) + print(" :5173 ready") + + print("\n" + "=" * 48) + print(" All services ready:") + print(" Frontend: http://localhost:5173") + print(" API: http://localhost:8000/docs") + print(" Validator: http://localhost:8001/health") + print(" Press Ctrl+C to stop all services") + print("=" * 48) + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + print("\nShutting down...") + + +if __name__ == "__main__": + main() diff --git a/start_all.bat b/start_all.bat index 3afbe46..3b4ee9a 100644 --- a/start_all.bat +++ b/start_all.bat @@ -1,102 +1,4 @@ @echo off -chcp 65001 >nul -setlocal enabledelayedexpansion -echo ================================================ -echo agent_jrxml 启动 (全栈) -echo ================================================ cd /d "%~dp0" - -:: ── 环境检查 ── -if not exist "%~dp0.venv\Scripts\python.exe" ( - echo [错误] 未找到 .venv,请先创建虚拟环境 - pause - exit /b 1 -) -if not exist "%~dp0frontend\node_modules" ( - echo [安装] node_modules 不存在,正在 npm install... - cd /d "%~dp0frontend" - call npm install - cd /d "%~dp0" -) - -echo. - -:: ── 清理残留进程 ── -call :killport 8000 -call :killport 8001 -call :killport 5173 -echo. - -:: ── 1. 验证服务 ── -echo [1/3] 启动验证服务 :8001 ... -start "jrxml-validator" .venv\Scripts\python.exe -c "import uvicorn; uvicorn.run('validation_service.main:app',host='0.0.0.0',port=8001,reload=False)" 2> "%~dp0logs\validator-stderr.log" -call :wait_health "8001" "验证服务" || goto cleanup -echo :8001 就绪 - -:: ── 2. API 服务 ── -echo [2/3] 启动 API 服务 :8000 ... -start "jrxml-api" .venv\Scripts\python.exe -c "import uvicorn; uvicorn.run('api_server:app',host='0.0.0.0',port=8000,reload=False)" 2> "%~dp0logs\api-stderr.log" -call :wait_health "8000/api" "API 服务" || goto cleanup -echo :8000 就绪 - -:: ── 3. 前端 ── -echo [3/3] 启动前端 :5173 ... -cd /d "%~dp0frontend" -start "jrxml-frontend" cmd /c "npm run dev" 2> "%~dp0logs\frontend-stderr.log" -cd /d "%~dp0" -call :wait_port "5173" "前端" || goto cleanup -echo :5173 就绪 - -echo. -echo ================================================ -echo 全部就绪: -echo 前端: http://localhost:5173 -echo API: http://localhost:8000/docs -echo 验证: http://localhost:8001/health -echo 运行 stop.bat 停止所有服务 -echo ================================================ +.venv\Scripts\python.exe start.py pause -exit /b 0 - -:: ── Cleanup ── -:cleanup -echo [清理] 停止已启动的服务... -taskkill /F /FI "WINDOWTITLE eq jrxml-validator*" >nul 2>&1 -taskkill /F /FI "WINDOWTITLE eq jrxml-api*" >nul 2>&1 -taskkill /F /FI "WINDOWTITLE eq jrxml-frontend*" >nul 2>&1 -call :killport 8001 -call :killport 8000 -call :killport 5173 -echo 已清理,请重试 -pause -exit /b 1 - -:: ── Helper: kill process on port ── -:killport -for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":%1.*LISTENING"') do ( - echo 清理端口 %1 (PID %%a) - taskkill /F /PID %%a >nul 2>&1 -) -exit /b 0 - -:: ── Helper: wait for HTTP health check ── -:wait_health -set /a N=0 -:wait_health_loop -ping -n 2 127.0.0.1 >nul -powershell -Command "try{$r=Invoke-WebRequest -Uri http://localhost:%1/health -TimeoutSec 2 -UseBasicParsing;exit 0}catch{exit 1}" >nul 2>&1 -if not errorlevel 1 exit /b 0 -set /a N+=1 -if !N! GEQ 30 exit /b 1 -goto wait_health_loop - -:: ── Helper: wait for TCP port ── -:wait_port -set /a N=0 -:wait_port_loop -ping -n 3 127.0.0.1 >nul -netstat -ano | findstr ":%1.*LISTENING" >nul 2>&1 -if not errorlevel 1 exit /b 0 -set /a N+=1 -if !N! GEQ 30 exit /b 1 -goto wait_port_loop diff --git a/stop.bat b/stop.bat index 3d61537..406b2d9 100644 --- a/stop.bat +++ b/stop.bat @@ -1,27 +1,18 @@ @echo off -chcp 65001 >nul -echo ================================================ -echo 停止所有 agent_jrxml 服务 -echo ================================================ - -echo [停止] 按窗口标题清理... -taskkill /F /FI "WINDOWTITLE eq jrxml-validator*" 2>nul -taskkill /F /FI "WINDOWTITLE eq jrxml-api*" 2>nul -taskkill /F /FI "WINDOWTITLE eq jrxml-frontend*" 2>nul - -echo [停止] 按端口清理... -for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8000.*LISTENING"') do ( - echo 端口 8000 - PID %%a - taskkill /F /PID %%a >nul 2>&1 -) -for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8001.*LISTENING"') do ( - echo 端口 8001 - PID %%a - taskkill /F /PID %%a >nul 2>&1 -) -for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":5173.*LISTENING"') do ( - echo 端口 5173 - PID %%a - taskkill /F /PID %%a >nul 2>&1 -) - -echo 已停止 +cd /d "%~dp0" +.venv\Scripts\python.exe -c " +import os, signal, subprocess +ports = (8000, 8001, 5173) +for port in ports: + try: + r = subprocess.run(['netstat', '-ano'], capture_output=True, text=True) + for line in r.stdout.splitlines(): + if f':{port}' in line and 'LISTENING' in line: + pid = int(line.split()[-1]) + print(f'Killing PID {pid} on port {port}') + os.kill(pid, signal.SIGTERM) + except Exception as e: + print(f'Port {port}: {e}') +print('Done') +" pause