#!/bin/sh set -eu SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) LOG_DIR="$ROOT_DIR/logs" CONSOLE_LOG="$LOG_DIR/backend-console.log" PID_FILE="$LOG_DIR/backend-java.pid" TARGET_DIR="$ROOT_DIR/ruoyi-admin/target" JAR_NAME="ruoyi-admin.jar" SERVER_PORT=62318 STOP_WAIT_SECONDS=30 APP_MARKER="-Dccdi.backend.root=$ROOT_DIR" JAVA_OPTS="$APP_MARKER -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" timestamp() { date "+%Y-%m-%d %H:%M:%S" } log_info() { printf '[%s] %s\n' "$(timestamp)" "$1" } log_error() { printf '[%s] %s\n' "$(timestamp)" "$1" >&2 } usage() { cat <<'EOF' 用法: ./bin/restart_java_backend.sh [start|stop|restart|status] 默认动作: restart 重新构建后端并重启,随后持续输出运行日志 EOF } ensure_command() { if ! command -v "$1" >/dev/null 2>&1; then log_error "缺少命令: $1" exit 1 fi } is_managed_backend_pid() { pid="$1" if [ -z "${pid:-}" ] || ! kill -0 "$pid" 2>/dev/null; then return 1 fi args=$(ps -o args= -p "$pid" 2>/dev/null || true) if [ -z "${args:-}" ]; then return 1 fi case "$args" in *"$APP_MARKER"*"$JAR_NAME"*|*"$JAR_NAME"*"$APP_MARKER"*) return 0 ;; esac if [ -f "$PID_FILE" ]; then file_pid=$(cat "$PID_FILE" 2>/dev/null || true) if [ "${file_pid:-}" = "$pid" ]; then case "$args" in *"java"*"-jar"*"$JAR_NAME"*) return 0 ;; esac fi fi return 1 } collect_pids() { all_pids="" if [ -f "$PID_FILE" ]; then file_pid=$(cat "$PID_FILE" 2>/dev/null || true) if [ -n "${file_pid:-}" ] && is_managed_backend_pid "$file_pid"; then all_pids="$all_pids $file_pid" fi fi marker_pids=$(pgrep -f -- "$APP_MARKER" 2>/dev/null || true) if [ -n "${marker_pids:-}" ]; then for pid in $marker_pids; do if is_managed_backend_pid "$pid"; then all_pids="$all_pids $pid" fi done fi port_pids=$(lsof -tiTCP:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true) if [ -n "${port_pids:-}" ]; then for pid in $port_pids; do if is_managed_backend_pid "$pid"; then all_pids="$all_pids $pid" fi done fi unique_pids="" for pid in $all_pids; do case " $unique_pids " in *" $pid "*) ;; *) unique_pids="$unique_pids $pid" ;; esac done printf '%s\n' "$(echo "$unique_pids" | xargs 2>/dev/null || true)" } build_backend() { log_info "开始构建后端: mvn -pl ruoyi-admin -am clean package -DskipTests" ( cd "$ROOT_DIR" mvn -pl ruoyi-admin -am clean package -DskipTests ) } stop_backend() { pids=$(collect_pids) if [ -z "${pids:-}" ]; then log_info "未发现运行中的后端进程" rm -f "$PID_FILE" return 0 fi log_info "准备停止后端进程: $pids" for pid in $pids; do kill -TERM "$pid" 2>/dev/null || true done remaining_pids="$pids" elapsed=0 while [ -n "${remaining_pids:-}" ] && [ "$elapsed" -lt "$STOP_WAIT_SECONDS" ]; do sleep 1 elapsed=$((elapsed + 1)) remaining_pids="" for pid in $pids; do if kill -0 "$pid" 2>/dev/null; then remaining_pids="$remaining_pids $pid" fi done remaining_pids=$(echo "$remaining_pids" | xargs 2>/dev/null || true) done if [ -n "${remaining_pids:-}" ]; then log_info "仍有进程未退出,执行强制停止: $remaining_pids" for pid in $remaining_pids; do kill -KILL "$pid" 2>/dev/null || true done fi rm -f "$PID_FILE" log_info "后端停止完成" } start_backend() { mkdir -p "$LOG_DIR" touch "$CONSOLE_LOG" printf '\n===== %s restart =====\n' "$(timestamp)" >> "$CONSOLE_LOG" log_info "开始启动后端,控制台日志输出到: $CONSOLE_LOG" if [ ! -f "$TARGET_DIR/$JAR_NAME" ]; then log_error "未找到打包产物: $TARGET_DIR/$JAR_NAME" exit 1 fi ( cd "$TARGET_DIR" nohup java $JAVA_OPTS -jar "$JAR_NAME" >> "$CONSOLE_LOG" 2>&1 & echo $! > "$PID_FILE" ) sleep 3 starter_pid=$(cat "$PID_FILE" 2>/dev/null || true) if [ -z "${starter_pid:-}" ] || ! kill -0 "$starter_pid" 2>/dev/null; then log_error "启动命令未保持运行,请检查日志: $CONSOLE_LOG" exit 1 fi log_info "启动命令已提交,PID: $starter_pid" } status_backend() { pids=$(collect_pids) if [ -n "${pids:-}" ]; then log_info "后端正在运行,进程: $pids" return 0 fi port_pids=$(lsof -tiTCP:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true) if [ -n "${port_pids:-}" ]; then log_info "未发现脚本托管的后端进程,但端口 $SERVER_PORT 被其他进程占用: $port_pids" else log_info "后端未运行" fi } follow_logs() { mkdir -p "$LOG_DIR" touch "$CONSOLE_LOG" log_info "持续输出日志中,按 Ctrl+C 仅退出日志查看" tail -n 200 -F "$CONSOLE_LOG" } start_action() { running_pids=$(collect_pids) if [ -n "${running_pids:-}" ]; then log_error "检测到已有后端进程在运行: ${running_pids},请先执行 stop 或 restart" exit 1 fi build_backend start_backend follow_logs } restart_action() { build_backend stop_backend start_backend follow_logs } main() { ensure_command mvn ensure_command lsof ensure_command pgrep ensure_command ps ensure_command tail action="${1:-restart}" case "$action" in start) start_action ;; stop) stop_backend ;; restart) restart_action ;; status) status_backend ;; -h|--help|help) usage ;; *) usage exit 1 ;; esac } main "$@"