Skip to content

代码质量门禁

将代码质量检查集成到构建流程,不达标则构建失败。

完整质量门禁配置

kotlin
// buildSrc/src/main/kotlin/quality-conventions.gradle.kts
plugins {
    java
    checkstyle
    jacoco
    id("com.github.spotbugs")
}

// Checkstyle:代码风格
checkstyle {
    toolVersion = "10.12.5"
    configFile = rootProject.file("config/checkstyle/checkstyle.xml")
    maxWarnings = 0
    isIgnoreFailures = false
}

// SpotBugs:静态 Bug 分析
spotbugs {
    toolVersion.set("4.8.3")
    excludeFilter.set(rootProject.file("config/spotbugs/exclude.xml"))
    effort.set(com.github.spotbugs.snom.Effort.DEFAULT)
    reportLevel.set(com.github.spotbugs.snom.Confidence.MEDIUM)
    isIgnoreFailures = false
}

// JaCoCo:测试覆盖率
jacoco {
    toolVersion = "0.8.11"
}

tasks.named<JacocoReport>("jacocoTestReport") {
    dependsOn(tasks.named("test"))
    reports {
        xml.required.set(true)
        html.required.set(true)
    }
    
    // 排除不需要统计的类
    classDirectories.setFrom(
        sourceSets.main.get().output.asFileTree.matching {
            exclude(
                "**/config/**",
                "**/*Application*",
                "**/*Exception*",
                "**/dto/**",
                "**/entity/**"
            )
        }
    )
}

tasks.named<JacocoCoverageVerification>("jacocoTestCoverageVerification") {
    dependsOn(tasks.named("jacocoTestReport"))
    
    violationRules {
        rule {
            limit {
                counter = "LINE"
                value = "COVEREDRATIO"
                minimum = "0.80".toBigDecimal()   // 80% 行覆盖率
            }
        }
        rule {
            limit {
                counter = "BRANCH"
                value = "COVEREDRATIO"
                minimum = "0.70".toBigDecimal()   // 70% 分支覆盖率
            }
        }
    }
}

// 将所有质量检查加入 check 任务
tasks.named("check") {
    dependsOn(
        tasks.named("checkstyleMain"),
        tasks.named("spotbugsMain"),
        tasks.named("jacocoTestCoverageVerification")
    )
}

SonarQube 集成

kotlin
// 根项目 build.gradle.kts
plugins {
    id("org.sonarqube") version "4.4.1.3373"
}

sonar {
    properties {
        property("sonar.projectKey", "my-project")
        property("sonar.projectName", "My Project")
        property("sonar.host.url", "https://sonarqube.example.com")
        property("sonar.token", System.getenv("SONAR_TOKEN"))
        
        // JaCoCo 报告路径
        property("sonar.coverage.jacoco.xmlReportPaths",
            subprojects.map { "${it.buildDir}/reports/jacoco/test/jacocoTestReport.xml" }.joinToString(","))
        
        // 测试报告路径
        property("sonar.junit.reportPaths",
            subprojects.map { "${it.buildDir}/test-results/test" }.joinToString(","))
        
        // 排除文件
        property("sonar.exclusions", "**/generated/**,**/*Exception.java,**/*Dto.java")
        
        // 质量门
        property("sonar.qualitygate.wait", "true")
    }
}

CI 流水线集成

yaml
# .github/workflows/quality.yml
name: Quality Gate

on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # SonarQube 需要完整历史
      
      - uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
      
      - uses: gradle/actions/setup-gradle@v3
      
      # 1. 构建+测试+质量检查
      - name: Build and Quality Check
        run: ./gradlew build checkstyleMain spotbugsMain jacocoTestReport
      
      # 2. SonarQube 分析
      - name: SonarQube Scan
        run: ./gradlew sonar
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

质量报告汇总

kotlin
// 生成汇总报告
tasks.register("qualityReport") {
    group = "reporting"
    dependsOn("jacocoTestReport", "checkstyleMain", "spotbugsMain")
    
    doLast {
        println("========== 质量检查报告 ==========")
        println("测试覆盖率:build/reports/jacoco/test/html/index.html")
        println("代码风格:build/reports/checkstyle/main.html")
        println("Bug 检测:build/reports/spotbugs/main.html")
        println("===================================")
    }
}