PMFlow/launcher.py

198 lines
5.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Neural Sync 启动器 - 跨平台版本
一键启动前后端服务
"""
import os
import sys
import time
import subprocess
import signal
import platform
from pathlib import Path
# 颜色输出
class Colors:
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
RED = '\033[0;31m'
BLUE = '\033[0;34m'
NC = '\033[0m'
def print_colored(color, text):
print(f"{color}{text}{Colors.NC}")
def get_project_dir():
return Path(__file__).parent
def check_venv():
venv_dir = get_project_dir() / "venv"
if not venv_dir.exists():
print_colored(Colors.RED, "❌ 虚拟环境不存在,请先创建: python -m venv venv")
sys.exit(1)
def kill_port(port):
"""终止占用指定端口的进程"""
system = platform.system()
try:
if system == "Darwin": # macOS
subprocess.run(["lsof", "-ti", f"{port}"],
capture_output=True, text=True, check=True)
result = subprocess.run(["lsof", "-ti", f"{port}"],
capture_output=True, text=True)
if result.stdout.strip():
subprocess.run(["kill", "-9"] + result.stdout.strip().split(),
capture_output=True)
elif system == "Linux":
result = subprocess.run(["fuser", "-k", f"{port}/tcp"],
capture_output=True)
except:
pass
def start_backend():
"""启动后端服务"""
print_colored(Colors.GREEN, "▶️ 启动后端服务...")
project_dir = get_project_dir()
backend_dir = project_dir / "backend"
log_dir = project_dir / "logs"
log_dir.mkdir(exist_ok=True)
# 激活虚拟环境并启动
venv_python = get_project_dir() / "venv" / "bin" / "python"
if platform.system() == "Windows":
venv_python = get_project_dir() / "venv" / "Scripts" / "python.exe"
log_file = log_dir / "backend.log"
process = subprocess.Popen(
[str(venv_python), "-m", "uvicorn", "app.main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"],
cwd=backend_dir,
stdout=open(log_file, "w"),
stderr=subprocess.STDOUT
)
# 等待后端启动
for i in range(10):
try:
response = subprocess.run(
["curl", "-s", "http://127.0.0.1:8000/docs"],
capture_output=True, timeout=2
)
if response.returncode == 0:
print_colored(Colors.GREEN, f"✅ 后端启动成功 (PID: {process.pid})")
return process
except:
pass
time.sleep(1)
print_colored(Colors.RED, "❌ 后端启动失败")
return None
def start_frontend():
"""启动前端服务 (React)"""
print_colored(Colors.GREEN, "▶️ 启动前端服务...")
project_dir = get_project_dir()
frontend_dir = project_dir / "frontend-react"
log_dir = project_dir / "logs"
log_dir.mkdir(exist_ok=True)
log_file = log_dir / "frontend.log"
process = subprocess.Popen(
["npm", "run", "dev", "--", "--host"],
cwd=frontend_dir,
stdout=open(log_file, "w"),
stderr=subprocess.STDOUT,
shell=True
)
# 等待前端启动
for i in range(15):
try:
response = subprocess.run(
["curl", "-s", "http://127.0.0.1:5174"],
capture_output=True, timeout=2
)
if response.returncode == 0:
print_colored(Colors.GREEN, f"✅ 前端启动成功 (PID: {process.pid})")
return process
except:
pass
time.sleep(1)
print_colored(Colors.YELLOW, "⚠️ 前端可能还在启动中")
return process
def main():
print_colored(Colors.BLUE, "🚀 Neural Sync 启动器")
print("=" * 30)
project_dir = get_project_dir()
os.chdir(project_dir)
# 检查环境
check_venv()
# 清理端口
print_colored(Colors.BLUE, "🧹 清理端口...")
kill_port(8000)
kill_port(5174)
time.sleep(1)
# 启动服务
backend_proc = start_backend()
if not backend_proc:
sys.exit(1)
frontend_proc = start_frontend()
# 保存 PID
log_dir = project_dir / "logs"
(log_dir / "backend.pid").write_text(str(backend_proc.pid))
(log_dir / "frontend.pid").write_text(str(frontend_proc.pid))
# 显示结果
print()
print_colored(Colors.GREEN, "🎉 所有服务启动完成!")
print("=" * 30)
# 获取本机局域网 IP
import socket
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
lan_ip = s.getsockname()[0]
s.close()
except:
lan_ip = "本机IP"
print(f" 本地: http://127.0.0.1:5174 (前端) | http://127.0.0.1:8000 (后端)")
print(f" 局域网: http://{lan_ip}:5174 (前端) | http://{lan_ip}:8000 (后端)")
print(f" API 文档: {Colors.YELLOW}http://127.0.0.1:8000/docs{Colors.NC}")
print()
print("📝 日志文件:")
print(" 后端: logs/backend.log")
print(" 前端: logs/frontend.log")
print()
print_colored(Colors.YELLOW, "💡 按 Ctrl+C 停止所有服务,或运行: python stop.py")
# 等待中断信号
def signal_handler(sig, frame):
print_colored(Colors.YELLOW, "\n🛑 正在停止服务...")
backend_proc.terminate()
frontend_proc.terminate()
print_colored(Colors.GREEN, "🎉 所有服务已停止")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
# 保持运行
try:
backend_proc.wait()
except KeyboardInterrupt:
signal_handler(None, None)
if __name__ == "__main__":
main()