|
|
@@ -0,0 +1,313 @@
|
|
|
+#!/bin/bash
|
|
|
+
|
|
|
+# ============================================
|
|
|
+# 安建医院纪检反馈系统 Backend 启动脚本
|
|
|
+# 用法: ./start.sh [选项]
|
|
|
+# ============================================
|
|
|
+
|
|
|
+set -euo pipefail
|
|
|
+
|
|
|
+RED='\033[0;31m'
|
|
|
+GREEN='\033[0;32m'
|
|
|
+YELLOW='\033[1;33m'
|
|
|
+BLUE='\033[0;34m'
|
|
|
+NC='\033[0m'
|
|
|
+
|
|
|
+HOST="${HOST:-0.0.0.0}"
|
|
|
+PORT="${PORT:-8000}"
|
|
|
+WORKERS="${WORKERS:-1}"
|
|
|
+RELOAD=false
|
|
|
+LOG_LEVEL="${LOG_LEVEL:-info}"
|
|
|
+FOREGROUND=false
|
|
|
+
|
|
|
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
+BACKEND_DIR="$SCRIPT_DIR"
|
|
|
+VENV_DIR="$BACKEND_DIR/venv"
|
|
|
+PYTHON="$VENV_DIR/bin/python"
|
|
|
+PIP="$VENV_DIR/bin/pip"
|
|
|
+UVICORN="$VENV_DIR/bin/uvicorn"
|
|
|
+LOG_DIR="$BACKEND_DIR/logs"
|
|
|
+LOG_FILE="$LOG_DIR/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 <n> 工作进程数 (默认: 1;与 --reload 互斥)
|
|
|
+ -r, --reload 开发模式,代码变更自动重载(前台运行)
|
|
|
+ -f, --foreground 前台运行(不加则后台 + PID/日志)
|
|
|
+ -l, --log-level <lv> 日志级别: debug/info/warning/error (默认: info)
|
|
|
+ --install 创建 venv 并安装 requirements.txt
|
|
|
+ --init 检查 Python / venv / .env
|
|
|
+ --stop 停止后台服务
|
|
|
+ --restart 重启后台服务
|
|
|
+ --status 查看服务状态
|
|
|
+ --logs [-f] 查看日志(-f 跟踪)
|
|
|
+ --help 显示帮助
|
|
|
+
|
|
|
+示例:
|
|
|
+ $0 --reload # 本地开发(前台 + 热重载)
|
|
|
+ $0 # 后台启动
|
|
|
+ $0 --port 8001 --workers 2 # 指定端口与进程数
|
|
|
+ $0 --stop
|
|
|
+ $0 --logs -f
|
|
|
+EOF
|
|
|
+}
|
|
|
+
|
|
|
+check_python() {
|
|
|
+ if ! command -v python3 >/dev/null 2>&1; then
|
|
|
+ log_error "未找到 python3,请先安装 Python 3.9+"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+ local ver
|
|
|
+ ver="$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')"
|
|
|
+ log_info "Python 版本: $ver"
|
|
|
+}
|
|
|
+
|
|
|
+create_venv() {
|
|
|
+ log_info "创建虚拟环境: $VENV_DIR"
|
|
|
+ python3 -m venv "$VENV_DIR"
|
|
|
+ log_success "虚拟环境已创建"
|
|
|
+}
|
|
|
+
|
|
|
+install_dependencies() {
|
|
|
+ check_python
|
|
|
+ if [ ! -d "$VENV_DIR" ]; then
|
|
|
+ create_venv
|
|
|
+ fi
|
|
|
+ # shellcheck disable=SC1091
|
|
|
+ source "$VENV_DIR/bin/activate"
|
|
|
+ log_info "升级 pip..."
|
|
|
+ "$PIP" install --upgrade pip
|
|
|
+ log_info "安装依赖..."
|
|
|
+ "$PIP" install -r "$BACKEND_DIR/requirements.txt"
|
|
|
+ log_success "依赖安装完成"
|
|
|
+}
|
|
|
+
|
|
|
+ensure_env_file() {
|
|
|
+ if [ -f "$BACKEND_DIR/.env" ]; then
|
|
|
+ return 0
|
|
|
+ fi
|
|
|
+ if [ -f "$BACKEND_DIR/.env.example" ]; then
|
|
|
+ cp "$BACKEND_DIR/.env.example" "$BACKEND_DIR/.env"
|
|
|
+ log_warn "已从 .env.example 生成 .env,请按环境修改数据库与 MinIO 配置"
|
|
|
+ else
|
|
|
+ log_error "缺少 .env 与 .env.example"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+}
|
|
|
+
|
|
|
+check_env() {
|
|
|
+ if [ ! -d "$VENV_DIR" ]; then
|
|
|
+ log_warn "虚拟环境不存在,正在创建并安装依赖..."
|
|
|
+ install_dependencies
|
|
|
+ fi
|
|
|
+ if ! "$PYTHON" -c "import fastapi" >/dev/null 2>&1; then
|
|
|
+ log_warn "依赖不完整,正在安装..."
|
|
|
+ install_dependencies
|
|
|
+ fi
|
|
|
+ if [ ! -x "$UVICORN" ]; then
|
|
|
+ log_error "未找到 uvicorn: $UVICORN"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+ ensure_env_file
|
|
|
+}
|
|
|
+
|
|
|
+setup_logs() {
|
|
|
+ mkdir -p "$LOG_DIR"
|
|
|
+}
|
|
|
+
|
|
|
+is_running() {
|
|
|
+ if [ ! -f "$PID_FILE" ]; then
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+ local pid
|
|
|
+ pid="$(cat "$PID_FILE")"
|
|
|
+ if ps -p "$pid" >/dev/null 2>&1; then
|
|
|
+ return 0
|
|
|
+ fi
|
|
|
+ rm -f "$PID_FILE"
|
|
|
+ return 1
|
|
|
+}
|
|
|
+
|
|
|
+start_server() {
|
|
|
+ check_python
|
|
|
+ check_env
|
|
|
+ cd "$BACKEND_DIR"
|
|
|
+ # shellcheck disable=SC1091
|
|
|
+ source "$VENV_DIR/bin/activate"
|
|
|
+
|
|
|
+ if [ "$RELOAD" = true ]; then
|
|
|
+ FOREGROUND=true
|
|
|
+ fi
|
|
|
+
|
|
|
+ if [ "$FOREGROUND" != true ] && is_running; then
|
|
|
+ log_error "服务已在运行 (PID: $(cat "$PID_FILE")),请先 ./start.sh --stop"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ local cmd=("$UVICORN" "app.main:app" "--host" "$HOST" "--port" "$PORT" "--log-level" "$LOG_LEVEL")
|
|
|
+ if [ "$RELOAD" = true ]; then
|
|
|
+ cmd+=(--reload)
|
|
|
+ log_info "启动模式: 开发(热重载,前台)"
|
|
|
+ else
|
|
|
+ cmd+=(--workers "$WORKERS")
|
|
|
+ log_info "启动模式: 后台(workers=$WORKERS)"
|
|
|
+ fi
|
|
|
+
|
|
|
+ log_info "=========================================="
|
|
|
+ log_info " ajyy Backend 启动中"
|
|
|
+ log_info " 地址: http://${HOST}:${PORT}"
|
|
|
+ log_info " 文档: http://127.0.0.1:${PORT}/docs"
|
|
|
+ log_info " 健康: http://127.0.0.1:${PORT}/api/health"
|
|
|
+ log_info "=========================================="
|
|
|
+
|
|
|
+ if [ "$FOREGROUND" = true ]; then
|
|
|
+ exec "${cmd[@]}"
|
|
|
+ fi
|
|
|
+
|
|
|
+ setup_logs
|
|
|
+ {
|
|
|
+ echo "========================================"
|
|
|
+ echo "启动时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
+ echo "命令: ${cmd[*]}"
|
|
|
+ echo "========================================"
|
|
|
+ } >>"$LOG_FILE"
|
|
|
+
|
|
|
+ nohup "${cmd[@]}" >>"$LOG_FILE" 2>&1 &
|
|
|
+ echo $! >"$PID_FILE"
|
|
|
+ sleep 2
|
|
|
+
|
|
|
+ if is_running; then
|
|
|
+ log_success "服务启动成功 (PID: $(cat "$PID_FILE"))"
|
|
|
+ log_info "日志: $LOG_FILE"
|
|
|
+ else
|
|
|
+ log_error "启动失败,请查看日志: $LOG_FILE"
|
|
|
+ rm -f "$PID_FILE"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+}
|
|
|
+
|
|
|
+stop_server() {
|
|
|
+ if [ ! -f "$PID_FILE" ]; then
|
|
|
+ log_warn "PID 文件不存在,服务可能未运行"
|
|
|
+ exit 0
|
|
|
+ fi
|
|
|
+ local pid
|
|
|
+ pid="$(cat "$PID_FILE")"
|
|
|
+ if ! ps -p "$pid" >/dev/null 2>&1; then
|
|
|
+ log_warn "进程不存在 (PID: $pid),清理 PID 文件"
|
|
|
+ rm -f "$PID_FILE"
|
|
|
+ exit 0
|
|
|
+ fi
|
|
|
+
|
|
|
+ log_info "停止服务 (PID: $pid)..."
|
|
|
+ kill -TERM "$pid" 2>/dev/null || true
|
|
|
+ for _ in $(seq 1 10); do
|
|
|
+ if ! ps -p "$pid" >/dev/null 2>&1; then
|
|
|
+ break
|
|
|
+ fi
|
|
|
+ sleep 1
|
|
|
+ done
|
|
|
+ if ps -p "$pid" >/dev/null 2>&1; then
|
|
|
+ log_warn "优雅退出超时,强制结束"
|
|
|
+ kill -9 "$pid" 2>/dev/null || true
|
|
|
+ fi
|
|
|
+ rm -f "$PID_FILE"
|
|
|
+ log_success "服务已停止"
|
|
|
+}
|
|
|
+
|
|
|
+show_status() {
|
|
|
+ if is_running; then
|
|
|
+ echo -e "${GREEN}[RUNNING]${NC} PID=$(cat "$PID_FILE") port=$PORT"
|
|
|
+ if command -v lsof >/dev/null 2>&1; then
|
|
|
+ lsof -i ":$PORT" 2>/dev/null | grep LISTEN || true
|
|
|
+ fi
|
|
|
+ else
|
|
|
+ echo -e "${YELLOW}[STOPPED]${NC} 服务未运行"
|
|
|
+ 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() {
|
|
|
+ if is_running || [ -f "$PID_FILE" ]; then
|
|
|
+ stop_server || true
|
|
|
+ sleep 1
|
|
|
+ fi
|
|
|
+ start_server
|
|
|
+}
|
|
|
+
|
|
|
+ACTION="start"
|
|
|
+LOG_FOLLOW=""
|
|
|
+
|
|
|
+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 ;;
|
|
|
+ -f|--foreground) FOREGROUND=true; shift ;;
|
|
|
+ -l|--log-level) LOG_LEVEL="$2"; shift 2 ;;
|
|
|
+ --install) ACTION="install"; shift ;;
|
|
|
+ --init) ACTION="init"; shift ;;
|
|
|
+ --stop) ACTION="stop"; shift ;;
|
|
|
+ --restart) ACTION="restart"; shift ;;
|
|
|
+ --status) ACTION="status"; shift ;;
|
|
|
+ --logs)
|
|
|
+ ACTION="logs"
|
|
|
+ shift
|
|
|
+ if [[ "${1:-}" == "-f" || "${1:-}" == "--follow" ]]; then
|
|
|
+ LOG_FOLLOW="-f"
|
|
|
+ shift
|
|
|
+ fi
|
|
|
+ ;;
|
|
|
+ --help) show_help; exit 0 ;;
|
|
|
+ *)
|
|
|
+ log_error "未知参数: $1"
|
|
|
+ show_help
|
|
|
+ exit 1
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+done
|
|
|
+
|
|
|
+case "$ACTION" in
|
|
|
+ install) install_dependencies ;;
|
|
|
+ init)
|
|
|
+ check_python
|
|
|
+ check_env
|
|
|
+ log_success "环境检查完成"
|
|
|
+ ;;
|
|
|
+ stop) stop_server ;;
|
|
|
+ restart) restart_server ;;
|
|
|
+ status) show_status ;;
|
|
|
+ logs) show_logs "$LOG_FOLLOW" ;;
|
|
|
+ start) start_server ;;
|
|
|
+ *)
|
|
|
+ log_error "未知动作: $ACTION"
|
|
|
+ exit 1
|
|
|
+ ;;
|
|
|
+esac
|