#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ YG_FT_Base 统一启动脚本 同时启动后端 API 服务和 Web 静态服务器 使用方法: python start_all.py start # 启动所有服务 python start_all.py stop # 停止所有服务 python start_all.py restart # 重启所有服务 python start_all.py status # 查看服务状态 """ import os import sys import yaml import signal import subprocess import time from pathlib import Path # 获取脚本所在目录 SCRIPT_DIR = Path(__file__).parent.absolute() CONFIG_FILE = SCRIPT_DIR / "config.yaml" WEB_DIR = SCRIPT_DIR / "web" # PID 文件路径 API_PID_FILE = SCRIPT_DIR / ".api.pid" WEB_PID_FILE = SCRIPT_DIR / ".web.pid" def load_config(): """加载配置文件""" if not CONFIG_FILE.exists(): print(f"❌ 配置文件不存在: {CONFIG_FILE}") sys.exit(1) with open(CONFIG_FILE, 'r', encoding='utf-8') as f: config = yaml.safe_load(f) app_config = config.get('app', {}) return { 'api_port': app_config.get('port', 7861), 'web_port': app_config.get('web_port', 7862) } def is_port_in_use(port): """检查端口是否被占用""" import socket with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex(('localhost', port)) == 0 def get_pid(pid_file): """获取 PID""" if pid_file.exists(): try: return int(pid_file.read_text().strip()) except: return None return None def is_process_running(pid): """检查进程是否在运行""" try: os.kill(pid, 0) return True except OSError: return False def kill_process(pid_file): """杀死进程""" pid = get_pid(pid_file) if pid and is_process_running(pid): try: os.kill(pid, signal.SIGTERM) time.sleep(0.5) # 如果还在运行,强制杀死 if is_process_running(pid): os.kill(pid, signal.SIGKILL) return True except OSError: pass pid_file.unlink(missing_ok=True) return False def check_virtual_env(): """检查虚拟环境是否存在""" venv_path = SCRIPT_DIR / "B_venv" if not venv_path.exists(): print("⚠️ 虚拟环境不存在,请先运行 create_venv.sh 创建") return False return True def start_api(): """启动后端 API 服务""" config = load_config() api_port = config['api_port'] print("🚀 启动后端 API 服务...") if not check_virtual_env(): return False if is_port_in_use(api_port): print(f"❌ 端口 {api_port} 已被占用,后端服务可能已在运行") return False venv_python = SCRIPT_DIR / "B_venv" / "bin" / "python" main_py = SCRIPT_DIR / "src" / "main.py" if not venv_python.exists(): venv_python = SCRIPT_DIR / "B_venv" / "Scripts" / "python.exe" # Windows if not main_py.exists(): print(f"❌ 找不到主程序文件: {main_py}") return False # 启动进程 env = os.environ.copy() env['PYTHONUNBUFFERED'] = '1' proc = subprocess.Popen( [str(venv_python), str(main_py)], cwd=str(SCRIPT_DIR), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, env=env ) # 写入 PID with open(API_PID_FILE, 'w') as f: f.write(str(proc.pid)) # 等待服务启动 time.sleep(2) if proc.poll() is not None: print("❌ 后端服务启动失败") API_PID_FILE.unlink(missing_ok=True) return False print(f"✅ 后端服务已启动 (PID: {proc.pid}, 端口: {api_port})") return True def start_web(): """启动 Web 静态服务器""" config = load_config() web_port = config['web_port'] print("🚀 启动 Web 静态服务器...") # 从项目根目录启动,这样 /lib/... 路径可以正确映射到 $PROJECT/lib/... web_root = SCRIPT_DIR if not web_root.exists(): print(f"❌ Web 目录不存在: {web_root}") return False if is_port_in_use(web_port): print(f"⚠️ 端口 {web_port} 已被占用,Web 服务可能已在运行") return False # 使用 python 启动简单 HTTP 服务器(从项目根目录) env = os.environ.copy() env['PYTHONUNBUFFERED'] = '1' proc = subprocess.Popen( [sys.executable, '-m', 'http.server', str(web_port)], cwd=str(web_root), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, env=env ) # 写入 PID with open(WEB_PID_FILE, 'w') as f: f.write(str(proc.pid)) time.sleep(1) if proc.poll() is not None: print("❌ Web 服务启动失败") WEB_PID_FILE.unlink(missing_ok=True) return False print(f"✅ Web 服务已启动 (PID: {proc.pid}, 端口: {web_port})") return True def stop_all(): """停止所有服务""" print("🛑 停止所有服务...") # 停止后端服务 if kill_process(API_PID_FILE): print("✅ 后端服务已停止") # 停止 Web 服务 if kill_process(WEB_PID_FILE): print("✅ Web 服务已停止") # 清理残留进程 subprocess.run(['pkill', '-f', 'src/main.py'], capture_output=True) config = load_config() subprocess.run(['pkill', '-f', f'http.server {config["web_port"]}'], capture_output=True) def show_status(): """显示服务状态""" config = load_config() api_port = config['api_port'] web_port = config['web_port'] print("\n📊 服务状态:") print("-" * 40) api_pid = get_pid(API_PID_FILE) if api_pid and is_process_running(api_pid): print(f"✅ 后端 API: 运行中 (PID: {api_pid}, 端口: {api_port})") else: print(f"❌ 后端 API: 未运行 (端口: {api_port})") web_pid = get_pid(WEB_PID_FILE) if web_pid and is_process_running(web_pid): print(f"✅ Web 服务: 运行中 (PID: {web_pid}, 端口: {web_port})") else: print(f"❌ Web 服务: 未运行 (端口: {web_port})") print("-" * 40) print("\n🌐 访问地址:") print(f" - 后端 API: http://localhost:{api_port}") print(f" - Web 页面: http://localhost:{web_port}/web/pages/main.html") print() def main(): if len(sys.argv) < 2: print("用法: python start_all.py {start|stop|restart|status}") print() print("命令:") print(" start - 启动所有服务") print(" stop - 停止所有服务") print(" restart - 重启所有服务") print(" status - 查看服务状态") sys.exit(1) command = sys.argv[1].lower() print("====================================") print("YG_FT_Base 统一启动脚本 (Python版)") print("====================================") print() config = load_config() print(f"📦 端口配置:") print(f" - 后端 API: {config['api_port']}") print(f" - Web 服务: {config['web_port']}") print() if command == 'start': start_api() start_web() print() print("====================================") print("所有服务已启动!") print("====================================") show_status() elif command == 'stop': stop_all() print("✅ 所有服务已停止") elif command == 'restart': stop_all() time.sleep(1) start_api() start_web() print() print("====================================") print("所有服务已重启!") print("====================================") show_status() elif command == 'status': show_status() else: print(f"❌ 未知命令: {command}") print("用法: python start_all.py {start|stop|restart|status}") sys.exit(1) if __name__ == '__main__': main()