Jenkins(Pipeline job)

文章目录

Pipeline job

Pipeline是什么

从某种抽象层次上讲,部署流水线(Deployment pipeline)是指从软件版本控制库到用户手中这一过程的自动化表现形式

Jenkins原本就支持pipeline,只不过最初该功能被称为"任务"

  • Jenkins 1.x仅支持界面手动配置流水线(Freestyle Job),而2.x则实现了"流水线即代码"(pipeline as a code)
  • 使用代码而非UI完成pipeline定义的意义在于
    • 更好地版本化:支持版本控制
    • 更好地协作:pipeline的修改对所有人可见,支持代码审查
    • 更好的可重用性

在Jenkins 2.x中,用于保存pipeline代码并可被Jenkins加载的文件称为Jenkinsfile;

  • 流水线既可以在pipeline类型的任务中创建,也可以定义在一个称为Jenkinsfile的外部文件中,它可以同代码

    保存在一起

  • Jenkinsfile就是一个文本文件,它是部署流水线概念在Jenkins中的表现形式

流水线语法初步(脚本式、声明式)

Jenkins 2.x支持两种pipeline语法:脚本式语法 和声明式语法

  • 脚本式语法:没有固定关键字,纯用 Groovy 语法写,想加什么逻辑就加什么;

  • 声明式语法:格式固定,用 pipeline、agent、stages、stage、steps 这些固定关键字,像填表格一样写;

groovy 复制代码
//脚本式流水线: node用于脚本式流水线,从技术层面上来说
//它是一个步骤,代表可以用于流水线中执行活动的资源
node('node01') {
    stages {
        stage('Build') {
            steps {
                echo 'Building...'
            }
        }
        stage('Test') {
            steps {
                echo 'Testing...'
            }
        }
    }
}



//声明式流水线:agent用于声明式流水线,它作为一个指令用于分配节点;
pipeline {
    agent {
        label 'node01'  // 节点标签,对应 Jenkins 中配置的节点名称
    }
    stages {
        stage('Build') {
            steps {
                echo 'Building...'
            }
        }
        stage('Test') {
            steps {
                echo 'Testing...'
            }
        }
    }
}

Pipeline的组成

  • pipeline实际上就是基于Groovy语言实现的一种DSL(Domain-Specific Language),用于描述代码编译到打包发布的整个流水线是如何进行的
  • 一个标准pipeline流水线基本组成元素
元素名称 核心角色 是否必选 关键说明
pipeline 流水线最外层结构 是 包裹整条流水线的完整逻辑,是声明式 Pipeline 的固定入口,所有配置需嵌套其中
stages 所有功能阶段(stage)的容器 流水线中只允许有一个stages 是 用于统一管理多个 stage,确保流水线按阶段顺序执行(不可单独存在,需嵌套在 pipeline 内)
stage 流水线的独立功能阶段(如编译、测试、部署) 是 每个 stage 对应一个具体功能,需嵌套在 stages 内,可包含 steps、post 等子配置
steps 单个 stage 内的具体执行步骤集合 是 定义完成该阶段功能的操作(如 sh 执行 Shell 命令、echo 输出日志),与其他配置分隔
agent 指定流水线的执行位置 是 分配执行资源(物理机、虚拟机、容器等),通过 label 关联 Jenkins 节点(如 agent { label 'node01' })
post 流水线 / 单个 stage 执行完成后的附加步骤(如通知、清理) 否 唯一可省略的核心元素,但实际应用中建议配置(如执行成功 / 失败的告警、工作目录清理)

创建一个pipeline项目

创建pipeline项目时,将首先打开一个基于Web表单形式的新项目配置页面

  • 每个主要的配置部分都有一个对应的选项卡

  • 底部还会有一个用于输入pipeline code的文本框,这是Jenkins内置的流水线编辑器

Pipline基础及代码示例

groovy 复制代码
pipeline {
    agent any

    stages {
        stage('Hello') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

通过左边菜单中的"Build Now"便可触发pipeline的一次运行

  • 蓝色条纹:运行中
  • 白色:stage尚未执行
  • 红色条纹:steps执行失败
  • 绿色:stage执行成功
  • 浅红色:stage执行成功,但是下游某个stage出现失败

Pipline语法

声明式Pipeline的结构(区块、指令、步骤)

groovy 复制代码
pipeline{
    agent any
    stages{
        stage('<stage_name>'){
            steps{...}
        }
    }
}

Pipeline声明结构由区块 和指令 组成,每一个区块 又可包含其他的区块 、指令 和步骤 ,以及一些条件的定义

类型 核心定义 作用说明 具体示例
Section(区块) 用于将某一个时间点需一同运行的条目组织在一起 划分 Pipeline 的功能模块,明确不同逻辑单元的执行范围和关联关系 1. agent:指定运行代码的节点(如 agent { label 'node01' }) 2. stages:组织所有 stage(如 stages { stage('Build') { ... } }); 3. steps:组织具体执行步骤(如 steps { sh 'mvn package' }); 4. post:封装 stage/pipeline 执行后的步骤(如 post { success { echo '执行成功' } })
Directive(指令) 负责完成特定功能的语句或代码块 提供 Pipeline 的核心配置能力(如环境变量、工具依赖、触发条件等),支撑流程控制 1. environment:定义环境变量(如 environment { JAVA_HOME = '/usr/lib/jvm/java-11' }); 2. tools:指定工具及版本(如 tools { maven 'maven-3.8' }); 3. triggers:配置触发方式(如 triggers { cron('H 2 * * *') }); 4. input:设置人工确认步骤(如 input message: '是否继续部署?'); 5. when:定义 stage 执行条件(如 when { branch 'master' })
Steps(步骤) 标识特定 区块 的名称,内部可包含任何合法的 DSL 语句,是 Pipeline 的执行单元 承载具体业务操作,是 Pipeline 的核心执行逻辑(如代码拉取、编译、部署等) 1. git:拉取代码(git url: 'xxx.git', branch: 'master'); 2. sh/bat:执行 Shell/Batch 命令(sh 'docker build -t app:v1 .'); 3. echo:输出日志(echo '开始编译'); 4. junit:解析测试报告(junit 'target/surefire-reports/*.xml')

Pipeline支持的指令

Jenkins Pipeline支持指令主要有如下这些

指令名称 核心功能 使用范围 关键特性 实操示例(精简语法)
environment 定义环境变量,支持 credentials () 引用 Jenkins 凭证 pipeline/stage 级别 全局 / 阶段复用,凭证安全无明文 全局:APP_NAME='my-app'、DB_PWD=credentials ('db-cred');阶段:environment { BUILD_PATH='target' }
tools 指定 Maven/JDK/Git 等工具,自动加入 PATH 可直接调用 pipeline/stage 级别 需提前在 Jenkins "全局工具配置" 定义,支持阶段差异化版本 全局:tools {jdk 'jdk-11'};阶段:tools { maven '3.8.8' },步骤直接用:java -version
parameters 触发时用户输入参数(字符串 / 选择 / 布尔等) 仅 pipeline 级别 动态传参,支持多参数类型 parameters { choice('DEPLOY_ENV', 'dev','test','prod');string('VERSION', 'v1.0') }
options 流水线全局配置(重试 / 超时 / 日志等),支持插件扩展 仅 pipeline 级别 控制运行规则,需对应插件(如 timestamps) options { retry(2);timeout(45, 'MINUTES');timestamps();disableConcurrentBuilds() }
triggers 自动触发(定时 / 代码轮询),与 Webhook 互补 仅 pipeline 级别 无需手动触发,Webhook 场景可省略 triggers { cron('H 3 * * *');pollSCM('H/30 * * * *') }
libraries 导入共享库,复用通用逻辑(部署脚本 / 工具类) 仅 pipeline 级别 需提前配置共享库,支持指定分支 / 版本 开头:@Library ('common-lib@main') _,步骤调用:deployToEnv (app:'my-app', env:'prod')
stage 封装功能阶段,包含 steps 及 when/input 等指令 仅 stages 段内 核心功能单元,支持嵌套 stage stage('Build&Test') { stages { stage('Compile') { sh 'mvn compile' } } }
input 暂停流水线,需用户输入 / 确认后继续(stage 专用) 仅 stage 级别 人工干预(如生产审核),支持超时配置 input (message:' 确认部署生产?', timeout:5, 'MINUTES'),之后执行:sh 'prod-deploy.sh'
when 设定 stage 运行条件,满足才执行(stage 专用) 仅 stage 级别 支持分支 / 参数等条件,可多条件组合 when { allOf { branch 'master';params.DEPLOY_ENV == 'prod' } }

Section: agent

参数类型 核心说明 使用场景 实操示例(精简语法)
any 匹配任意可用节点,是最常用的默认配置 无需指定特定节点,流水线可在任意节点执行 pipeline { agent any; stages { ... } }(pipeline 顶端默认配置)
none pipeline 顶端使用时,不定义默认 agent; 需为每个 stage 单独指定 agent 不同 stage 需在完全不同节点执行(无通用节点) pipeline { agent none; stages { stage('Build') { agent { label 'build' } ... } } }
label 匹配带有指定标签的节点(节点标签需提前在 Jenkins 配置) 需固定节点环境(如 "编译节点""部署节点") pipeline { agent { label 'build-node-01' }; ... }(全局)或 stage { agent { label 'deploy-node' } }(stage 单独指定)
node 功能同 label,额外支持指定自定义工作目录(customWorkspace) 需固定节点 + 自定义工作目录(如单独存储构建文件) agent { node { label 'test-node'; customWorkspace '/data/jenkins/workspace' } }
docker 在动态创建的容器中执行,需指定镜像;可匹配 label 节点或预配置容器节点 容器化部署、环境一致性要求高(避免节点依赖冲突) agent { docker { image 'maven:3.8.8'; label 'docker-node'; args '-v /tmp:/tmp' } }
dockerfile 基于指定 Dockerfile 构建镜像,再在容器中执行;要求 Jenkinsfile 从 SCM 加载 需自定义容器镜像(非公共镜像),多分支流水线场景 agent { dockerfile { filename 'Dockerfile'; dir './docker'; label 'build-node' } }
kubernetes 在 K8s 集群的 Pod 中执行;要求 Jenkinsfile 从 SCM 加载,需指定 Pod 模板 K8s 环境部署、微服务集群场景 agent { kubernetes { yaml '''apiVersion: v1; kind: Pod; spec: { containers: { name: 'maven', image: 'maven:3.8.8' } }''' } }

官方文档:https://www.jenkins.io/doc/book/pipeline/syntax/

Section: post

post section在stage或pipeline的尾部定义一些step,并根据其所在stage或pipeline的完成情况来判定是否运行这些step;

Condition(条件) 触发规则 使用场景 实操示例(精简语法)
always 无论 stage/pipeline 状态如何(成功 / 失败 / 中止),必运行 清理资源、通用通知 pipeline 级:post {always { sh 'rm -rf /tmp/build/*'} };stage 级:post { always { echo "Test 阶段结束" } }
changed 本次与前一次运行状态不同(如前败→今成、前成→今败) 状态变更告警 post {changed { echo "流水线状态变更!前次 vs 本次不同"} }
fixed 本次成功,前一次为失败(failed)或不稳定(unstable) 故障恢复通知 post {fixed { echo "构建恢复成功!前次失败已修复"} }
regression 前一次成功,本次为失败 / 不稳定 / 中止 构建回退告警 post {regression { echo "构建回退!前次成功,本次异常"} }
aborted 运行被手动中止(Web UI 点击 "中止") 中止记录、通知负责人 stage ('Deploy') { post { aborted { echo "部署被中止,负责人:${BUILD_USER}" } } }
failure 运行状态为失败(脚本报错、命令执行失败) 失败告警、上报故障 post {failure { sh'send-alert.sh "流水线失败,构建号:${BUILD_NUMBER}"' } }
success 运行状态为成功(无报错,步骤正常完成) 成功通知、成果归档 post { success { sh 'cp target/app.war /data/archive/' } }
unstable 测试失败 / 代码冲突,状态为 "不稳定"(Web UI 黄色标识) 不稳定告警、提醒修复 post {unstable { echo "流水线不稳定!存在测试失败 / 代码冲突"} }
unsuccessful 非成功状态(失败 + 不稳定 + 中止,等价于!success) 非成功统一处理(记录日志) post {unsuccessful { echo "构建异常,日志路径:${WORKSPACE}/logs" } }
cleanup 所有 post 条件执行完毕后,最后运行(无论任何状态) 最终清理(关容器、删临时文件) post {success { echo "构建成功"}; cleanup { sh 'docker stop test-container' } }

Section: stages和steps

核心概念 核心作用 关键说明
stages 封装 pipeline 主体和逻辑的所有 stage 定义,描述 pipeline 中绝大部分实际工作 至少需包含一个 stage 指令,用于定义 CD 过程的离散部分(如构建、测试、部署等)
steps 在 stage 中定义一到多个 DSL 语句,完成该 stage 特定功能,是 pipeline 最核心的组成部分 1. 除 script 外,几乎是不可拆分的原子操作; 2. 内置大量 step,参考https://www.jenkins.io/doc/pipeline/steps; 3. 部分插件可直接当作 step 使用; 4. script {} 可引入脚本(非必要),复杂脚本应组织为 Shared Libraries 并导入使用; 5. DSL 语句可与 environment 等其他语句分隔开

官方文档:https://www.jenkins.io/doc/pipeline/steps/

Pipeline内置的常用step

文件 / 目录相关

step 名称 核心作用 关键说明
deleteDir 删除当前目录 无额外参数,直接执行删除操作
dir("/path/to/dir") 切换到指定目录 参数为目标目录路径,用于指定后续操作的工作目录
fileExists ("/path/to/dir") 判断文件或目录是否存在 参数为目标文件 / 目录路径,返回布尔值结果
isUnix 判断当前系统是否为类 Unix 系统 无参数,返回布尔值结果
pwd 打印当前工作目录 无参数,输出当前所在的工作目录路径
writeFile 将指定内容写入目标文件 参数包括:file(文件路径,支持相对 / 绝对路径)、text(待写入内容)、encoding(目标文件编码,可选,空值为系统默认,支持 base64)
readFile 读取指定文件的内容 参数包括:file(文件路径,支持相对 / 绝对路径)、encoding(读取时使用的编码格式,可选)

消息或控制

step 名称 核心作用 关键说明
echo("message") 打印指定的消息 参数为待打印的消息内容
error("message") 主动报错,并中止当前 pipeline 参数为报错消息内容
retry(count){} 重复执行指定次数的代码块 参数 count 为执行次数,{} 内为待重复执行的代码块
sleep 让 pipeline 休眠一段时间 参数包括:time(整数值,休眠时长)、unit(时间单位,可选,支持 NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS、MINUTES、HOURS、DAYS)
timeout 限制代码块的超时时长 参数包括:time(整数值,超时时长)、unit(时间单位,可选,支持 NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS、MINUTES、HOURS、DAYS)、activity (optional)(布尔类型,true 时无日志活动才算超时)
waitUntil 等待指定条件满足后执行代码块 参数包括:initialRecurrencePeriod (optional)(初始重试周期,默认 250ms)、quiet (optional)(是否禁止条件测试日志,默认 false,记入日志),{} 内为待执行代码块

发送通知

step 名称 核心作用 关键说明
mail 向指定邮箱发送邮件 参数包括:subject(邮件标题)、body(邮件正文)、from (optional)(发件人地址列表,逗号分隔)、cc (optional)(抄送地址列表,逗号分隔)、bcc (optional)(密送地址列表,逗号分隔)、charset (optional)(编码格式)、mimeType (optional)(正文 MIME 类型,默认 text/plain)、replyTo (optional)(回件地址,默认 Jenkins 全局配置邮箱)

Pipline的常用步骤

step 名称 核心作用 关键说明
sh 运行 shell 脚本 参数包括: ◆ 如果有多个命令需要执行可以使用sh ''' 命令 ''' ◆ script {}:脚本代码块,支持指定脚本解释器(如 "#!/usr/bin/perl"),未指定则使用系统默认解释器,且默认启用 - xe 选项; ◆ encoding (optional):脚本输出日志的编码格式,未定义时使用系统默认; ◆ label (optional):Web UI 中显示的详细描述信息; ◆ returnStdout (optional):布尔型,true 时标准输出作为 step 返回值(不打印到日志),错误仍记入日志; ◆ returnStatus (optional):布尔型,true 时返回 step 执行结果而非状态码,命令执行失败也不返回非零状态码
bat 执行 Windows 的批处理脚本 无额外参数说明,直接运行批处理命令或脚本
node 在指定的节点上运行后续脚本 参数为目标节点标识,用于指定 Pipeline 后续操作的执行节点
ws 为 Pipeline 分配工作空间 无额外参数说明,用于指定或分配 Pipeline 执行的工作目录空间
powershell 运行指定的 PowerShell 脚本 支持 Microsoft PowerShell 3 及以上版本
pwsh 运行 PowerShell Core 脚本 专门用于执行 PowerShell Core 相关脚本

stages和stage

维度 具体内容
核心作用 Pipeline 中最重要的 section,描述绝大部分实际工作,定义 CD 过程的离散部分(如构建、测试、部署等)
运行顺序 按照内部定义的顺序自下而后执行各个 stage
嵌套规则 1. 单个 stage 内部可嵌套 stages {}:内部 stage 以串行(顺序) 方式运行; 2. 单个 stage 内部可嵌套 parallel {}:内部 stage 以并行方式运行
注意事项 1. stage 内部仅能定义 steps、stages、parallel 或 matrix 四者之一; 2. 多层嵌套仅能用于最后一个 stage; 3. 已嵌套在 parallel 或 matrix 内部的 stage,不可再嵌套 parallel 或 matrix; 4. 上述嵌套场景下的 stage,仍可使用 agent、tools、when 等指令,也可嵌套 stages {} 以顺序运行子 stage

Pipeline代码片段生成器

指令配置段生成器能够帮助用户生成配置指令,包括agent、stages等

Pipline应用

在Pipline Job上使用环境变量

核心维度 具体内容
变量分类 1. 内置变量(Jenkins 自带); 2. 用户自定义变量(用户手动定义)
定义指令 统一使用 environment 指令,定义位置决定作用域
作用域规则 1. 定义在 pipeline{} 顶部:作用域为整个 Pipeline,所有 stage 均可引用; 2. 定义在 stage{} 内部:作用域仅为当前 stage,其他 stage 无法引用; 3. Jenkins 全局环境变量:作用域为所有 Pipeline,默认以 env. 为前缀
全局变量引用格式 1. ${env.<ENV_VAR_NAME>}(推荐,最规范); 2. $env.<ENV_VAR_NAME>;3. ${ENV_VAR_NAME}(简洁,常用)
groovy 复制代码
pipeline {
	agent any
	environment {
		APP_NAME = 'my-app'
		BUILD_INFO = "构建编号: ${env.BUILD_NUMBER} | 分支: ${env.GIT_BRANCH}"
	}
	stages{
		stage('测试全局变量'){
			steps{
				echo "引用自定义全局变量: ${APP_NAME}"
				echo "引用内置全局变量(格式1): ${env.BUILD_URL}"
				echo "引用内置全局变量(格式2): $env.JOB_NAME"
				echo "拼接全局变量: ${BUILD_INFO}"
			}
		}
		stage('测试stage局部变量'){
			environment{
				STAGE_TAG = 'test-001'
			}
			steps {
                echo "引用当前stage局部变量:${STAGE_TAG}"
                echo "当前stage仍可引用全局变量:${APP_NAME}"
			}
		}
	}
}

执行结果

在Pipline Job 的step中使用认证凭据

凭据类型 关键参数 / 说明
Username with password(用户名 + 密码) 脚本式:credentialsId(凭据 ID)、usernameVariable(用户名变量)、passwordVariable(密码变量) 声明式:变量值自动格式化为 用户名:密码,直接用于需要组合认证的场景
SSH 密钥 1.脚本式:keyFileVariable(私钥文件路径)、passphraseVariable(密钥口令)、usernameVariable(SSH 用户名) sshagent():直接传入 credentialsId 列表,自动管理 SSH 代理(推荐)
Secret text(秘文) credentialsId、variable(秘文变量名) 适用于 API 令牌、机器人 Token 等单一字符串秘钥
Secret file(秘文文件) credentialsId、variable(秘文文件路径变量) 适用于证书、kubeconfig、配置文件等文件类秘钥

Git拉取Gitlab代码(SSH密钥)

git拉取操作官方文档:https://www.jenkins.io/doc/pipeline/steps/git/#git-git

groovy 复制代码
pipeline {
	agent any  //任意Jenkins节点都能跑
	environment{
		//仓库地址
		GIT_URL = "git@gitlab.chenshiquan.xyz:root/docker-java-hello.git"
		//引用内置变量,构建号
		BUILD_INFO = "这次构建编号是: ${BUILD_NUMBER}"
	}
	stages{
		//步骤1.拉取代码
		stage('拉代码'){
			steps{
				echo "${BUILD_INFO}" //打印构建号
				git(
					url: "${GIT_URL}", //URL
					branch: "master", //拉取主分支代码
					credentialsId: "71751bf3-bc66-48b1-8d7a-7ff7c0ee5c71" //凭据ID
				)
				echo "代码拉取完成!"
			}
		}
		//步骤2: 简单构建
		stage('构建'){
			steps{
				echo "工作目录是: ${env.WORKSPACE}" //工作目录(内置变量)
				sh "echo 构建成功" //使用简单shell命令模拟构建成功
			}
		}
	}
}

Docker登陆Harbor仓库(用户名+密码)

将凭据绑定为变量文档:www.jenkins.io/doc/pipeline/steps/credentials-binding/

场景:Docker 登录 Harbor 仓库

groovy 复制代码
pipeline {
	agent any
	environment{
		HARBOR_URL = 'harbor.chenshiquan.xyz'
	}
	stages{
		stage('登陆Harbor仓库'){
			steps{
				withCredentials([
					usernamePassword(
                        //将账号设置为变量
						usernameVariable: 'HARBOR_USER',
                        //将密码绑定为变量
						passwordVariable: 'HARBOR_PWD',
                        //凭证ID用于判断是哪个凭证
						credentialsId: 'harbor-key'
						)
					]){
					//密码自动隐藏,日志不会泄露
					sh "docker login -u ${HARBOR_USER} -p ${HARBOR_PWD} ${HARBOR_URL}"
				}
				echo "Harbor登陆成功"
			}
		}
	}
}

调用钉钉机器人发送通知(密钥文本)

https://jenkinsci.github.io/dingtalk-plugin/guide/pipeline.html

Secret text(密钥文本)

场景:调用钉钉机器人发送通知

groovy 复制代码
pipeline {
    agent any
    stages {
        stage('钉钉通知') {
            steps {
                withCredentials([string(
                    credentialsId: 'dingtalk-robot-token', //凭证ID
                    variable: 'DING_TOKEN' //凭证TOKEN
                )]) {
                    //调用API发送消息
                    sh '''
                        curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=${DING_TOKEN}" \
                        -H "Content-Type: application/json" \
                        -d '{"msgtype":"text","text":{"content":"hello!"}}'
                    '''
                    //content发送消息的内容
                }
                echo "钉钉通知已发送!"
            }
        }
    }
}

凭证优雅用法

场景:整合凭据到环境变量,全局复用

groovy 复制代码
pipeline {
    agent any
    // 直接在environment中定义凭据变量,全局可用
    environment {
        DING_TOKEN = credentials('dingtalk-robot-token') // Secret text类型
        HARBOR_CRED = credentials('harbor-key') // Username with password类型(变量为"用户名:密码"格式)
    }
    stages {
        stage('测试全局凭据变量') {
            steps {
                // 钉钉通知(直接使用环境变量)
                    sh '''
                        curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=${DING_TOKEN}" \
                        -H "Content-Type: application/json" \
                        -d '{"msgtype":"text","text":{"content":"hello!测试全局变量"}}'
                    '''
            }
        }
    }
    post {
        success {
            echo "凭据变量自动掩码:${DING_TOKEN}" // 输出时自动显示为****
        }
    }
}

在Pipline Job中构建和推送Docker image

事前准备(必须先搞定这 3 件事)

准备项 作用 简单要求(一看就懂)
Docker 环境 构建、推送镜像必须依赖 Jenkins 运行的机器(agent)上装了 Docker(能跑 docker --version)
Docker 仓库(Harbor) 存放推送的镜像 有可用的仓库地址(比如 hub.magedu.com),且有登录账号密码
项目 Dockerfile 构建镜像的 "配方" 项目根目录下有 Dockerfile(不然没法 docker build)

Pipeline核心步骤

阶段名 核心操作 说明
Source(拉代码) git 拉取项目代码 从 Git 仓库把代码拉到 Jenkins 工作目录
Build(编译) mvn 打包 Java 项目 生成可运行的 jar 包(适配 Spring Boot 项目)
Test(测试) mvn test 执行测试 可选(不想测试可以删掉这个阶段)
Docker Build docker build 构建镜像 用 Dockerfile 把项目打包成镜像,打上标签
Docker Push 登录 Harbor + 推送镜像 先登录仓库,再把镜像推上去(需要账号密码凭据)

案例

  • 替换 GitRepo 为你的项目 Git 地址
  • 替换 HarborServer 为你的 Harbor 地址(比如 你的仓库地址.com)
  • 替换 credentialsId: 'harbor-user-cred' 为你 Jenkins 里的 Harbor 账号密码凭据 ID
groovy 复制代码
pipeline {
    agent any // Jenkins 任意节点都能跑
    //自定义变量
    environment {
        GitRepo = "git@gitlab.chenshiquan.xyz:root/docker-java-hello.git" //GitLab地址
        HarborServer = "harbor.chenshiquan.xyz" // Harbor地址
        ImageName = "library/docker-java-hello" // 镜像名称
        DING_TOKEN = credentials('dingtalk-robot-token')
        ImageTag = "v1.0"
    }
    stages {
        // 步骤1:拉取代码
		stage('拉代码'){
			steps{
				deleteDir()  //清理工作空间缓存
				git(
					url: "${GitRepo}", //URL
					branch: "master", //拉取主分支代码
					credentialsId: "71751bf3-bc66-48b1-8d7a-7ff7c0ee5c71" //凭据ID
				)
				echo "代码拉取完成!"
			}
		}

        // 步骤2:构建 Docker 镜像
        stage('构建镜像') {
            steps {
                // 构建镜像并打标签:仓库地址/镜像名:标签
                sh "docker build -t ${HarborServer}/${ImageName}:${ImageTag} ."
            }
        }

        // 步骤4:推送镜像到 Harbor
        stage('推送镜像') {
            steps {
                // 用凭据登录 Harbor
                withCredentials([
                    usernamePassword(
                        credentialsId: 'harbor-key', //harbor凭据ID
                        usernameVariable: 'HARBOR_USER', // 用户名变量
                        passwordVariable: 'HARBOR_PASS'  // 密码变量
                    )
                ]) {
                    // 1. 登录 Harbor(日志里密码会显示成 ****,安全)
                    sh "docker login -u ${HARBOR_USER} -p ${HARBOR_PASS} ${HarborServer}"
                    // 2. 推送镜像
                    sh "docker push ${HarborServer}/${ImageName}:${ImageTag}"
                    // 3. 登出
                    sh "docker logout ${HarborServer}"
                }
            }
        }
    }
    post {
        success {
            // 构建成功通知:包含项目、Git标签、镜像地址
            sh '''
                curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=${DING_TOKEN}" \
                -H "Content-Type: application/json" \
                -d '{
                    "msgtype":"text",
                    "text":{
                        "content":"hello! 构建成功!\n项目名称:${JOB_NAME}\n镜像地址:'${HarborServer}'/'${ImageName}':'${ImageTag}'\n构建编号:'${BUILD_NUMBER}'"
                    }
                }'
            '''
            echo "构建成功!钉钉通知已发送"
        }
        failure {
            // 构建失败通知:提示检查日志
            sh '''
                curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=${DING_TOKEN}" \
                -H "Content-Type: application/json" \
                -d '{
                    "msgtype":"text",
                    "text":{
                        "content":"hello! 构建失败!\n项目名称:${JOB_NAME}\n构建编号:'${BUILD_NUMBER}'\n请登录Jenkins检查日志"
                    }
                }'
            '''
            echo "构建失败!钉钉通知已发送"
        }
    }
}

配套的简单 Dockerfile(项目根目录下新建)

dockerfile 复制代码
FROM harbor.chenshiquan.xyz/library/maven:3-eclipse-temurin-11-alpine AS build
ENV CODE_DIR=/app/code
ARG NAME=test
ENV pord_NAME=${NAME}
WORKDIR ${CODE_DIR}
COPY settings.xml /usr/share/maven/conf/
COPY  docker-java-hello  ./
RUN  mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
  -Dsonar.projectKey=${pord_NAME} \
  -Dsonar.projectName=${pord_NAME} \
  -Dsonar.host.url=http://10.0.0.123:9000 \
  -Dsonar.sources=. \
  -Dsonar.token=squ_6ec3aa500b872c85e62e5705d5fd87e0bea32e4d

FROM harbor.chenshiquan.xyz/library/tomcat:9.0-jdk8 
LABEL author=csq destcation="tomcat war包"
COPY --from=build /app/code/target/*.war webapps/ROOT.war
EXPOSE 8080
CMD ["catalina.sh","run"]

参数化Pipeline

参数类型 用途(我能用它干嘛) 语法模板(直接抄) 关键说明
string(字符串) 传单行文本(比如环境名、版本号) parameters { string(name: '参数名', defaultValue: '默认值', description: '说明') } 最常用,比如传 v1.0.0、dev 这类短文本
text(多行文本) 传多行内容(比如配置文件、脚本) parameters { text(name: '参数名', defaultValue: '第一行\n第二行', description: '说明') } 支持换行,比如传多行的 nginx.conf 配置
booleanParam(布尔) 选 "是 / 否"(比如是否发布、是否调试) parameters { booleanParam(name: '参数名', defaultValue: true, description: '说明') } 只有两个选项:true(是)/false(否),引用时是字符串类型
choice(下拉选项) 从固定选项里选(比如分支、环境) parameters { choice(name: '参数名', choices: ['选项1', '选项2'], description: '说明') } 避免手动输错,比如固定可选分支 main/test
password(密码) 传敏感密码 / 令牌(隐藏显示) parameters { password(name: '参数名', defaultValue: '默认密码', description: '说明') } 输入和日志中都会隐藏(显示 ****),安全

使用变量拉取master分支v1.0标签

groovy 复制代码
pipeline {
    agent any
    // 变量集中定义,修改直接改这里
    environment {
        GIT_URL = "git@gitlab.chenshiquan.xyz:root/docker-java-hello.git" // SSH 仓库地址
        GIT_CRED_ID = "71751bf3-bc66-48b1-8d7a-7ff7c0ee5c71" // SSH 私钥凭据ID(替换为你的实际ID)
        TARGET_BRANCH = "master" // 标签所属分支
        TARGET_TAG = "v1.0" // 要拉取的标签
    }
    stages {
        stage('拉取 master 分支的 v1.0 标签代码(含子模块)') {
            steps {
                echo "拉取配置:仓库=${GIT_URL},分支=${TARGET_BRANCH},标签=${TARGET_TAG}"
                checkout scmGit(
                    branches: [[name: "refs/tags/${TARGET_TAG}"]], // 标签完整引用(必选)
                    extensions: [
                        // 仅保留子模块核心配置(递归拉取 + 复用主仓库SSH凭据)
                        submodule(
                            parentCredentials: true, // 子模块复用主仓库SSH凭据
                            recursiveSubmodules: true, // 递归拉取所有子模块
                            reference: ''
                        )
                    ],
                    userRemoteConfigs: [[
                        url: "${GIT_URL}",
                        credentialsId: "${GIT_CRED_ID}", // 必须是 SSH 私钥类型凭据
                        // SSH 协议无需额外配置,Jenkins 会自动用凭据中的私钥认证
                    ]]
                )

                // 可选验证:确保标签属于 master 分支(避免拉错同名标签)
                script {
                    sh "git branch -r --contains refs/tags/${TARGET_TAG} | grep -w origin/${TARGET_BRANCH} || error '标签 ${TARGET_TAG} 不属于 ${TARGET_BRANCH} 分支!'"
                }

                echo "代码拉取完成!当前版本:${TARGET_TAG}(含所有子模块)"
            }
        }
    }
}

单个参数示例

=示例 1:string(字符串参数)------ 传递部署环境

groovy 复制代码
pipeline {
    agent any
    // 定义参数:部署环境(默认 dev)
    parameters {
        string(
            name: 'DEPLOY_ENV', // 参数名(引用时用这个)
            defaultValue: 'dev', // 默认值(不填时自动用这个)
            description: '请输入部署环境(比如 dev/test/prod)' // 提示用户怎么填
        )
    }
    stages {
        stage('打印参数') {
            steps {
                // 引用参数:用 ${params.参数名}(必写 params.)
                echo "本次部署的环境是:${params.DEPLOY_ENV}"
            }
        }
    }
}

//运行效果
//构建时会弹出输入框,默认显示 dev,可改成 test,构建日志会打印 本次部署的环境是:test

示例 2:choice(下拉选项)------ 选择构建分支

groovy 复制代码
pipeline {
    agent any
    // 定义参数:下拉选择分支
    parameters {
        choice(
            name: 'BUILD_BRANCH',
            choices: ['master', 'test', 'dev'], // 固定选项(用户只能选,不能输)
            description: '请选择要构建的分支'
        )
    }
    stages {
        stage('拉取代码') {
            steps {
                echo "正在拉取分支:${params.BUILD_BRANCH}"
                git branch: "${params.BUILD_BRANCH}", url: "git@gitlab.chenshiquan.xyz:root/docker-java-hello.git"
            }
        }
    }
}

示例 3:booleanParam(布尔参数)------ 是否发布版本

groovy 复制代码
pipeline {
    agent any
    // 定义参数:是否发布(默认不发布)
    parameters {
        booleanParam(
            name: 'IS_PUBLISH',
            defaultValue: false, // 默认 false(不发布)
            description: '是否将构建结果发布到生产环境(是选true,否选false)'
        )
    }
    stages {
        stage('判断是否发布') {
            steps {
                echo "是否发布:${params.IS_PUBLISH}" // 输出 true 或 false(字符串类型)
                // 条件判断:如果是 true,执行发布命令
                script {
                    if (params.IS_PUBLISH == 'true') { // 注意:布尔参数引用后是字符串!
                        echo "开始发布到生产环境..."
                        // sh "发布脚本" // 实际发布命令写这里
                    } else {
                        echo "仅构建,不发布"
                    }
                }
            }
        }
    }
}

示例 4:password(密码参数)------ 传递 Harbor 密码

groovy 复制代码
pipeline {
    agent any
    // 定义参数:Harbor 密码(隐藏显示)
    parameters {
        password(
            name: 'HARBOR_PASS',
            defaultValue: '', // 密码默认空,让用户手动输
            description: '请输入 Harbor 仓库的登录密码(输入时隐藏)'
        )
    }
    stages {
        stage('登录 Harbor') {
            steps {
                withCredentials([
                    usernamePassword(
                        credentialsId: 'harbor-user', // Jenkins 里的用户名凭据(只存用户名)
                        usernameVariable: 'HARBOR_USER'
                    )
                ]) {
                    // 密码用参数 ${params.HARBOR_PASS},日志中会显示 ****
                    sh "docker login -u ${HARBOR_USER} -p ${params.HARBOR_PASS} hub.magedu.com"
                }
            }
        }
    }
}

//运行效果:
//输入密码时显示圆点 / 星号,日志中密码会被隐藏,避免泄露

示例 5:text(多行文本)------ 传递配置文件内容

groovy 复制代码
pipeline {
    agent any
    // 定义参数:多行配置(比如 nginx 配置)
    parameters {
        text(
            name: 'NGINX_CONFIG',
            defaultValue: 'server {\n  listen 80;\n  server_name localhost;\n}', // 换行用 \n
            description: '请粘贴 nginx 配置文件内容(支持多行)'
        )
    }
    stages {
        stage('生成配置文件') {
            steps {
                // 将多行参数内容写入文件
                sh "echo '${params.NGINX_CONFIG}' > /etc/nginx/conf.d/default.conf"
                echo "已生成 nginx 配置文件"
            }
        }
    }
}

//运行效果:
//用户可以粘贴多行配置,Pipeline 会自动生成对应的配置文件。

多参数综合示例

下面的 Pipeline 包含 4 种参数,模拟 "选择分支→输入版本号→是否调试→输入密码" 的完整流程:

groovy 复制代码
pipeline {
    agent any
    // 多个参数一起定义(顺序不影响)
    parameters {
        // 1. 下拉选择分支
        choice(
            name: 'BRANCH',
            choices: ['master', 'test', 'dev'],
            description: '请选择构建分支'
        )
        // 2. 输入版本号(字符串)
        string(
            name: 'VERSION',
            defaultValue: 'v1.0',
            description: '请输入版本号'
        )
        // 3. 是否开启调试(布尔)
        booleanParam(
            name: 'DEBUG',
            defaultValue: false,
            description: '是否开启调试模式(true=开启,false=关闭)'
        )
        // 4. 输入数据库密码(密码类型)
    }
    stages {
        stage('构建前准备') {
            steps {
                echo "===== 构建参数汇总 ====="
                echo "分支:${params.BRANCH}"
                echo "版本号:${params.VERSION}"
                echo "调试模式:${params.DEBUG}"
            }
        }
        stage('拉取代码') {
            steps {
                git branch: "${params.BRANCH}", url: "git@gitlab.chenshiquan.xyz:root/java-hello.git"
            }
        }
        stage('构建打包') {
            steps {
                // 根据调试模式决定是否加调试参数
                script {
                    if (params.DEBUG == 'true') {
                        sh "mvn clean package -Ddebug" // 开启调试
                    } else {
                        sh "mvn clean package" // 正常构建
                    }
                }
            }
        }
        stage('部署') {
            steps {
                echo "部署 ${params.VERSION} 版本到 ${params.BRANCH} 分支对应的环境..."
            }
        }
    }
}


//运行效果:
//构建时会依次显示参数输入界面(下拉框 + 输入框 + 勾选框 + 密码框),用户填写后,Pipeline 会根据参数执行不同逻辑。

实现交互式功能

核心功能 支持的输入参数类型 变量作用域 关键特点 / 语法说明
运行时暂停,等待用户手动输入 string(字符串)、choice(下拉)、booleanParam(布尔)等(同 parameters 指令) 1. 局部:仅当前 Stage 2. 全局:所有 Stage 1. 需包裹在 script{} 中(声明式 Pipeline 要求) 2. 用变量接收输入结果,格式为 Map(键 = 参数名,值 = 用户输入)
限定审批人 / 审批组 (属于 input 自身配置) 无 submitter: 'admin,dev组'(仅指定用户 / 组可审批)
记录审批人账号 (属于 input 自身配置) 同输入参数 submitterParameter: 'APPROVER'(自动存储审批人账号到 Map 中)
优雅全局复用 所有支持类型 所有 Stage 变量声明在 environment{} 中,或 Pipeline 外部 def 变量

示例 1:局部作用域(单参数)------ 仅当前 Stage 可用

场景:构建过程中暂停,让用户输入备注,仅当前 Stage 能引用该输入。

groovy 复制代码
pipeline {
    agent any
    stages {
        stage('用户输入参数') {
            steps {
                script {
                    def userInput = input(
                        message: '请填写构建相关参数',
                        parameters: [
                            string(name: 'REMARK', defaultValue: '常规构建', description: '构建备注'),
                            string(name: 'VERSION_DESC', defaultValue: 'V1.0.0', description: '版本说明')
                        ]
                    )
                    
                    // 多参数存入环境变量
                    env.BUILD_REMARK = userInput.REMARK
                    env.VERSION_DESC = userInput.VERSION_DESC
                    
                    echo "构建备注:${env.BUILD_REMARK}"
                    echo "版本说明:${env.VERSION_DESC}"
                }
            }
        }
        
        stage('跨Stage复用参数') {
            steps {
                echo "跨Stage引用 - 备注:${env.BUILD_REMARK}"
                echo "跨Stage引用 - 版本:${env.VERSION_DESC}"
            }
        }
    }
}

//运行效果:
//1. 构建到 "用户输入备注" Stage 时,会暂停,弹出输入框;
//2. 用户输入 "修复支付 bug",点击 "确定" 后,Pipeline 继续运行;
//3. 日志打印 "本次构建备注:修复支付 bug",下一个 Stage 无法引用该变量。

示例 2:全局作用域(多参数)------ 跨 Stage 可用

场景:部署前让用户选择目标环境、输入自定义参数,后续所有 Stage 都能引用这些输入(用 Pipeline 外部的 def 变量存储)。

groovy 复制代码
// 关键:在pipeline{}外部声明变量(全局作用域,所有Stage可引用)
def approvalInfo

pipeline {
    agent any
    stages {
        stage('审批与输入参数') {
            steps {
                script {
                    // input接收多参数,结果存入全局变量approvalInfo
                    approvalInfo = input(
                        message: '请审批并填写部署参数', // 弹窗标题
                        ok: '确认提交', // 弹窗确认按钮文字
                        submitter: 'admin,deploy组', // 仅指定用户/组能审批(可选)
                        submitterParameter: 'APPROVER', // 自动记录审批人账号,参数名=APPROVER
                        parameters: [
                            // 第一个参数:下拉选择目标环境
                            choice(
                                name: 'DEPLOY_ENV',
                                choices: ['dev(开发)', 'test(测试)', 'prod(生产)'],
                                description: '请选择部署环境(生产环境需谨慎)'
                            ),
                            // 第二个参数:字符串输入自定义备注
                            string(
                                name: 'CUSTOM_PARAM',
                                defaultValue: '',
                                description: '可选:输入额外部署参数(比如版本号、配置项)'
                            )
                        ]
                    )
                }
            }
        }
        stage('执行部署') {
            steps {
                echo "===== 部署信息汇总 ====="
                echo "审批人:${approvalInfo['APPROVER']}" // 引用审批人(自动记录)
                echo "目标环境:${approvalInfo['DEPLOY_ENV']}" // 引用下拉选择的环境
                echo "自定义参数:${approvalInfo['CUSTOM_PARAM']}" // 引用输入的字符串
                echo "开始部署到 ${approvalInfo['DEPLOY_ENV']} 环境..."
                // sh "部署脚本 --env ${approvalInfo['DEPLOY_ENV']}" // 实际部署命令
            }
        }
    }
}


//运行效果:
//1. 构建到 "审批与输入参数" Stage 时暂停,弹窗显示下拉框 + 输入框;
//2. 只有 admin 或 "deploy 组" 用户能看到 "确认提交" 按钮,普通用户无法操作;
//3. 用户选择 "test(测试)",输入 "v2.0.1",提交后,"执行部署" Stage 能正常引用所有参数。

示例 3:优雅方式(用环境变量存储 input 参数)

场景:把 input 输入的参数存入 environment 变量,全局可用,语法更简洁(无需在 Pipeline 外部声明 def)。

groovy 复制代码
pipeline {
    agent any
    // 环境变量:存储input参数(全局可用)
    environment {
        // 先声明环境变量,初始值为空
        DEPLOY_INFO = ''
    }
    stages {
        stage('获取部署参数') {
            steps {
                script {
                    // input结果存入环境变量DEPLOY_INFO(Map类型)
                    DEPLOY_INFO = input(
                        message: '填写部署参数',
                        parameters: [
                            booleanParam(
                                name: 'IS_ROLLBACK',
                                defaultValue: false,
                                description: '是否回滚(true=回滚,false=正常部署)'
                            ),
                            string(
                                name: 'ROLLBACK_VERSION',
                                defaultValue: 'v1.0.0',
                                description: '若回滚,输入回滚版本'
                            )
                        ]
                    )
                }
            }
        }
        stage('判断部署类型') {
            steps {
                script {
                    // 引用环境变量中的input参数(env.可省略)
                    if (DEPLOY_INFO['IS_ROLLBACK'] == 'true') {
                        echo "执行回滚,目标版本:${DEPLOY_INFO['ROLLBACK_VERSION']}"
                        // sh "回滚脚本 --version ${DEPLOY_INFO['ROLLBACK_VERSION']}"
                    } else {
                        echo "执行正常部署,不回滚"
                        // sh "部署脚本"
                    }
                }
            }
        }
    }
}

//运行效果
//1. 暂停时用户勾选 "是否回滚" 为 true,输入回滚版本 "v1.9.8";
//2. 后续 Stage 通过 `DEPLOY_INFO['参数名']` 引用,无需关心变量声明位置,语法更统一。

输入等待超时

input步骤与timeout步骤协同使用,可实现超时自动中止pipeline,以避免无限等待

参数名称 参数说明(新手友好) 必填性 取值示例
time 超时等待时长(整数) 是 1(分)、30(秒)、2(时)
unit 时间单位(默认分钟) 否 SECONDS(秒)、MINUTES(分)、HOURS(时)、DAYS(天)
activity 按 "无活动" 计时(默认绝对时长) 否 true(用户没操作才计时)、false(到点就超时)

示例 1:基础版(简单确认 + 1 分钟超时)

场景:暂停等待用户点击确认,1 分钟没操作自动失败。

groovy 复制代码
pipeline {
    agent any
    stages {
        stage('等待确认') {
            steps {
                echo "1分钟内未点击确认,Pipeline将自动失败"
                // 超时配置:1分钟(unit默认MINUTES,可省略)
                timeout(time: 1, unit: 'MINUTES') {
                    // 简单输入:仅确认按钮
                    input message: '是否继续执行下一步?'
                }
                echo "用户已确认,继续构建~"
            }
        }
    }
}

//运行效果
//1 分钟内点 "确认":打印后续日志,正常执行;
//1 分钟未操作:Pipeline 失败,日志提示 "Timeout waiting for input"。

示例 2:带参数版(选择环境 + 3 分钟超时)

场景:部署前让用户选环境,3 分钟超时自动中止,适合需要输入参数的场景。

groovy 复制代码
pipeline {
    agent any
    stages {
        stage('部署审批') {
            steps {
                script {
                    echo "请3分钟内选择部署环境并确认"
                    // 超时配置:明确单位为分钟(更清晰,避免歧义)
                    timeout(time: 3, unit: 'MINUTES') {
                        def deployEnv = input(
                            message: '选择部署环境(3分钟超时)',
                            parameters: [
                                choice(
                                    name: 'ENV', // 参数名(仅用于输入框标识,返回值不是Map)
                                    choices: ['dev', 'test', 'prod'],
                                    description: '只能选这3个环境'
                                )
                            ],
                            ok: '确认部署'
                        )
                        // 直接使用 deployEnv(字符串值),无需加 .ENV
                        echo "已选择部署环境:${deployEnv}"
                        
                        // 可选:存入环境变量,支持后续Stage引用
                        env.DEPLOY_ENV = deployEnv
                    }
                }
            }
        }
        
        // 可选:测试跨Stage引用部署环境
        stage('执行部署') {
            steps {
                echo "开始部署到 ${env.DEPLOY_ENV} 环境..."
                // 后续部署命令(如部署脚本、容器启动等)
                // sh "./deploy.sh ${env.DEPLOY_ENV}"
            }
        }
    }
}

//运行效果:

//3 分钟内选环境 + 确认:打印环境名称,继续部署;
//3 分钟未操作:直接失败,不执行部署。

示例 3:自定义超时行为(2 分钟超时不失败,跳过步骤)

场景:非关键步骤,2 分钟超时后不失败,仅跳过该步骤。

groovy 复制代码
pipeline {
    agent any
    stages {
        stage('可选测试步骤') {
            steps {
                script {
                    echo "2分钟内未确认,将跳过测试步骤"
                    // catchError:捕获超时错误,不中断整体构建
                    catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
                        timeout(time: 2, unit: 'MINUTES') {
                            input message: '是否执行额外测试?'
                            echo "执行额外测试..."
                            sh "echo 测试脚本运行中"
                        }
                    }
                }
            }
        }
        stage('核心构建') {
            steps {
                echo "核心构建步骤不受超时影响,继续执行~"
            }
        }
    }
}

//运行效果:
//2 分钟内确认:执行测试步骤,核心构建正常;
//2 分钟超时:测试步骤标记为 "不稳定",直接执行核心构建,整体构建状态为成功。
相关推荐
小羊没烦恼!4 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
俊昭喜喜里4 天前
java中的继承和多态的区别
java
小羊没烦恼!4 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
譕痕4 天前
JSONObject与JSONArray封装数据格式区别
java·json
胡写代码4 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖4 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商
此时不提桶,更待何时4 天前
01-06-A-JVM排查实战详解
java·jvm
vipxieliang4 天前
ValidX 在 DDD 领域驱动设计中的实践
java·spring boot
ba_pi4 天前
mysql 查询所有表名并授权
java
ProcessOn官方账号4 天前
java函数式编程--入门基础
java·编程·函数式编程