Jenkins 读取 Spring Boot 的 application.yaml 并执行 SQL 脚本到数据库

下面是一个完整的 Jenkins Pipeline 实现方案,用于读取 Spring Boot 项目的 application.yaml 中的 app.version,并根据版本号执行 db 目录下对应的 SQL 脚本,数据库连接信息从服务器环境变量获取。

TIPS:为了保证连接数据库信息的安全性,建议把数据库信息的url、用户名、密码配置到jenkins服务器的环境变量中。

1. 项目结构假设

your-springboot-project/
├── src/main/resources/
│   └── application.yaml
├── db/
│   ├── 1.0.0/
│   │   ├── create_tables.sql
│   │   └── initial_data.sql
│   ├── 1.1.0/
│   │   └── add_new_columns.sql
│   └── 1.2.0/
│       └── update_views.sql
└── Jenkinsfile

2. application.yaml 示例

app:
  version: 1.2.0
  # 其他配置...

3. Jenkins Pipeline 实现

pipeline {
    agent any
    

    stages {
        stage('Checkout') {
            steps {
                // 仓库地址
                git branch: 'main', url: 'https://your-git-repo.git'
            }
        }
        
        stage('Read app.version') {
            steps {
                script {
                    // 读取Spring Boot的application.yaml
                    // 注意路径是相对于项目根目录的
                    def yamlContent = readYaml file: 'src/main/resources/application.yaml'
                    
                    // 获取版本号
                    currentVersion = yamlContent.app.version
                    echo "Current app version: ${currentVersion}"
                    
                    // 验证版本号格式
                    if (!(currentVersion =~ /^\d+\.\d+\.\d+$/)) {
                        error "Invalid version format: ${currentVersion}"
                    }
                }
            }
        }
        
        stage('Execute SQL Scripts') {
            environment {
                // 从服务器环境变量获取数据库配置
                // 这些变量需要在Jenkins服务器或agent上设置
                DB_URL = credentials('DB_URL')  // 或者直接使用 env.DB_URL
                DB_USER = credentials('DB_USER') // 或者直接使用 env.DB_USER
                DB_PASSWORD = credentials('DB_PASSWORD') // 或者直接使用 env.DB_PASSWORD
                DB_NAME = credentials('DB_NAME') // 可选
            }
            steps {
                script {
                    // 检查SQL目录是否存在
                    def sqlDir = "db/${currentVersion}"
                    if (!fileExists(sqlDir)) {
                        error "SQL directory for version ${currentVersion} does not exist!"
                    }
                    
                    // 获取所有.sql文件并按名称排序
                    def sqlFiles = findFiles glob: "${sqlDir}/*.sql"
                    
                    if (sqlFiles.isEmpty()) {
                        echo "No SQL files found for version ${currentVersion}"
                    } else {
                        echo "Found ${sqlFiles.size()} SQL files for version ${currentVersion}"
                        
                        // 按文件名排序执行
                        sqlFiles.sort { it.name }.each { file ->
                            def fileName = file.name
                            echo "Executing SQL script: ${fileName}"
                            
                            // 根据数据库类型选择执行方式
                            if (env.DB_TYPE == 'mysql' || env.DB_TYPE == null) {
                                // MySQL执行方式
                                sh """
                                    mysql -h ${DB_URL} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} \
                                    < ${file.path}
                                """
                            } else if (env.DB_TYPE == 'postgresql') {
                                // PostgreSQL执行方式
                                withEnv(["PGPASSWORD=${DB_PASSWORD}"]) {
                                    sh """
                                        psql -h ${DB_URL} -U ${DB_USER} -d ${DB_NAME} \
                                        -f ${file.path}
                                    """
                                }
                            } else {
                                error "Unsupported database type: ${env.DB_TYPE}"
                            }
                            
                            echo "Successfully executed ${fileName}"
                        }
                    }
                }
            }
        }
    }
    
    post {
        success {
            echo "Database update completed successfully for version ${currentVersion}"
        }
        failure {
            echo "Database update failed for version ${currentVersion}"
            // 可以添加通知逻辑
        }
    }
}

4. 配置说明

环境变量设置

  1. 在服务器上设置环境变量(以Linux为例):

    # 编辑/etc/environment或用户profile文件
    export DB_URL=your-db-host
    export DB_USER=your-db-user
    export DB_PASSWORD=your-db-password
    export DB_NAME=your-db-name
    export DB_TYPE=mysql  # 或 postgresql
    
  2. 或者在Jenkins中配置

    • 进入 Jenkins → Manage Jenkins → Configure System → Global properties
    • 勾选 "Environment variables" 并添加键值对

安全建议

  1. 使用Jenkins凭据管理敏感信息

    environment {
        DB_URL = credentials('db-prod-url')
        DB_USER = credentials('db-prod-user')
        DB_PASSWORD = credentials('db-prod-password')
    }
    
  2. 添加执行前确认(特别是生产环境):

    stage('Confirm Deployment') {
        when {
            expression { env.BRANCH_NAME == 'main' || env.BRANCH_NAME == 'master' }
        }
        steps {
            timeout(time: 1, unit: 'MINUTES') {
                input message: "Confirm deployment to PROD for version ${currentVersion}?", 
                      ok: "Deploy"
            }
        }
    }
    

5. 高级改进建议

  1. 添加版本验证逻辑

    script {
        // 获取当前数据库版本(需要实现查询逻辑)
        def currentDbVersion = sh(script: 'mysql -N -h ${DB_URL} -u ${DB_USER} -p${DB_PASSWORD} -e "SELECT version FROM db_version"', returnStdout: true).trim()
        
        if (compareVersions(currentVersion, currentDbVersion) <= 0) {
            error "Target version ${currentVersion} is not newer than current DB version ${currentDbVersion}"
        }
    }
    
  2. 实现版本比较函数(放在Pipeline开头):

    def compareVersions(String v1, String v2) {
        def parts1 = v1.tokenize('.').collect { it as int }
        def parts2 = v2.tokenize('.').collect { it as int }
        
        for (int i = 0; i < Math.max(parts1.size(), parts2.size()); i++) {
            def p1 = i < parts1.size() ? parts1[i] : 0
            def p2 = i < parts2.size() ? parts2[i] : 0
            if (p1 != p2) {
                return p1 <=> p2
            }
        }
        return 0
    }
    
  3. 添加事务支持(复杂场景):

    • 考虑将所有SQL脚本合并为一个事务,可能造成执行性能问题
    • 或者使用专业的数据库迁移工具(Flyway/Liquibase)
  4. 添加SQL重复执行判断(复杂场景)

可以根据你的具体需求进一步调整。

posted @ 2025-05-08 10:06  Vincezon  阅读(44)  评论(0)    收藏  举报