Files
shz-backend/buildAndStart-test.sh
2026-07-15 15:14:25 +08:00

326 lines
8.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# SHZ 后端测试环境部署脚本
#
# 默认部署方式:
# ./buildAndStart-test.sh
#
# 常用命令:
# ./buildAndStart-test.sh status
# ./buildAndStart-test.sh stop
# ./buildAndStart-test.sh logs
#
# deploy/start 成功后会自动跟踪后端日志;按 Ctrl+C 退出日志跟踪,不会停止后端进程。
#
# 可通过环境变量覆盖默认配置,例如:
# PROFILE=test MAVEN_BIN=/opt/apache-maven-3.9.9/bin/mvn ./buildAndStart-test.sh
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# 目录和应用配置。默认使用脚本所在目录,适配当前服务器的
# /data/code/shz-backendBASE_PATH 仍可用于部署到其他目录。
BASE_PATH="${BASE_PATH:-$SCRIPT_DIR}"
JAR_PATH="${JAR_PATH:-$BASE_PATH/ruoyi-admin/target/ruoyi-admin.jar}"
LOG_PATH="${LOG_PATH:-$BASE_PATH/logs}"
LOG_FILE="${LOG_FILE:-$LOG_PATH/backend.log}"
PID_FILE="${PID_FILE:-$LOG_PATH/backend.pid}"
LOCK_FILE="${LOCK_FILE:-$LOG_PATH/deploy.lock}"
PROFILE="${PROFILE:-test}"
GIT_REMOTE="${GIT_REMOTE:-origin}"
GIT_BRANCH="${GIT_BRANCH:-main}"
UPDATE_CODE="${UPDATE_CODE:-true}"
MAVEN_THREADS="${MAVEN_THREADS:-4}"
STARTUP_TIMEOUT="${STARTUP_TIMEOUT:-120}"
HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:9091/}"
# 测试 profile 默认使用这些本机目录。目录不存在时由脚本创建。
RUOYI_PROFILE_DIR="${RUOYI_PROFILE_DIR:-/home/ruoyi/uploadPath}"
FILE_UPLOAD_DIR="${FILE_UPLOAD_DIR:-/opt/service/file/data/file}"
JVM_MEMORY="${JVM_MEMORY:--Xms2048M -Xmx2048M -XX:MaxDirectMemorySize=2048M}"
JVM_DEBUG="${JVM_DEBUG:-}"
JAVA_BIN="${JAVA_BIN:-}"
MAVEN_BIN="${MAVEN_BIN:-}"
__green() {
printf '\033[1;32m%s\033[0m\n' "$1"
}
__red() {
printf '\033[1;31m%s\033[0m\n' "$1" >&2
}
__yellow() {
printf '\033[1;33m%s\033[0m\n' "$1"
}
is_true() {
case "${1:-}" in
1|true|TRUE|yes|YES|y|Y) return 0 ;;
*) return 1 ;;
esac
}
fail() {
__red "$1"
exit 1
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "缺少必要命令: $1"
}
resolve_java() {
if [[ -n "$JAVA_BIN" ]]; then
[[ -x "$JAVA_BIN" ]] || fail "JAVA_BIN 不可执行: $JAVA_BIN"
return
fi
if command -v java >/dev/null 2>&1; then
JAVA_BIN="$(command -v java)"
elif [[ -n "${JAVA_HOME:-}" && -x "$JAVA_HOME/bin/java" ]]; then
JAVA_BIN="$JAVA_HOME/bin/java"
else
fail "未找到 Java。请安装 JDK 8+,或设置 JAVA_BIN/JAVA_HOME。"
fi
}
resolve_maven() {
if [[ -n "$MAVEN_BIN" ]]; then
[[ -x "$MAVEN_BIN" ]] || fail "MAVEN_BIN 不可执行: $MAVEN_BIN"
return
fi
if command -v mvn >/dev/null 2>&1; then
MAVEN_BIN="$(command -v mvn)"
return
fi
# 兼容服务器上未加入 PATH 的 Maven 安装。
local candidate
for candidate in \
"$BASE_PATH/mvnw" \
"${MAVEN_HOME:-}/bin/mvn" \
/usr/local/apache-maven/bin/mvn \
/opt/apache-maven/bin/mvn \
/opt/apache-maven-3.9.9/bin/mvn \
/root/apache-maven/bin/mvn \
/root/apache-maven-3.9.9/bin/mvn; do
if [[ -n "$candidate" && -x "$candidate" ]]; then
MAVEN_BIN="$candidate"
return
fi
done
fail "未找到 Maven。请先安装 Maven 3.9+,或执行时设置 MAVEN_BIN=/实际路径/bin/mvn。"
}
prepare_environment() {
[[ -d "$BASE_PATH" ]] || fail "项目目录不存在: $BASE_PATH"
[[ -f "$BASE_PATH/pom.xml" ]] || fail "不是有效的 Maven 项目目录(缺少 pom.xml: $BASE_PATH"
[[ -f "$BASE_PATH/ruoyi-admin/src/main/resources/application-${PROFILE}.yml" ]] \
|| fail "未找到 profile 配置文件: $BASE_PATH/ruoyi-admin/src/main/resources/application-${PROFILE}.yml"
mkdir -p "$LOG_PATH" "$RUOYI_PROFILE_DIR" "$FILE_UPLOAD_DIR"
}
find_app_pids() {
local pid main_class pid_from_file cmdline
declare -A seen=()
# 优先读取本脚本记录的 PID并确认该 PID 仍然是目标 JAR。
if [[ -s "$PID_FILE" ]]; then
pid_from_file="$(<"$PID_FILE")"
if [[ "$pid_from_file" =~ ^[0-9]+$ && -r "/proc/$pid_from_file/cmdline" ]]; then
cmdline="$(tr '\0' ' ' < "/proc/$pid_from_file/cmdline")"
if [[ "$cmdline" == *"-jar $JAR_PATH"* ]]; then
printf '%s\n' "$pid_from_file"
seen["$pid_from_file"]=1
fi
fi
fi
# 兼容脚本升级前启动的实例。
if command -v jps >/dev/null 2>&1; then
while read -r pid main_class; do
if [[ "$main_class" == "$JAR_PATH" && -z "${seen[$pid]+x}" ]]; then
printf '%s\n' "$pid"
seen["$pid"]=1
fi
done < <(jps -l 2>/dev/null || true)
fi
}
pid_is_running() {
local pid="$1"
[[ "$pid" =~ ^[0-9]+$ ]] && kill -0 "$pid" 2>/dev/null
}
stop_app() {
local pid second
local -a pids=()
mapfile -t pids < <(find_app_pids)
if [[ "${#pids[@]}" -eq 0 ]]; then
rm -f "$PID_FILE"
__green "未找到运行中的测试环境进程"
return 0
fi
__green "停止进程: ${pids[*]}"
for pid in "${pids[@]}"; do
kill "$pid" 2>/dev/null || true
done
for second in $(seq 1 30); do
local remaining=()
for pid in "${pids[@]}"; do
pid_is_running "$pid" && remaining+=("$pid")
done
if [[ "${#remaining[@]}" -eq 0 ]]; then
rm -f "$PID_FILE"
__green "旧进程已停止"
return 0
fi
sleep 1
done
__yellow "进程未在 30 秒内退出,执行强制终止: ${remaining[*]}"
for pid in "${remaining[@]}"; do
kill -9 "$pid" 2>/dev/null || true
done
rm -f "$PID_FILE"
}
update_code() {
if ! is_true "$UPDATE_CODE"; then
__yellow "跳过 Git 更新UPDATE_CODE=$UPDATE_CODE"
return
fi
[[ -d "$BASE_PATH/.git" ]] || fail "项目不是 Git 仓库,无法更新代码: $BASE_PATH"
__green "更新代码: $GIT_REMOTE/$GIT_BRANCH"
git -C "$BASE_PATH" fetch "$GIT_REMOTE" "$GIT_BRANCH"
git -C "$BASE_PATH" reset --hard "$GIT_REMOTE/$GIT_BRANCH"
}
build_app() {
resolve_maven
__green "开始构建Maven: $MAVEN_BIN"
(
cd "$BASE_PATH"
"$MAVEN_BIN" -B -T "$MAVEN_THREADS" clean install -DskipTests
) || fail "Maven 构建失败"
[[ -s "$JAR_PATH" ]] || fail "构建完成但 JAR 文件不存在: $JAR_PATH"
__green "构建完成: $JAR_PATH"
}
start_app() {
local pid second
local -a jvm_options=()
read -r -a jvm_options <<< "$JVM_MEMORY $JVM_DEBUG"
stop_app
: > "$LOG_FILE"
__green "启动测试环境profile=$PROFILE"
(
cd "$BASE_PATH"
nohup "$JAVA_BIN" "${jvm_options[@]}" -jar "$JAR_PATH" \
"--spring.profiles.active=$PROFILE" >> "$LOG_FILE" 2>&1 < /dev/null &
echo "$!" > "$PID_FILE"
)
pid="$(<"$PID_FILE")"
for second in $(seq 1 "$STARTUP_TIMEOUT"); do
if ! pid_is_running "$pid"; then
__red "应用启动失败,进程已退出。最近日志:"
tail -n 80 "$LOG_FILE" >&2 || true
rm -f "$PID_FILE"
return 1
fi
if command -v curl >/dev/null 2>&1 && curl -fsS --max-time 2 "$HEALTH_URL" >/dev/null 2>&1; then
__green "应用启动成功: PID=$pid,健康检查通过($HEALTH_URL"
return 0
fi
sleep 1
done
__red "应用进程仍在运行,但健康检查超时(${STARTUP_TIMEOUT}s: $HEALTH_URL"
__red "最近日志:"
tail -n 80 "$LOG_FILE" >&2 || true
return 1
}
deploy() {
prepare_environment
exec 9>"$LOCK_FILE"
flock -n 9 || fail "已有另一个部署任务正在执行: $LOCK_FILE"
update_code
build_app
start_app
__green "部署完成。日志文件: $LOG_FILE"
logs
}
status() {
prepare_environment
local -a pids=()
mapfile -t pids < <(find_app_pids)
if [[ "${#pids[@]}" -eq 0 ]]; then
__yellow "测试环境未运行"
return 1
fi
__green "测试环境运行中: PID=${pids[*]}"
if command -v curl >/dev/null 2>&1 && curl -fsS --max-time 3 "$HEALTH_URL" >/dev/null 2>&1; then
__green "健康检查通过: $HEALTH_URL"
else
__yellow "健康检查未通过: $HEALTH_URL"
fi
}
logs() {
prepare_environment
touch "$LOG_FILE"
tail -f "$LOG_FILE"
}
main() {
# 非交互式部署也加载系统 PATH不存在时不影响运行。
# 某些服务器的 /etc/profile 会引用未定义变量,不能在 nounset 下直接 source。
if [[ -r /etc/profile ]]; then
set +u
source /etc/profile || true
set -u
fi
resolve_java
case "${1:-deploy}" in
deploy|start)
deploy
;;
stop)
prepare_environment
stop_app
;;
status)
status
;;
logs|log)
logs
;;
*)
__red "用法: $0 [deploy|start|stop|status|logs]"
exit 2
;;
esac
}
main "$@"