Skip to content

建议将本篇的文字修改为jenkins的参数化构建和前端缓存优化

第二个考虑需要将nodejs动态设置版本的功能加进来

这个功能之前已经整理成为具体的文档了

加缓存

groovy
pipeline {
    agent any
    
    tools {
        nodejs 'Node20.16.0' // 这里的 'NodeJS_14' 是你在 Jenkins 中配置的 Node.js 名称
    }

    options {
        timestamps() // 启用时间戳
    }

    environment {
        GIT_URL = 'https://gitlab.cghbh.com/open-source/npm-jenkins-cache.git'
        GIT_BRANCH = 'master'
    }

    stages {
        stage('Checkout Code') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 拉取代码
                    checkout([
                        $class: 'GitSCM',
                        branches: [[name: "*/${env.GIT_BRANCH}"]],
                        doGenerateSubmoduleConfigurations: false,
                        extensions: [],
                        submoduleCfg: [],
                        userRemoteConfigs: [[
                            url: "${env.GIT_URL}"
                        ]]
                    ])
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "代码拉取阶段耗时: ${duration} 秒"
                }
            }
        }

        stage('Install Dependencies') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 设置 npm 缓存目录
                    sh 'npm config set cache ~/.npm-cache'                    
                    // 安装依赖
                    sh 'npm install'
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "安装依赖阶段耗时: ${duration} 秒"
                }
            }
        }
        
        stage('Build') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 构建项目
                    sh 'npm run build'
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "代码构建阶段耗时: ${duration} 秒"
                }
            }
        }
    }
}

不使用缓存的方法1

请注意:使用npm ci清除构建时候的缓存,需要项目必须有一个现有的 package-lock.json,如果没有的话则不能命中这个规则。

此命令与 npm install 类似,不同之处在于它旨在用于自动化环境,例如测试平台、持续集成和部署——或任何您希望确保对依赖项进行全新安装的情况。

npm ci 在以下情况下会明显更快:

  • 有一个 package-lock.jsonnpm-shrinkwrap.json 文件。
  • node_modules 文件夹丢失或为空。

简而言之,使用 npm installnpm ci 的主要区别在于:

  • 项目必须有一个现有的 package-lock.jsonnpm-shrinkwrap.json
  • 如果包锁中的依赖项与 package.json 中的依赖项不匹配,npm ci 将退出并出错,而不是更新包锁。
  • npm ci 一次只能安装整个项目:无法使用此命令添加单个依赖项。
  • 如果 node_modules 已经存在,它将在 npm ci 开始安装之前自动删除。
  • 它永远不会写入 package.json 或任何包锁:安装基本上是冻结的。
groovy
pipeline {
    agent any
    
    tools {
        nodejs 'Node20.16.0' // 这里的 'NodeJS_14' 是你在 Jenkins 中配置的 Node.js 名称
    }

    options {
        timestamps() // 启用时间戳
    }

    environment {
        GIT_URL = 'https://gitlab.cghbh.com/open-source/npm-jenkins-cache.git'
        GIT_BRANCH = 'master'
    }

    stages {
        stage('Checkout Code') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 拉取代码
                    checkout([
                        $class: 'GitSCM',
                        branches: [[name: "*/${env.GIT_BRANCH}"]],
                        doGenerateSubmoduleConfigurations: false,
                        extensions: [],
                        submoduleCfg: [],
                        userRemoteConfigs: [[
                            url: "${env.GIT_URL}"
                        ]]
                    ])
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "代码拉取阶段耗时: ${duration} 秒"
                }
            }
        }

        stage('Install Dependencies') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    // 清空并重新安装依赖
                    sh 'npm ci'

                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "安装依赖阶段耗时: ${duration} 秒"
                }
            }
        }
        
        stage('Build') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 构建项目
                    sh 'npm run build'
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "代码构建阶段耗时: ${duration} 秒"
                }
            }
        }
    }
}

不使用缓存的方法2

groovy
pipeline {
    agent any

    tools {
        nodejs 'Node20.16.0' // 这里的 'NodeJS_14' 是你在 Jenkins 中配置的 Node.js 名称
    }

    options {
        timestamps() // 启用时间戳
    }

    environment {
        GIT_URL = 'https://gitlab.cghbh.com/open-source/npm-jenkins-cache.git'
        GIT_BRANCH = 'master'
    }

    stages {
        stage('Checkout Code') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 拉取代码
                    checkout([
                        $class: 'GitSCM',
                        branches: [[name: "*/${env.GIT_BRANCH}"]],
                        doGenerateSubmoduleConfigurations: false,
                        extensions: [],
                        submoduleCfg: [],
                        userRemoteConfigs: [[
                            url: "${env.GIT_URL}",
                            credentialsId: "${env.GIT_CREDENTIALS_ID}"
                        ]]
                    ])
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "代码拉取阶段耗时: ${duration} 秒"
                }
            }
        }

        stage('Clean Node Modules') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 删除 node_modules 目录
                    sh 'rm -rf node_modules'
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "删除缓存阶段耗时: ${duration} 秒"
                }
            }
        }

        stage('Install Dependencies') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 安装依赖
                    sh 'npm install'
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "下载依赖阶段耗时: ${duration} 秒"
                }
            }
        }
        
        stage('Build') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 构建项目
                    sh 'npm run build'
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "项目构建阶段耗时: ${duration} 秒"
                }
            }
        }
    }
}

将以上两种方法聚合的方法3

groovy
pipeline {
    agent any

    tools {
        nodejs 'Node20.16.0' // 这里的 'NodeJS_14' 是你在 Jenkins 中配置的 Node.js 名称
    }

    options {
        timestamps() // 启用时间戳
    }

    environment {
        GIT_URL = 'https://gitlab.cghbh.com/open-source/npm-jenkins-cache.git'
        GIT_BRANCH = 'master'
    }

    stages {
        stage('Checkout Code') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 拉取代码
                    checkout([
                        $class: 'GitSCM',
                        branches: [[name: "*/${env.GIT_BRANCH}"]],
                        doGenerateSubmoduleConfigurations: false,
                        extensions: [],
                        submoduleCfg: [],
                        userRemoteConfigs: [[
                            url: "${env.GIT_URL}",
                            credentialsId: "${env.GIT_CREDENTIALS_ID}"
                        ]]
                    ])
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "代码拉取阶段耗时: ${duration} 秒"
                }
            }
        }

        stage('Install Dependencies') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 判断是否存在 package-lock.json 文件
                    def hasPackageLock = fileExists('package-lock.json')
                    
                    if (hasPackageLock) {
                        echo 'package-lock.json 文件存在,使用 npm ci重新安装依赖'
                        // 使用 npm ci 清除缓存并重新安装依赖
                        sh 'npm ci'
                    } else {
                        echo 'package-lock.json 文件不存在,将删除 node_modules 并重新安装依赖'
                        // 删除 node_modules 目录并重新安装依赖
                        sh 'rm -rf node_modules'
                        sh 'npm install'
                    }
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "安装依赖阶段耗时: ${duration} 秒"
                }
            }
        }
        
        stage('Build') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 构建项目
                    sh 'npm run build'
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "项目构建阶段耗时: ${duration} 秒"
                }
            }
        }
    }
}

参数化决定缓存的构建

groovy
pipeline {
    agent any

    tools {
        nodejs 'Node20.16.0' // 这里的 'NodeJS_14' 是你在 Jenkins 中配置的 Node.js 名称
    }

    options {
        timestamps() // 启用时间戳
    }
  
     parameters {
          booleanParam(name: 'ENABLE_CACHE', defaultValue: true, description: '是否使用开启node_modules缓存')
      }

    environment {
        GIT_URL = 'https://gitlab.cghbh.com/open-source/npm-jenkins-cache.git'
        GIT_BRANCH = 'master'
    }

    stages {
        stage('Checkout Code') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 拉取代码
                    checkout([
                        $class: 'GitSCM',
                        branches: [[name: "*/${env.GIT_BRANCH}"]],
                        doGenerateSubmoduleConfigurations: false,
                        extensions: [],
                        submoduleCfg: [],
                        userRemoteConfigs: [[
                            url: "${env.GIT_URL}",
                            credentialsId: "${env.GIT_CREDENTIALS_ID}"
                        ]]
                    ])
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "代码拉取阶段耗时: ${duration} 秒"
                }
            }
        }

        stage('Install Dependencies') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 判断是否存在 package-lock.json 文件
                    def hasPackageLock = fileExists('package-lock.json')
                  
                  if (params.ENABLE_CACHE) {
                    // 设置 npm 缓存目录
                    sh 'npm config set cache ~/.npm-cache'                    
                    // 继续安装可能更新的依赖
                    if (hasPackageLock) {
                      sh 'npm install'
                    } else {
                      // 如果本来就没有package-lock.json的文件,则阻止这个文件的生成,避免对后续的操作造成干扰
                      sh 'npm install --no-package-lock'
                    }
                    
                  } else {
                    // 不开启缓存的话,则根据不同的方案进行依赖的重新下载
                    if (hasPackageLock) {
                        echo 'package-lock.json 文件存在,使用 npm ci重新安装依赖'
                        // 使用 npm ci 清除缓存并重新安装依赖
                        sh 'npm ci'
                    } else {
                        echo 'package-lock.json 文件不存在,将删除 node_modules 并重新安装依赖'
                        // 删除 node_modules 目录并重新安装依赖
                        sh 'rm -rf node_modules'
                        // 如果本来就没有package-lock.json的文件,则阻止这个文件的生成,避免对后续的操作造成干扰
                        sh 'npm install --no-package-lock'
                    }
                  }
                    
                    
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "安装依赖阶段耗时: ${duration} 秒"
                }
            }
        }
        
        stage('Build') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    
                    // 构建项目
                    sh 'npm run build'
                    
                    def endTime = System.currentTimeMillis()
                    def duration = (endTime - startTime) / 1000
                    echo "项目构建阶段耗时: ${duration} 秒"
                }
            }
        }
    }
}

jenkins的参数化配置

groovy
pipeline {
    agent any
    parameters {
        string(name: 'BUILD_VERSION', defaultValue: '1.0', description: 'Version number to build')
        booleanParam(name: 'DEPLOY_TO_PROD', defaultValue: false, description: 'Deploy to production server?')
        choice(name: 'ENVIRONMENT', choices: ['dev', 'qa', 'prod'], description: 'Select environment for deployment')
    }
    stages {
        stage('Build') {
            steps {
                echo "Building version ${params.BUILD_VERSION}"
            }
        }
        stage('Test') {
            steps {
                echo "Running tests on version ${params.BUILD_VERSION}"
            }
        }
        stage('Deploy') {
            when {
                expression { params.DEPLOY_TO_PROD == true }
            }
            steps {
                echo "Deploying to ${params.ENVIRONMENT}"
            }
        }
    }
}

在这个示例中,我们定义了三个参数:

  • BUILD_VERSION:一个字符串类型的参数,用于指定要构建的版本号。如果用户没有提供该参数,则默认为 1.0。
  • DEPLOY_TO_PROD:一个布尔类型的参数,用于指示是否要将构建结果部署到生产环境。如果用户没有提供该参数,则默认为 false。
  • ENVIRONMENT:一个选择类型的参数,用于指定要部署到的环境。选择项为 dev、qa 和 prod。如果用户没有提供该参数,则不会有默认值。
  • 在Pipeline的不同阶段中,我们使用了这些参数。例如,在 Build 阶段中,我们使用了 $params.BUILD_VERSION 来打印要构建的版本号。在 Deploy 阶段中,我们使用了 $params.DEPLOY_TO_PROD 和 $params.ENVIRONMENT 来决定是否将构建结果部署到生产环境以及要部署到哪个环境。
  • 当用户运行Pipeline时,他们将被要求提供这些参数的值。用户可以在Jenkins界面中输入这些值,也可以通过Jenkins API来动态地提供这些值。

jenkins的api参考

js
const Jenkins = require('jenkins');
const jenkins = Jenkins({ baseUrl: 'http://your-username:your-api-token@your-jenkins-url', crumbIssuer: true });

const jobName = 'your-job-name';
const parameters = {
  ENVIRONMENT: 'production',
  GIT_URL: 'https://your-git-repo-url.git',
  GIT_BRANCH: 'main',
  GIT_CREDENTIALS_ID: 'your-credentials-id'
};

// Trigger the Jenkins job with parameters
jenkins.job.build({ name: jobName, parameters }, (err, data) => {
  if (err) {
    return console.error('Error triggering Jenkins job:', err);
  }
  console.log('Jenkins job triggered successfully:', data);
});

// 上面的参数相当于下面流水线的参数如下
  parameters {
        choice(name: 'ENVIRONMENT', choices: ['development', 'staging', 'production'], description: '选择部署环境')
        string(name: 'GIT_URL', defaultValue: 'https://your-git-repo-url.git', description: 'Git 仓库 URL')
        string(name: 'GIT_BRANCH', defaultValue: 'main', description: 'Git 分支')
        string(name: 'GIT_CREDENTIALS_ID', defaultValue: 'your-credentials-id', description: 'Git 凭据 ID')
    }

前端静态资源全部参考如下:

groovy
pipeline {
    agent any

    tools {
        nodejs 'NodeJS_14' // 这里的 'NodeJS_14' 是你在 Jenkins 中配置的 Node.js 名称
    }

    options {
        timestamps() // 启用时间戳
    }

    parameters {
        choice(name: 'ENVIRONMENT', choices: ['development', 'staging', 'production'], description: '选择部署环境')
        string(name: 'GIT_URL', defaultValue: 'https://your-git-repo-url.git', description: 'Git 仓库 URL')
        string(name: 'GIT_BRANCH', defaultValue: 'main', description: 'Git 分支')
        string(name: 'GIT_CREDENTIALS_ID', defaultValue: 'your-credentials-id', description: 'Git 凭据 ID')
        booleanParam(name: 'UPLOAD_TO_OSS', defaultValue: false, description: '是否上传到 OSS')
    }

    environment {
        API_URL = 'https://your-api-endpoint.com/report' // 你的接口地址
        OSS_BUCKET = 'your-oss-bucket-name' // 你的 OSS 桶名称
        OSS_ENDPOINT = 'your-oss-endpoint' // 你的 OSS 终端
        OSS_ACCESS_KEY_ID = 'your-oss-access-key-id' // 你的 OSS 访问密钥 ID
        OSS_ACCESS_KEY_SECRET = 'your-oss-access-key-secret' // 你的 OSS 访问密钥
    }

    stages {
        stage('Checkout Code') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    def status = 'SUCCESS'
                    try {
                        // 输出参数到控制台
                        echo "ENVIRONMENT: ${params.ENVIRONMENT}"
                        echo "GIT_URL: ${params.GIT_URL}"
                        echo "GIT_BRANCH: ${params.GIT_BRANCH}"
                        echo "GIT_CREDENTIALS_ID: ${params.GIT_CREDENTIALS_ID}"
                        
                        // 拉取代码
                        checkout([
                            $class: 'GitSCM',
                            branches: [[name: "*/${params.GIT_BRANCH}"]],
                            doGenerateSubmoduleConfigurations: false,
                            extensions: [],
                            submoduleCfg: [],
                            userRemoteConfigs: [[
                                url: "${params.GIT_URL}",
                                credentialsId: "${params.GIT_CREDENTIALS_ID}"
                            ]]
                        ])
                    } catch (Exception e) {
                        status = 'FAILURE'
                        throw e
                    } finally {
                        def endTime = System.currentTimeMillis()
                        def duration = (endTime - startTime) / 1000
                        echo "Checkout Code 阶段耗时: ${duration} 秒"
                        
                        // 发送结果和执行时间到指定接口
                        sh """
                        curl -X POST -H "Content-Type: application/json" -d '{
                            "stage": "Checkout Code",
                            "status": "${status}",
                            "duration": "${duration} 秒"
                        }' ${env.API_URL}
                        """
                    }
                }
            }
        }

        stage('Install Dependencies') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    def status = 'SUCCESS'
                    try {
                        // 判断是否存在 package-lock.json 文件
                        def hasPackageLock = fileExists('package-lock.json')
                        
                        if (hasPackageLock) {
                            echo 'package-lock.json 文件存在,使用 npm ci'
                            // 使用 npm ci 清除缓存并重新安装依赖
                            sh 'npm ci'
                        } else {
                            echo 'package-lock.json 文件不存在,删除 node_modules 并重新安装依赖'
                            // 删除 node_modules 目录并重新安装依赖
                            sh 'rm -rf node_modules'
                            sh 'npm install --no-package-lock'
                        }
                    } catch (Exception e) {
                        status = 'FAILURE'
                        throw e
                    } finally {
                        def endTime = System.currentTimeMillis()
                        def duration = (endTime - startTime) / 1000
                        echo "Install Dependencies 阶段耗时: ${duration} 秒"
                        
                        // 发送结果和执行时间到指定接口
                        sh """
                        curl -X POST -H "Content-Type: application/json" -d '{
                            "stage": "Install Dependencies",
                            "status": "${status}",
                            "duration": "${duration} 秒"
                        }' ${env.API_URL}
                        """
                    }
                }
            }
        }
        
        stage('Build') {
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    def status = 'SUCCESS'
                    try {
                        // 根据选择的环境设置不同的构建命令
                        if (params.ENVIRONMENT == 'development') {
                            sh 'npm run build:dev'
                        } else if (params.ENVIRONMENT == 'staging') {
                            sh 'npm run build:staging'
                        } else if (params.ENVIRONMENT == 'production') {
                            sh 'npm run build:prod'
                        }
                    } catch (Exception e) {
                        status = 'FAILURE'
                        throw e
                    } finally {
                        def endTime = System.currentTimeMillis()
                        def duration = (endTime - startTime) / 1000
                        echo "Build 阶段耗时: ${duration} 秒"
                        
                        // 发送结果和执行时间到指定接口
                        sh """
                        curl -X POST -H "Content-Type: application/json" -d '{
                            "stage": "Build",
                            "status": "${status}",
                            "duration": "${duration} 秒"
                        }' ${env.API_URL}
                        """
                    }
                }
            }
        }

        stage('Upload to OSS') {
            when {
                expression { return params.UPLOAD_TO_OSS }
            }
            steps {
                script {
                    def startTime = System.currentTimeMillis()
                    def status = 'SUCCESS'
                    try {
                        // 上传静态资源到 OSS
                        sh """
                        ossutil cp -r ./dist oss://${env.OSS_BUCKET}/path/to/upload --endpoint=${env.OSS_ENDPOINT} --access-key-id=${env.OSS_ACCESS_KEY_ID} --access-key-secret=${env.OSS_ACCESS_KEY_SECRET}
                        """
                    } catch (Exception e) {
                        status = 'FAILURE'
                        throw e
                    } finally {
                        def endTime = System.currentTimeMillis()
                        def duration = (endTime - startTime) / 1000
                        echo "Upload to OSS 阶段耗时: ${duration} 秒"
                        
                        // 发送结果和执行时间到指定接口
                        sh """
                        curl -X POST -H "Content-Type: application/json" -d '{
                            "stage": "Upload to OSS",
                            "status": "${status}",
                            "duration": "${duration} 秒"
                        }' ${env.API_URL}
                        """
                    }
                }
            }
        }
    }
}

在这个示例中:

  • parameters 块中添加了一个布尔参数 UPLOAD_TO_OSS,用户可以选择是否上传到 OSS。
  • Upload to OSS 阶段使用 when 块来判断是否执行该阶段。只有当 UPLOAD_TO_OSS 参数为 true 时,才会执行上传到 OSS 的操作。
  • 每个阶段都使用 try-catch-finally 块来捕获异常,并在 finally 块中发送结果和执行时间到指定的接口。

通过这种方式,你可以在 Jenkins 流水线中动态控制是否执行 Upload to OSS 阶段,并在每个阶段完成后将结果和执行时间发送到指定的接口,同时在执行失败时也发送结果。

你可以通过在 Jenkins Pipeline 中添加一个布尔参数来控制是否执行 Upload to OSS 这一步。用户可以在运行时选择是否需要发布到 OSS。以下是一个更新后的示例,展示了如何通过布尔参数动态控制 Upload to OSS 阶段的执行:

1. 配置 Jenkins Pipeline 脚本

jenkins需要考虑的问题如下

  • 配置发送构建钉钉消息
  • 配置发送邮件构建信息