528 lines
17 KiB
Bash
Executable File
528 lines
17 KiB
Bash
Executable File
#!/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, requests, docx, PIL" > /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" | sed 's/#.*$//' | sed '/^\s*$/d' | 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 被占用,正在停止占用进程..."
|
|
OCCUPIED_PIDS=$(lsof -ti:$BACKEND_PORT)
|
|
for pid in $OCCUPIED_PIDS; do
|
|
print_message $YELLOW " 停止进程 $pid..."
|
|
kill -9 $pid 2>/dev/null || true
|
|
done
|
|
sleep 1
|
|
print_message $GREEN "✅ 后端端口 $BACKEND_PORT 已释放"
|
|
fi
|
|
|
|
if check_port $FRONTEND_PORT; then
|
|
print_message $YELLOW "⚠️ 前端端口 $FRONTEND_PORT 被占用,正在停止占用进程..."
|
|
OCCUPIED_PIDS=$(lsof -ti:$FRONTEND_PORT)
|
|
for pid in $OCCUPIED_PIDS; do
|
|
print_message $YELLOW " 停止进程 $pid..."
|
|
kill -9 $pid 2>/dev/null || true
|
|
done
|
|
sleep 1
|
|
print_message $GREEN "✅ 前端端口 $FRONTEND_PORT 已释放"
|
|
fi
|
|
|
|
# 后端虚拟环境检测和复用
|
|
print_message $BLUE "🔍 检查后端虚拟环境..."
|
|
if [ ! -d "backend/venv" ]; then
|
|
print_message $BLUE "🔧 创建后端虚拟环境 (使用 Python 3.12)..."
|
|
cd backend
|
|
# 优先使用 Python 3.12,如果不存在则使用 python3
|
|
if command -v python3.12 &> /dev/null; then
|
|
python3.12 -m venv venv
|
|
print_message $GREEN "✅ 使用 Python 3.12 创建虚拟环境"
|
|
else
|
|
python3 -m venv venv
|
|
print_message $YELLOW "⚠️ Python 3.12 未找到,使用默认 python3"
|
|
fi
|
|
cd ..
|
|
print_message $GREEN "✅ 虚拟环境创建成功"
|
|
else
|
|
print_message $GREEN "✅ 发现现有虚拟环境,将复用"
|
|
fi
|
|
|
|
# 激活后端虚拟环境
|
|
print_message $BLUE "🔧 激活后端虚拟环境..."
|
|
source backend/venv/bin/activate
|
|
|
|
# 检查并安装后端依赖(强制检查所有依赖)
|
|
print_message $BLUE "🔍 检查后端依赖完整性..."
|
|
cd backend
|
|
if [ ! -f "requirements.txt" ]; then
|
|
print_message $RED "❌ requirements.txt 不存在"
|
|
exit 1
|
|
fi
|
|
|
|
# 检查是否所有依赖都已安装
|
|
DEPS_MISSING=false
|
|
if ! python -c "import fastapi, uvicorn, sqlalchemy, pydantic, jwt, bcrypt, pandas, pdfplumber, fitz, dotenv, psycopg2, multipart, requests, docx, PIL" > /dev/null 2>&1; then
|
|
DEPS_MISSING=true
|
|
fi
|
|
|
|
if [ "$DEPS_MISSING" = true ]; then
|
|
print_message $BLUE "🔧 安装/更新后端依赖..."
|
|
pip install --upgrade pip -q
|
|
if ! pip install -r requirements.txt; then
|
|
print_message $RED "❌ 后端依赖安装失败"
|
|
exit 1
|
|
fi
|
|
print_message $GREEN "✅ 后端依赖安装完成"
|
|
else
|
|
print_message $GREEN "✅ 后端依赖检查通过"
|
|
fi
|
|
cd ..
|
|
|
|
# 检查并安装前端依赖
|
|
print_message $BLUE "🔍 检查前端依赖..."
|
|
FRONTEND_DEPS_MISSING=false
|
|
|
|
if [ ! -d "frontend/node_modules" ]; then
|
|
FRONTEND_DEPS_MISSING=true
|
|
else
|
|
# 检查关键依赖是否存在
|
|
cd frontend
|
|
if [ ! -d "node_modules/echarts" ] || \
|
|
[ ! -d "node_modules/vue" ] || \
|
|
[ ! -d "node_modules/element-plus" ] || \
|
|
[ ! -d "node_modules/pinia" ] || \
|
|
[ ! -d "node_modules/vue-router" ]; then
|
|
FRONTEND_DEPS_MISSING=true
|
|
fi
|
|
cd ..
|
|
fi
|
|
|
|
if [ "$FRONTEND_DEPS_MISSING" = true ]; then
|
|
print_message $BLUE "🔧 安装/更新前端依赖..."
|
|
cd frontend
|
|
if ! npm install; then
|
|
print_message $RED "❌ 前端依赖安装失败"
|
|
exit 1
|
|
fi
|
|
cd ..
|
|
print_message $GREEN "✅ 前端依赖安装完成"
|
|
else
|
|
print_message $GREEN "✅ 前端依赖检查通过"
|
|
fi
|
|
|
|
# 启动后端
|
|
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 "✅ 应用程序已在后台稳定运行!" |