跳转至

第七章:触发器与通知

触发器概述

Jenkins Pipeline 可以通过多种方式触发构建:

  • 定时触发:cron 表达式定时执行
  • SCM 轮询:定期检查代码变更
  • Webhook:Git 仓库推送触发
  • 上游 Job:其他 Job 完成后触发
  • 手动触发:用户手动执行

triggers 指令

cron 定时触发

pipeline {
    agent any

    triggers {
        cron('H/15 * * * *')  // 每 15 分钟执行一次
    }

    stages {
        stage('Build') {
            steps {
                echo 'Scheduled build'
            }
        }
    }
}

cron 表达式

┌───────────── 分钟 (0-59)
│ ┌───────────── 小时 (0-23)
│ │ ┌───────────── 日 (1-31)
│ │ │ ┌───────────── 月 (1-12)
│ │ │ │ ┌───────────── 星期 (0-7, 0 和 7 都是周日)
│ │ │ │ │
* * * * *

H 表示自动分散负载(Hash)

常用表达式:

triggers {
    // 每 15 分钟
    cron('H/15 * * * *')

    // 每小时
    cron('H * * * *')

    // 每天凌晨 2 点
    cron('H 2 * * *')

    // 每周一早上 9 点
    cron('H 9 * * 1')

    // 每月 1 号凌晨
    cron('H H 1 * *')

    // 工作日早上 9 点
    cron('H 9 * * 1-5')

    // 每小时 30 分
    cron('30 * * * *')
}

pollSCM 轮询

pipeline {
    agent any

    triggers {
        pollSCM('H/5 * * * *')  // 每 5 分钟检查一次 SCM
    }

    stages {
        stage('Build') {
            steps {
                echo 'SCM change detected'
            }
        }
    }
}

GitHub Webhook

pipeline {
    agent any

    triggers {
        githubPush()
    }

    stages {
        stage('Build') {
            steps {
                echo 'GitHub push triggered'
            }
        }
    }
}

GitLab Webhook

pipeline {
    agent any

    triggers {
        gitlab(
            triggerOnPush: true,
            triggerOnMergeRequest: true,
            branchFilterType: 'All'
        )
    }

    stages {
        stage('Build') {
            steps {
                echo 'GitLab event triggered'
            }
        }
    }
}

上游 Job 触发

pipeline {
    agent any

    triggers {
        upstream(
            upstreamProjects: 'build-job',
            threshold: hudson.model.Result.SUCCESS
        )
    }

    stages {
        stage('Deploy') {
            steps {
                echo 'Upstream job completed'
            }
        }
    }
}

组合触发器

pipeline {
    agent any

    triggers {
        cron('H 2 * * *')           // 每天凌晨 2 点
        pollSCM('H/5 * * * *')      // 每 5 分钟检查 SCM
        githubPush()                 // GitHub 推送触发
    }

    stages {
        stage('Build') {
            steps {
                script {
                    def causes = currentBuild.getBuildCauses()
                    causes.each { cause ->
                        echo "Triggered by: ${cause.shortDescription}"
                    }
                }
            }
        }
    }
}

通知机制

Email 通知

pipeline {
    agent any

    post {
        success {
            mail to: 'team@example.com',
                 subject: "✅ Build Success: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                 body: """
Build succeeded!

Job: ${env.JOB_NAME}
Build: ${env.BUILD_NUMBER}
URL: ${env.BUILD_URL}
"""
        }

        failure {
            mail to: 'team@example.com',
                 subject: "❌ Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                 body: """
Build failed!

Job: ${env.JOB_NAME}
Build: ${env.BUILD_NUMBER}
URL: ${env.BUILD_URL}
"""
        }
    }
}

emailext 插件

pipeline {
    agent any

    post {
        always {
            emailext(
                subject: '${DEFAULT_SUBJECT}',
                body: '${DEFAULT_CONTENT}',
                to: 'team@example.com',
                attachLog: true,
                compressLog: true
            )
        }
    }
}

Slack 通知

pipeline {
    agent any

    post {
        success {
            slackSend(
                channel: '#builds',
                color: 'good',
                message: "✅ Build Success: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
            )
        }

        failure {
            slackSend(
                channel: '#builds',
                color: 'danger',
                message: "❌ Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}\nURL: ${env.BUILD_URL}"
            )
        }
    }
}

钉钉通知

pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'npm run build'
            }
        }
    }

    post {
        success {
            dingtalk(
                robot: 'dingtalk-robot',
                type: 'MARKDOWN',
                title: '构建成功',
                text: [
                    "### 构建成功 ✅",
                    "- 任务: ${env.JOB_NAME}",
                    "- 构建: #${env.BUILD_NUMBER}",
                    "- [查看详情](${env.BUILD_URL})"
                ]
            )
        }

        failure {
            dingtalk(
                robot: 'dingtalk-robot',
                type: 'MARKDOWN',
                title: '构建失败',
                text: [
                    "### 构建失败 ❌",
                    "- 任务: ${env.JOB_NAME}",
                    "- 构建: #${env.BUILD_NUMBER}",
                    "- [查看详情](${env.BUILD_URL})"
                ]
            )
        }
    }
}

企业微信通知

pipeline {
    agent any

    post {
        always {
            script {
                def status = currentBuild.result ?: 'SUCCESS'
                def emoji = status == 'SUCCESS' ? '✅' : '❌'
                def color = status == 'SUCCESS' ? 'green' : 'red'

                def message = """
{
    "msgtype": "markdown",
    "markdown": {
        "content": "${emoji} **构建${status == 'SUCCESS' ? '成功' : '失败'}**\\n> 任务: ${env.JOB_NAME}\\n> 构建: #${env.BUILD_NUMBER}\\n> [查看详情](${env.BUILD_URL})"
    }
}
"""

                withCredentials([string(credentialsId: 'wechat-webhook', variable: 'WEBHOOK_URL')]) {
                    sh "curl -X POST -H 'Content-Type: application/json' -d '${message}' $WEBHOOK_URL"
                }
            }
        }
    }
}

HTTP 通知

pipeline {
    agent any

    post {
        always {
            script {
                def payload = """
{
    "job": "${env.JOB_NAME}",
    "build": "${env.BUILD_NUMBER}",
    "status": "${currentBuild.result ?: 'SUCCESS'}",
    "url": "${env.BUILD_URL}"
}
"""

                withCredentials([string(credentialsId: 'api-token', variable: 'API_TOKEN')]) {
                    sh """
                        curl -X POST \
                            -H "Authorization: Bearer $API_TOKEN" \
                            -H "Content-Type: application/json" \
                            -d '${payload}' \
                            https://api.example.com/webhook/jenkins
                    """
                }
            }
        }
    }
}

条件通知

根据分支发送

pipeline {
    agent any

    post {
        success {
            script {
                if (env.BRANCH_NAME == 'main') {
                    slackSend(
                        channel: '#production',
                        color: 'good',
                        message: "Production deployed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
                    )
                } else if (env.BRANCH_NAME == 'develop') {
                    slackSend(
                        channel: '#development',
                        color: 'good',
                        message: "Staging deployed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
                    )
                }
            }
        }
    }
}

根据构建原因发送

pipeline {
    agent any

    post {
        success {
            script {
                def causes = currentBuild.getBuildCauses()
                def isScheduled = causes.any { it._class.contains('TimerTriggerCause') }

                if (isScheduled) {
                    mail to: 'team@example.com',
                         subject: "Scheduled build completed: ${env.JOB_NAME}",
                         body: "Scheduled build finished successfully."
                }
            }
        }
    }
}

通知责任人

pipeline {
    agent any

    post {
        failure {
            script {
                // 获取最近提交者
                def committer = sh(
                    script: 'git log -1 --pretty=format:"%ae"',
                    returnStdout: true
                ).trim()

                mail to: committer,
                     subject: "Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                     body: "Your commit caused the build to fail.\n\nURL: ${env.BUILD_URL}"
            }
        }
    }
}

构建状态徽章

生成徽章

pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'npm run build'
            }
        }
    }

    post {
        always {
            // 添加构建状态到描述
            script {
                def status = currentBuild.result ?: 'SUCCESS'
                def emoji = status == 'SUCCESS' ? '✅' : '❌'
                currentBuild.description = "${emoji} Build ${status}"
            }
        }
    }
}

README 徽章

在 README 中添加 Jenkins 构建状态徽章:

[![Jenkins Build](https://jenkins.example.com/buildStatus/icon?job=my-job)](https://jenkins.example.com/job/my-job/)

完整示例

pipeline {
    agent any

    triggers {
        cron('H 2 * * *')       // 每天凌晨 2 点
        githubPush()             // GitHub 推送
    }

    environment {
        SLACK_CHANNEL = '#builds'
    }

    stages {
        stage('Build') {
            steps {
                sh 'npm run build'
            }
        }

        stage('Test') {
            steps {
                sh 'npm test'
            }
        }

        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh 'npm run deploy'
            }
        }
    }

    post {
        always {
            // 清理
            cleanWs()

            // 归档日志
            archiveArtifacts artifacts: 'logs/**', allowEmptyArchive: true
        }

        success {
            script {
                // Slack 通知
                slackSend(
                    channel: env.SLACK_CHANNEL,
                    color: 'good',
                    message: """
✅ Build Success

• Job: ${env.JOB_NAME}
• Build: #${env.BUILD_NUMBER}
• Branch: ${env.BRANCH_NAME ?: 'N/A'}
• Duration: ${currentBuild.durationString}
• URL: ${env.BUILD_URL}
"""
                )

                // 主分支发送邮件
                if (env.BRANCH_NAME == 'main') {
                    emailext(
                        subject: "✅ Production Deployed: ${env.JOB_NAME}",
                        body: """
Production deployment completed successfully.

Job: ${env.JOB_NAME}
Build: #${env.BUILD_NUMBER}
Duration: ${currentBuild.durationString}
URL: ${env.BUILD_URL}
""",
                        to: 'team@example.com'
                    )
                }
            }
        }

        failure {
            script {
                // 获取提交者
                def committer = sh(
                    script: 'git log -1 --pretty=format:"%ae"',
                    returnStdout: true
                ).trim()

                // Slack 通知
                slackSend(
                    channel: env.SLACK_CHANNEL,
                    color: 'danger',
                    message: """
❌ Build Failed

• Job: ${env.JOB_NAME}
• Build: #${env.BUILD_NUMBER}
• Branch: ${env.BRANCH_NAME ?: 'N/A'}
• Committer: ${committer}
• URL: ${env.BUILD_URL}
"""
                )

                // 邮件通知提交者
                emailext(
                    subject: "❌ Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                    body: """
Your commit caused the build to fail.

Job: ${env.JOB_NAME}
Build: #${env.BUILD_NUMBER}
Branch: ${env.BRANCH_NAME ?: 'N/A'}
URL: ${env.BUILD_URL}

Please check the build log for details.
""",
                    to: committer
                )
            }
        }

        unstable {
            slackSend(
                channel: env.SLACK_CHANNEL,
                color: 'warning',
                message: "⚠️ Build Unstable: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
            )
        }
    }
}

小结

本章介绍了触发器与通知:

  • cron 定时触发和 pollSCM 轮询
  • Webhook 触发(GitHub、GitLab)
  • 上游 Job 触发
  • Email、Slack、钉钉、企业微信通知
  • 条件通知和责任人通知
  • 构建状态徽章

下一章将介绍分布式构建。