| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- #!/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
|