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
This commit is contained in:
@@ -1,72 +1,4 @@
|
|||||||
@echo off
|
@echo off
|
||||||
chcp 65001 >nul
|
|
||||||
setlocal enabledelayedexpansion
|
|
||||||
echo ================================================
|
|
||||||
echo agent_jrxml 启动 (API + 验证)
|
|
||||||
echo ================================================
|
|
||||||
cd /d "%~dp0"
|
cd /d "%~dp0"
|
||||||
|
.venv\Scripts\python.exe start.py
|
||||||
:: ── 环境检查 ──
|
|
||||||
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 已停止所有服务
|
|
||||||
pause
|
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
|
|
||||||
|
|||||||
@@ -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()
|
||||||
+1
-99
@@ -1,102 +1,4 @@
|
|||||||
@echo off
|
@echo off
|
||||||
chcp 65001 >nul
|
|
||||||
setlocal enabledelayedexpansion
|
|
||||||
echo ================================================
|
|
||||||
echo agent_jrxml 启动 (全栈)
|
|
||||||
echo ================================================
|
|
||||||
cd /d "%~dp0"
|
cd /d "%~dp0"
|
||||||
|
.venv\Scripts\python.exe start.py
|
||||||
:: ── 环境检查 ──
|
|
||||||
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 ================================================
|
|
||||||
pause
|
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
|
|
||||||
|
|||||||
@@ -1,27 +1,18 @@
|
|||||||
@echo off
|
@echo off
|
||||||
chcp 65001 >nul
|
cd /d "%~dp0"
|
||||||
echo ================================================
|
.venv\Scripts\python.exe -c "
|
||||||
echo 停止所有 agent_jrxml 服务
|
import os, signal, subprocess
|
||||||
echo ================================================
|
ports = (8000, 8001, 5173)
|
||||||
|
for port in ports:
|
||||||
echo [停止] 按窗口标题清理...
|
try:
|
||||||
taskkill /F /FI "WINDOWTITLE eq jrxml-validator*" 2>nul
|
r = subprocess.run(['netstat', '-ano'], capture_output=True, text=True)
|
||||||
taskkill /F /FI "WINDOWTITLE eq jrxml-api*" 2>nul
|
for line in r.stdout.splitlines():
|
||||||
taskkill /F /FI "WINDOWTITLE eq jrxml-frontend*" 2>nul
|
if f':{port}' in line and 'LISTENING' in line:
|
||||||
|
pid = int(line.split()[-1])
|
||||||
echo [停止] 按端口清理...
|
print(f'Killing PID {pid} on port {port}')
|
||||||
for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":8000.*LISTENING"') do (
|
os.kill(pid, signal.SIGTERM)
|
||||||
echo 端口 8000 - PID %%a
|
except Exception as e:
|
||||||
taskkill /F /PID %%a >nul 2>&1
|
print(f'Port {port}: {e}')
|
||||||
)
|
print('Done')
|
||||||
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 已停止
|
|
||||||
pause
|
pause
|
||||||
|
|||||||
Reference in New Issue
Block a user