Jenkins流水线部署+webhook2.0

文章目录

  • [1. 环境](#1. 环境)
  • [2. 用到的插件](#2. 用到的插件)
  • [3. 流水线部署脚本](#3. 流水线部署脚本)
      • [一 后端接口](#一 后端接口)
      • [二 前端VUE项目](#二 前端VUE项目)
  • 4.遇到的问题
  • 5.实际效果

1. 环境

  • Centos7
  • Jenkins2.520
  • JDKopen17
  • 阿里云仓库

注意:这个版本兼容需要特别注意,要不然会很麻烦

2. 用到的插件

  • Generic Webhook Trigger
  • HTTP Request
  • Extra Columns
  • Pipeline: Stage View
  • NodeJS

3. 流水线部署脚本

一 后端接口

  • 兼容钩子部署(webhook)和手动参数化部署
  • 优先取钩子的推送参数,没有的话取手动参数
  • 根据推送的分支进行不同的部署操作
  • 推送结果到钉钉通知
groovy 复制代码
pipeline {
    agent any

    // 定义参数化构建
    parameters {
        string(
            name: 'MANUAL_BRANCH', 
            defaultValue: '', 
            description: '手动指定要部署的分支(如 master, dev, test)'
        )
    }

    triggers {
        GenericTrigger(
            genericVariables: [
                [key: 'ref', value: '$.ref'], // 获取分支信息
                [key: 'repository_name', value: '$.repository.name'] // 获取仓库名称
            ],
            causeString: 'Triggered by push event on branch $ref',
            token: 'AZWSDD2555SSWS', // 自定义的Token
            printContributedVariables: true, // 打印传递的变量
            silentResponse: false,
            regexpFilterText: '$ref', // 匹配分支名
            regexpFilterExpression: '^refs/heads/(dev|test)$' // 只处理特定分支
        )
    }

   stages {
        stage('Determine Branch') {
            steps {
                script {
                    // 优先使用Webhook传递的分支信息
                    if (env.ref) {
                        env.BRANCH_NAME = env.ref.tokenize('/')[-1]
                        echo "Using webhook-triggered branch: ${env.BRANCH_NAME}"
                    } 
                    // 如果没有Webhook信息,检查是否有手动输入的分支
                    else if (params.MANUAL_BRANCH?.trim()) {
                        env.BRANCH_NAME = params.MANUAL_BRANCH.trim()
                        echo "Using manually specified branch: ${env.BRANCH_NAME}"
                    } 
                    // 如果两者都没有,抛出错误
                    else {
                        error "No branch specified! Please provide a branch via manual input or webhook."
                    }
                }
            }
        }

        stage('Checkout Code') {
            steps {
                git branch: env.BRANCH_NAME,
                    url: 'git@codeup.aliyun.com:test.git',
                    credentialsId: 'jenkins密钥ID'
            }
        }

         stage('Build and Deploy') {
            steps {
                echo "Performing actions for branch: ${env.BRANCH_NAME}"
                script {
                    if (env.BRANCH_NAME == 'master') {
                        sh '''
                            ssh 1.11.11.11 "cd /home/test && git pull"
                        '''
                    } else if (env.BRANCH_NAME == 'dev') {
                        sh '''
                            cd /home/dev && git pull
                        '''
                    } else if (env.BRANCH_NAME == 'test') {
                        sh '''
                            cd /home/test && git pull
                        '''
                    } else {
                        error "Unsupported branch: ${env.BRANCH_NAME}. No deployment logic defined."
                    }
                }
            }
        }
    }
    
    post {
        success {
            notifyDingTalk("SUCCESS") // 构建成功时通知
        }

        failure {
            notifyDingTalk("FAILURE") // 构建失败时通知
        }
    }
}

// 定义通用的钉钉通知方法
def notifyDingTalk(String buildStatus) {
    script {
        // 获取构建信息
        def branchName = env.BRANCH_NAME
        def duration = currentBuild.durationString
        def executor = currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause')?.userId ?: 'webhook'
        def commitHash = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
        def committer = sh(script: 'git log -1 --pretty=format:"%an"', returnStdout: true).trim()
        def commitMessage = sh(script: 'git log -1 --pretty=format:"%s"', returnStdout: true).trim()
        def rawCommitTime = sh(script: 'git log -1 --pretty=format:"%cd" --date=iso', returnStdout: true).trim()
        def formattedCommitTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(
            new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(rawCommitTime)
        )

        // 获取变更内容(修改的文件列表)
        def changedFiles = sh(script: 'git diff --name-only HEAD~1 HEAD', returnStdout: true).trim()

        // 动态生成标题和状态颜色
        def (statusTitle, statusColor) = buildStatus == "SUCCESS" ? ["构建成功", "#00FF00"] : ["构建失败", "#FF0000"]

        // 获取项目名(从环境变量或静态配置中)
        def projectName = env.JOB_NAME ?: "未知项目"

        // 构造钉钉消息内容
        def message = """
        ## Jenkins <font color="${statusColor}">${statusTitle}</font>通知
 - **项目**: ${projectName}
 - **分支**: ${branchName}
 - **状态**: <font color="${statusColor}">${buildStatus}</font>
 - **持续时间**: ${duration}
 - **执行人**: ${executor}
 - **代码推送人**: ${committer}
 - **提交哈希**: ${commitHash}
 - **提交时间**: ${formattedCommitTime}
 - **提交信息**: ${commitMessage}
 - **变更内容**: ${changedFiles ?: "无变更内容"}
""".stripIndent()

        // 钉钉机器人配置
        def dingtalkWebhookUrl = 'https://oapi.dingtalk.com/robot/send?access_token=your_token'

           // 使用 JsonOutput 生成 JSON 数据
        def payload = groovy.json.JsonOutput.toJson([
            msgtype: "markdown",
            markdown: [
                title: "[${projectName}] Jenkins ${statusTitle}通知",
                text: message
            ],
            at: [
                isAtAll: true
            ]
        ])
        httpRequest(
            url: dingtalkWebhookUrl,
            httpMode: 'POST',
            contentType: 'APPLICATION_JSON_UTF8',  // 确保 UTF-8 编码
            requestBody: payload,
            validResponseCodes: '200:299'  // 接受 200-299 状态码
        )
    }
}

二 前端VUE项目

  • 环境配置:设置 npm 镜像源和缓存目录。

  • 参数化构建:支持选择分支和强制重装依赖。

  • 代码检出:从指定分支拉取代码。

  • 依赖缓存管理:通过哈希值判断是否需要重新安装依赖,并缓存 node_modules。

  • 项目构建:根据分支执行不同构建命令(如生产、测试、开发环境)。 产物验证:检查构建产物是否存在。

  • 部署:将构建产物通过 rsync 部署到本地或远程服务器。

  • 通知:构建成功或失败时通过钉钉机器人发送通知。

groovy 复制代码
pipeline {
    agent any
    environment {
        NPM_REGISTRY = "https://registry.npmmirror.com"
        CACHE_DIR = "${JENKINS_HOME}/cache/${JOB_NAME}"
        
        }
    
    parameters {
        choice(
            name: 'BRANCH',
            choices: ['test', 'master'],
            description: '选择要构建部署的分支'
        )
        
        booleanParam(
            name: 'FORCE_REINSTALL',
            defaultValue: false,
            description: '强制重新安装所有依赖(忽略缓存)'
        )
  
    }
    
    tools {
        // 指定 Node.js 版本为 14.16.1
        nodejs "NodeJS-14.16.1"
    }
    
    triggers {
    // 阿里云Code Webhook配置
    GenericTrigger(
        genericVariables: [
            [key: 'ref', value: '$.ref'], // 获取分支信息
            [key: 'repository_name', value: '$.repository.name'] // 获取仓库名称
        ],
        causeString: 'Triggered by push event on branch $ref',
        token: '15444AWQWQSS75221GRTHESDDSFSFSFS', // 自定义的Token
        printContributedVariables: true, // 打印传递的变量
        printPostContent: true,
        silentResponse: false,
        regexpFilterText: '$ref', // 匹配分支名
        regexpFilterExpression: '^refs/heads/(main|dev|test)$' // 只处理特定分支
    )
}
    
    stages {
        stage('初始化') {
            steps {
                script {
                    // 确定构建分支(Webhook优先)
                    if (env.ref) {
                        env.BUILD_BRANCH = env.ref.tokenize('/')[-1]
                        echo "Webhook触发构建分支: ${env.BUILD_BRANCH}"
                    } else {
                        env.BUILD_BRANCH = params.BRANCH
                        echo "手动触发构建分支: ${params.BRANCH}"
                    }
                    
                    // 验证 Node.js 版本
                    sh 'node -v && npm -v'
                }
            }
        }
        
        stage('检出代码') {
            steps {
                git branch: env.BUILD_BRANCH,
                    url: '阿里云代码仓库地址',
                    credentialsId: 'jenkin凭证ID'
            }
        }
        
        stage('依赖变更检测') {
            steps {
                script {
                    // 强制刷新模式
                    if (params.FORCE_REINSTALL) {
                        env.DEPENDENCIES_CHANGED = "true"
                        echo "强制刷新依赖缓存"
                        return
                    }
                    
                    // 确保缓存目录存在
                    sh "mkdir -p ${CACHE_DIR}"
                    
                    // 计算package.json哈希值
                    def lockFile = fileExists("yarn.lock") ? "yarn.lock" : "package-lock.json"
                    def currentHash = sh(
                        script: "sha256sum package.json ${lockFile} 2>/dev/null | sha256sum | cut -d ' ' -f1",
                        returnStdout: true
                    ).trim()
                    
                    // 获取上次哈希值
                    def cachedHash = ""
                    if (fileExists("${CACHE_DIR}/package_hash.txt")) {
                        cachedHash = readFile("${CACHE_DIR}/package_hash.txt").trim()
                    }
                    
                    // 判断依赖变更
                    env.DEPENDENCIES_CHANGED = (currentHash != cachedHash) ? "true" : "false"
                    echo "依赖变更状态: ${env.DEPENDENCIES_CHANGED}"
                    
                    // 保存当前哈希值
                    if (currentHash) {
                        writeFile file: "${CACHE_DIR}/package_hash.txt", text: currentHash
                    }
                }
            }
        }
        
        stage('恢复缓存') {
            when {
                expression {
                    return env.DEPENDENCIES_CHANGED == "false" && fileExists("${CACHE_DIR}/node_modules.tar")
                }
            }
            steps {
                sh """
                    mkdir -p node_modules
                    tar -xf ${CACHE_DIR}/node_modules.tar -C .
                """
            }
        }
        
        stage('安装依赖') {
            steps {
                script {
                    sh """
                        # 配置镜像源加速
                        npm config set registry ${NPM_REGISTRY}
                        npm config set sass_binary_site ${NPM_REGISTRY}/mirrors/node-sass/
                    """
                    
                    if (env.DEPENDENCIES_CHANGED == "true" || !fileExists("node_modules")) {
                        echo "执行完整依赖安装..."
                        sh "npm ci --prefer-offline --no-audit --silent"
                    } else {
                        echo "依赖未变更,跳过安装..."
                    }
                }
            }
        }
        
      stage('构建') {
            steps {
                sh '''
                    # 优化构建参数
                    export NODE_OPTIONS="--max-old-space-size=4096"
                    export GENERATE_SOURCEMAP=false
                    
                    echo "开始构建项目..."
                    echo "Node version: $(node -v)"
                    echo "NPM version: $(npm -v)"
                    
                    # 根据不同环境使用不同构建配置
                    if [ "${BUILD_BRANCH}" = "master" ]; then
                        echo "生产环境构建"
                        npm run build
                    elif [ "${BUILD_BRANCH}" = "test" ]; then
                        echo "test环境构建"
                        npm run test
                    else
                        echo "dev环境构建"
                        npm run dev
                    fi
                    
                    # 检查构建结果
                    if [ -d "dist" ]; then
                        echo "构建成功完成"
                        echo "构建产物大小: $(du -sh dist)"
                    else
                        echo "构建失败:未找到构建产物"
                        exit 1
                    fi
                '''
            }
         }
        
        stage('验证构建产物') {
            steps {
                sh '''
                    if [ ! -d "dist" ]; then
                        echo "构建产物目录不存在"
                        exit 1
                    fi
                    
                    if [ ! -f "dist/index.html" ]; then
                        echo "入口文件不存在"
                        exit 1
                    fi
                    
                    echo "构建产物验证通过"
                    ls -la dist/
                '''
            }
        }
        
        stage('保存缓存') {
            when {
                expression {
                    return env.DEPENDENCIES_CHANGED == "true"
                }
            }
            steps {
                sh """
                    mkdir -p ${CACHE_DIR}
                    tar -cf ${CACHE_DIR}/node_modules.tar node_modules
                    
                    # 清理策略:保留最近5次缓存
                    cd ${CACHE_DIR}
                    ls -t node_modules.tar 2>/dev/null | tail -n +6 | xargs rm -f
                """
            }
        }
        
        stage('部署') {
            steps {
                script {
                    def DEPLOY_MAP = [
                        "dev": "/home/wwwroot/dev",
                        "test": "/home/wwwroot/test",
                        "master": "IP:/home/wwwroot/master"
                     ]
                    echo "确定部署目标..............."
                    // 确定部署目标
                    def deployTarget = ""
                    def isRemoteDeploy = false
                    deployTarget = DEPLOY_MAP[env.BUILD_BRANCH]
                    echo "部署目录为...............${deployTarget}"
                    if (!deployTarget) {
                            error "未配置分支 ${env.BUILD_BRANCH} 的部署映射"
                        }
                    if (env.BUILD_BRANCH == 'master') {
                            isRemoteDeploy = true
                        }
                   
                    if (isRemoteDeploy) {
                        echo "远程部署中..............."
                        // 远程部署(SSH方式)
                        // 拆分服务器和路径
                        def (server, path) = deployTarget.tokenize(':')
                         // 安全检查
                        if (!server || !path || path == '/') {
                            error "无效的部署路径配置: server=${server}, path=${path}"
                        }
                        sh """
                                rsync -avz --delete \\
                                  --exclude='*.map' \\
                                  --checksum \\
                                  --compress \\
                                  --partial \\
                                  dist/ ${server}:${path}/
                        """
                    } else {
                        echo "本地部署中..............."
                        // 本地部署
                        def path = deployTarget
                        if (!path || path == '/') {
                            error "无效的本地部署路径: ${path}"
                        }
                        sh """
                            rsync -avz --delete \\
                              --exclude='*.map' \\
                              --checksum \\
                              dist/ ${path}/
                        """
                    }
                }
            }
        }
    }
    
   post {
	    
        success {
            notifyDingTalk("SUCCESS") // 构建成功时通知
        }

        failure {
            notifyDingTalk("FAILURE") // 构建失败时通知
        }
    }
}

// 定义通用的钉钉通知方法
def notifyDingTalk(String buildStatus) {
    script {
        // 获取构建信息
        def branchName = env.BUILD_BRANCH
        def duration = currentBuild.durationString
        def executor = currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause')?.userId ?: 'webhook'
        def commitHash = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
        def committer = sh(script: 'git log -1 --pretty=format:"%an"', returnStdout: true).trim()
        def commitMessage = sh(script: 'git log -1 --pretty=format:"%s"', returnStdout: true).trim()
        def rawCommitTime = sh(script: 'git log -1 --pretty=format:"%cd" --date=iso', returnStdout: true).trim()
        def formattedCommitTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(
            new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(rawCommitTime)
        )

        // 获取变更内容(修改的文件列表)
        def changedFiles = sh(script: 'git diff --name-only HEAD~1 HEAD', returnStdout: true).trim()

        // 动态生成标题和状态颜色
        def (statusTitle, statusColor) = buildStatus == "SUCCESS" ? ["构建成功", "#00FF00"] : ["构建失败", "#FF0000"]

        // 获取项目名(从环境变量或静态配置中)
        def projectName = env.JOB_NAME ?: "未知项目"

        // 构造钉钉消息内容
        def message = """
        ## Jenkins <font color="${statusColor}">${statusTitle}</font>通知
- **项目**: ${projectName}
- **分支**: ${branchName}
- **状态**: <font color="${statusColor}">${buildStatus}</font>
- **持续时间**: ${duration}
- **执行人**: ${executor}
- **代码推送人**: ${committer}
- **提交哈希**: ${commitHash}
- **提交时间**: ${formattedCommitTime}
- **提交信息**: ${commitMessage}
- **变更内容**: ${changedFiles ?: "无变更内容"}
""".stripIndent()

        // 钉钉机器人配置
        def dingtalkWebhookUrl = 'webhook地址'

           // 使用 JsonOutput 生成 JSON 数据
        def payload = groovy.json.JsonOutput.toJson([
            msgtype: "markdown",
            markdown: [
                title: "[${projectName}] Jenkins ${statusTitle}通知",
                text: message
            ],
            at: [
                isAtAll: true
            ]
        ])
        httpRequest(
            url: dingtalkWebhookUrl,
            httpMode: 'POST',
            contentType: 'APPLICATION_JSON_UTF8',  // 确保 UTF-8 编码
            requestBody: payload,
            validResponseCodes: '200:299'  // 接受 200-299 状态码
        )
        cleanWs()
    }
}

4.遇到的问题

  • 1.环境兼容问题-linux版本/jenkins版本/java版本三者之间的兼容

  • 2.环境变量作用范围的问题-切换java版本配置环境的生效问题

    通过/etc/profile或/etc/environment配置的环境变量仅对当前终端会话有效,而通过/etc/systemd/system设置的环境变量可确保服务启动时自动加载‌

5.实际效果

相关推荐
梦在深巷@18 分钟前
ica1靶机练习
linux·运维·服务器
岚天start21 分钟前
Linux ps -ef 命令解析
linux·运维·服务器·ps·ef
cpsvps1 小时前
磁盘IO优先级控制对美国服务器存储子系统的调优验证
运维·服务器
孙克旭_1 小时前
day065-ALB负载均衡与云盘扩容
linux·运维·阿里云·负载均衡
lenvonsam2 小时前
崩溃!公司 GitLab 掉链子!莫慌,交给AI助手吧~
运维·gitlab
行星0082 小时前
centos7 aarch64上安装PostgreSQL14.3
linux·运维·数据库·postgresql
雪域迷影3 小时前
Ubuntu22.04中生成gitee码云的ssh-key并添加到gitee网站上
运维·gitee·ssh·ubuntu22.04
YY188193953953 小时前
负载均衡集群HAproxy
运维·负载均衡
Lovyk3 小时前
rsync+sersync实现文件实时同步
linux·运维
G_H_S_3_4 小时前
【网络运维】Linux:软件包管理
linux·运维·网络