优化启动脚本
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"
|
COMPOSE_FILE="${ROOT_DIR}/deploy/docker-compose.dev.yml"
|
||||||
BACKEND_ENV="${ROOT_DIR}/backend/.env"
|
BACKEND_ENV="${ROOT_DIR}/backend/.env"
|
||||||
BACKEND_ENV_EXAMPLE="${ROOT_DIR}/backend/.env.example"
|
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"
|
MIGRATIONS_DIR="${ROOT_DIR}/backend/migrations"
|
||||||
MYSQL_DATABASE="${DEV_MYSQL_DATABASE:-hfb_sys}"
|
MYSQL_DATABASE="${DEV_MYSQL_DATABASE:-hfb_sys}"
|
||||||
BACKEND_PORT="${DEV_BACKEND_PORT:-8080}"
|
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}"
|
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}/}"
|
FRONTEND_HEALTH_URL="${DEV_FRONTEND_HEALTH_URL:-http://127.0.0.1:${FRONTEND_PORT}/}"
|
||||||
HTTP_READY_TIMEOUT="${DEV_HTTP_READY_TIMEOUT:-120}"
|
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=""
|
BACKEND_PID=""
|
||||||
FRONTEND_PID=""
|
FRONTEND_PID=""
|
||||||
@@ -64,15 +71,19 @@ show_help() {
|
|||||||
DEV_MYSQL_DATABASE=hfb_sys
|
DEV_MYSQL_DATABASE=hfb_sys
|
||||||
开发数据库名
|
开发数据库名
|
||||||
DEV_BACKEND_PORT=8080
|
DEV_BACKEND_PORT=8080
|
||||||
后端端口占用检查端口
|
后端启动端口
|
||||||
DEV_FRONTEND_PORT=5173
|
DEV_FRONTEND_PORT=5173
|
||||||
前端端口占用检查端口
|
前端启动端口
|
||||||
DEV_BACKEND_HEALTH_URL=http://127.0.0.1:8080/health
|
DEV_BACKEND_HEALTH_URL=http://127.0.0.1:8080/health
|
||||||
后端健康检查地址
|
后端健康检查地址
|
||||||
DEV_FRONTEND_HEALTH_URL=http://127.0.0.1:5173/
|
DEV_FRONTEND_HEALTH_URL=http://127.0.0.1:5173/
|
||||||
前端健康检查地址
|
前端健康检查地址
|
||||||
DEV_HTTP_READY_TIMEOUT=120
|
DEV_HTTP_READY_TIMEOUT=120
|
||||||
HTTP 服务就绪等待秒数
|
HTTP 服务就绪等待秒数
|
||||||
|
DEV_AUTO_KILL_PORTS=1
|
||||||
|
前后端端口被占用时自动停止占用的 Docker 容器或本机进程
|
||||||
|
DEV_FORCE_NPM_CI=0
|
||||||
|
强制重新执行前端 npm ci
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
./scripts/dev.sh # 正常启动
|
./scripts/dev.sh # 正常启动
|
||||||
@@ -186,21 +197,175 @@ wait_http_ready() {
|
|||||||
exit 1
|
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() {
|
ensure_port_free() {
|
||||||
local port="$1"
|
local port="$1"
|
||||||
local label="$2"
|
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 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} 已被占用,请先停止旧进程或调整端口"
|
log_error "${label}端口 ${port} 已被占用,请先停止旧进程或调整端口"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command -v nc >/dev/null 2>&1; then
|
if command -v nc >/dev/null 2>&1; then
|
||||||
if nc -z 127.0.0.1 "${port}" >/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
|
exit 1
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
@@ -345,18 +510,55 @@ start_backend() {
|
|||||||
BACKEND_PID="$!"
|
BACKEND_PID="$!"
|
||||||
}
|
}
|
||||||
|
|
||||||
start_frontend() {
|
frontend_dependencies_need_ci() {
|
||||||
if [[ ! -d "${ROOT_DIR}/frontend/node_modules" ]]; then
|
if [[ "${FORCE_NPM_CI}" == "1" ]]; then
|
||||||
log "安装前端依赖..."
|
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
|
npm ci
|
||||||
)
|
)
|
||||||
|
log_success "前端依赖已同步"
|
||||||
|
else
|
||||||
|
log "前端依赖已同步,跳过 npm ci"
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_frontend() {
|
||||||
|
ensure_frontend_dependencies
|
||||||
|
|
||||||
log "启动前端:http://localhost:5173"
|
log "启动前端:http://localhost:5173"
|
||||||
(
|
(
|
||||||
cd "${ROOT_DIR}/frontend"
|
cd "${FRONTEND_DIR}"
|
||||||
npm run dev
|
npm run dev
|
||||||
) &
|
) &
|
||||||
FRONTEND_PID="$!"
|
FRONTEND_PID="$!"
|
||||||
|
|||||||
Reference in New Issue
Block a user