#!/bin/sh set -eu JAVA_BIN="/home/webapp/env/java/bin/java" BACKEND_PORT=63310 SPRING_PROFILE="pro" JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd) BACKEND_DIR="$SCRIPT_DIR/backend" FRONTEND_DIR="$SCRIPT_DIR/frontend" BACKEND_JAR_TARGET="$BACKEND_DIR/ruoyi-admin.jar" BACKEND_PID_FILE="$BACKEND_DIR/backend.pid" BACKEND_LOG_FILE="$BACKEND_DIR/backend-console.log" FRONTEND_DIST_ARCHIVE="$FRONTEND_DIR/dist.zip" FRONTEND_DIST_DIR="$FRONTEND_DIR/dist" BACKEND_MARKER="-Dloan.pricing.home=$SCRIPT_DIR" usage() { cat <<'EOF' 用法: ./deploy_from_package.sh EOF } timestamp() { date "+%Y%m%d%H%M%S" } log_info() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" } log_error() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >&2 } cleanup() { if [ -n "${WORK_DIR:-}" ] && [ -d "$WORK_DIR" ]; then rm -rf "$WORK_DIR" fi } require_dir() { if [ ! -d "$1" ]; then log_error "缺少目录: $1" exit 1 fi } require_command() { if ! command -v "$1" >/dev/null 2>&1; then log_error "缺少命令: $1" exit 1 fi } find_release_archive() { archives=$(find "$SCRIPT_DIR" -maxdepth 1 -type f -name '*.zip' ! -name 'dist.zip') count=$(printf '%s\n' "$archives" | sed '/^$/d' | wc -l | tr -d ' ') if [ "$count" -ne 1 ]; then log_error "脚本同目录发布 zip 数量不正确,期望 1 个,实际 $count 个" exit 1 fi printf '%s\n' "$archives" } extract_release_package() { release_archive="$1" release_extract_dir="$2" mkdir -p "$release_extract_dir" unzip -oq "$release_archive" -d "$release_extract_dir" } assert_single_jar() { search_dir="$1" count=$(find "$search_dir" -type f -name '*.jar' | wc -l | tr -d ' ') if [ "$count" -ne 1 ]; then log_error "后端 jar 数量不正确,期望 1 个,实际 $count 个" exit 1 fi find "$search_dir" -type f -name '*.jar' | head -n 1 } assert_single_dist_zip() { search_dir="$1" count=$(find "$search_dir" -type f -name 'dist.zip' | wc -l | tr -d ' ') if [ "$count" -ne 1 ]; then log_error "前端 dist.zip 数量不正确,期望 1 个,实际 $count 个" exit 1 fi find "$search_dir" -type f -name 'dist.zip' | head -n 1 } backup_backend_jar() { if [ -f "$BACKEND_JAR_TARGET" ]; then mv "$BACKEND_JAR_TARGET" "$BACKEND_JAR_TARGET.$(timestamp).bak" fi } backup_frontend_dist() { if [ -d "$FRONTEND_DIST_DIR" ]; then mv "$FRONTEND_DIST_DIR" "$FRONTEND_DIR/dist-$(timestamp)" fi } deploy_backend_jar() { source_jar="$1" mv "$source_jar" "$BACKEND_JAR_TARGET" } deploy_frontend_dist() { source_dist_zip="$1" rm -f "$FRONTEND_DIST_ARCHIVE" rm -rf "$FRONTEND_DIST_DIR" mv "$source_dist_zip" "$FRONTEND_DIST_ARCHIVE" unzip -oq "$FRONTEND_DIST_ARCHIVE" -d "$FRONTEND_DIR" if [ ! -d "$FRONTEND_DIST_DIR" ]; then log_error "dist.zip 解压后未找到 $FRONTEND_DIST_DIR" exit 1 fi } collect_backend_pids() { ps -ef | awk -v marker="$BACKEND_MARKER" -v jar="$BACKEND_JAR_TARGET" ' index($0, marker) > 0 && index($0, jar) > 0 {print $2} ' | xargs 2>/dev/null || true } stop_backend() { pids=$(collect_backend_pids) if [ -z "${pids:-}" ]; then rm -f "$BACKEND_PID_FILE" log_info "未发现运行中的后端进程" return 0 fi log_info "停止后端进程: $pids" for pid in $pids; do kill -TERM "$pid" 2>/dev/null || true done elapsed=0 remaining="$pids" while [ "$elapsed" -lt 30 ]; do remaining="" for pid in $pids; do if kill -0 "$pid" 2>/dev/null; then remaining="$remaining $pid" fi done remaining=$(echo "$remaining" | xargs 2>/dev/null || true) if [ -z "${remaining:-}" ]; then break fi sleep 1 elapsed=$((elapsed + 1)) done if [ -n "${remaining:-}" ]; then log_info "执行强制停止: $remaining" for pid in $remaining; do kill -KILL "$pid" 2>/dev/null || true done fi rm -f "$BACKEND_PID_FILE" } start_backend() { if [ ! -x "$JAVA_BIN" ]; then log_error "未检测到可执行 Java: $JAVA_BIN" exit 1 fi if [ ! -f "$BACKEND_JAR_TARGET" ]; then log_error "未找到后端 jar: $BACKEND_JAR_TARGET" exit 1 fi if [ -n "$(collect_backend_pids)" ]; then log_error "检测到后端已在运行,请先停止旧进程" exit 1 fi printf '\n===== %s deploy =====\n' "$(date '+%Y-%m-%d %H:%M:%S')" >> "$BACKEND_LOG_FILE" nohup "$JAVA_BIN" $JAVA_OPTS "$BACKEND_MARKER" -jar "$BACKEND_JAR_TARGET" \ --spring.profiles.active="$SPRING_PROFILE" \ --server.port="$BACKEND_PORT" >> "$BACKEND_LOG_FILE" 2>&1 & backend_pid=$! printf '%s\n' "$backend_pid" > "$BACKEND_PID_FILE" sleep 1 if ! kill -0 "$backend_pid" 2>/dev/null; then log_error "后端启动失败,请检查日志: $BACKEND_LOG_FILE" exit 1 fi log_info "后端已启动,PID: $backend_pid" } main() { if [ "$#" -ne 0 ]; then usage exit 1 fi require_dir "$BACKEND_DIR" require_dir "$FRONTEND_DIR" require_command unzip require_command find require_command ps require_command nohup release_archive=$(find_release_archive) WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/deploy_from_package.XXXXXX") trap cleanup EXIT INT TERM extract_release_package "$release_archive" "$WORK_DIR/package" backend_jar_source=$(assert_single_jar "$WORK_DIR/package") frontend_dist_source=$(assert_single_dist_zip "$WORK_DIR/package") backup_backend_jar backup_frontend_dist stop_backend deploy_backend_jar "$backend_jar_source" deploy_frontend_dist "$frontend_dist_source" start_backend log_info "部署完成" log_info "后端 jar: $BACKEND_JAR_TARGET" log_info "前端目录: $FRONTEND_DIST_DIR" } main "$@"