基于Java Spring Boot微服务,jenkins pipeline拉取gitlab构建与部署

✍️Auth:运维笔记       Date:2025/05/21       Cat:Linux服务器       👁️:9 次浏览

pipeline基本语法参考以前文章:Jenkins 配置gitlab的 pipeline流水线任务

本篇文章基于以前文章优化与扩展,看不懂的可以参考前面文章熟悉语法与配置。

后端:

微服务架构一个仓库下,多个服务。需要每个服务单独构建发布。如:

project-root/
├── common/
├── service-gateway/
│   └── pom.xml
├── service-api/
│   └── pom.xml
├── pom.xml

和以前的构建脚本一样,只是拉取完整个库后,单独构建发布服务,构建命令不同而已。

#构建打包整个项目的所有服务
mvn clean package -DskipTests

#只构建打包其中一个服务,如gateway
mvn clean package -pl service-gateway -am -DskipTests
#-pl (--projects):只构建某个模块(如 gateway)
#-am (--also-make):同时构建这个模块依赖的模块(如 common)

其他服务可以单独创建流水线,便于管理,如果用一个流水线也可以用参数选择构建,但服务太多的话,一个流水线不好管理。

Active Choices Reactive Parameter

根据BRANCH_NAME环境不同,显示对应的分支文件夹。这里同样以xxl-job-admin作为示例。

如:

dev环境保存包: /opt/jar/dev/xxl-job-admin/

test环境保存包: /opt/jar/test/xxl-job-admin/

// 判断是否为“以往版本发布”
if (RELEASE_TYPE != "以往版本发布") {
    return []
}

// 读取存储目录下的所有 jar 文
def branch = BRANCH_NAME
if (!branch) return []

def dir = new File("/opt/jar/${branch}/xxl-job-admin/")
if (!dir.exists()) return ["目录不存在"]
def files = dir.listFiles().findAll { it.name.endsWith(".jar") }
if (files.isEmpty()) return ["无可用版本"]

return files*.name.sort().reverse()

Referenced Parameters: RELEASE_TYPE,BRANCH_NAME

pipeline script

def REMOTE_IP_LIST = ''

pipeline {
    agent {
        node {
                //需在系统管理 >> 节点和云管理 >> 选择对应的节点(如:master),标签添加 any。
            label 'any'
            //自定义工作空间,防止不同环境覆盖冲突。以“工作名+分支”重命名空间(xxl-job-admin_dev)。
            customWorkspace "${JENKINS_HOME}/workspace/${JOB_NAME}_${params.BRANCH_NAME}"
        }
    }

    parameters {
        choice(name: 'BRANCH_NAME', choices: ['==选择环境分支==','dev', 'test', 'main'], description: '请选择构建分支')
        choice(name: 'RELEASE_TYPE', choices: ['全新构建发布', '以往版本发布'], description: '发布方式')
    }

    environment {
        APP_NAME = 'xxl-job-admin'
        PORT = '8080'
        JAR_PATTERN = "${JENKINS_HOME}/workspace/${JOB_NAME}_${params.BRANCH_NAME}/${APP_NAME}/target/"
        JAR_PATH = "/opt/jar/${params.BRANCH_NAME}/${APP_NAME}"
        REMOTE_DIR = "/app/${APP_NAME}"
        DEV_IPS = '198.19.249.107'
        TEST_IPS = '198.19.249.107'
        MAIN_IPS = '198.19.249.103 198.19.249.104' //空格作为分割
    }

    tools {
        maven "maven-3.9.6"
        jdk 'jdk17'
    }

    stages {
        stage('部署环境') {
            steps {
                script {
                    switch (params.BRANCH_NAME) {
                        case 'dev':  REMOTE_IP_LIST = env.DEV_IPS; break
                        case 'test': REMOTE_IP_LIST = env.TEST_IPS; break
                        case 'main': REMOTE_IP_LIST = env.MAIN_IPS; break
                        default:     error "未知分支:${params.BRANCH_NAME}"
                    }
                    echo "部署环境分支:${params.BRANCH_NAME}"
                    echo "部署方式:${params.RELEASE_TYPE}"
                    if (params.RELEASE_TYPE != '全新构建发布') {
                        echo "部署版本包: ${params.APP_VERSION}"
                    } 
                    echo "部署服务器:${REMOTE_IP_LIST}"
                    //每次构建清理空间,防止旧文件影响,插件:Workspace Cleanup Plugin
                    echo "清理workspace: ${JOB_NAME}_${params.BRANCH_NAME}"
                        cleanWs()
                }
            }
        }

        stage('Git 拉取代码') {
            when {
                expression { return !params.APP_VERSION } // 如果是空,表示全新构建
            }
            steps {
                git branch: "${params.BRANCH_NAME}",
                    credentialsId: '25238080-093d-4e3c-884c-f70a027eb1c9',
                    url: '[email protected]:project-1/xxl-job.git'
                echo "拉取 ${params.BRANCH_NAME} 分支成功"
            }
        }

        stage('Maven 构建') {
            when {
                expression { return !params.APP_VERSION } // 只在构建新版本时执行
            }
            steps {
                sh "mvn -v"
                sh "java --version"
                sh "mvn clean package -Dmaven.test.skip=true"
                echo "构建完成"
            }
        }

        stage('存储备份 JAR 包') {
            when {
                expression { return !params.APP_VERSION } // 只在构建新版本时执行
            }
            steps {
                script {
                    def regex = "^${env.APP_NAME}(-[0-9.]+)?\\.jar\$"
                    def jarFile = sh(
                        script: "ls -t ${env.JAR_PATTERN} | grep -E '${regex}' | head -n 1",
                        returnStdout: true
                    ).trim()

                    if (!jarFile) {
                        error("未找到构建生成的 jar 包")
                    }
                    // 获取当前时间戳
                    def timestamp = sh(script: "date +%Y%m%d%H%M", returnStdout: true).trim()

                    // 生成带时间的备份文件名
                    def newFileName = jarFile.replace(".jar", "-${timestamp}.jar")

                    sh """
                        mkdir -p ${env.JAR_PATH}
                        cp ${env.JAR_PATTERN}${jarFile} ${env.JAR_PATH}/${newFileName}
                        ls -tp ${env.JAR_PATH}/*.jar | grep -v '/\$' | tail -n +11 | xargs -r rm --
                    """

                    echo "备份完成,保留最新 10 个版本:${jarFile}"
                }
            }
        }

        stage('拷贝 JAR 到远程服务器') {
            steps {
                script {
                    def jarFileName
                    def jarFilePath

                    if (params.APP_VERSION?.trim()) {
                        // 指定了版本:用选定的版本文件名
                        jarFileName = params.APP_VERSION.trim()
                        jarFilePath = "${env.JAR_PATH}/${jarFileName}"
                    } else {
                        // 未指定版本:自动找最新的
                        jarFileName = sh(script: "ls -t ${env.JAR_PATH}/*.jar | head -n 1", returnStdout: true).trim().tokenize("/").last()
                        jarFilePath = "${env.JAR_PATH}/${jarFileName}"
                    }

                    if (!jarFileName || !fileExists(jarFilePath)) {
                        error("未找到 JAR 包用于部署: ${jarFilePath}")
                    }

                    echo "部署使用 JAR 包:${jarFilePath}"

                    for (ip in REMOTE_IP_LIST.split()) {
                        echo "拷贝到远程 IP: ${ip}"
                        sh """
                            ssh -o StrictHostKeyChecking=no root@${ip} 'mkdir -p ${REMOTE_DIR} /app/logs /app/shell'
                            rsync -az ${jarFilePath} root@${ip}:${REMOTE_DIR}/${APP_NAME}.jar 
                        """
                    }
                }
            }
        }

        stage('远程启动服务') {
            steps {
                script {
                    for (ip in REMOTE_IP_LIST.split()) {
                        echo "部署到远程 IP: ${ip}"
                        sh """
                            ssh -o StrictHostKeyChecking=no root@${ip} '/bin/bash /app/shell/start.sh ${APP_NAME} ${PORT}'
                        """
                    }
                }
                echo "${env.APP_NAME}服务部署完成"
            }
        }
    }
}

部署后端服务器的start.sh部署脚本

脚本使用传参运行,即pipeline脚本传的两个参数:

1: 名称(用于匹配jar包名称运行)

2: 端口 (用于健康检查)

启动为添加到systemd系统服务启动。

#!/bin/bash
#set -x

APP_NAME=$1
PORT=$2
#环境参数也可以传参,不想手动可以改脚本为第三个参数 $3
ENV=test

# 匹配检查规则
[ -z "$APP_NAME" ] || [ -z "$PORT" ] && {
    echo "Usage: sh run-service.sh [app_name] [port]"
    exit 1
}

# 目录定义
SCRIPT_DIR=$(cd $(dirname $0); pwd)
APP_HOME="/app/${APP_NAME}"
LOG_HOME="/app/logs"
GC_LOG_DIR="${LOG_HOME}/gc/${APP_NAME}"
DUMP_DIR="${LOG_HOME}/heapdump/${APP_NAME}"
STDOUT_LOG="${LOG_HOME}/stdout/${APP_NAME}.log"
SERVICE_FILE="/etc/systemd/system/${APP_NAME}.service"
GC_FILE_NAME="gc-$(date +%Y%m%d%H%M%S).log"
DUMP_FILE_NAME="errorDump-$(date +%Y%m%d%H%M%S).hprof"

#健康检查链接,这里xxl-job-admin为例,不同服务链接不同
CHECK_URL="http://127.0.0.1:${PORT}/xxl-job-admin/actuator/health"

# 创建目录
mkdir -p "$GC_LOG_DIR" "$DUMP_DIR" "$(dirname $STDOUT_LOG)" "$APP_HOME"

# 创建 systemd 服务(如不存在)
echo "[INFO] 创建更新 systemd 服务文件:$SERVICE_FILE"
cat <<EOF > "$SERVICE_FILE"
[Unit]
Description=${APP_NAME} service
After=network.target

[Service]
Type=simple
ExecStart=/opt/jdk-17.0.11/bin/java -Xmx2048m -Xms2048m -Xmn1024m \\
  -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=256M \\
  -XX:MaxDirectMemorySize=512m \\
  -Dfile.encoding=UTF-8 \\
  -Duser.timezone=Asia/Shanghai \\
  -Xlog:gc*:file=${GC_LOG_DIR}/${GC_FILE_NAME}:tags,uptime,time,level \\
  -XX:+HeapDumpOnOutOfMemoryError \\
  -XX:HeapDumpPath=${DUMP_DIR}/${DUMP_FILE_NAME} \\
  -Dspring.profiles.active=${ENV} \\
  -jar ${APP_HOME}/${APP_NAME}.jar \\
  --spring.cloud.nacos.config.namespace=${ENV} \\
  --spring.cloud.nacos.discovery.namespace=${ENV}
WorkingDirectory=${APP_HOME}
Restart=on-failure
StandardOutput=append:${STDOUT_LOG}
StandardError=append:${STDOUT_LOG}
User=root

[Install]
WantedBy=multi-user.target
EOF
  systemctl daemon-reexec
  systemctl daemon-reload
  systemctl enable ${APP_NAME}

# 用 awk 提取 [Service] 段落的内容(包括[Service]这一行)
service_section=$(awk '
  /^\[Service\]/{flag=1; next}    # 遇到[Service]时开启标记,不打印该行
  /^\[/{flag=0}                  # 遇到新段落时关闭标记
  flag
' "${SERVICE_FILE}")

echo "[INFO] 服务启动参数:"
echo "$service_section"

# 优雅停止服务
echo "[INFO] 尝试停止旧服务..."
if systemctl is-active --quiet ${APP_NAME}.service; then
  systemctl stop ${APP_NAME}
  sleep 5
fi

# 检查是否仍有残留进程
PIDS=$(pgrep -f "${APP_NAME}.jar" || true)
if [ -n "$PIDS" ]; then
  echo "[WARN] 服务未完全停止,强制 kill:$PIDS"
  kill -9 $PIDS
fi

# 启动服务
echo "[INFO] 启动 ${APP_NAME} 服务..."
systemctl start ${APP_NAME}

# 健康检查函数
check_health() {
    local response
    response=$(curl -s --max-time 2 "$CHECK_URL" || echo "")

    echo "[HEALTH] 第$((DURATION+1))次检查: ${response:-NULL}"

    # 宽松判断,只要包含 "UP" 就视为健康
    #[[ "$response" == *UP* ]]
    #简单判断,不为空,则启动
    [ -n "$response" ]
}

# 健康检查等待
TIMEOUT=75
DURATION=0
echo "[INFO] 健康检查中... curl -s ${CHECK_URL}"

while [ $DURATION -lt $TIMEOUT ]; do
    sleep 1
    if check_health; then
        echo "[SUCCESS] ${APP_NAME} 启动成功,用时 ${DURATION} 秒"
        break
    fi
    DURATION=$((DURATION + 1))
done

# 不管成功或失败,都输出最近日志
echo "[INFO] 检查日志输出:$STDOUT_LOG"
echo "[INFO] 最后50条启动日志:"
tail -n 50 "$STDOUT_LOG" || echo "[WARN] 日志文件不存在"

# 成功或失败的退出判断
if [ $DURATION -lt $TIMEOUT ]; then
    exit 0
else
    echo "[ERROR] 启动超时(${TIMEOUT}s),服务未能成功启动"
    exit 1
fi

构建所有服务并存储备份

这个只需要一个构建参数即可,选择环境分支:dev, test

此pipeline只构建,不部署,用于测试代码构建功能。

配置需要保存的服务在APP_LIST 里面

def REMOTE_IP_LIST = ''

pipeline {
    agent {
        node {
            label 'any'
            customWorkspace "${JENKINS_HOME}/workspace/${JOB_NAME}_${params.BRANCH_NAME}"
        }
    }

    parameters {
        choice(name: 'BRANCH_NAME', choices: ['==选择环境分支==','dev', 'test'], description: '请选择构建分支')
    }

    environment {
        BASE_PATH = "/opt/jar"
        WORKSPACE = "${JENKINS_HOME}/workspace/${JOB_NAME}_${params.BRANCH_NAME}"
        APP_LIST = 'service-gateway service-api'  // 用空格分隔的字符串,需要保存的服务可以一直添加
    }

    tools {
        maven "maven-3.9.6"
        jdk 'jdk17'
    }

    stages {
        stage('部署环境') {
            steps {
                script {
                    if (!params.BRANCH_NAME || params.BRANCH_NAME == '==选择环境分支==') {
                        error("构建失败:请选择一个有效的分支参数")
                    }
                    echo "部署环境分支:${params.BRANCH_NAME}"
                    sh "mvn -v"
                    sh "java --version"
                    echo "清理workspace 空间"
                    cleanWs()
                }
            }
        }

        stage('Git 拉取代码') {
            when {
                expression { return !params.APP_VERSION }
            }
            steps {
                git branch: "${params.BRANCH_NAME}", 
                    credentialsId: '8e0973eb-d725-4153-89bd-503f23ef2683', 
                    url: '[email protected]:project-1/project-root.git'
                echo "拉取 ${params.BRANCH_NAME} 分支成功"
            }
        }

        stage('Maven 构建所有模块') {
            when {
                expression { return !params.APP_VERSION }
            }
            steps {
                sh "mvn clean package -U -Dmaven.test.skip=true -P ${params.BRANCH_NAME}"
                echo "构建完成"
            }
        }

        stage('存储构建的JAR包') {
            steps {
                script {
                    def apps = env.APP_LIST.split(' ')
                    def branch = params.BRANCH_NAME

                    for (app in apps) {
                        echo "正在查找模块:${app}"

                        def jarPath = sh(
                            script: "find ${env.WORKSPACE} -name '${app}*.jar' -type f | head -n 1",
                            returnStdout: true
                        ).trim()

                        if (!jarPath) {
                            echo "⚠️ 未找到 ${app} 模块的 jar 包"
                            continue
                        }

                        // 生成时间戳命名
                        def timestamp = sh(script: "date +%Y%m%d%H%M", returnStdout: true).trim()
                        def newFileName = jarPath.tokenize('/').last().replace('.jar', "-${timestamp}.jar")
                        def targetDir = "${env.BASE_PATH}/${branch}/${app}"

                        // 备份 jar
                        sh """
                            mkdir -p ${targetDir}
                            cp ${jarPath} ${targetDir}/${newFileName}
                            echo "✅ 已备份 ${app} 到 ${targetDir}/${newFileName}"

                            # 保留最新 10 个版本
                            ls -tp ${targetDir}/*.jar | grep -v '/\$' | tail -n +11 | xargs -r rm --
                        """
                    }
                }
            }
        }
    }
}

前端

Active Choices Reactive Parameter

Groovy Script 脚本,和后端差不多

// 判断是否为“以往版本发布”
if (RELEASE_TYPE != "以往版本发布") {
    return []
}

// 读取存储目录下的所有 tar.gz 文件
def branch = BRANCH_NAME
if (!branch) return []

def dir = new File("/opt/frontend/${branch}/sports-admin-web/")
if (!dir.exists()) return ["目录不存在"]
def files = dir.listFiles().findAll { it.name.endsWith(".tar.gz") }
if (files.isEmpty()) return ["无可用版本"]

return files*.name.sort().reverse()

pipeline script

流水线也差不多,逻辑是将整个构建的文件夹tar打包,保存在相应目录,并传送至远程服务器。

def REMOTE_IP_LIST = ''

pipeline {
    agent {
        node {
            label 'any'
            customWorkspace "${JENKINS_HOME}/workspace/${JOB_NAME}_${params.BRANCH_NAME}"
        }
    }
    parameters {
        choice(name: 'BRANCH_NAME', choices: ['==选择环境分支==','dev', 'test'], description: '请选择构建分支')
        choice(name: 'RELEASE_TYPE', choices: ['全新构建发布', '以往版本发布'], description: '发布方式')
    }

    environment {
        APP_NAME = 'admin-web'
        DIST_DIR = "${JENKINS_HOME}/workspace/"
        BUILD_CMD = 'pnpm install'
        STATIC_PATH = "/opt/frontend/${params.BRANCH_NAME}/${APP_NAME}"
        REMOTE_DIR = "/app/package/"
        DEV_IPS = '192.168.47.29'
        TEST_IPS = '192.168.47.49'
    }

    tools {
        nodejs 'node-18'
    }

    stages {
        stage('部署环境') {
            steps {
                script {
                    switch (params.BRANCH_NAME) {
                        case 'dev':  REMOTE_IP_LIST = env.DEV_IPS; break
                        case 'test': REMOTE_IP_LIST = env.TEST_IPS; break
                        default:     error "未知分支:${params.BRANCH_NAME}"
                    }
                    echo "部署环境分支:${params.BRANCH_NAME}"
                    echo "部署方式:${params.RELEASE_TYPE}"
                    if (params.RELEASE_TYPE != '全新构建发布') {
                        echo "部署版本包: ${params.APP_VERSION}"
                    } 
                    echo "部署服务器:${REMOTE_IP_LIST}"
                    echo "清理旧workspace: ${JOB_NAME}_${params.BRANCH_NAME}"
                    cleanWs()
                }
            }
        }

        stage('Git 拉取代码') {
            when { expression { return !params.APP_VERSION } }
            steps {
                git branch: "${params.BRANCH_NAME}", 
                    credentialsId: '25238080-093d-4e3c-884c-f70a027eb1c9',
                    url: '[email protected]:project-1/admin-web.git'

                echo "拉取 ${params.BRANCH_NAME} 分支成功"
            }
        }

        stage('Node 构建') {
            when { expression { return !params.APP_VERSION } }
            steps {
                sh 'node -v'
                //sh 'npm install pnpm -g'
                //sh 'pnpm install'
                sh "${env.BUILD_CMD}"
                echo "构建完成"
            }
        }

        stage('备份构建包') {
            when { expression { return !params.APP_VERSION } }
            steps {
                script {
                    def timestamp = sh(script: "date +%Y%m%d%H%M", returnStdout: true).trim()
                    def archiveName = "${env.APP_NAME}-${timestamp}.tar.gz"

                    sh """
                        mkdir -p ${env.STATIC_PATH}
                        cd ${env.DIST_DIR}
                        tar -czf ${archiveName} -C ${env.DIST_DIR} ${JOB_NAME}_${params.BRANCH_NAME}
                        mv ${archiveName} ${env.STATIC_PATH}/
                        ls -tp ${env.STATIC_PATH}/*.tar.gz | grep -v '/\$' | tail -n +11 | xargs -r rm --
                    """

                    echo "构建产物备份完成:${archiveName}"
                }
            }
        }

        stage('拷贝至远程服务器') {
            steps {
                script {
                    def deployFile

                    if (params.APP_VERSION?.trim()) {
                        deployFile = "${env.STATIC_PATH}/${params.APP_VERSION.trim()}"
                    } else {
                        deployFile = sh(script: "ls -t ${env.STATIC_PATH}/*.tar.gz | head -n 1", returnStdout: true).trim()
                    }

                    if (!fileExists(deployFile)) {
                        error "未找到构建产物:${deployFile}"
                    }

                    for (ip in REMOTE_IP_LIST.split()) {
                        echo "拷贝到远程 IP: ${ip}"
                        def remoteFile = "${REMOTE_DIR}${APP_NAME}.tar.gz"
                        sh """
                            ssh -o StrictHostKeyChecking=no root@${ip} 'mkdir -p ${REMOTE_DIR}'
                            rsync -az ${deployFile} root@${ip}:${remoteFile}
                        """
                    }
                }
            }
        }

       stage('远程部署服务') {
           steps {
               script {
                   for (ip in REMOTE_IP_LIST.split()) {
                      sh """
                           ssh -o StrictHostKeyChecking=no root@${ip} '/bin/bash /app/shell/deploy.sh ${APP_NAME} ${JOB_NAME}_${params.BRANCH_NAME}'
                        """
                       echo "✅ 前端服务 ${APP_NAME} 已部署到 ${ip}"
                   }
               }
           }
        }
    }
}

deploy.sh 脚本

同样采取传参运行。因为打包名称,和部署名称不一样。

如果对名称不敏感,也不需要传参数运行,直接解压到指定文件夹就行了。

此脚本会移除原来文件夹所有文件,如果有图片也在原来的文件夹,可以根据需求更改脚本逻辑。

#!/bin/bash

#set -x

# 参数校验
if [ $# -ne 2 ]; then
  echo "用法: $0 <APP名称> <tar包内目录名>"
  echo "示例: $0 admin-web frontend-admin-dev"
  exit 1
fi

APP_NAME=$1              # 目标目录名(新服务目录)
ORIG_NAME=$2             # 包中原始目录名
PACKAGE_DIR="/app/package"
DEPLOY_DIR="/app"
BACKUP_DIR="${DEPLOY_DIR}/backup"
TAR_FILE=$(ls ${PACKAGE_DIR}/${APP_NAME}.tar.gz 2>/dev/null | head -n 1)
STDOUT_LOG="/app/logs/stdout"

if [ -z "$TAR_FILE" ]; then
  echo " 未找到 tar 包:${PACKAGE_DIR}/${APP_NAME}.tar.gz"
  exit 2
fi

echo "开始部署 ${APP_NAME},解压包:$TAR_FILE"

# 创建备份目录
mkdir -p $BACKUP_DIR $STDOUT_LOG

# 如果已有旧版本,移动到备份
if [ -d "${DEPLOY_DIR}/${APP_NAME}" ]; then
  TIMESTAMP=$(date +'%Y%m%d%H%M%S')
  mv "${DEPLOY_DIR}/${APP_NAME}" "${BACKUP_DIR}/${APP_NAME}-${TIMESTAMP}"
  echo "已备份旧目录为 ${BACKUP_DIR}/${APP_NAME}-${TIMESTAMP}"

    # 保留最新 10 个备份,其余删除
  echo " 清理旧备份,仅保留最近 10 个:"
  ls -dt ${BACKUP_DIR}/${APP_NAME}-* 2>/dev/null | tail -n +11 | xargs -r rm -rf
fi

# 解压 tar 包
tar -xzf "$TAR_FILE" -C "$DEPLOY_DIR"

# 检查是否解压出了预期目录
if [ ! -d "${DEPLOY_DIR}/${ORIG_NAME}" ]; then
  echo " 解压失败:未找到目录 ${DEPLOY_DIR}/${ORIG_NAME}"
  exit 3
fi

#检查目录是否移走
if [ -d "${DEPLOY_DIR}/${APP_NAME}" ]; then
  echo "目录${DEPLOY_DIR}/${APP_NAME} 已存在,准备删除"
  rm -rf ${DEPLOY_DIR}/${APP_NAME}
fi

# 重命名解压目录为目标目录名
mv "${DEPLOY_DIR}/${ORIG_NAME}" "${DEPLOY_DIR}/${APP_NAME}"

# 查找已运行的服务并终止(pnpm)
EXIST_PIDS=$(pgrep -f "pnpm")

if [ -n "$EXIST_PIDS" ]; then
  echo "检测到以下 pnpm 相关进程将被终止:"
  echo "$EXIST_PIDS"
  kill -9 $EXIST_PIDS
  pkill -f vite.js
  echo "所有 pnpm 相关进程已终止"
  sleep 5
fi

cd ${DEPLOY_DIR}/${APP_NAME}

echo "启动调试命令:nohup pnpm test > ${STDOUT_LOG}/${APP_NAME}.log 2>&1 &"
nohup pnpm test > ${STDOUT_LOG}/${APP_NAME}.log 2>&1 &

sleep 5

echo "启动日志预览:tail -n 30"
tail -n 30 "${STDOUT_LOG}/${APP_NAME}.log"

echo "✅ 部署完成:${DEPLOY_DIR}/${APP_NAME}"
打赏作者

发表评论