优化启动脚本
This commit is contained in:
+211
-9
@@ -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 "${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 "${ROOT_DIR}/frontend"
|
||||
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="$!"
|
||||
|
||||
Reference in New Issue
Block a user