#!/bin/bash #=============================================================================== # RuoYi-Vue-Plus 风控平台 - 服务管理脚本 # 用法: ./server.sh {start|stop|restart|status|logs} #=============================================================================== set -e # ==================== 配置区(按需修改) ==================== # 应用名称 APP_NAME="risk-control-platform" # JAR 包路径(相对于脚本目录) JAR_PATH="ruoyi-admin/target/ruoyi-admin.jar" # Spring 激活环境(dev / prod / test) PROFILE="${SPRING_PROFILE:-prod}" # 服务端口(用于健康检查) SERVER_PORT=9224 # PID 文件 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PID_FILE="$SCRIPT_DIR/${APP_NAME}.pid" # 日志文件 LOG_DIR="$SCRIPT_DIR/logs" LOG_FILE="$LOG_DIR/${APP_NAME}.log" GC_LOG_FILE="$LOG_DIR/gc.log" # JVM 参数(直接指定 JDK 路径,避免环境变量丢失问题) JAVA_HOME="/usr/local/graalvm-jdk-25.0.2" JAVA_OPTS="-Xms512m -Xmx2048m -Xmn512m" JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=200" JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$LOG_DIR" # JDK 9+ 统一 GC 日志格式,不再使用 PrintGCDetails/PrintGCDateStamps/Xloggc JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:file=$GC_LOG_FILE:time,level,tags:filecount=5,filesize=50M" JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8 -Djava.awt.headless=true" # 优雅停机等待时间(秒) GRACEFUL_TIMEOUT=30 # 启动后健康检查重试次数与间隔(秒) HEALTH_RETRIES=30 HEALTH_INTERVAL=2 # ==================== 函数定义 ==================== # 获取 Java 命令(优先使用 JAVA_HOME,fallback 到系统 PATH) get_java() { if [ -x "$JAVA_HOME/bin/java" ]; then echo "$JAVA_HOME/bin/java" return 0 fi # fallback 到系统 java local sys_java sys_java=$(command -v java 2>/dev/null) if [ -n "$sys_java" ]; then echo "[WARN] $JAVA_HOME/bin/java 不存在或不可执行,fallback 到 $sys_java" >&2 echo "$sys_java" return 0 fi return 1 } # 检查 Java 版本 check_java() { local java_cmd java_cmd=$(get_java) if [ -z "$java_cmd" ] || ! command -v "$java_cmd" &>/dev/null; then echo "[ERROR] Java 未找到" echo "[HINT] 请设置 JAVA_HOME 或将 java 加入 PATH" echo " 例如: export JAVA_HOME=/usr/local/java && ./server.sh start" exit 1 fi local full_version full_version=$("$java_cmd" -version 2>&1 | head -1) echo "[INFO] Java 命令: $java_cmd" echo "[INFO] Java 版本: $full_version" # 提取主版本号(兼容 Linux/macOS,处理 1.8、9、11、17、21、25 等格式) local version version=$(echo "$full_version" | sed 's/.*version "//' | sed 's/\..*//') # JDK 1.8 格式: version="1.8.0_402" → 提取后为 "1" → 需改为 "8" if [ "$version" = "1" ]; then version=$(echo "$full_version" | sed 's/.*version "1\.//' | sed 's/\..*//') fi if [ -z "$version" ] || ! echo "$version" | grep -qE '^[0-9]+$'; then echo "[ERROR] 无法解析 Java 版本号" exit 1 fi if [ "$version" -lt 17 ]; then echo "[ERROR] 需要 Java 17+,当前主版本: $version" echo "[HINT] 当前使用的是 JDK 8,请安装 JDK 17+ 并设置 JAVA_HOME:" echo " export JAVA_HOME=/usr/local/graalvm-jdk-25.0.2 && ./server.sh start" exit 1 fi echo "[INFO] Java 版本检查通过 (主版本: $version)" } # 获取进程 PID get_pid() { if [ -f "$PID_FILE" ]; then local pid pid=$(cat "$PID_FILE") if ps -p "$pid" &>/dev/null; then echo "$pid" return 0 fi fi # 备选:通过端口找 PID local port_pid port_pid=$(lsof -t -i:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null | head -1) if [ -n "$port_pid" ]; then echo "$port_pid" return 0 fi return 1 } # 检查是否运行中 is_running() { local pid pid=$(get_pid 2>/dev/null) [ -n "$pid" ] } # 启动服务 start() { if is_running; then local pid pid=$(get_pid) echo "[INFO] $APP_NAME 已在运行 (PID: $pid)" return 0 fi check_java # 确保 JAR 存在 if [ ! -f "$SCRIPT_DIR/$JAR_PATH" ]; then echo "[ERROR] JAR 包不存在: $SCRIPT_DIR/$JAR_PATH" echo "[HINT] 请先执行: mvn clean package -DskipTests" exit 1 fi # 创建日志目录 mkdir -p "$LOG_DIR" local java_cmd java_cmd=$(get_java) echo "[INFO] 正在启动 $APP_NAME ..." echo "[INFO] Profile: $PROFILE | 端口: $SERVER_PORT" nohup "$java_cmd" $JAVA_OPTS \ -jar "$SCRIPT_DIR/$JAR_PATH" \ --spring.profiles.active="$PROFILE" \ --server.port="$SERVER_PORT" \ >> "$LOG_FILE" 2>&1 & local new_pid=$! echo "$new_pid" > "$PID_FILE" echo "[INFO] 进程已启动 (PID: $new_pid),等待服务就绪..." # 健康检查 local retries=0 while [ $retries -lt $HEALTH_RETRIES ]; do sleep $HEALTH_INTERVAL if ! ps -p "$new_pid" &>/dev/null; then echo "[ERROR] 进程已退出,请查看日志: $LOG_FILE" tail -20 "$LOG_FILE" exit 1 fi if curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:$SERVER_PORT/" 2>/dev/null | grep -qE "^(200|302|401|403)$"; then echo "[OK] $APP_NAME 启动成功 (PID: $new_pid, 端口: $SERVER_PORT)" return 0 fi retries=$((retries + 1)) echo -n "." done echo "" echo "[WARN] 端口 $SERVER_PORT 在 ${HEALTH_RETRIES} 次重试后仍未响应,但进程存活" echo "[INFO] 请查看日志: tail -f $LOG_FILE" } # 停止服务 stop() { local pid pid=$(get_pid 2>/dev/null) if [ -z "$pid" ]; then echo "[INFO] $APP_NAME 未运行" rm -f "$PID_FILE" return 0 fi echo "[INFO] 正在停止 $APP_NAME (PID: $pid) ..." # 优雅停机 kill "$pid" 2>/dev/null || true local waited=0 while [ $waited -lt $GRACEFUL_TIMEOUT ]; do if ! ps -p "$pid" &>/dev/null; then echo "[OK] $APP_NAME 已停止" rm -f "$PID_FILE" return 0 fi sleep 1 waited=$((waited + 1)) done # 强制终止 echo "[WARN] 优雅停机超时,强制终止..." kill -9 "$pid" 2>/dev/null || true sleep 2 if ! ps -p "$pid" &>/dev/null; then echo "[OK] $APP_NAME 已强制停止" else echo "[ERROR] 无法停止 PID: $pid" exit 1 fi rm -f "$PID_FILE" } # 重启服务 restart() { echo "[INFO] ====== 重启 $APP_NAME ======" stop sleep 2 start } # 查看状态 status() { if is_running; then local pid pid=$(get_pid) echo "[INFO] $APP_NAME 运行中" echo " PID: $pid" echo " 端口: $SERVER_PORT" echo " 启动时间: $(ps -o lstart= -p "$pid" 2>/dev/null)" echo " 内存: $(ps -o rss= -p "$pid" 2>/dev/null | awk '{printf "%.0f MB", $1/1024}')" else echo "[INFO] $APP_NAME 未运行" fi } # 查看日志 logs() { if [ -f "$LOG_FILE" ]; then tail -f "$LOG_FILE" else echo "[INFO] 日志文件不存在: $LOG_FILE" fi } # ==================== 主入口 ==================== case "${1:-}" in start) start ;; stop) stop ;; restart) restart ;; status) status ;; logs) logs ;; *) echo "用法: $0 {start|stop|restart|status|logs}" echo "" echo " start - 启动服务" echo " stop - 停止服务" echo " restart - 重启服务" echo " status - 查看运行状态" echo " logs - 实时查看日志" echo "" echo "环境变量:" echo " JAVA_HOME - JDK 路径 (默认: /usr/local/graalvm-jdk-25.0.2)" echo " SPRING_PROFILE - 环境 (默认: prod)" echo "" exit 1 ;; esac