From 5e780530de8f561a3f6a74ea8c743820332d0070 Mon Sep 17 00:00:00 2001 From: lapuda <577732344@qq.com> Date: Wed, 15 Jul 2026 14:25:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B5=8B=E8=AF=95=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E9=83=A8=E7=BD=B2=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- buildAndStart-test.sh | 322 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100755 buildAndStart-test.sh diff --git a/buildAndStart-test.sh b/buildAndStart-test.sh new file mode 100755 index 0000000..382748b --- /dev/null +++ b/buildAndStart-test.sh @@ -0,0 +1,322 @@ +#!/usr/bin/env bash + +# SHZ 后端测试环境部署脚本 +# +# 默认部署方式: +# ./buildAndStart-test.sh +# +# 常用命令: +# ./buildAndStart-test.sh status +# ./buildAndStart-test.sh stop +# ./buildAndStart-test.sh logs +# +# 可通过环境变量覆盖默认配置,例如: +# 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-backend;BASE_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" +} + +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 "$@"