#!/bin/bash # ============================================ # 陇耘鲜达 Backend 测试环境启动脚本 # 适配: python3.9+(推荐 venv;显式不使用 python2) # # 测试服务器目录示例: /root/zkxw/backend # 默认监听: 127.0.0.1:10002(裸机 Nginx 反代;勿与生产 8111 冲突) # 旧双机拓扑 0.0.0.0:8112 已废弃;手动 start/restart 时也走本默认,避免 Nginx 502。 # # 首次部署: # cd /root/zkxw/backend # cp .env.test.example .env.test # 或从 .env.example 复制后按测试库填写 # python3.9 -m venv .venv && ./.venv/bin/pip install -r requirements.txt # ./start-test.sh activate # source .venv/bin/activate # ./start-test.sh start # ============================================ set -euo pipefail BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$BASE_DIR" APP_MODULE="${APP_MODULE:-app.main:app}" HOST="${HOST:-127.0.0.1}" PORT="${PORT:-10002}" WORKERS="${WORKERS:-1}" LOG_LEVEL="${LOG_LEVEL:-debug}" ENV_FILE="${ENV_FILE:-$BASE_DIR/.env.test}" VENV_DIR="${VENV_DIR:-$BASE_DIR/.venv}" ACTIVATE_SCRIPT="$VENV_DIR/bin/activate" PYTHON_BIN="${PYTHON_BIN:-$VENV_DIR/bin/python}" MIN_PYTHON_MAJOR=3 MIN_PYTHON_MINOR=9 SYSTEM_PYTHON="" LOG_DIR="$BASE_DIR/logs" LOG_FILE="$LOG_DIR/backend-test.log" PID_FILE="$BASE_DIR/backend-test.pid" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } show_help() { cat << 'EOF' 用法: ./start-test.sh [start|stop|restart|status|logs|activate] 命令: start 启动服务(默认) stop 停止服务 restart 重启服务 status 查看状态 logs 查看日志(最近100行) activate 激活 .venv 并进入交互 shell(等价 source .venv/bin/activate) 可选环境变量: HOST=127.0.0.1 PORT=10002 WORKERS=1 LOG_LEVEL=debug APP_MODULE=app.main:app VENV_DIR=./.venv PYTHON_FOR_VENV=python3.9 ENV_FILE=./.env.test 测试部署目录(示例): /root/zkxw/backend cd /root/zkxw/backend && ./start-test.sh start 说明: - 读取 .env.test(非 .env.production) - 默认 127.0.0.1:10002(Nginx 反代);生产 start-prod.sh 使用 8111 - 可与生产共用 venv,但 PID/日志文件独立,勿混用 start/stop 命令 EOF } ensure_dirs() { mkdir -p "$LOG_DIR" } python_version_ge() { local ver="$1" local major="${ver%%.*}" local minor="${ver#*.}" if [ "$major" -gt "$MIN_PYTHON_MAJOR" ]; then return 0 fi if [ "$major" -eq "$MIN_PYTHON_MAJOR" ] && [ "$minor" -ge "$MIN_PYTHON_MINOR" ]; then return 0 fi return 1 } resolve_system_python() { local candidate ver if [ -n "${PYTHON_FOR_VENV:-}" ] && command -v "$PYTHON_FOR_VENV" >/dev/null 2>&1; then ver="$("$PYTHON_FOR_VENV" -c 'import sys; print("{}.{}".format(sys.version_info[0], sys.version_info[1]))')" if python_version_ge "$ver"; then echo "$PYTHON_FOR_VENV" return 0 fi log_error "PYTHON_FOR_VENV=$PYTHON_FOR_VENV 版本为 $ver,需要 >= ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}" exit 1 fi for candidate in python3.11 python3.10 python3.9 python3; do if command -v "$candidate" >/dev/null 2>&1; then ver="$("$candidate" -c 'import sys; print("{}.{}".format(sys.version_info[0], sys.version_info[1]))')" if python_version_ge "$ver"; then echo "$candidate" return 0 fi fi done return 1 } check_python() { if ! SYSTEM_PYTHON="$(resolve_system_python)"; then log_error "未找到 python${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+,请安装 python39 或设置 PYTHON_FOR_VENV=python3.9" exit 1 fi log_info "系统 Python: $SYSTEM_PYTHON ($("$SYSTEM_PYTHON" --version 2>&1))" } ensure_venv_python_version() { local ver ver="$("$PYTHON_BIN" -c 'import sys; print("{}.{}".format(sys.version_info[0], sys.version_info[1]))')" if python_version_ge "$ver"; then log_info "虚拟环境 Python: $ver ($VENV_DIR)" return 0 fi log_error "虚拟环境 Python 版本过低: $ver(需要 >= ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR})" log_error "当前虚拟环境目录: $VENV_DIR" log_warn "请执行: rm -rf \"$VENV_DIR\" && $SYSTEM_PYTHON -m venv \"$VENV_DIR\" && \"$VENV_DIR/bin/pip\" install -r requirements.txt" exit 1 } has_uvicorn() { "$PYTHON_BIN" -c "import uvicorn" >/dev/null 2>&1 } ensure_venv_and_runtime() { log_info "虚拟环境目录: $VENV_DIR" if [ ! -d "$VENV_DIR" ]; then log_warn "未找到虚拟环境,开始自动创建: $VENV_DIR" if ! "$SYSTEM_PYTHON" -m venv "$VENV_DIR" >/dev/null 2>&1; then log_error "自动创建 venv 失败,请先安装 python3-venv 或手动创建虚拟环境" log_warn "可尝试: $SYSTEM_PYTHON -m venv \"$VENV_DIR\"" exit 1 fi fi if [ ! -x "$PYTHON_BIN" ]; then log_error "虚拟环境 python 不存在: $PYTHON_BIN" exit 1 fi ensure_venv_python_version if [ ! -x "$VENV_DIR/bin/pip" ]; then log_warn "虚拟环境 pip 不存在,尝试引导安装" "$PYTHON_BIN" -m ensurepip --upgrade >/dev/null 2>&1 || true fi if ! has_uvicorn; then log_warn "未检测到 uvicorn,开始安装依赖 requirements.txt" if [ ! -x "$VENV_DIR/bin/pip" ]; then log_error "pip 仍不可用,请手动执行依赖初始化" exit 1 fi "$VENV_DIR/bin/pip" install --upgrade pip "$VENV_DIR/bin/pip" install -r "$BASE_DIR/requirements.txt" if ! has_uvicorn; then log_error "依赖安装后仍无法 import uvicorn,请检查: $PYTHON_BIN -m pip install -r requirements.txt" exit 1 fi fi } is_running() { if [ ! -f "$PID_FILE" ]; then return 1 fi local pid pid="$(cat "$PID_FILE")" if [ -z "$pid" ]; then return 1 fi if ps -p "$pid" >/dev/null 2>&1; then return 0 fi return 1 } start_server() { check_python ensure_venv_and_runtime ensure_dirs if is_running; then local running_pid running_pid="$(cat "$PID_FILE")" log_warn "测试服务已在运行 (PID: $running_pid)" exit 0 fi if [ -f "$PID_FILE" ]; then rm -f "$PID_FILE" fi log_info "启动测试环境服务..." log_info "监听地址: $HOST:$PORT" log_info "工作进程: $WORKERS" log_info "日志文件: $LOG_FILE" log_info "环境文件: $ENV_FILE" if [ ! -f "$ENV_FILE" ]; then log_error "环境文件不存在: $ENV_FILE" log_warn "可先执行: cp .env.test.example .env.test 并按测试库修改" exit 1 fi export ENV_FILE nohup "$PYTHON_BIN" -m uvicorn "$APP_MODULE" \ --host "$HOST" \ --port "$PORT" \ --workers "$WORKERS" \ --log-level "$LOG_LEVEL" \ >> "$LOG_FILE" 2>&1 & echo $! > "$PID_FILE" sleep 2 if is_running; then log_info "测试服务启动成功 (PID: $(cat "$PID_FILE"))" log_info "健康检查: http://127.0.0.1:$PORT/health" log_info "API 文档: http://127.0.0.1:$PORT/docs" else log_error "测试服务启动失败,请检查日志: $LOG_FILE" rm -f "$PID_FILE" exit 1 fi } stop_server() { if ! is_running; then log_warn "测试服务未运行" [ -f "$PID_FILE" ] && rm -f "$PID_FILE" exit 0 fi local pid pid="$(cat "$PID_FILE")" log_info "正在停止测试服务 (PID: $pid)" kill -TERM "$pid" >/dev/null 2>&1 || true for _ in 1 2 3 4 5 6 7 8 9 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" >/dev/null 2>&1 || true fi rm -f "$PID_FILE" log_info "测试服务已停止" } status_server() { if is_running; then echo -e "${GREEN}RUNNING (test)${NC} PID=$(cat "$PID_FILE") HOST=$HOST PORT=$PORT" else echo -e "${RED}STOPPED (test)${NC}" fi } show_logs() { ensure_dirs if [ ! -f "$LOG_FILE" ]; then log_warn "暂无日志文件: $LOG_FILE" exit 0 fi tail -n 100 "$LOG_FILE" } activate_venv() { if [ ! -f "$ACTIVATE_SCRIPT" ]; then log_error "虚拟环境激活脚本不存在: $ACTIVATE_SCRIPT" log_warn "可先执行: python3.9 -m venv .venv && ./.venv/bin/pip install -r requirements.txt" exit 1 fi log_info "激活虚拟环境: source $ACTIVATE_SCRIPT" exec "$SHELL" -i -c "cd '$BASE_DIR' && source '$ACTIVATE_SCRIPT' && exec -i '$SHELL'" } ACTION="${1:-start}" case "$ACTION" in start) start_server ;; stop) stop_server ;; restart) stop_server || true start_server ;; status) status_server ;; logs) show_logs ;; activate) activate_venv ;; -h|--help|help) show_help ;; *) log_error "未知命令: $ACTION" show_help exit 1 ;; esac