start-test.sh 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. #!/bin/bash
  2. # ============================================
  3. # 陇耘鲜达 Backend 测试环境启动脚本
  4. # 适配: python3.9+(推荐 venv;显式不使用 python2)
  5. #
  6. # 测试服务器目录示例: /root/zkxw-backend/backend
  7. # 默认监听: 0.0.0.0:8112(生产为 8111,同机部署勿冲突)
  8. #
  9. # 首次部署:
  10. # cd /root/zkxw-backend/backend
  11. # cp .env.test.example .env.test # 或从 .env.example 复制后按测试库填写
  12. # python3.9 -m venv .venv && ./.venv/bin/pip install -r requirements.txt
  13. # ./start-test.sh activate # source .venv/bin/activate
  14. # ./start-test.sh start
  15. # ============================================
  16. set -euo pipefail
  17. BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  18. cd "$BASE_DIR"
  19. APP_MODULE="${APP_MODULE:-app.main:app}"
  20. HOST="${HOST:-0.0.0.0}"
  21. PORT="${PORT:-8112}"
  22. WORKERS="${WORKERS:-1}"
  23. LOG_LEVEL="${LOG_LEVEL:-debug}"
  24. ENV_FILE="${ENV_FILE:-$BASE_DIR/.env.test}"
  25. VENV_DIR="${VENV_DIR:-$BASE_DIR/.venv}"
  26. ACTIVATE_SCRIPT="$VENV_DIR/bin/activate"
  27. PYTHON_BIN="${PYTHON_BIN:-$VENV_DIR/bin/python}"
  28. MIN_PYTHON_MAJOR=3
  29. MIN_PYTHON_MINOR=9
  30. SYSTEM_PYTHON=""
  31. LOG_DIR="$BASE_DIR/logs"
  32. LOG_FILE="$LOG_DIR/backend-test.log"
  33. PID_FILE="$BASE_DIR/backend-test.pid"
  34. RED='\033[0;31m'
  35. GREEN='\033[0;32m'
  36. YELLOW='\033[1;33m'
  37. NC='\033[0m'
  38. log_info() {
  39. echo -e "${GREEN}[INFO]${NC} $1"
  40. }
  41. log_warn() {
  42. echo -e "${YELLOW}[WARN]${NC} $1"
  43. }
  44. log_error() {
  45. echo -e "${RED}[ERROR]${NC} $1"
  46. }
  47. show_help() {
  48. cat << 'EOF'
  49. 用法:
  50. ./start-test.sh [start|stop|restart|status|logs|activate]
  51. 命令:
  52. start 启动服务(默认)
  53. stop 停止服务
  54. restart 重启服务
  55. status 查看状态
  56. logs 查看日志(最近100行)
  57. activate 激活 .venv 并进入交互 shell(等价 source .venv/bin/activate)
  58. 可选环境变量:
  59. HOST=0.0.0.0
  60. PORT=8112
  61. WORKERS=1
  62. LOG_LEVEL=debug
  63. APP_MODULE=app.main:app
  64. VENV_DIR=./.venv
  65. PYTHON_FOR_VENV=python3.9
  66. ENV_FILE=./.env.test
  67. 测试部署目录(示例):
  68. /root/zkxw-backend/backend
  69. cd /root/zkxw-backend/backend && ./start-test.sh start
  70. 说明:
  71. - 读取 .env.test(非 .env.production)
  72. - 默认端口 8112;生产 start-prod.sh 使用 8111
  73. - 可与生产共用 venv,但 PID/日志文件独立,勿混用 start/stop 命令
  74. EOF
  75. }
  76. ensure_dirs() {
  77. mkdir -p "$LOG_DIR"
  78. }
  79. python_version_ge() {
  80. local ver="$1"
  81. local major="${ver%%.*}"
  82. local minor="${ver#*.}"
  83. if [ "$major" -gt "$MIN_PYTHON_MAJOR" ]; then
  84. return 0
  85. fi
  86. if [ "$major" -eq "$MIN_PYTHON_MAJOR" ] && [ "$minor" -ge "$MIN_PYTHON_MINOR" ]; then
  87. return 0
  88. fi
  89. return 1
  90. }
  91. resolve_system_python() {
  92. local candidate ver
  93. if [ -n "${PYTHON_FOR_VENV:-}" ] && command -v "$PYTHON_FOR_VENV" >/dev/null 2>&1; then
  94. ver="$("$PYTHON_FOR_VENV" -c 'import sys; print("{}.{}".format(sys.version_info[0], sys.version_info[1]))')"
  95. if python_version_ge "$ver"; then
  96. echo "$PYTHON_FOR_VENV"
  97. return 0
  98. fi
  99. log_error "PYTHON_FOR_VENV=$PYTHON_FOR_VENV 版本为 $ver,需要 >= ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}"
  100. exit 1
  101. fi
  102. for candidate in python3.11 python3.10 python3.9 python3; do
  103. if command -v "$candidate" >/dev/null 2>&1; then
  104. ver="$("$candidate" -c 'import sys; print("{}.{}".format(sys.version_info[0], sys.version_info[1]))')"
  105. if python_version_ge "$ver"; then
  106. echo "$candidate"
  107. return 0
  108. fi
  109. fi
  110. done
  111. return 1
  112. }
  113. check_python() {
  114. if ! SYSTEM_PYTHON="$(resolve_system_python)"; then
  115. log_error "未找到 python${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+,请安装 python39 或设置 PYTHON_FOR_VENV=python3.9"
  116. exit 1
  117. fi
  118. log_info "系统 Python: $SYSTEM_PYTHON ($("$SYSTEM_PYTHON" --version 2>&1))"
  119. }
  120. ensure_venv_python_version() {
  121. local ver
  122. ver="$("$PYTHON_BIN" -c 'import sys; print("{}.{}".format(sys.version_info[0], sys.version_info[1]))')"
  123. if python_version_ge "$ver"; then
  124. log_info "虚拟环境 Python: $ver ($VENV_DIR)"
  125. return 0
  126. fi
  127. log_error "虚拟环境 Python 版本过低: $ver(需要 >= ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR})"
  128. log_error "当前虚拟环境目录: $VENV_DIR"
  129. log_warn "请执行: rm -rf \"$VENV_DIR\" && $SYSTEM_PYTHON -m venv \"$VENV_DIR\" && \"$VENV_DIR/bin/pip\" install -r requirements.txt"
  130. exit 1
  131. }
  132. has_uvicorn() {
  133. "$PYTHON_BIN" -c "import uvicorn" >/dev/null 2>&1
  134. }
  135. ensure_venv_and_runtime() {
  136. log_info "虚拟环境目录: $VENV_DIR"
  137. if [ ! -d "$VENV_DIR" ]; then
  138. log_warn "未找到虚拟环境,开始自动创建: $VENV_DIR"
  139. if ! "$SYSTEM_PYTHON" -m venv "$VENV_DIR" >/dev/null 2>&1; then
  140. log_error "自动创建 venv 失败,请先安装 python3-venv 或手动创建虚拟环境"
  141. log_warn "可尝试: $SYSTEM_PYTHON -m venv \"$VENV_DIR\""
  142. exit 1
  143. fi
  144. fi
  145. if [ ! -x "$PYTHON_BIN" ]; then
  146. log_error "虚拟环境 python 不存在: $PYTHON_BIN"
  147. exit 1
  148. fi
  149. ensure_venv_python_version
  150. if [ ! -x "$VENV_DIR/bin/pip" ]; then
  151. log_warn "虚拟环境 pip 不存在,尝试引导安装"
  152. "$PYTHON_BIN" -m ensurepip --upgrade >/dev/null 2>&1 || true
  153. fi
  154. if ! has_uvicorn; then
  155. log_warn "未检测到 uvicorn,开始安装依赖 requirements.txt"
  156. if [ ! -x "$VENV_DIR/bin/pip" ]; then
  157. log_error "pip 仍不可用,请手动执行依赖初始化"
  158. exit 1
  159. fi
  160. "$VENV_DIR/bin/pip" install --upgrade pip
  161. "$VENV_DIR/bin/pip" install -r "$BASE_DIR/requirements.txt"
  162. if ! has_uvicorn; then
  163. log_error "依赖安装后仍无法 import uvicorn,请检查: $PYTHON_BIN -m pip install -r requirements.txt"
  164. exit 1
  165. fi
  166. fi
  167. }
  168. is_running() {
  169. if [ ! -f "$PID_FILE" ]; then
  170. return 1
  171. fi
  172. local pid
  173. pid="$(cat "$PID_FILE")"
  174. if [ -z "$pid" ]; then
  175. return 1
  176. fi
  177. if ps -p "$pid" >/dev/null 2>&1; then
  178. return 0
  179. fi
  180. return 1
  181. }
  182. start_server() {
  183. check_python
  184. ensure_venv_and_runtime
  185. ensure_dirs
  186. if is_running; then
  187. local running_pid
  188. running_pid="$(cat "$PID_FILE")"
  189. log_warn "测试服务已在运行 (PID: $running_pid)"
  190. exit 0
  191. fi
  192. if [ -f "$PID_FILE" ]; then
  193. rm -f "$PID_FILE"
  194. fi
  195. log_info "启动测试环境服务..."
  196. log_info "监听地址: $HOST:$PORT"
  197. log_info "工作进程: $WORKERS"
  198. log_info "日志文件: $LOG_FILE"
  199. log_info "环境文件: $ENV_FILE"
  200. if [ ! -f "$ENV_FILE" ]; then
  201. log_error "环境文件不存在: $ENV_FILE"
  202. log_warn "可先执行: cp .env.test.example .env.test 并按测试库修改"
  203. exit 1
  204. fi
  205. export ENV_FILE
  206. nohup "$PYTHON_BIN" -m uvicorn "$APP_MODULE" \
  207. --host "$HOST" \
  208. --port "$PORT" \
  209. --workers "$WORKERS" \
  210. --log-level "$LOG_LEVEL" \
  211. >> "$LOG_FILE" 2>&1 &
  212. echo $! > "$PID_FILE"
  213. sleep 2
  214. if is_running; then
  215. log_info "测试服务启动成功 (PID: $(cat "$PID_FILE"))"
  216. log_info "健康检查: http://127.0.0.1:$PORT/health"
  217. log_info "API 文档: http://127.0.0.1:$PORT/docs"
  218. else
  219. log_error "测试服务启动失败,请检查日志: $LOG_FILE"
  220. rm -f "$PID_FILE"
  221. exit 1
  222. fi
  223. }
  224. stop_server() {
  225. if ! is_running; then
  226. log_warn "测试服务未运行"
  227. [ -f "$PID_FILE" ] && rm -f "$PID_FILE"
  228. exit 0
  229. fi
  230. local pid
  231. pid="$(cat "$PID_FILE")"
  232. log_info "正在停止测试服务 (PID: $pid)"
  233. kill -TERM "$pid" >/dev/null 2>&1 || true
  234. for _ in 1 2 3 4 5 6 7 8 9 10; do
  235. if ! ps -p "$pid" >/dev/null 2>&1; then
  236. break
  237. fi
  238. sleep 1
  239. done
  240. if ps -p "$pid" >/dev/null 2>&1; then
  241. log_warn "优雅停止超时,执行强制停止"
  242. kill -9 "$pid" >/dev/null 2>&1 || true
  243. fi
  244. rm -f "$PID_FILE"
  245. log_info "测试服务已停止"
  246. }
  247. status_server() {
  248. if is_running; then
  249. echo -e "${GREEN}RUNNING (test)${NC} PID=$(cat "$PID_FILE") HOST=$HOST PORT=$PORT"
  250. else
  251. echo -e "${RED}STOPPED (test)${NC}"
  252. fi
  253. }
  254. show_logs() {
  255. ensure_dirs
  256. if [ ! -f "$LOG_FILE" ]; then
  257. log_warn "暂无日志文件: $LOG_FILE"
  258. exit 0
  259. fi
  260. tail -n 100 "$LOG_FILE"
  261. }
  262. activate_venv() {
  263. if [ ! -f "$ACTIVATE_SCRIPT" ]; then
  264. log_error "虚拟环境激活脚本不存在: $ACTIVATE_SCRIPT"
  265. log_warn "可先执行: python3.9 -m venv .venv && ./.venv/bin/pip install -r requirements.txt"
  266. exit 1
  267. fi
  268. log_info "激活虚拟环境: source $ACTIVATE_SCRIPT"
  269. exec "$SHELL" -i -c "cd '$BASE_DIR' && source '$ACTIVATE_SCRIPT' && exec -i '$SHELL'"
  270. }
  271. ACTION="${1:-start}"
  272. case "$ACTION" in
  273. start)
  274. start_server
  275. ;;
  276. stop)
  277. stop_server
  278. ;;
  279. restart)
  280. stop_server || true
  281. start_server
  282. ;;
  283. status)
  284. status_server
  285. ;;
  286. logs)
  287. show_logs
  288. ;;
  289. activate)
  290. activate_venv
  291. ;;
  292. -h|--help|help)
  293. show_help
  294. ;;
  295. *)
  296. log_error "未知命令: $ACTION"
  297. show_help
  298. exit 1
  299. ;;
  300. esac