1. 集成共享库后Pipeline
总体思路,将一些常用且用法比较固定的流水线抽离出来做为方法,在其他流水线中可以进行服用减少代码量
php
#!groovy
//引入共享库
@Library('ops-share-librarya@master') _
//引入方法
def nexus = new org.devops.nexus()
def sonarq = new org.devops.sonarq()
def dingtalk = new org.devops.dingtalk()
//这里因为有些插件不能使用environment定义的变量所以直接放在这里
String robotId = "BuildBoy"
String GIT_REPO_URL_String = 'git@gitee.com:xxx/spring-boot-3-hello-world-jdk21.git'
pipeline {
agent { node { label "node47"}}
tools {
//...
}
environment {
GIT_REPO_URL = 'git@gitee.com:xxxx/spring-boot-3-hello-world-jdk21.git'
REGISTRY_URL = 'harbor.xxxx.top'
HARBOR_URL = 'https://harbor.xxxx.top'
PROJECT_GROUP = 'devops'
PROJECT_NAME = 'hello-world-jdk21'
CONTAINER_NAME = 'hello-world-jdk21'
OPS_SHARE_LIBRARY = 'git@gitee.com:xxxx/ops-share-librarya.git'
REPOSITORY = 'DevopsArtifact'
}
options {
timeout(time: 10, unit: 'MINUTES')
disableConcurrentBuilds()
timestamps()
}
parameters {
gitParameter(
name: 'BRANCH_TAG',
type: 'PT_BRANCH_TAG',
defaultValue: 'master',
description: '请选择你要部署的分支或Tag',
useRepository: GIT_REPO_URL_String, // 这里要使用完整的Git仓库地址
quickFilterEnabled: true // 启用快速筛选
)
booleanParam(defaultValue: false, description: '是否进行项目回滚?', name: 'ROLLBACK_TAG')
}
stages {
stage('Example') {
steps {
script {
// 测试了下打印方法
PrintMes("Generated version: green", 'green')
//def version = createVersion()
//PrintMes("Generated version: ${version}", 'red')
}
}
}
stage('Check requirement') {
steps {
script {
// 判断条件
//...省略
}
}
}
stage('CleanWorkDir') {
steps {
//...省略
}
}
stage('Checkout') {
steps {
script {
echo "Using repository: ${env.GIT_REPO_URL}"
checkout([$class: 'GitSCM',
branches: [[name: params.BRANCH_TAG]],
userRemoteConfigs: [[url: "${GIT_REPO_URL}", credentialsId: "GiteeKey"]]]
)
// 增加了一些构建信息变量
def PULL_TIME = sh(script: "echo `date +'%Y-%m-%d %H:%M:%S'`", returnStdout: true).trim() // 构建开始时间
def COMMIT_ID = sh(script: 'git log -1 --pretty=format:%h', returnStdout: true).trim() // 代码COMMIT_ID
def TRACE_ID = sh(script: "echo `head -c 32 /dev/random | base64`", returnStdout: true).trim() // 随机生成TRACE_ID
def COMMIT_USER = sh(script: 'git log -1 --pretty=format:%an', returnStdout: true).trim() // 代码最后提交者
def COMMIT_TIME = sh(script: 'git log -1 --pretty=format:%ai', returnStdout: true).trim() // 提交最后时间
def COMMIT_INFO = sh(script: 'git log -1 --pretty=format:%s', returnStdout: true).trim() // 提交最后信息
PrintMes("Commit User: ${COMMIT_USER}", 'purple')
PrintMes("Commit Time: ${COMMIT_TIME}", 'purple')
PrintMes("Commit Info: ${COMMIT_INFO}", 'purple')
if (params.ROLLBACK_TAG) {
wrap([$class: 'BuildUser']) {
echo "Built by: ${env.BUILD_USER_ID}"
currentBuild.description =
"流水线执行者: ${env.BUILD_USER_ID}\n" +
"项目回滚ID: ${env.BRANCH_TAG}\n" +
"tag: '项目回滚'"
}
} else {
env._tag = createVersion()
wrap([$class: 'BuildUser']) {
echo "Built by: ${env.BUILD_USER_ID}"
currentBuild.description =
"流水线执行者: ${env.BUILD_USER_ID}\n" +
"分支: ${env.BRANCH_TAG}\n" +
"tag: ${_tag}\n" +
"代码提交者: ${COMMIT_USER}\n" +
"构建开始时间: ${PULL_TIME}\n" +
"提交时间: ${COMMIT_TIME}\n" +
"提交信息: ${COMMIT_INFO}"
}
}
}
}
}
stage('Build') {
when {
expression {
return !params.BRANCH_TAG.startsWith('rel-')
}
}
steps {
PrintMes("项目开始构建", 'green')
sh 'mvn clean package -Dmaven.test.skip=true'
sh 'tar zcf ${PROJECT_NAME}.tar.gz target/spring-boot-3-hello-world-1.0.0-SNAPSHOT.jar'
}
}
stage('Upload to Nexus') {
when {
expression {
return !params.BRANCH_TAG.startsWith('rel-')
}
}
steps {
script {
PrintMes("Upload to Nexus", 'green')
echo "Uploading ${PROJECT_NAME} to ${REPOSITORY} with tag ${_tag}"
//使用方法进行上传,传入参数的时候不要带有${},直接使用变量名即可,否则报错
nexus.NexusUploadTargz(PROJECT_NAME,REPOSITORY,_tag)
}
}
}
stage("SonarQube Analysis") {
when {
expression {
// 检查 BRANCH_TAG 是否不以 'rel-' 开头
return !params.BRANCH_TAG.startsWith('rel-')
}
}
steps {
script {
PrintMes("SonarQube Analysis", 'green')
//使用方法进行 代码检查
sonarq.SonarQubeAnalysis("${JOB_NAME}", 'SonarqubeServ13', 'Jenkins-SonarqubeServ')
}
}
}
stage("Quality Gate") {
when {
expression {
// 检查 BRANCH_TAG 是否不以 'rel-' 开头
return !params.BRANCH_TAG.startsWith('rel-')
}
}
steps {
script {
//使用方法进行 代码检查状态查询
PrintMes("Checking Quality Gate", 'green')
sonarq.checkQualityGate(5)
}
}
}
stage('Wait for SonarQube Analysis') {
when {
expression {
// 检查 BRANCH_TAG 是否不以 'rel-' 开头
return !params.BRANCH_TAG.startsWith('rel-')
}
}
steps {
script {
PrintMes("Wait for SonarQube Analysis'", 'green')
//使用方法进行 代码检查状态查询
sonarq.waitForSonarQubeAnalysis('SonarqubeServ13', 'Jenkins-SonarqubeServ', "${JOB_NAME}", 10)
}
}
}
//部署过程这里就省略了,可以根据自己实际情况或者我前面的ansible脚本进行修改
}
post {
always{
script {
println("流水线结束后,经常做的事情")
}
}
success {
script {
//使用dingtalk方法进行告警
println("流水线结束后,经常做的事情")
dingtalk.DingdingReq(robotId, "构建成功 ✅")
}
}
failure{
script {
println("流水线结束后,经常做的事情")
dingtalk.DingdingReq(robotId, "构建失败 ❌")
}
}
aborted{
script {
println("流水线取消后,做的事情 ")
dingtalk.DingdingReq(robotId, "构建取消 ⚠️")
}
}
}
}
2.方法代码
src/org/devops/dingtalk.groovy(钉钉告警方法),这里采集了下构建信息然后定义了2个参数在使用时进行传参。
perl
package org.devops
def GetChangeString() {
MAX_MSG_LEN = 100
def changeString = ""
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
commitTime = new Date(entry.timestamp).format("yyyy-MM-dd HH:mm:ss")
changeString += "> - {truncated_msg} [{entry.author} {commitTime}]\n"
}
}
if (!changeString) {
changeString = "> - No new changes"
}
return changeString
}
def DingdingReq(RobotID, Status) {
// 确保使用正确的上下文
wrap([$class: 'BuildUser']) {
def changeString = GetChangeString()
dingtalk (
robot: RobotID,
type: 'MARKDOWN',
title: '你有新的消息,请注意查收',
text: [
"### 构建信息",
"> - 应用名称:**${env.JOB_NAME}**",
"> - 构建结果:**${Status}**",
"> - 构建分支Or回滚Tag:**${env.BRANCH_TAG}**",
"> - 构建发起:**${env.BUILD_USER}**",
"> - 持续时间:**${currentBuild.durationString}**",
"> - 构建日志:[点击查看详情](${env.BUILD_URL}console)",
"### 更新记录:",
"${changeString}"
],
at: [
'xxx'
]
)
}
}
src/org/devops/nexus.groovy Nexus上传制品防范,定义了2个方法,一个上传tar.gz方法,一个上传jar,上传jar的方法如果你要使用根据自己要求,要调试下
dart
package org.devops
def NexusUploadTargz(String PROJECT_NAME, String REPOSITORY, String VERSION){
// 使用之前生成的 version 变量
nexusArtifactUploader artifacts: [
[
artifactId: "${PROJECT_NAME}", // 替换为您的 artifactId
classifier: '',
file: "${PROJECT_NAME}.tar.gz", // 替换为您的文件路径
type: 'tar.gz' // 根据您的文件类型进行修改
]
],
//文件file类型有jar,pom,war,zip,tar.gz
credentialsId: 'Nexus3-DevOps',
groupId: 'top.xxx',
nexusUrl: 'registryv.xxx.top',
nexusVersion: 'nexus3',
protocol: 'https',
repository: "${REPOSITORY}",
version: "${VERSION}"
}
def NexusUploadJar(){
// 使用之前生成的 version 变量
nexusArtifactUploader artifacts: [
[
artifactId: 'basejar', // 替换为您的 artifactId
classifier: '',
file: 'target/spring-boot-3-hello-world-1.0.0-SNAPSHOT.jar', // 替换为您的文件路径
type: 'jar' // 根据您的文件类型进行修改
],
[
artifactId: 'spring-boot-3-hello-world', // 同样的 artifactId,或根据需要修改
classifier: 'pom',
file: 'pom.xml', // 指向您的 pom.xml 文件
type: 'pom' // 文件类型为 pom
]
],
//文件file类型有jar,pom,war,zip,tar.gz
credentialsId: 'Nexus3-DevOps',
groupId: 'top.xxxx',
nexusUrl: 'registryv.xxxx.top',
nexusVersion: 'nexus3',
protocol: 'https',
repository: 'DevopsArtifact',
version: "v8"
}
src/org/devops/sonarq.groovy 代码审查方法,这里定义了3个方法,扫描的,探测状态的,虽然这玩意没人用,但是还是先做出来以后万一用呢
typescript
package org.devops
def SonarQubeAnalysis(String projectName, String sonarServer, String credentialsId) {
def sonarqubeScanner = tool name: 'SonarScanner501'
// 使用 SonarQube 环境
withSonarQubeEnv(sonarServer) {
// 使用 Jenkins 凭据中的 SonarQube 令牌
withCredentials([string(credentialsId: credentialsId, variable: 'SONAR_TOKEN')]) {
// 执行 SonarQube Scanner CLI 分析
sh """
${sonarqubeScanner}/bin/sonar-scanner \
-Dsonar.projectKey=${projectName} \
-Dsonar.projectName=${projectName} \
-Dsonar.sources=src \
-Dsonar.java.binaries=target/classes \
-Dsonar.host.url=${env.SONAR_HOST_URL} \
-Dsonar.login=${env.SONAR_AUTH_TOKEN}
"""
}
}
}
def checkQualityGate(int timeoutMinutes) {
timeout(time: timeoutMinutes, unit: 'MINUTES') {
script {
def qg = waitForQualityGate() // 等待 SonarQube 的质量门结果
if (qg.status != 'OK') { // 检查质量门的状态
error "Pipeline aborted due to quality gate failure: ${qg.status}" // 中止流水线
}
}
}
}
def waitForSonarQubeAnalysis(String sonarServer, String credentialsId, String projectName, int timeoutMinutes) {
//echo "Project Name: ${projectName}"
withSonarQubeEnv(sonarServer) {
withCredentials([string(credentialsId: credentialsId, variable: 'SONAR_AUTH_TOKEN')]) {
timeout(time: timeoutMinutes, unit: 'MINUTES') {
waitUntil {
def curlCommand = "curl -s -u $SONAR_AUTH_TOKEN: $SONAR_HOST_URL/api/project_analyses/search?project=${projectName}"
def response = sh(script: curlCommand, returnStdout: true).trim()
def exitCode = sh(script: curlCommand, returnStatus: true)
echo "Response from SonarQube: ${response}"
if (exitCode != 0) {
error("Curl command failed with exit code: ${exitCode}. Response: ${response}")
}
if (response) {
try {
def jsonResponse = readJSON(text: response)
if (jsonResponse.analyses && jsonResponse.analyses.size() > 0) {
def latestAnalysis = jsonResponse.analyses[0]
echo "Latest Analysis Key: ${latestAnalysis.key}, Date: ${latestAnalysis.date}"
if (latestAnalysis.events.size() > 0) {
echo "New events found in analysis."
} else {
echo "No new events found in analysis."
}
return true
} else {
error("No analyses found in the response.")
}
} catch (Exception e) {
error("Failed to parse JSON response: ${e.message}. Response: ${response}")
}
} else {
error("Received empty response from SonarQube.")
}
}
}
}
}
}
3.全局方法
src/org/vars/createVersion.groovy 创建tag的全局方法
javascript
def call(){
return new Date().format('yyyyMMddHHmmss') + "_${env.BUILD_ID}"
}
src/org/vars/PrintMes.groovy 打印彩色字体方法
javascript
def call(String value, String color) {
def colors = [
'red' : "\033[40;31m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m",
'green' : "\033[40;32m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m",
'purple' : "\033[40;35m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m",
'yellow' : "\033[40;33m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m"
]
ansiColor('xterm') {
echo colors[color]
}
}
4.共享流水线代码库目录结构

image-20250924162741866
5.引用共享库前后对比

image-20250924163127359

image-20250924163311175
可以看出来,如果将一些共性的东西抽离出来可以极大的简化你的流水线,同时减少你重复写流水线过程,还是很nice的,好了大概Jenkins就这么完结撒花了,等以后在有什么新的发现在更新(这周必须要完工虽有流水线更新,压力大,可能晚点更新内容了撒! 如果你觉得文章还算有用可以帮我点个赞,转个发 嘿嘿!)