1、common.sh :部署脚本的公共工具库
#!/usr/bin/env bash
# 公共函数:被其他脚本 source
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
load_env() {
local env_file="${SCRIPT_DIR}/.env"
local example_file="${SCRIPT_DIR}/env.example"
# Windows 上传的 CRLF 会导致 source 报 $'\r',先规范化到临时文件
normalize_and_source() {
local src="$1"
local tmp
tmp="$(mktemp)"
tr -d '\r' < "${src}" > "${tmp}"
# shellcheck disable=SC1090
set -a
# shellcheck disable=SC1090
source "${tmp}"
set +a
rm -f "${tmp}"
}
if [[ -f "${env_file}" ]]; then
normalize_and_source "${env_file}"
else
echo "[WARN] 未找到 ${env_file},使用 env.example 默认值"
normalize_and_source "${example_file}"
fi
APP_HOME="${APP_HOME:-${PROJECT_ROOT}}"
APP_NAME="${APP_NAME:-linkey-website}"
JAR_NAME="${JAR_NAME:-website-0.0.1-SNAPSHOT.jar}"
SERVER_PORT="${SERVER_PORT:-8898}"
SPRING_PROFILES_ACTIVE="${SPRING_PROFILES_ACTIVE:-prod}"
JAVA_OPTS="${JAVA_OPTS:--Xms256m -Xmx512m -Dfile.encoding=UTF-8}"
DB_HOST="${DB_HOST:-127.0.0.1}"
DB_PORT="${DB_PORT:-3306}"
DB_NAME="${DB_NAME:-website}"
DB_USER="${DB_USER:-root}"
DB_PASSWORD="${DB_PASSWORD:-root}"
HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:${SERVER_PORT}/}"
HEALTH_RETRIES="${HEALTH_RETRIES:-30}"
HEALTH_INTERVAL="${HEALTH_INTERVAL:-2}"
SKIP_TESTS="${SKIP_TESTS:-true}"
if [[ -n "${JAVA_HOME:-}" && -z "${JAVA_BIN:-}" ]]; then
JAVA_BIN="${JAVA_HOME}/bin/java"
fi
JAVA_BIN="${JAVA_BIN:-java}"
MVN_BIN="${MVN_BIN:-mvn}"
RUN_DIR="${APP_HOME}/deploy/run"
LOG_DIR="${APP_HOME}/logs"
BACKUP_DIR="${APP_HOME}/deploy/backup"
PID_FILE="${RUN_DIR}/${APP_NAME}.pid"
JAR_PATH="${APP_HOME}/target/${JAR_NAME}"
UPLOAD_DIR="${APP_HOME}/src/main/resources/static/img"
}
ensure_dirs() {
mkdir -p "${RUN_DIR}" "${LOG_DIR}" "${BACKUP_DIR}" "${UPLOAD_DIR}"
}
require_cmd() {
local cmd="$1"
if ! command -v "${cmd}" >/dev/null 2>&1 && [[ ! -x "${cmd}" ]]; then
echo "[ERROR] 找不到命令: ${cmd}"
exit 1
fi
}
is_running() {
if [[ -f "${PID_FILE}" ]]; then
local pid
pid="$(cat "${PID_FILE}")"
if [[ -n "${pid}" ]] && kill -0 "${pid}" >/dev/null 2>&1; then
return 0
fi
fi
# 兜底:按 jar 名查进程
if pgrep -f "${JAR_NAME}" >/dev/null 2>&1; then
return 0
fi
return 1
}
get_pid() {
if [[ -f "${PID_FILE}" ]]; then
local pid
pid="$(cat "${PID_FILE}")"
if [[ -n "${pid}" ]] && kill -0 "${pid}" >/dev/null 2>&1; then
echo "${pid}"
return 0
fi
fi
pgrep -f "${JAR_NAME}" | head -n 1 || true
}
wait_health() {
local i=1
echo "[INFO] 健康检查: ${HEALTH_URL}"
while (( i <= HEALTH_RETRIES )); do
if curl -fsS --max-time 3 "${HEALTH_URL}" >/dev/null 2>&1; then
echo "[OK] 服务已就绪 (${i}/${HEALTH_RETRIES})"
return 0
fi
echo "[INFO] 等待启动... (${i}/${HEALTH_RETRIES})"
sleep "${HEALTH_INTERVAL}"
((i++))
done
echo "[ERROR] 健康检查失败,请查看 ${LOG_DIR}/app.log"
return 1
}
print_banner() {
echo "========================================"
echo " ${APP_NAME} 部署工具"
echo " APP_HOME : ${APP_HOME}"
echo " PORT : ${SERVER_PORT}"
echo " PROFILE : ${SPRING_PROFILES_ACTIVE}"
echo "========================================"
}
2、deploy.sh:一键部署入口实现构建 → 备份旧包 → 停服 → 启服 → 健康检查
#!/usr/bin/env bash
# 一键部署:构建 → 备份旧包 → 停服 → 启服 → 健康检查
#
# 用法:
# bash deploy/deploy.sh
# bash deploy/deploy.sh --skip-build # 跳过 Maven 构建,仅重启
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=common.sh
source "${SCRIPT_DIR}/common.sh"
load_env
ensure_dirs
print_banner
SKIP_BUILD=false
for arg in "$@"; do
case "${arg}" in
--skip-build) SKIP_BUILD=true ;;
-h|--help)
echo "Usage: bash deploy/deploy.sh [--skip-build]"
exit 0
;;
esac
done
cd "${APP_HOME}"
# 构建前备份旧 jar,便于回滚
if [[ -f "${JAR_PATH}" ]]; then
TS="$(date +%Y%m%d%H%M%S)"
cp -f "${JAR_PATH}" "${BACKUP_DIR}/${JAR_NAME}.${TS}.bak"
echo "[INFO] 已备份旧包 → ${BACKUP_DIR}/${JAR_NAME}.${TS}.bak"
ls -1t "${BACKUP_DIR}/${JAR_NAME}".*.bak 2>/dev/null | tail -n +6 | xargs -r rm -f
fi
if [[ "${SKIP_BUILD}" != "true" ]]; then
require_cmd "${MVN_BIN}"
echo "[INFO] Maven 构建中..."
if [[ "${SKIP_TESTS}" == "true" ]]; then
"${MVN_BIN}" clean package -DskipTests
else
"${MVN_BIN}" clean package
fi
if [[ ! -f "${JAR_PATH}" ]]; then
echo "[ERROR] 构建完成但未找到 ${JAR_PATH}"
exit 1
fi
else
echo "[INFO] 跳过构建 (--skip-build)"
if [[ ! -f "${JAR_PATH}" ]]; then
echo "[ERROR] 找不到 ${JAR_PATH},无法跳过构建"
exit 1
fi
fi
bash "${SCRIPT_DIR}/stop.sh"
bash "${SCRIPT_DIR}/start.sh"
echo "[DONE] 部署完成 → http://$(hostname -I 2>/dev/null | awk '{print $1}'):${SERVER_PORT}/"