start-test.sh 8.6 KB

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