305 lines
7.9 KiB
Python
305 lines
7.9 KiB
Python
#!/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()
|