#!/bin/bash # 统一启动脚本 - 一键启动前后端应用(优化版) # 作者: Qwen Assistant # 功能: 自动设置环境、启动后端和前端、管理进程、处理依赖和端口冲突 set -e # 遇到错误立即退出 # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' # No Color # 打印带颜色的消息 print_message() { local color=$1 local message=$2 echo -e "${color}${message}${NC}" } # 日志记录函数 log_message() { local level=$1 local message=$2 local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $message" | tee -a "startup.log" } # 检查端口是否被占用 check_port() { local port=$1 if lsof -i :$port > /dev/null 2>&1; then return 0 # 端口被占用 else return 1 # 端口未被占用 fi } # 生成随机可用端口 get_available_port() { local start_port=$1 local max_attempts=50 local port=$start_port local attempts=0 while check_port $port && [ $attempts -lt $max_attempts ]; do port=$((port + 1)) attempts=$((attempts + 1)) done if [ $attempts -eq $max_attempts ]; then print_message $RED "❌ 无法找到可用端口,请手动释放相关端口" exit 1 fi echo $port } # 检查依赖安装状态 check_dependencies() { local missing_deps=() # 检查 Python 依赖 if ! command -v python3 &> /dev/null; then missing_deps+=("python3") else if ! python3 -c "import fastapi, uvicorn, sqlalchemy, pydantic, jwt, bcrypt, pandas, fitz, dotenv" > /dev/null 2>&1; then missing_deps+=("python_backend_deps") fi fi # 检查 Node.js 依赖 if ! command -v node &> /dev/null; then missing_deps+=("nodejs") fi if ! command -v npm &> /dev/null; then missing_deps+=("npm") fi # 检查前端依赖 if [ -f "frontend/package.json" ] && [ ! -d "frontend/node_modules" ]; then missing_deps+=("frontend_deps") fi if [ ${#missing_deps[@]} -ne 0 ]; then print_message $YELLOW "⚠️ 检测到以下依赖缺失或未安装:" for dep in "${missing_deps[@]}"; do echo " - $dep" done return 1 fi return 0 } # 检查系统资源 check_system_resources() { print_message $BLUE "📊 系统资源检查:" if command -v free &> /dev/null; then # Linux system mem_available=$(free -m | awk 'NR==2{printf "%.1f", $7/1024}') disk_available=$(df . | awk 'NR==2{printf "%d", $4/1024}') print_message $CYAN " 可用内存: ${mem_available}GB" print_message $CYAN " 可用磁盘: ${disk_available}MB" if (( $(echo "$mem_available < 0.5" | bc -l 2>/dev/null || echo "0") )); then print_message $YELLOW "⚠️ 可用内存较少,可能影响启动性能" fi if [ $disk_available -lt 500 ]; then print_message $YELLOW "⚠️ 可用磁盘空间较少,可能影响应用运行" fi else # macOS system - use vm_stat for memory and df for disk # Memory check for macOS using vm_stat page_size=$(vm_stat | grep "page size" | awk '{print $6}' | sed 's/bytes//') free_pages=$(vm_stat | grep free | awk '{print $3}' | sed 's/\.//') inactive_pages=$(vm_stat | grep inactive | awk '{print $3}' | sed 's/\.//') free_memory=$(( (free_pages + inactive_pages) * page_size / 1024 / 1024 / 1024 )) # Disk space check for macOS disk_available=$(df . | awk 'NR==2{printf "%d", $4/1024}') # Convert to MB print_message $CYAN " 可用内存估算: ${free_memory}GB" print_message $CYAN " 可用磁盘: ${disk_available}MB" if [ $free_memory -lt 1 ]; then print_message $YELLOW "⚠️ 可用内存较少,可能影响启动性能" fi if [ $disk_available -lt 500 ]; then print_message $YELLOW "⚠️ 可用磁盘空间较少,可能影响应用运行" fi fi } print_message $BLUE "🚀 开始启动 WordMaster Pro 系统 (优化版)..." # 记录启动过程 log_message "INFO" "开始启动 WordMaster Pro 系统" # 获取脚本目录 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # 检查系统资源 check_system_resources # 检查必要命令 print_message $BLUE "🔍 检查系统环境..." if ! command -v python3 &> /dev/null; then log_message "ERROR" "Python3 未安装,请先安装 Python 3.8+" print_message $RED "❌ Python3 未安装,请先安装 Python 3.8+" exit 1 fi if ! command -v node &> /dev/null; then log_message "ERROR" "Node.js 未安装,请先安装 Node.js 16+" print_message $RED "❌ Node.js 未安装,请先安装 Node.js 16+" exit 1 fi if ! command -v npm &> /dev/null; then log_message "ERROR" "npm 未安装,请先安装 Node.js 16+ (包含npm)" print_message $RED "❌ npm 未安装,请先安装 Node.js 16+ (包含npm)" exit 1 fi if ! command -v lsof &> /dev/null; then log_message "ERROR" "lsof 未安装,无法检查端口占用" print_message $RED "❌ lsof 未安装,无法检查端口占用" exit 1 fi print_message $GREEN "✅ 系统环境检查通过" # 加载环境变量 ENV_FILE=".env" if [ -f "$ENV_FILE" ]; then print_message $BLUE "🔧 加载环境变量配置..." export $(grep -v '^#' "$ENV_FILE" | xargs) BACKEND_PORT=${BACKEND_PORT:-8888} FRONTEND_PORT=${FRONTEND_PORT:-3008} DATABASE_URL=${DATABASE_URL:-"sqlite:///./wordmaster.db"} else print_message $YELLOW "⚠️ 未找到 $ENV_FILE 文件,使用默认配置" BACKEND_PORT=8888 FRONTEND_PORT=3008 DATABASE_URL="sqlite:///./wordmaster.db" fi print_message $BLUE "🔧 使用配置信息:" print_message $GREEN " 后端端口: $BACKEND_PORT" print_message $GREEN " 前端端口: $FRONTEND_PORT" print_message $GREEN " 数据库: $DATABASE_URL" # 检查并清理之前的进程 print_message $BLUE "🧹 检查并清理之前的进程..." if [ -f ".backend.pid" ]; then OLD_BACKEND_PID=$(cat .backend.pid) if ps -p $OLD_BACKEND_PID > /dev/null 2>&1; then print_message $YELLOW "⚠️ 发现已存在的后端进程 $OLD_BACKEND_PID,正在停止..." kill $OLD_BACKEND_PID 2>/dev/null || true sleep 2 # 等待进程终止 attempts=0 while ps -p $OLD_BACKEND_PID > /dev/null 2>&1 && [ $attempts -lt 10 ]; do sleep 1 attempts=$((attempts + 1)) done # 如果仍未终止,强制杀死 if ps -p $OLD_BACKEND_PID > /dev/null 2>&1; then print_message $YELLOW "⏳ 强制终止后端进程..." kill -9 $OLD_BACKEND_PID 2>/dev/null || true fi fi rm -f .backend.pid fi if [ -f ".frontend.pid" ]; then OLD_FRONTEND_PID=$(cat .frontend.pid) if ps -p $OLD_FRONTEND_PID > /dev/null 2>&1; then print_message $YELLOW "⚠️ 发现已存在的前端进程 $OLD_FRONTEND_PID,正在停止..." kill $OLD_FRONTEND_PID 2>/dev/null || true sleep 2 # 等待进程终止 attempts=0 while ps -p $OLD_FRONTEND_PID > /dev/null 2>&1 && [ $attempts -lt 10 ]; do sleep 1 attempts=$((attempts + 1)) done # 如果仍未终止,强制杀死 if ps -p $OLD_FRONTEND_PID > /dev/null 2>&1; then print_message $YELLOW "⏳ 强制终止前端进程..." kill -9 $OLD_FRONTEND_PID 2>/dev/null || true fi fi rm -f .frontend.pid fi # 检查端口占用情况 print_message $BLUE "🔍 检查端口占用情况..." if check_port $BACKEND_PORT; then print_message $YELLOW "⚠️ 后端端口 $BACKEND_PORT 被占用,寻找可用端口..." BACKEND_PORT=$(get_available_port 8888) print_message $GREEN "ℹ️ 使用后端端口: $BACKEND_PORT" fi if check_port $FRONTEND_PORT; then print_message $YELLOW "⚠️ 前端端口 $FRONTEND_PORT 被占用,寻找可用端口..." FRONTEND_PORT=$(get_available_port 3008) print_message $GREEN "ℹ️ 使用前端端口: $FRONTEND_PORT" fi # 检查依赖状态 print_message $BLUE "🔍 检查依赖安装状态..." if ! check_dependencies; then print_message $YELLOW "⚠️ 检测到依赖缺失,正在尝试自动安装..." # 检查并创建后端虚拟环境 if [ ! -d "backend/venv" ]; then print_message $BLUE "🔧 创建后端虚拟环境..." python3 -m venv backend/venv print_message $GREEN "✅ 虚拟环境创建成功" fi # 激活虚拟环境 source backend/venv/bin/activate # 检查并安装后端依赖 if ! python -c "import fastapi, uvicorn, sqlalchemy, pydantic, jwt, bcrypt, pandas, fitz, dotenv" > /dev/null 2>&1; then print_message $BLUE "🔧 安装后端依赖..." cd backend if [ ! -f "requirements.txt" ]; then print_message $RED "❌ requirements.txt 不存在" exit 1 fi pip install --upgrade pip if ! pip install -r requirements.txt; then print_message $RED "❌ 后端依赖安装失败" exit 1 fi cd .. print_message $GREEN "✅ 后端依赖安装完成" fi # 检查并安装前端依赖 if [ -f "frontend/package.json" ] && [ ! -d "frontend/node_modules" ]; then print_message $BLUE "🔧 安装前端依赖..." cd frontend if ! npm install; then print_message $RED "❌ 前端依赖安装失败" exit 1 fi cd .. print_message $GREEN "✅ 前端依赖安装完成" fi else print_message $GREEN "✅ 依赖检查通过" fi # 激活后端虚拟环境 print_message $BLUE "🔧 激活后端虚拟环境..." source backend/venv/bin/activate # 启动后端 print_message $BLUE "📡 启动后端服务 (端口: $BACKEND_PORT)..." cd backend # 清理旧日志文件(保留最近的) if [ -f "backend.log" ]; then mv backend.log backend.log.prev 2>/dev/null || true fi # 启动后端服务 nohup uvicorn main:app --host 0.0.0.0 --port $BACKEND_PORT --log-level info > backend.log 2>&1 & BACKEND_PID=$! echo $BACKEND_PID > ../.backend.pid print_message $GREEN "✅ 后端已启动 (PID: $BACKEND_PID, 端口: $BACKEND_PORT)" log_message "INFO" "后端服务启动成功 (PID: $BACKEND_PID, 端口: $BACKEND_PORT)" # 等待后端启动并验证 print_message $BLUE "⏳ 验证后端服务启动状态..." backend_attempts=0 backend_ready=false while [ $backend_attempts -lt 30 ]; do # 最多等待30秒 if kill -0 $BACKEND_PID 2>/dev/null; then if curl -s --max-time 5 "http://localhost:$BACKEND_PORT/docs" > /dev/null; then print_message $GREEN "✅ 后端API服务运行正常" backend_ready=true break else print_message $CYAN "⏳ 等待后端服务完全启动... ($backend_attempts/30)" fi else print_message $RED "❌ 后端进程已终止" break fi sleep 1 backend_attempts=$((backend_attempts + 1)) done if [ "$backend_ready" = false ]; then print_message $RED "❌ 后端服务启动超时或无法访问" log_message "ERROR" "后端服务启动失败" # 检查后端日志 if [ -f "backend.log" ]; then print_message $RED "📋 后端错误日志最后10行:" tail -10 backend.log fi exit 1 fi # 启动前端 print_message $BLUE "🎨 启动前端服务 (端口: $FRONTEND_PORT)..." cd ../frontend # 清理旧日志文件(保留最近的) if [ -f "frontend.log" ]; then mv frontend.log frontend.log.prev 2>/dev/null || true fi # 确保环境变量文件包含正确的端口 if [ -f ".env.development" ]; then # 更新 .env.development with the correct backend port sed -i.bak "s|VITE_BACKEND_PORT=.*|VITE_BACKEND_PORT=$BACKEND_PORT|" .env.development 2>/dev/null || true sed -i.bak "s|VITE_SERVER_PORT=.*|VITE_SERVER_PORT=$FRONTEND_PORT|" .env.development 2>/dev/null || true else # Create .env.development if it doesn't exist cat > .env.development << EOF # 开发环境配置 NODE_ENV=development VITE_MODE=development # API配置 - 开发环境使用代理 VITE_API_BASE_URL=/api VITE_FILE_BASE_URL=/api VITE_WS_BASE_URL=ws://localhost:$BACKEND_PORT/ws # 后端服务地址 (仅用于代理配置) VITE_BACKEND_HOST=localhost VITE_BACKEND_PORT=$BACKEND_PORT # 调试配置 VITE_DEBUG=true VITE_LOG_LEVEL=info VITE_SERVER_PORT=$FRONTEND_PORT EOF print_message $GREEN "✅ 创建了前端环境配置文件" fi # 启动前端 - 使用指定端口 if command -v timeout >/dev/null 2>&1; then # timeout 命令可用 timeout 60s npm run dev -- --port $FRONTEND_PORT > ../frontend.log 2>&1 & else # timeout 命令不可用,直接启动 npm run dev -- --port $FRONTEND_PORT > ../frontend.log 2>&1 & fi FRONTEND_PID=$! echo $FRONTEND_PID > ../.frontend.pid print_message $GREEN "✅ 前端已启动 (PID: $FRONTEND_PID, 端口: $FRONTEND_PORT)" log_message "INFO" "前端服务启动成功 (PID: $FRONTEND_PID, 端口: $FRONTEND_PORT)" # 等待前端启动并验证 print_message $BLUE "⏳ 验证前端服务启动状态..." frontend_attempts=0 frontend_ready=false while [ $frontend_attempts -lt 45 ]; do # 最多等待45秒,因为前端启动通常需要更多时间 if kill -0 $FRONTEND_PID 2>/dev/null; then # 尝试访问前端服务 if curl -s --max-time 5 "http://localhost:$FRONTEND_PORT" > /dev/null; then print_message $GREEN "✅ 前端服务运行正常" frontend_ready=true break else print_message $CYAN "⏳ 等待前端服务完全启动... ($frontend_attempts/45)" fi else print_message $YELLOW "⚠️ 前端进程可能已重启或仍在启动中... ($frontend_attempts/45)" fi sleep 1 frontend_attempts=$((frontend_attempts + 1)) done if [ "$frontend_ready" = false ]; then print_message $YELLOW "⚠️ 前端服务可能需要更多时间启动(后台继续运行)" log_message "WARNING" "前端服务启动时间较长" else log_message "INFO" "前端服务启动完成" fi # 显示启动完成信息 print_message $GREEN "" print_message $GREEN "🎉 WordMaster Pro 系统启动成功!" print_message $GREEN "" echo "" print_message $BLUE "📝 访问地址:" print_message $GREEN "📖 前端访问地址: http://localhost:$FRONTEND_PORT" print_message $GREEN "🔧 后端 API 地址: http://localhost:$BACKEND_PORT" print_message $GREEN "📚 API 文档: http://localhost:$BACKEND_PORT/docs" print_message $GREEN "🌐 支持远程访问 (通过服务器IP:端口访问)" echo "" print_message $BLUE "📋 进程信息:" print_message $GREEN "📡 后端进程 PID: $(cat .backend.pid)" print_message $GREEN "🎨 前端进程 PID: $(cat .frontend.pid)" echo "" print_message $BLUE "📁 日志文件:" print_message $CYAN " 后端日志: $(pwd)/backend/backend.log" print_message $CYAN " 前端日志: $(pwd)/frontend/frontend.log" print_message $CYAN " 启动日志: $(pwd)/startup.log" echo "" print_message $YELLOW "💡 提示:" print_message $YELLOW " - 使用 ./stop_app.sh 停止应用程序" print_message $YELLOW " - 使用 ./status_app.sh 检查应用程序状态" print_message $YELLOW " - 实时查看后端日志: tail -f backend/backend.log" print_message $YELLOW " - 实时查看前端日志: tail -f frontend/frontend.log" print_message $YELLOW " - 如遇问题,请查看日志并重新启动" echo "" print_message $GREEN "📋 要实时监控日志,可使用以下命令:" print_message $CYAN " 后端实时日志: tail -f backend/backend.log" print_message $CYAN " 前端实时日志: tail -f frontend/frontend.log" print_message $CYAN " 同时查看: tail -f backend/backend.log frontend/frontend.log" log_message "INFO" "WordMaster Pro 系统启动完成" print_message $GREEN "✅ 应用程序已在后台稳定运行!"