#!/bin/bash # ============================================================ # 一键部署脚本 (2核4G 低配服务器优化版) # 用法: ./deploy.sh [admin|miniapp|all] [--skip-frontend] [--no-stop] # --no-stop 构建前不停止服务 (零停机,JVM 已限制内存) # 调试: bash -x ./deploy.sh # ============================================================ APP_DIR="/data/app" CODE_DIR="/data/code/car-wash-java" BACKUP_DIR="/data/backup" LOG_DIR="/data/logs" HISTORY_FILE="/data/history" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log() { echo -e "[$(date '+%H:%M:%S')] $1"; } info() { log "${GREEN}INFO${NC} $1"; } warn() { log "${YELLOW}WARN${NC} $1"; } error() { log "${RED}ERROR${NC} $1"; } mkdir -p "$LOG_DIR" "$BACKUP_DIR" # ============================================================ # 资源限制配置 (2核4G 服务器) # ============================================================ # ---- 构建阶段 ---- NODE_MAX_MEM="${NODE_MAX_MEM:-1024}" # Node 最大 old-space MB MAVEN_XMX="${MAVEN_XMX:-512}" # Maven JVM 最大堆 MB MAVEN_XMS="${MAVEN_XMS:-128}" # Maven JVM 初始堆 MB MAVEN_THREADS="${MAVEN_THREADS:-1}" # Maven 编译线程数 # ---- 运行时 JVM (admin 模块) ---- ADMIN_XMX="${ADMIN_XMX:-512}" # 最大堆 MB (含 POI Excel 处理) ADMIN_XMS="${ADMIN_XMS:-128}" # 初始堆 MB ADMIN_META="${ADMIN_META:-128}" # 元空间上限 MB ADMIN_CODE_CACHE="${ADMIN_CODE_CACHE:-64}" # JIT 代码缓存 MB ADMIN_XSS="${ADMIN_XSS:-256}" # 线程栈 KB # ---- 运行时 JVM (miniapp 模块) ---- MINIAPP_XMX="${MINIAPP_XMX:-384}" MINIAPP_XMS="${MINIAPP_XMS:-128}" MINIAPP_META="${MINIAPP_META:-128}" MINIAPP_CODE_CACHE="${MINIAPP_CODE_CACHE:-64}" MINIAPP_XSS="${MINIAPP_XSS:-256}" # 低优先级包装器 low_prio() { nice -n 19 ionice -c 2 -n 7 "$@" } # ---- 参数解析 ---- TARGET="${1:-all}" SKIP_FRONTEND=false NO_STOP=false for arg in "$@"; do [ "$arg" = "--skip-frontend" ] && SKIP_FRONTEND=true [ "$arg" = "--no-stop" ] && NO_STOP=true done info "开始部署 (目标=$TARGET, 零停机=$NO_STOP)" info "Node:${NODE_MAX_MEM}M | Maven:${MAVEN_XMX}M | Admin-JVM:${ADMIN_XMX}M | Miniapp-JVM:${MINIAPP_XMX}M" # ---- 拉取代码 ---- info "拉取最新代码..." cd "$CODE_DIR" || { error "无法进入 $CODE_DIR"; exit 1; } low_prio git pull origin master || { error "git pull 失败"; exit 1; } # ============================================================ # 阶段 1: 停止服务 (释放内存给构建) # ============================================================ stop_service() { local module=$1 local pid=$(ps -ef | grep java | grep "$module" | grep -v grep | awk '{print $2}') if [ -z "$pid" ]; then info "$module 未在运行" return 0 fi for p in $pid; do info "终止 $module (pid=$p)..." kill "$p" 2>/dev/null for i in $(seq 1 10); do if ! kill -0 "$p" 2>/dev/null; then info "进程 $p 已退出" break fi sleep 1 done if kill -0 "$p" 2>/dev/null; then warn "强制终止 $p" kill -9 "$p" 2>/dev/null sleep 1 fi done } if [ "$NO_STOP" = false ]; then case "$TARGET" in admin) stop_service "car-wash-admin" ;; miniapp) stop_service "car-wash-miniapp" ;; all) stop_service "car-wash-admin" stop_service "car-wash-miniapp" ;; esac sleep 3 fi # ============================================================ # 阶段 2: 构建 # ============================================================ if [ "$SKIP_FRONTEND" = false ] && [ "$TARGET" != "miniapp" ]; then for item in "admin-web:admin-web" "admin-h5:admin-h5"; do dir="${item%%:*}" name="${item##*:}" info "构建前端 $name ..." ( cd "$CODE_DIR/$dir" || exit 1 export NODE_OPTIONS="--max-old-space-size=$NODE_MAX_MEM" low_prio npm run build ) || { error "$name 构建失败"; exit 1; } info "$name 构建完成" sleep 2 done info "前端构建全部完成" fi export MAVEN_OPTS="-Xmx${MAVEN_XMX}m -Xms${MAVEN_XMS}m -XX:+UseSerialGC" if [ "$TARGET" = "miniapp" ]; then info "Maven 打包 (仅 miniapp)..." ( cd "$CODE_DIR" low_prio mvn clean package -DskipTests -pl car-wash-miniapp -am \ -T "${MAVEN_THREADS}" -Dmaven.test.skip=true ) || { error "Maven 打包失败"; exit 1; } else info "Maven 打包 (全量, 线程=${MAVEN_THREADS})..." ( cd "$CODE_DIR" low_prio mvn clean package -DskipTests \ -T "${MAVEN_THREADS}" \ -Dmaven.test.skip=true \ -Dmaven.compiler.fork=false ) || { error "Maven 打包失败"; exit 1; } fi sleep 2 # ============================================================ # 阶段 3: 备份 & 部署 & 启动 # ============================================================ backup_and_copy() { local module=$1 local jar_src="$CODE_DIR/$module/target/${module}-0.0.1-SNAPSHOT.jar" local jar_dst="$APP_DIR/${module}-0.0.1-SNAPSHOT.jar" if [ ! -f "$jar_src" ]; then error "未找到 jar 文件: $jar_src" exit 1 fi if [ -f "$jar_dst" ]; then cp "$jar_dst" "$BACKUP_DIR/${module}-0.0.1-SNAPSHOT.jar.$(date +%Y%m%d_%H%M%S)" info "已备份 $module (保留最近 5 个)" ls -t "$BACKUP_DIR/${module}"-*.jar 2>/dev/null | tail -n +6 | xargs -r rm -f fi cp "$jar_src" "$jar_dst" info "$module jar 已复制到 $APP_DIR" } # 构建模块专用的 JVM 参数 build_jvm_opts() { local module=$1 if [ "$module" = "car-wash-admin" ]; then echo "-Xmx${ADMIN_XMX}m -Xms${ADMIN_XMS}m -Xss${ADMIN_XSS}k \ -XX:MaxMetaspaceSize=${ADMIN_META}m -XX:ReservedCodeCacheSize=${ADMIN_CODE_CACHE}m \ -XX:+UseSerialGC -XX:+UseStringDeduplication" else echo "-Xmx${MINIAPP_XMX}m -Xms${MINIAPP_XMS}m -Xss${MINIAPP_XSS}k \ -XX:MaxMetaspaceSize=${MINIAPP_META}m -XX:ReservedCodeCacheSize=${MINIAPP_CODE_CACHE}m \ -XX:+UseSerialGC -XX:+UseStringDeduplication" fi } start_service() { local module=$1 local jar_file="${module}-0.0.1-SNAPSHOT.jar" local start_log="$LOG_DIR/${module}.log" local jvm_opts=$(build_jvm_opts "$module") info "启动 $module (JVM: $(echo $jvm_opts | tr -s ' '))..." local cert_arg="" if [ "$module" = "car-wash-admin" ]; then cert_arg="-Dwechat.payment.certPath=/data/wash-pem/apiclient_cert.pem -Dwechat.payment.keyPath=/data/wash-pem/apiclient_key.pem" fi cd "$APP_DIR" nohup java $jvm_opts -jar -Dspring.profiles.active=prod $cert_arg "$jar_file" > "$start_log" 2>&1 & sleep 5 local new_pid=$(ps -ef | grep java | grep "$module" | grep -v grep | awk '{print $2}') if [ -n "$new_pid" ]; then info "$module 启动成功 (pid=$new_pid)" echo "$(date) $module restarted" >> "$HISTORY_FILE" else error "$module 可能启动失败,查看日志:" echo "--- $start_log ---" tail -30 "$start_log" echo "--- end ---" exit 1 fi } case "$TARGET" in admin) backup_and_copy "car-wash-admin" start_service "car-wash-admin" ;; miniapp) backup_and_copy "car-wash-miniapp" start_service "car-wash-miniapp" ;; all) backup_and_copy "car-wash-admin" backup_and_copy "car-wash-miniapp" start_service "car-wash-admin" start_service "car-wash-miniapp" ;; *) error "未知模块: $TARGET (可选: admin | miniapp | all)" exit 1 ;; esac echo "========================================" info "部署完成" echo "========================================"