优化启动脚本

This commit is contained in:
yml2213
2026-06-07 10:06:18 +08:00
parent 8ae76036a8
commit 8e7c181b27
+211 -9
View File
@@ -5,6 +5,11 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMPOSE_FILE="${ROOT_DIR}/deploy/docker-compose.dev.yml"
BACKEND_ENV="${ROOT_DIR}/backend/.env"
BACKEND_ENV_EXAMPLE="${ROOT_DIR}/backend/.env.example"
FRONTEND_DIR="${ROOT_DIR}/frontend"
FRONTEND_PACKAGE_JSON="${FRONTEND_DIR}/package.json"
FRONTEND_PACKAGE_LOCK="${FRONTEND_DIR}/package-lock.json"
FRONTEND_NODE_MODULES="${FRONTEND_DIR}/node_modules"
FRONTEND_NODE_MODULES_LOCK="${FRONTEND_NODE_MODULES}/.package-lock.json"
MIGRATIONS_DIR="${ROOT_DIR}/backend/migrations"
MYSQL_DATABASE="${DEV_MYSQL_DATABASE:-hfb_sys}"
BACKEND_PORT="${DEV_BACKEND_PORT:-8080}"
@@ -12,6 +17,8 @@ FRONTEND_PORT="${DEV_FRONTEND_PORT:-5173}"
BACKEND_HEALTH_URL="${DEV_BACKEND_HEALTH_URL:-http://127.0.0.1:${BACKEND_PORT}/health}"
FRONTEND_HEALTH_URL="${DEV_FRONTEND_HEALTH_URL:-http://127.0.0.1:${FRONTEND_PORT}/}"
HTTP_READY_TIMEOUT="${DEV_HTTP_READY_TIMEOUT:-120}"
AUTO_KILL_PORTS="${DEV_AUTO_KILL_PORTS:-1}"
FORCE_NPM_CI="${DEV_FORCE_NPM_CI:-0}"
BACKEND_PID=""
FRONTEND_PID=""
@@ -64,15 +71,19 @@ show_help() {
DEV_MYSQL_DATABASE=hfb_sys
开发数据库名
DEV_BACKEND_PORT=8080
后端端口占用检查端口
后端启动端口
DEV_FRONTEND_PORT=5173
前端端口占用检查端口
前端启动端口
DEV_BACKEND_HEALTH_URL=http://127.0.0.1:8080/health
后端健康检查地址
DEV_FRONTEND_HEALTH_URL=http://127.0.0.1:5173/
前端健康检查地址
DEV_HTTP_READY_TIMEOUT=120
HTTP 服务就绪等待秒数
DEV_AUTO_KILL_PORTS=1
前后端端口被占用时自动停止占用的 Docker 容器或本机进程
DEV_FORCE_NPM_CI=0
强制重新执行前端 npm ci
示例:
./scripts/dev.sh # 正常启动
@@ -186,21 +197,175 @@ wait_http_ready() {
exit 1
}
port_in_use() {
local port="$1"
if command -v lsof >/dev/null 2>&1; then
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
return $?
fi
if command -v nc >/dev/null 2>&1; then
nc -z 127.0.0.1 "${port}" >/dev/null 2>&1
return $?
fi
return 1
}
wait_port_released() {
local port="$1"
local timeout="${2:-5}"
local attempt
for ((attempt = 1; attempt <= timeout; attempt++)); do
if ! port_in_use "${port}"; then
return 0
fi
sleep 1
done
return 1
}
is_allowed_container() {
local container_name="$1"
shift
local allowed_name
for allowed_name in "$@"; do
if [[ "${container_name}" == "${allowed_name}" ]]; then
return 0
fi
done
return 1
}
docker_containers_on_host_port() {
local port="$1"
docker ps --format '{{.ID}}\t{{.Names}}\t{{.Ports}}' 2>/dev/null |
awk -F '\t' -v port="${port}" 'index($3, ":" port "->") || $3 ~ ("(^|[ ,])" port "->") { print $1 "\t" $2 }'
}
stop_docker_containers_on_port() {
local port="$1"
local label="$2"
shift 2
local containers container_id container_name allowed_found=0 stopped_found=0
containers="$(docker_containers_on_host_port "${port}" || true)"
if [[ -z "${containers}" ]]; then
return 1
fi
while IFS=$'\t' read -r container_id container_name; do
[[ -z "${container_id}" ]] && continue
if is_allowed_container "${container_name}" "$@"; then
allowed_found=1
log "${label}端口 ${port} 已由本项目容器 ${container_name} 使用,保留运行"
continue
fi
if [[ "${AUTO_KILL_PORTS}" != "1" ]]; then
log_error "${label}端口 ${port} 被 Docker 容器 ${container_name} 占用"
exit 1
fi
log_warn "${label}端口 ${port} 被 Docker 容器 ${container_name} 占用,正在停止..."
docker stop "${container_id}" >/dev/null
stopped_found=1
log_success "已停止容器 ${container_name}"
done <<< "${containers}"
if [[ "${allowed_found}" == "1" ]]; then
return 0
fi
if [[ "${stopped_found}" == "1" ]]; then
wait_port_released "${port}" 5 || true
fi
return 1
}
process_args() {
local pid="$1"
ps -p "${pid}" -o args= 2>/dev/null || true
}
is_docker_proxy_process() {
local args="$1"
[[ "${args}" == *"com.docker"* || "${args}" == *"Docker Desktop"* || "${args}" == *"vpnkit"* || "${args}" == *"OrbStack"* ]]
}
ensure_port_free() {
local port="$1"
local label="$2"
shift 2
if stop_docker_containers_on_port "${port}" "${label}" "$@"; then
return 0
fi
if command -v lsof >/dev/null 2>&1; then
if lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1; then
local pids pid args
pids="$(lsof -nP -tiTCP:"${port}" -sTCP:LISTEN 2>/dev/null | sort -u || true)"
if [[ -z "${pids}" ]]; then
return 0
fi
if [[ "${AUTO_KILL_PORTS}" != "1" ]]; then
log_error "${label}端口 ${port} 已被占用,请先停止旧进程或调整端口"
exit 1
fi
log_warn "${label}端口 ${port} 已被占用,正在停止占用进程..."
while IFS= read -r pid; do
[[ -z "${pid}" ]] && continue
args="$(process_args "${pid}")"
if is_docker_proxy_process "${args}"; then
log_error "${label}端口 ${port} 仍由 Docker 代理进程占用,但未找到可自动停止的容器,请执行 docker ps 检查"
exit 1
fi
log_warn "停止 PID ${pid}: ${args:-未知进程}"
kill "${pid}" >/dev/null 2>&1 || true
done <<< "${pids}"
if ! wait_port_released "${port}" 5; then
log_warn "${label}端口 ${port} 未及时释放,尝试强制停止..."
pids="$(lsof -nP -tiTCP:"${port}" -sTCP:LISTEN 2>/dev/null | sort -u || true)"
while IFS= read -r pid; do
[[ -z "${pid}" ]] && continue
args="$(process_args "${pid}")"
if is_docker_proxy_process "${args}"; then
log_error "${label}端口 ${port} 仍由 Docker 代理进程占用,但未找到可自动停止的容器,请执行 docker ps 检查"
exit 1
fi
log_warn "强制停止 PID ${pid}: ${args:-未知进程}"
kill -9 "${pid}" >/dev/null 2>&1 || true
done <<< "${pids}"
if ! wait_port_released "${port}" 5; then
log_error "${label}端口 ${port} 释放失败,请手动检查占用进程"
exit 1
fi
fi
log_success "${label}端口 ${port} 已释放"
return 0
fi
if command -v nc >/dev/null 2>&1; then
if nc -z 127.0.0.1 "${port}" >/dev/null 2>&1; then
log_error "${label}端口 ${port} 已被占用,请先停止旧进程或调整端口"
log_error "${label}端口 ${port} 已被占用,但未找到 lsof,无法自动定位并停止占用进程"
exit 1
fi
return 0
@@ -345,18 +510,55 @@ start_backend() {
BACKEND_PID="$!"
}
start_frontend() {
if [[ ! -d "${ROOT_DIR}/frontend/node_modules" ]]; then
log "安装前端依赖..."
frontend_dependencies_need_ci() {
if [[ "${FORCE_NPM_CI}" == "1" ]]; then
return 0
fi
if [[ ! -d "${FRONTEND_NODE_MODULES}" ]]; then
return 0
fi
if [[ ! -f "${FRONTEND_NODE_MODULES_LOCK}" ]]; then
return 0
fi
if [[ "${FRONTEND_PACKAGE_JSON}" -nt "${FRONTEND_NODE_MODULES_LOCK}" || "${FRONTEND_PACKAGE_LOCK}" -nt "${FRONTEND_NODE_MODULES_LOCK}" ]]; then
return 0
fi
(
cd "${ROOT_DIR}/frontend"
cd "${FRONTEND_DIR}"
npm ls --depth=0 >/dev/null 2>&1
) || return 0
return 1
}
ensure_frontend_dependencies() {
if [[ ! -f "${FRONTEND_PACKAGE_LOCK}" ]]; then
log_error "缺少 frontend/package-lock.json,无法执行 npm ci"
exit 1
fi
if frontend_dependencies_need_ci; then
log "同步前端依赖(npm ci..."
(
cd "${FRONTEND_DIR}"
npm ci
)
log_success "前端依赖已同步"
else
log "前端依赖已同步,跳过 npm ci"
fi
}
start_frontend() {
ensure_frontend_dependencies
log "启动前端:http://localhost:5173"
(
cd "${ROOT_DIR}/frontend"
cd "${FRONTEND_DIR}"
npm run dev
) &
FRONTEND_PID="$!"