#!/bin/bash # ============================================ # 中华药典 AI Agent — Java 后端部署脚本 # 用法: ./java-deploy.sh {start|stop|restart|status|logs|build|deploy|rollback} # ============================================ set -e APP_DIR="/opt/pharmacopoeia-ai/backend-java" PROJECT_DIR="/opt/pharmacopoeia-ai" JAR_FILE="$APP_DIR/target/pharmacopoeia-ai-1.0.0.jar" LOG_DIR="/var/log/pharmacopoeia" PID_FILE="$APP_DIR/app.pid" # 加载 .env 环境变量 load_env() { if [ -f "$PROJECT_DIR/.env" ]; then set -a source "$PROJECT_DIR/.env" set +a fi } load_env mkdir -p "$LOG_DIR" # ---- 服务端口 ---- PORT=${SERVER_PORT:-9000} HEALTH_URL="http://localhost:${PORT}/health" # ============================================ # 通用函数 # ============================================ running() { [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null } get_pid() { cat "$PID_FILE" 2>/dev/null || echo "N/A" } health_check() { curl -sf "$HEALTH_URL" > /dev/null 2>&1 } # ============================================ # 启动 # ============================================ start() { if running; then echo "⚠️ 后端已在运行中 (PID: $(get_pid))" curl -sf "$HEALTH_URL" | python3 -m json.tool 2>/dev/null || true return 0 fi # 自动编译(如果 JAR 不存在) if [ ! -f "$JAR_FILE" ]; then echo "📦 JAR 不存在,正在编译..." cd "$APP_DIR" mvn clean package -DskipTests -q echo "✅ 编译完成" fi echo "🚀 启动后端..." cd "$APP_DIR" JVM_OPTS="-Xms512m -Xmx2g" JVM_OPTS="$JVM_OPTS -XX:+UseG1GC" JVM_OPTS="$JVM_OPTS -Dfile.encoding=UTF-8" JVM_OPTS="$JVM_OPTS -Djava.security.egd=file:/dev/./urandom" nohup java $JVM_OPTS -jar "$JAR_FILE" \ --server.port="$PORT" \ >> "$LOG_DIR/backend.log" 2>&1 & echo $! > "$PID_FILE" echo " PID: $(get_pid)" # 等启动完成 echo -n " 等待启动" for i in $(seq 1 30); do if health_check 2>/dev/null; then echo "" echo "✅ 启动成功!" curl -sf "$HEALTH_URL" | python3 -m json.tool 2>/dev/null || true return 0 fi echo -n "." sleep 1 done echo "" echo "❌ 启动超时,查看日志:" tail -30 "$LOG_DIR/backend.log" rm -f "$PID_FILE" return 1 } # ============================================ # 停止 # ============================================ stop() { if ! running; then echo "后端未运行" rm -f "$PID_FILE" return 0 fi PID=$(get_pid) echo "🛑 停止后端 (PID: $PID)..." kill "$PID" for i in $(seq 1 30); do if ! kill -0 "$PID" 2>/dev/null; then echo "✅ 已停止" rm -f "$PID_FILE" return 0 fi sleep 1 done echo "⚠️ 超时,强制终止..." kill -9 "$PID" 2>/dev/null || true rm -f "$PID_FILE" echo "✅ 已强制停止" } # ============================================ # 重启 # ============================================ restart() { echo "⏳ 重启后端..." stop sleep 2 start } # ============================================ # 状态 # ============================================ status() { echo "========================================" echo " 中华药典 AI — Java 后端" echo "========================================" if running; then echo " 状态: ✅ 运行中" echo " PID: $(get_pid)" echo " 端口: $PORT" echo "" curl -sf "$HEALTH_URL" | python3 -m json.tool 2>/dev/null || echo " (健康检查无响应)" else echo " 状态: ❌ 未运行" fi echo "" echo " 日志: $LOG_DIR/backend.log" echo "========================================" } # ============================================ # 日志 # ============================================ logs() { if [ "$1" = "-f" ]; then tail -f "$LOG_DIR/backend.log" else echo "=== 最近 100 行日志 ===" tail -${1:-100} "$LOG_DIR/backend.log" fi } # ============================================ # 编译 # ============================================ build() { echo "📦 拉取最新代码..." cd "$PROJECT_DIR" git pull echo "🔨 编译打包..." cd "$APP_DIR" mvn clean package -DskipTests -q if [ -f "$JAR_FILE" ]; then echo "✅ 编译成功: $JAR_FILE" ls -lh "$JAR_FILE" else echo "❌ 编译失败" return 1 fi } # ============================================ # 一键部署(拉代码 → 编译 → 备份 → 重启) # ============================================ deploy() { echo "========================================" echo " 一键部署" echo "========================================" echo "" echo "📥 拉取代码..." cd "$PROJECT_DIR" git pull echo "" echo "🔨 编译打包..." cd "$APP_DIR" mvn clean package -DskipTests -q if [ ! -f "$JAR_FILE" ]; then echo "❌ 编译失败,取消部署" return 1 fi echo "✅ 编译完成: $(ls -lh "$JAR_FILE" | awk '{print $5}')" # 备份当前 JAR BACKUP_DIR="$APP_DIR/backups" mkdir -p "$BACKUP_DIR" if [ -f "$JAR_FILE" ]; then BACKUP_FILE="$BACKUP_DIR/pharmacopoeia-ai-$(date +%Y%m%d-%H%M%S).jar" cp "$JAR_FILE" "$BACKUP_FILE" echo "📦 已备份: $BACKUP_FILE" # 保留最近 5 个备份 ls -t "$BACKUP_DIR"/*.jar 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null || true fi echo "" restart } # ============================================ # 回滚 # ============================================ rollback() { BACKUP_DIR="$APP_DIR/backups" if [ ! -d "$BACKUP_DIR" ] || [ -z "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]; then echo "❌ 没有可用的备份" return 1 fi echo "可用备份:" ls -lht "$BACKUP_DIR"/*.jar 2>/dev/null | head -10 echo "" if [ -n "$1" ]; then ROLLBACK_FILE="$1" else ROLLBACK_FILE=$(ls -t "$BACKUP_DIR"/*.jar 2>/dev/null | head -1) echo "回滚到最新备份: $(basename "$ROLLBACK_FILE")" fi if [ ! -f "$ROLLBACK_FILE" ]; then echo "❌ 备份文件不存在: $ROLLBACK_FILE" return 1 fi echo "" echo "⏪ 回滚..." cp "$ROLLBACK_FILE" "$JAR_FILE" echo "✅ 已替换为: $(basename "$ROLLBACK_FILE")" restart } # ============================================ # 入口 # ============================================ case "${1:-start}" in start) start ;; stop) stop ;; restart) restart ;; status) status ;; logs) logs "${2:-100}" ;; logf) logs -f ;; build) build ;; deploy) deploy ;; rollback) rollback "$2" ;; help|--help|-h) echo "用法: $0 {start|stop|restart|status|logs|logf|build|deploy|rollback}" echo "" echo " start 启动后端" echo " stop 停止后端" echo " restart 重启后端" echo " status 查看状态" echo " logs 查看最近 100 行日志" echo " logf 实时跟踪日志" echo " build 拉代码 + 编译" echo " deploy 拉代码 + 编译 + 备份 + 重启(一键上线)" echo " rollback 回滚到上一个备份(可指定备份文件路径)" exit 0 ;; *) echo "用法: $0 {start|stop|restart|status|logs|logf|build|deploy|rollback}" exit 1 ;; esac