Jenkins自动化构建与CI/CD流水线,并配置GitLab

1.安装Jenkins镜像

bash 复制代码
docker pull docker.1ms.run/jenkins/jenkins:2.555.3-lts

2.查看Jenkins镜像

bash 复制代码
docker images

3.运行Jenkins容器

bash 复制代码
docker run -d --name jenkins --user root -p 8080:8080 -p 50000:50000 -v /jenkins_home:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime -e JAVA_OPTS="-Dhudson.model.DownloadService.noSignatureCheck=true" --restart unless-stopped docker.1ms.run/jenkins/jenkins:2.555.3-lts

4.查看Jenkins容器

bash 复制代码
docker ps

5.查看Jenkins日志

bash 复制代码
docker logs -f <容器ID>

6.复制初始密码

7.粘贴初始密码

8.安装推荐插件

9.创建用户,点击保存并完成

10.实例配置,点击保存并完成

11.安装完成

12.在插件管理->Available plugins中,搜索gitlab插件并安装

13.重启Jenkins

14.在系统配置->GitLab中,输入名称,URL,点击添加Credentials

15.选择GitLab API token

16.输入token,ID会自动生成,再点击Save

17.在全局工具配置->JDK安装中,配置JDK,再点击Save

18.在全局工具配置->Maven安装中,配置Maven,再点击Save

19.新建任务

20.选择流水线

21.在Triggers中勾选Build when a change is pushed to GitLab,并复制webhook URL

22.点击高级,生成Secret token,并复制token

23.编写流水线脚本,再点击Save

24.使用复制的URL和token在GitLab中配置webhook,当有代码推送到远程仓库时,会自动触发流水线构建

25.流水线脚本示例

powershell 复制代码
// ================================================================
//                   Groovy Script-Level 辅助函数
// ================================================================

/**
 * 根据 Git 引用推断目标环境
 */
def inferEnvironment(String gitRef) {
    if (gitRef.startsWith('refs/tags/')) {
        def tag = gitRef.replace('refs/tags/', '')
        if (tag.toLowerCase().contains('prod') || tag.toLowerCase().contains('release')) {
            return 'prod'
        }
        return 'test'
    }
    def branch = gitRef.replace('refs/heads/', '')
    if (branch == 'master' || branch == 'main') { return 'prod' }
    if (branch.startsWith('release/')) { return 'test' }
    return 'dev'
}

/**
 * 解析需要构建的模块列表
 */
def resolveModulesToBuild(buildScope, selectedModules) {
    def ALL_SERVICES = [
        'gateway', 'auth', 'system'
    ]
    def CORE_SERVICES = ['gateway', 'auth', 'system']

    switch (buildScope) {
        case 'all':            return ALL_SERVICES.join(',')
        case 'core-only':      return CORE_SERVICES.join(',')
        case 'select-modules': return selectedModules?.trim() ?: ''
        default:               return ALL_SERVICES.join(',')
    }
}

/**
 * 将模块简称转换为 Maven -pl 参数路径
 */
def moduleToMavenPath(String module) {
    def mapping = [
        'gateway':                      'gateway',
        'auth':                         'auth',
        'system':                       'system'
    ]
    return mapping[module] ?: module
}

/**
 * 获取模块 jar 输出目录 (相对于项目根目录)
 */
def getJarOutputDir(String module) {
    return moduleToMavenPath(module)
}

/**
 * 判断是否为可独立部署的服务模块 (非 library)
 */
def isDeployableService(String module) {
    return !(module in ['common-core'])
}

/**
 * 模块简称 → Docker 镜像名
 */
def moduleToImageName(String module) {
    return "jenkins-${module}"
}

/**
 * 获取服务端口
 */
def getServicePort(String module) {
    def ports = [
        'gateway':                      '9001',
        'auth':                         '9002',
        'system':                       '9003'
    ]
    return ports[module] ?: '8080'
}

/**
 * 获取健康检查 URL (本地)
 */
def getHealthUrl(String module) {
    def port = getServicePort(module)
    def contextPaths = [
        'gateway': '/',
        'auth':    '/auth',
        'system':  '/system'
    ]
    def context = contextPaths[module] ?: ''
    if (context == '/') { context = '' }
    return "http://127.0.0.1:${port}${context}/actuator/health"
}

/**
 * 生成通用 Dockerfile
 */
def generateGenericDockerfile(String module, String mavenProfile) {
    return """\
        |FROM eclipse-temurin:8-jre-jammy
        |
        |WORKDIR /app
        |
        |COPY ./target/*.jar ./app.jar
        |
        |ENV JAVA_ENV="-Xms512m -Xmx1024m -Dspring.profiles.active=${mavenProfile}"
        |ENV MODULE_NAME="${module}"
        |
        |RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \\
        |    && echo "Asia/Shanghai" > /etc/timezone
        |
        |EXPOSE ${getServicePort(module)}
        |
        |ENTRYPOINT ["sh", "-c", "java \$JAVA_ENV -jar ./app.jar"]
        |""".stripMargin()
}

/**
 * 生成 docker-compose.yml 内容
 */
def generateDockerComposeFile(String modules, String profile, String baseDir,
                               String nacosServerAddr, String nacosNs, String discoveryIp) {
    def serviceDefs = []
    def deployModules = modules.tokenize(',')

    deployModules.each { module ->
        def m = module.trim()
        if (!isDeployableService(m)) { return }

        def imageName = moduleToImageName(m)
        def port = getServicePort(m)

        serviceDefs << """\
  jenkins-${m}:
    image: ${imageName}
    container_name: jenkins-${m}
    restart: unless-stopped
    ports:
      - "${port}:${port}"
    environment:
      - SPRING_PROFILES_ACTIVE=${profile}
      - SERVER_PORT=${port}
      - NACOS_SERVER_ADDR=${nacosServerAddr}
      - SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=${nacosServerAddr}
      - SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=${nacosServerAddr}
      - SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE=${nacosNs}
      - SPRING_CLOUD_NACOS_CONFIG_NAMESPACE=${nacosNs}
      - SPRING_CLOUD_NACOS_DISCOVERY_IP=${discoveryIp}
    volumes:
      - ${baseDir}/${m}/logs:/app/logs
      - /etc/localtime:/etc/localtime:ro
"""
    }

    return """\
version: '3.8'
services:
${serviceDefs.join('\n')}
"""
}


// ================================================================
//                        Pipeline 主流程
// ================================================================

pipeline {
    agent any

    // ======================== 触发器 ========================
    triggers {
        gitlab(
            triggerOnPush: true,
            triggerOnMergeRequest: true,
            triggerOnAcceptedMergeRequest: true,
            triggerOnClosedMergeRequest: false,
            triggerOnApprovedMergeRequest: false,
            triggerOnNoteRequest: false,
            branchFilterType: 'NameBasedFilter',
            includeBranchesSpec: 'develop',
            excludeBranchesSpec: '',
            secretToken: 'a6b2cb5e8b1feac013a85469dd7609c3'
        )
    }

    // ======================== 环境变量 ========================
    environment {
        PROJECT_NAME       = 'jenkins'
        PROJECT_VERSION    = '3.6.5'
        // Nacos 配置中心
        NACOS_SERVER_ADDR  = '127.0.0.1:8848'
        NACOS_NAMESPACE    = 'docker'
        DISCOVERY_IP       = '127.0.0.1'
        // 本地部署目录
        DEPLOY_BASE_DIR    = '/opt/jenkins'
    }

    // ======================== 构建参数 ========================
    parameters {
        // ------ Git ------
        string(
            name: 'GIT_URL',
            defaultValue: '',
            description: 'GitLab 仓库地址'
        )
        string(
            name: 'GIT_BRANCH',
            defaultValue: 'develop',
            description: 'Git 分支名称 (e.g. master, develop, feature/xxx)'
        )
        string(
            name: 'GIT_TAG',
            defaultValue: '',
            description: 'Git Tag (若填写则优先使用 Tag, 忽略分支)'
        )

        // ------ Maven 构建 ------
        choice(
            name: 'MAVEN_PROFILE',
            choices: ['dev', 'prod', 'local'],
            description: 'Maven / Spring 激活的 Profile'
        )
        booleanParam(
            name: 'SKIP_TESTS',
            defaultValue: true,
            description: '是否跳过单元测试'
        )

        // ------ 模块选择 ------
        choice(
            name: 'BUILD_SCOPE',
            choices: ['all', 'core-only', 'select-modules'],
            description: '''构建范围:
  all             --- 全量构建所有模块
  core-only       --- 仅构建核心模块 (gateway, auth, system)
  select-modules  --- 手动指定要构建的业务模块'''
        )
        choice(
            name: 'SELECTED_MODULES',
            choices: [
				'',
                'gateway',
                'auth',
                'system'
            ],
            description: 'BUILD_SCOPE=select-modules 时选择要构建的模块'
        )

        // ------ Docker 镜像 ------
        booleanParam(
            name: 'BUILD_DOCKER',
            defaultValue: true,
            description: '是否构建 Docker 镜像'
        )
        string(
            name: 'DOCKER_IMAGE_TAG',
            defaultValue: '',
            description: 'Docker 镜像 Tag (留空则自动生成: <分支名>-<构建号>)'
        )

        // ------ 部署 ------
        booleanParam(
            name: 'DO_DEPLOY',
            defaultValue: true,
            description: '是否在本地启动/重启 Docker 容器'
        )
        string(
            name: 'DEPLOY_MODULES',
            defaultValue: '',
            description: '待部署模块, 逗号分隔; 留空则根据 BUILD_SCOPE 自动推断'
        )
    }

    stages {

        // ==================== Stage 0: 初始化 ====================
        stage('0. Initialize') {
            steps {
                script {
                    env.START_TIME = "${System.currentTimeMillis()}"

                    // Git 引用
                    if (params.GIT_TAG?.trim()) {
                        env.GIT_REF = "refs/tags/${params.GIT_TAG.trim()}"
                    } else {
                        env.GIT_REF = "refs/heads/${params.GIT_BRANCH.trim()}"
                    }

                    // 目标环境
                    env.TARGET_ENV = inferEnvironment(env.GIT_REF)
                    echo "目标环境: ${env.TARGET_ENV}"

                    // Docker 镜像 Tag
                    if (!params.DOCKER_IMAGE_TAG?.trim()) {
                        def safeBranch = env.GIT_BRANCH.replaceAll('/','-')
                        env.DOCKER_TAG = "${safeBranch}-${env.BUILD_NUMBER}"
                    } else {
                        env.DOCKER_TAG = params.DOCKER_IMAGE_TAG.trim()
                    }

                    // 构建模块列表
                    env.MODULES_TO_BUILD = resolveModulesToBuild(params.BUILD_SCOPE, params.SELECTED_MODULES)
                    echo "待构建模块: ${env.MODULES_TO_BUILD}"

                    // 部署模块列表
                    if (params.DO_DEPLOY) {
                        env.MODULES_TO_DEPLOY = params.DEPLOY_MODULES?.trim()
                            ? params.DEPLOY_MODULES.trim()
                            : env.MODULES_TO_BUILD
                    } else {
                        env.MODULES_TO_DEPLOY = ''
                    }
                    echo "待部署模块: ${env.MODULES_TO_DEPLOY ?: '(无)'}"

                    // 校验
                    if (params.BUILD_SCOPE == 'select-modules' && !params.SELECTED_MODULES?.trim()) {
                        error("BUILD_SCOPE=select-modules 时必须填写 SELECTED_MODULES 参数!")
                    }
                    if (params.DO_DEPLOY && !params.BUILD_DOCKER) {
                        error("部署前必须先构建 Docker 镜像, 请启用 BUILD_DOCKER!")
                    }
                }
            }
        }

        // ==================== Stage 1: 代码拉取 ====================
        stage('1. Checkout') {
            steps {
                script {
                    echo "正在拉取代码: ${env.GIT_REF}"

                    def gitUrl = params.GIT_URL?.trim() ?: env.GIT_URL?.trim()
                    if (!gitUrl) {
                        try {
                            gitUrl = scm.userRemoteConfigs?.url?.first()
                        } catch (e) { /* 非 Multibranch */ }
                    }
                    if (!gitUrl) {
                        error("请提供 GIT_URL 参数或在 Jenkins Job 中配置 Pipeline script from SCM")
                    }

                    checkout([
                        $class: 'GitSCM',
                        branches: [[name: env.GIT_REF]],
                        userRemoteConfigs: [[
                            url: gitUrl,
                            credentialsId: '27437595-afee-41b5-b7f0-d9e7a4781ca5'
                        ]],
                        extensions: [
                            [$class: 'CleanBeforeCheckout'],
                            [$class: 'CloneOption', noTags: false, reference: '', shallow: false, depth: 0]
                        ]
                    ])

                    env.GIT_COMMIT       = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
                    env.GIT_COMMIT_SHORT = env.GIT_COMMIT.take(7)
                    env.GIT_AUTHOR       = sh(script: 'git log -1 --pretty=format:"%an"', returnStdout: true).trim()
                    env.GIT_MESSAGE      = sh(script: 'git log -1 --pretty=format:"%s"', returnStdout: true).trim()

                    echo """
                    ======== 代码信息 ========
                    分支/Tag : ${env.GIT_REF}
                    Commit   : ${env.GIT_COMMIT}
                    作者     : ${env.GIT_AUTHOR}
                    提交信息 : ${env.GIT_MESSAGE}
                    ==========================
                    """.stripIndent()
                }
            }
        }

        // ==================== Stage 2: 单元测试 (SKIP_TESTS=false 时才执行) ====================
        stage('2. Test') {
            when {
                expression { !params.SKIP_TESTS }
            }
            steps {
                script {
                    def mvnHome = tool name: 'maven3', type: 'maven'
                    def javaHome = tool name: 'jdk8', type: 'jdk'

                    withEnv(["JAVA_HOME=${javaHome}", "PATH+MAVEN=${mvnHome}/bin"]) {
                        echo '===== 单元测试 ====='
                        try {
                            sh "mvn test -P${params.MAVEN_PROFILE} -T 1C"
                        } catch (e) {
                            echo "WARNING: 单元测试存在失败, 请检查测试报告"
                        }
                    }
                }
            }
            post {
                always {
                    junit(
                        allowEmptyResults: true,
                        testResults: '**/target/surefire-reports/*.xml'
                    )
                }
            }
        }

        // ==================== Stage 3: Maven 打包 ====================
        stage('3. Package') {
            steps {
                script {
                    def mvnHome = tool name: 'maven3', type: 'maven'
                    def javaHome = tool name: 'jdk8', type: 'jdk'

                    withEnv(["JAVA_HOME=${javaHome}", "PATH+MAVEN=${mvnHome}/bin"]) {
                        echo '===== Maven Package ====='

                        def modulesToPackage = env.MODULES_TO_BUILD
                        def packageCmd

                        if (modulesToPackage == 'ALL') {
                            packageCmd = "mvn clean package -Dmaven.test.skip=true -P${params.MAVEN_PROFILE}"
                        } else {
                            def moduleArgs = modulesToPackage.tokenize(',').collect { m ->
                                moduleToMavenPath(m.trim())
                            }.join(',')
                            packageCmd = "mvn clean package -pl ${moduleArgs} -am -Dmaven.test.skip=true -P${params.MAVEN_PROFILE}"
                        }

                        sh(script: packageCmd)

                        echo '===== 构建产物 ====='
                        modulesToPackage.tokenize(',').each { m ->
                            def jarDir = getJarOutputDir(m.trim())
                            sh(
                                script: "ls -lh ${jarDir}/target/*.jar 2>/dev/null || echo '  (library 模块 --- 无独立 jar)'",
                                returnStdout: true
                            )
                        }
                    }
                }
            }
            post {
                success {
                    archiveArtifacts(
                        artifacts: 'jenkins-gateway/target/*.jar, jenkins-auth/target/*.jar, jenkins-modules/jenkins-system/target/*.jar',
                        fingerprint: true,
                        onlyIfSuccessful: true
                    )
                }
            }
        }

        // ==================== Stage 4: Docker 镜像构建 ====================
        stage('4. Docker Build') {
            when {
                expression { params.BUILD_DOCKER }
            }
            steps {
                script {
                    echo '===== Docker 镜像构建 ====='

                    def buildableModules = env.MODULES_TO_BUILD.tokenize(',').findAll { m ->
                        isDeployableService(m.trim())
                    }

                    if (buildableModules.isEmpty()) {
                        echo '没有需要构建 Docker 镜像的服务模块'
                        return
                    }

                    def dockerBuildSteps = [:]

                    buildableModules.each { module ->
                        def m = module.trim()
                        def imageName = moduleToImageName(m)
                        def contextDir = getJarOutputDir(m)
                        def profile = params.MAVEN_PROFILE
                        def version  = env.PROJECT_VERSION
                        def commitShort = env.GIT_COMMIT_SHORT ?: 'unknown'

                        dockerBuildSteps[m] = {
                            echo ">>> 构建镜像: ${imageName}"

                            // 删除旧镜像
                            sh "docker rmi -f ${imageName} 2>/dev/null || true"

                            // 生成 .dockerignore, 只传 jar 文件给 Docker daemon
                            writeFile(
                                file: "${contextDir}/.dockerignore",
                                text: "*\n!target/*.jar"
                            )

                            // 统一使用自动生成的 Dockerfile
                            writeFile(
                                file: "${contextDir}/Dockerfile",
                                text: generateGenericDockerfile(m, profile)
                            )

                            sh """
                                docker build \
                                    --build-arg GIT_COMMIT="${commitShort}" \
                                    --build-arg MODULE_NAME="${m}" \
                                    --build-arg APP_VERSION="${version}" \
                                    -t ${imageName} \
                                    ${contextDir}
                            """.stripIndent()

                            echo "  镜像构建完成: ${imageName}"
                        }
                    }

                    dockerBuildSteps.each { name, step -> step.call() }
                }
            }
        }

        // ==================== Stage 5: 本地 Docker 部署 ====================
        stage('5. Deploy') {
            when {
                expression { params.DO_DEPLOY }
            }
            stages {

                stage('5.1 Stop Old Containers') {
                    steps {
                        script {
                            echo '===== 停止旧容器 (docker-compose down) ====='
                            def baseDir = env.DEPLOY_BASE_DIR
                            def nacosServerAddr = env.NACOS_SERVER_ADDR
                            def nacosNs = env.NACOS_NAMESPACE
                            def discoveryIp = env.DISCOVERY_IP
                            def profile = params.MAVEN_PROFILE
                            def deployModules = env.MODULES_TO_DEPLOY.tokenize(',')

                            // 生成 docker-compose.yml
                            def composeContent = generateDockerComposeFile(
                                env.MODULES_TO_DEPLOY, profile, baseDir,
                                nacosServerAddr, nacosNs, discoveryIp
                            )
                            def composeFile = 'docker-compose.yml'
                            sh "mkdir -p ${baseDir}"
                            writeFile(file: composeFile, text: composeContent)

                            // 停止并移除容器
                            def hasCompose = sh(
                                script: 'command -v docker-compose >/dev/null 2>&1 && echo yes || echo no',
                                returnStdout: true
                            ).trim()
                            def hasComposeV2 = sh(
                                script: 'docker compose version >/dev/null 2>&1 && echo yes || echo no',
                                returnStdout: true
                            ).trim()

                            if (hasCompose == 'yes') {
                                sh "docker-compose -f ${composeFile} down"
                            } else if (hasComposeV2 == 'yes') {
                                sh "docker compose -f ${composeFile} down"
                            } else {
                                echo 'WARNING: docker-compose 不可用, 回退到 docker 命令停止容器'
                                deployModules.each { module ->
                                    def m = module.trim()
                                    if (!isDeployableService(m)) { return }
                                    sh """
                                        docker stop jenkins-${m} 2>/dev/null || true
                                        docker rm jenkins-${m} 2>/dev/null || true
                                        echo "  已停止 jenkins-${m}"
                                    """.stripIndent()
                                }
                            }
                        }
                    }
                }

                stage('5.2 Start New Containers') {
                    steps {
                        script {
                            echo '===== 启动新容器 (docker-compose up) ====='
                            def baseDir = env.DEPLOY_BASE_DIR
                            def nacosServerAddr = env.NACOS_SERVER_ADDR
                            def nacosNs = env.NACOS_NAMESPACE
                            def discoveryIp = env.DISCOVERY_IP
                            def profile = params.MAVEN_PROFILE

                            // 生成 docker-compose.yml
                            def composeContent = generateDockerComposeFile(
                                env.MODULES_TO_DEPLOY, profile, baseDir,
                                nacosServerAddr, nacosNs, discoveryIp
                            )
                            def composeFile = 'docker-compose.yml'
                            writeFile(file: composeFile, text: composeContent)

                            // 确保各模块日志目录存在
                            def deployModules = env.MODULES_TO_DEPLOY.tokenize(',')
                            deployModules.each { module ->
                                def m = module.trim()
                                if (isDeployableService(m)) {
                                    sh "mkdir -p ${baseDir}/${m}/logs"
                                }
                            }

                            // 检测 docker-compose 可用性
                            def hasCompose = sh(
                                script: 'command -v docker-compose >/dev/null 2>&1 && echo yes || echo no',
                                returnStdout: true
                            ).trim()
                            def hasComposeV2 = sh(
                                script: 'docker compose version >/dev/null 2>&1 && echo yes || echo no',
                                returnStdout: true
                            ).trim()

                            if (hasCompose == 'yes') {
                                sh "docker-compose -f ${composeFile} up -d"
                            } else if (hasComposeV2 == 'yes') {
                                sh "docker compose -f ${composeFile} up -d"
                            } else {
                                echo 'WARNING: docker-compose 不可用, 回退到 docker run 启动容器'
                                deployModules.each { module ->
                                    def m = module.trim()
                                    if (!isDeployableService(m)) { return }
                                    def imageName = moduleToImageName(m)
                                    def port = getServicePort(m)
                                    echo "--- 启动 ${m} (端口 ${port}) ---"
                                    sh """
                                        docker run -d \\
                                            --name jenkins-${m} \\
                                            --restart unless-stopped \\
                                            -p ${port}:${port} \\
                                            -e SPRING_PROFILES_ACTIVE=${profile} \\
                                            -e SERVER_PORT=${port} \\
                                            -e NACOS_SERVER_ADDR=${nacosServerAddr} \\
                                            -e SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=${nacosServerAddr} \\
                                            -e SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=${nacosServerAddr} \\
                                            -e SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE=${nacosNs} \\
                                            -e SPRING_CLOUD_NACOS_CONFIG_NAMESPACE=${nacosNs} \\
                                            -e SPRING_CLOUD_NACOS_DISCOVERY_IP=${discoveryIp} \\
                                            -v ${baseDir}/${m}/logs:/app/logs \\
                                            -v /etc/localtime:/etc/localtime:ro \\
                                            ${imageName}
                                    """.stripIndent()
                                }
                            }

                            echo '  所有容器已启动'
                            // 展示运行中的容器
                            if (hasCompose == 'yes') {
                                sh "docker-compose -f ${composeFile} ps || true"
                            } else if (hasComposeV2 == 'yes') {
                                sh "docker compose -f ${composeFile} ps || true"
                            } else {
                                sh 'docker ps --filter "name=jenkins-" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" || true'
                            }
                        }
                    }
                }

            }
        }
    }

    // ======================== 构建后处理 ========================
    post {
        always {
            script {
                def startMs = (env.START_TIME ?: System.currentTimeMillis().toString()) as long
                def totalSecs = (System.currentTimeMillis() - startMs) / 1000L as long
                def minutes = totalSecs / 60L as long
                def seconds = totalSecs % 60L as long
                def durationStr = "${minutes} 分 ${seconds} 秒"
                env.BUILD_DURATION = durationStr

                echo """
                ======== 构建总结 ========
                分支      : ${env.GIT_REF}
                环境      : ${env.TARGET_ENV}
                镜像 Tag  : ${env.DOCKER_TAG}
                耗时      : ${durationStr}
                结果      : ${currentBuild.result ?: 'SUCCESS'}
                ==========================
                """.stripIndent()

                // 查看当前运行的容器
                sh 'echo "当前运行容器:"; docker ps --filter "name=jenkins-" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" || true'
            }

            cleanWs(
                cleanWhenAborted: true,
                cleanWhenFailure: true,
                cleanWhenNotBuilt: true,
                cleanWhenSuccess: true,
                deleteDirs: true,
                disableDeferredWipeout: true
            )
        }

        success {
            echo '✓ 流水线执行成功'
        }

        failure {
            echo '✗ 流水线执行失败, 请检查日志'
        }

        unstable {
            echo '⚠ 流水线执行不稳定, 部分阶段未通过'
        }
    }
}
相关推荐
菩提树下的打坐43 分钟前
CI 中的测试分层:让金字塔真正落地
学习·ci/cd
大不点wow1 小时前
为什么 Spring 更推荐构造器注入,而不是字段 `@Autowired`
java·spring·intellij-idea
wenzhangli71 小时前
OODER Studio设计工具导入体系深度解析
人工智能·自动化
不加糖4351 小时前
CI/CD 与 GitHub Actions 入门
ci/cd·github
用户298698530141 小时前
Java 创建 CSV 文件的三种实用方法:从零构建、数组写入与 Excel 转换
java·后端·excel
Cry丶1 小时前
一次 FTP 上传引发 504 Gateway Timeout 的排查复盘
java·nginx·ftp·openresty·被动模式·504 gateway·生产问题排查
码农进化录1 小时前
Java 程序员的 AI 进化论 | AI 辅助 CI/CD,让 AI 帮你做代码审查和部署决策
java·spring boot·openai
zyplayer-doc1 小时前
企业文档的进化:网盘和在线文档的下一个升级方向—知识库系统
java·大数据·数据库·人工智能·ocr
吃饱了得干活1 小时前
从零设计一个高并发点赞系统:缓存+MQ异步持久化实战
java·架构
奇牙coding2 小时前
gpt-5.6-luna 调用报 400 bad_request 怎么办?同样的 messages 在 gpt-5.5 没问题——两处校验差异和修复写法
java·windows·gpt·ai