dev.sh 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
  4. frontend_pid=""
  5. backend_pid=""
  6. cleanup_done=0
  7. run_for_seconds="${ZBT_DEV_RUN_SECONDS:-0}"
  8. die() {
  9. echo "错误:$*" >&2
  10. exit 1
  11. }
  12. require_command() {
  13. local command_name="$1"
  14. command -v "$command_name" >/dev/null 2>&1 || \
  15. die "未找到 $command_name,请先安装并确保它位于 PATH 中。"
  16. }
  17. assert_port_available() {
  18. local port="$1"
  19. local application_name="$2"
  20. local listeners
  21. listeners="$(lsof -nP -iTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)"
  22. if [[ -n "$listeners" ]]; then
  23. echo "$listeners" >&2
  24. die "$application_name 端口 $port 已被占用,请先结束上方进程后重试。"
  25. fi
  26. }
  27. process_group_is_alive() {
  28. local group_id="$1"
  29. [[ -n "$group_id" ]] && kill -0 -- "-$group_id" 2>/dev/null
  30. }
  31. stop_process_group() {
  32. local group_id="$1"
  33. if process_group_is_alive "$group_id"; then
  34. kill -TERM -- "-$group_id" 2>/dev/null || true
  35. fi
  36. }
  37. cleanup() {
  38. local exit_status=$?
  39. local deadline
  40. if (( cleanup_done )); then
  41. return "$exit_status"
  42. fi
  43. cleanup_done=1
  44. trap - INT TERM HUP
  45. if [[ -n "$frontend_pid" || -n "$backend_pid" ]]; then
  46. echo
  47. echo "正在停止智保通开发服务..."
  48. fi
  49. stop_process_group "$frontend_pid"
  50. stop_process_group "$backend_pid"
  51. deadline=$((SECONDS + 5))
  52. while (( SECONDS < deadline )); do
  53. if ! process_group_is_alive "$frontend_pid" && \
  54. ! process_group_is_alive "$backend_pid"; then
  55. break
  56. fi
  57. sleep 0.2
  58. done
  59. if process_group_is_alive "$frontend_pid"; then
  60. kill -KILL -- "-$frontend_pid" 2>/dev/null || true
  61. fi
  62. if process_group_is_alive "$backend_pid"; then
  63. kill -KILL -- "-$backend_pid" 2>/dev/null || true
  64. fi
  65. if [[ -n "$frontend_pid" ]]; then
  66. wait "$frontend_pid" 2>/dev/null || true
  67. fi
  68. if [[ -n "$backend_pid" ]]; then
  69. wait "$backend_pid" 2>/dev/null || true
  70. fi
  71. if [[ -n "$frontend_pid" || -n "$backend_pid" ]]; then
  72. echo "智保通开发服务已停止。"
  73. fi
  74. return "$exit_status"
  75. }
  76. http_ready() {
  77. local url="$1"
  78. curl --fail --silent --show-error --max-time 2 "$url" >/dev/null 2>&1
  79. }
  80. wait_for_services() {
  81. local deadline=$((SECONDS + 45))
  82. while (( SECONDS < deadline )); do
  83. kill -0 "$frontend_pid" 2>/dev/null || \
  84. die "前端开发服务器启动失败,请检查上方 pnpm 输出。"
  85. kill -0 "$backend_pid" 2>/dev/null || \
  86. die "后端开发服务器启动失败,请检查上方 Uvicorn 输出。"
  87. if http_ready "http://127.0.0.1:5173/" && \
  88. http_ready "http://127.0.0.1:5174/" && \
  89. http_ready "http://127.0.0.1:8000/docs"; then
  90. return 0
  91. fi
  92. sleep 0.3
  93. done
  94. die "开发服务在 45 秒内未全部就绪,请检查上方启动日志。"
  95. }
  96. case "$run_for_seconds" in
  97. ''|*[!0-9]*)
  98. die "ZBT_DEV_RUN_SECONDS 必须是非负整数。"
  99. ;;
  100. esac
  101. trap cleanup EXIT
  102. trap 'exit 130' INT
  103. trap 'exit 143' TERM
  104. trap 'exit 129' HUP
  105. require_command pnpm
  106. require_command uv
  107. require_command curl
  108. require_command lsof
  109. assert_port_available 5173 "H5"
  110. assert_port_available 5174 "管理后台"
  111. assert_port_available 8000 "后端 API"
  112. echo "正在启动 H5、管理后台和后端 API..."
  113. # Bash 3.2(macOS 系统自带版本)没有 setsid。临时启用 job control,
  114. # 可让两个后台任务各自成为独立进程组,清理时连同 Vite/Uvicorn 子进程一起结束。
  115. set -m
  116. (
  117. cd "$project_root/frontend"
  118. exec pnpm dev
  119. ) &
  120. frontend_pid=$!
  121. (
  122. cd "$project_root/backend"
  123. exec uv run uvicorn zbt.main:app \
  124. --host 127.0.0.1 \
  125. --port 8000 \
  126. --reload
  127. ) &
  128. backend_pid=$!
  129. set +m
  130. wait_for_services
  131. echo
  132. echo "智保通开发服务已全部启动:"
  133. echo "H5:http://127.0.0.1:5173"
  134. echo "管理后台:http://127.0.0.1:5174"
  135. echo "后端 API:http://127.0.0.1:8000/docs"
  136. echo "按 Ctrl+C 可一次性停止全部服务。"
  137. ready_at=$SECONDS
  138. while true; do
  139. kill -0 "$frontend_pid" 2>/dev/null || \
  140. die "前端开发服务器意外退出,请检查上方日志。"
  141. kill -0 "$backend_pid" 2>/dev/null || \
  142. die "后端开发服务器意外退出,请检查上方日志。"
  143. if (( run_for_seconds > 0 && SECONDS - ready_at >= run_for_seconds )); then
  144. break
  145. fi
  146. sleep 0.5
  147. done