| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- #!/bin/bash
- # ============================================
- # 叁强鲜配 Backend 启动脚本
- # ============================================
- set -e
- # 颜色定义
- RED='\033[0;31m'
- GREEN='\033[0;32m'
- YELLOW='\033[1;33m'
- BLUE='\033[0;34m'
- NC='\033[0m' # No Color
- # 默认配置
- HOST="0.0.0.0"
- PORT=8000
- WORKERS=1
- RELOAD=false
- LOG_LEVEL="info"
- # 脚本目录
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- BACKEND_DIR="$SCRIPT_DIR"
- # 虚拟环境目录
- VENV_DIR="$BACKEND_DIR/venv"
- PIP="$VENV_DIR/bin/pip"
- PYTHON="$VENV_DIR/bin/python"
- UVICORN="$VENV_DIR/bin/uvicorn"
- # 日志文件
- LOG_FILE="$BACKEND_DIR/logs/app.log"
- PID_FILE="$BACKEND_DIR/app.pid"
- # ============================================
- # 函数定义
- # ============================================
- log_info() {
- echo -e "${GREEN}[INFO]${NC} $1"
- }
- log_warn() {
- echo -e "${YELLOW}[WARN]${NC} $1"
- }
- log_error() {
- echo -e "${RED}[ERROR]${NC} $1"
- }
- log_success() {
- echo -e "${GREEN}[SUCCESS]${NC} $1"
- }
- # 显示帮助信息
- show_help() {
- cat << EOF
- 叁强鲜配 Backend 启动脚本
- 用法: $0 [选项]
- 选项:
- -h, --host <ip> 监听地址 (默认: 0.0.0.0)
- -p, --port <port> 监听端口 (默认: 8000)
- -w, --workers <num> 工作进程数 (默认: 1)
- -r, --reload 开发模式, 自动重载
- -l, --log-level 日志级别: debug/info/warning/error (默认: info)
- --install 安装依赖
- --init 初始化环境
- --stop 停止服务
- --restart 重启服务
- --status 查看服务状态
- --logs 查看日志
- --help 显示帮助信息
- 示例:
- $0 --port 8080 --reload # 开发模式, 端口 8080
- $0 --install # 安装依赖
- $0 --stop # 停止服务
- $0 --logs # 查看日志
- EOF
- }
- # 检查 Python 是否安装
- check_python() {
- if ! command -v python3 &> /dev/null; then
- log_error "Python3 未安装, 请先安装 Python 3.10+"
- exit 1
- fi
- PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
- log_info "Python 版本: $PYTHON_VERSION"
- }
- # 创建虚拟环境
- create_venv() {
- log_info "创建虚拟环境..."
- python3 -m venv "$VENV_DIR"
- log_success "虚拟环境创建完成: $VENV_DIR"
- }
- # 安装依赖
- install_dependencies() {
- log_info "安装依赖..."
- # 检查虚拟环境
- if [ ! -d "$VENV_DIR" ]; then
- create_venv
- fi
- # 激活虚拟环境
- source "$VENV_DIR/bin/activate"
- # 升级 pip
- pip install --upgrade pip
- # 安装依赖
- pip install -r "$BACKEND_DIR/requirements.txt"
- log_success "依赖安装完成"
- }
- # 检查环境
- check_env() {
- # 检查虚拟环境
- if [ ! -d "$VENV_DIR" ]; then
- log_warn "虚拟环境不存在, 正在创建..."
- create_venv
- install_dependencies
- fi
- # 检查依赖
- if ! "$PYTHON" -c "import fastapi" &> /dev/null; then
- log_warn "依赖未安装, 正在安装..."
- install_dependencies
- fi
- # 检查 .env 文件
- if [ ! -f "$BACKEND_DIR/.env" ]; then
- if [ -f "$BACKEND_DIR/.env.example" ]; then
- log_warn ".env 文件不存在, 复制 .env.example 作为模板"
- cp "$BACKEND_DIR/.env.example" "$BACKEND_DIR/.env"
- log_warn "请编辑 $BACKEND_DIR/.env 配置文件"
- else
- log_error ".env 文件不存在且无 .env.example 模板"
- fi
- fi
- }
- # 创建日志目录
- setup_logs() {
- if [ ! -d "$BACKEND_DIR/logs" ]; then
- mkdir -p "$BACKEND_DIR/logs"
- log_info "创建日志目录: $BACKEND_DIR/logs"
- fi
- }
- # 启动服务
- start_server() {
- # 检查服务是否已运行
- if [ -f "$PID_FILE" ]; then
- PID=$(cat "$PID_FILE")
- if ps -p "$PID" &> /dev/null; then
- log_error "服务已在运行 (PID: $PID), 请先停止后再启动"
- exit 1
- else
- log_warn "发现旧的 PID 文件, 已清理"
- rm -f "$PID_FILE"
- fi
- fi
- # 激活虚拟环境
- source "$VENV_DIR/bin/activate"
- # 构建 uvicorn 命令
- UVICORN_CMD="uvicorn app.main:app"
- UVICORN_CMD="$UVICORN_CMD --host $HOST"
- UVICORN_CMD="$UVICORN_CMD --port $PORT"
- UVICORN_CMD="$UVICORN_CMD --log-level $LOG_LEVEL"
- if [ "$RELOAD" = true ]; then
- UVICORN_CMD="$UVICORN_CMD --reload"
- log_info "启动模式: 开发模式 (自动重载)"
- else
- UVICORN_CMD="$UVICORN_CMD --workers $WORKERS"
- log_info "启动模式: 生产模式 (Workers: $WORKERS)"
- fi
- # 创建日志目录
- setup_logs
- # 切换到后端目录
- cd "$BACKEND_DIR"
- # 记录启动信息
- echo "========================================" >> "$LOG_FILE"
- echo "启动时间: $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE"
- echo "启动参数: $UVICORN_CMD" >> "$LOG_FILE"
- echo "========================================" >> "$LOG_FILE"
- # 启动服务
- log_info "=========================================="
- log_info " 叁强鲜配 Backend 启动中..."
- log_info "=========================================="
- log_info " 监听地址: $HOST:$PORT"
- log_info " 日志级别: $LOG_LEVEL"
- log_info " 日志文件: $LOG_FILE"
- log_info " 文档地址: http://$HOST:$PORT/docs"
- log_info "=========================================="
- # 后台运行并保存 PID
- nohup $UVICORN_CMD >> "$LOG_FILE" 2>&1 &
- echo $! > "$PID_FILE"
- # 等待服务启动
- sleep 2
- # 检查服务是否启动成功
- if ps -p $(cat "$PID_FILE") &> /dev/null; then
- log_success "服务启动成功 (PID: $(cat "$PID_FILE"))"
- log_info "访问地址: http://localhost:$PORT"
- log_info "API 文档: http://localhost:$PORT/docs"
- else
- log_error "服务启动失败, 请查看日志: $LOG_FILE"
- rm -f "$PID_FILE"
- exit 1
- fi
- }
- # 停止服务
- stop_server() {
- if [ ! -f "$PID_FILE" ]; then
- log_error "PID 文件不存在, 服务可能未运行"
- exit 1
- fi
- PID=$(cat "$PID_FILE")
- if ! ps -p "$PID" &> /dev/null; then
- log_warn "服务未在运行 (PID: $PID)"
- rm -f "$PID_FILE"
- exit 0
- fi
- log_info "正在停止服务 (PID: $PID)..."
- # 尝试优雅关闭
- kill -TERM "$PID" 2>/dev/null
- # 等待进程退出
- for i in {1..10}; do
- if ! ps -p "$PID" &> /dev/null; then
- break
- fi
- sleep 1
- done
- # 强制关闭
- if ps -p "$PID" &> /dev/null; then
- log_warn "优雅关闭超时, 强制关闭..."
- kill -9 "$PID" 2>/dev/null
- fi
- rm -f "$PID_FILE"
- log_success "服务已停止"
- }
- # 查看状态
- show_status() {
- if [ ! -f "$PID_FILE" ]; then
- echo -e "${YELLOW}[WARN]${NC} 服务未运行 (无 PID 文件)"
- exit 0
- fi
- PID=$(cat "$PID_FILE")
- if ps -p "$PID" &> /dev/null; then
- echo -e "${GREEN}[RUNNING]${NC} 服务运行中 (PID: $PID)"
- # 获取端口信息
- if command -v lsof &> /dev/null; then
- PORT_INFO=$(lsof -i :$PORT 2>/dev/null | grep LISTEN | head -1)
- if [ -n "$PORT_INFO" ]; then
- echo ""
- echo "端口信息:"
- lsof -i :$PORT 2>/dev/null | grep LISTEN | head -5
- fi
- fi
- else
- echo -e "${RED}[STOPPED]${NC} 服务已停止"
- rm -f "$PID_FILE"
- fi
- }
- # 查看日志
- show_logs() {
- if [ ! -f "$LOG_FILE" ]; then
- log_error "日志文件不存在: $LOG_FILE"
- exit 1
- fi
- if [ "$1" = "-f" ] || [ "$1" = "--follow" ]; then
- log_info "实时查看日志 (Ctrl+C 退出)..."
- tail -f "$LOG_FILE"
- else
- echo -e "${BLUE}=== 最近 100 行日志 ===${NC}"
- tail -100 "$LOG_FILE"
- fi
- }
- # 重启服务
- restart_server() {
- log_info "重启服务..."
- stop_server
- sleep 2
- start_server
- }
- # 初始化环境
- init_env() {
- check_python
- check_env
- log_success "环境初始化完成"
- }
- # ============================================
- # 命令行参数解析
- # ============================================
- while [[ $# -gt 0 ]]; do
- case $1 in
- -h|--host)
- HOST="$2"
- shift 2
- ;;
- -p|--port)
- PORT="$2"
- shift 2
- ;;
- -w|--workers)
- WORKERS="$2"
- shift 2
- ;;
- -r|--reload)
- RELOAD=true
- shift
- ;;
- -l|--log-level)
- LOG_LEVEL="$2"
- shift 2
- ;;
- --install)
- check_python
- install_dependencies
- exit 0
- ;;
- --init)
- init_env
- exit 0
- ;;
- --stop)
- stop_server
- exit 0
- ;;
- --restart)
- restart_server
- exit 0
- ;;
- --status)
- show_status
- exit 0
- ;;
- --logs)
- show_logs "${2:-}"
- exit 0
- ;;
- --help)
- show_help
- exit 0
- ;;
- *)
- log_error "未知参数: $1"
- show_help
- exit 1
- ;;
- esac
- done
- # 默认启动
- check_python
- check_env
- start_server
|