Sfoglia il codice sorgente

chore(backend): add start.sh for local and background uvicorn

Provide install/init/start/stop/restart/status/logs with reload support
and ignore runtime logs, pid, and upload artifacts.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 mesi fa
parent
commit
8565ecee81
2 ha cambiato i file con 316 aggiunte e 0 eliminazioni
  1. 3 0
      backend/.gitignore
  2. 313 0
      backend/start.sh

+ 3 - 0
backend/.gitignore

@@ -3,3 +3,6 @@ __pycache__/
 .pytest_cache/
 *.pyc
 .env
+logs/
+app.pid
+uploads/

+ 313 - 0
backend/start.sh

@@ -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