本文目录导读:

我将为您提供一个完整的Java代码质量门禁案例,包括配置、实现和示例。
项目结构
quality-gate/
├── pom.xml
├── src/
│ ├── main/
│ │ └── java/
│ │ └── com/example/qualitygate/
│ │ ├── QualityGateConfig.java
│ │ ├── QualityGateRunner.java
│ │ └── QualityGateResult.java
│ └── test/
│ └── java/
│ └── com/example/qualitygate/
│ └── QualityGateRunnerTest.java
└── quality-gate-config.yml
核心实现代码
1 质量门禁配置类
package com.example.qualitygate;
import java.util.HashMap;
import java.util.Map;
public class QualityGateConfig {
private Map<String, Object> thresholds = new HashMap<>();
private boolean failOnError = true;
// 质量门禁阈值配置
public static QualityGateConfig defaultConfig() {
QualityGateConfig config = new QualityGateConfig();
config.thresholds.put("complexity.max", 10);
config.thresholds.put("coverage.min", 80.0);
config.thresholds.put("duplication.max", 5.0);
config.thresholds.put("bugs.max", 0);
config.thresholds.put("vulnerabilities.max", 0);
config.thresholds.put("codeSmells.max", 100);
config.thresholds.put("testRatio.min", 0.8);
config.thresholds.put("linesOfCode.max", 2000);
config.thresholds.put("methods.max", 50);
config.thresholds.put("classes.max", 30);
return config;
}
public Object getThreshold(String key) {
return thresholds.get(key);
}
public void setThreshold(String key, Object value) {
thresholds.put(key, value);
}
public boolean isFailOnError() {
return failOnError;
}
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
}
2 质量门禁结果类
package com.example.qualitygate;
import java.util.ArrayList;
import java.util.List;
public class QualityGateResult {
private boolean passed = true;
private List<String> violations = new ArrayList<>();
private String gateName;
private long timestamp;
private Map<String, Object> metrics = new HashMap<>();
public void addViolation(String violation) {
violations.add(violation);
passed = false;
}
public boolean isPassed() {
return passed;
}
public List<String> getViolations() {
return new ArrayList<>(violations);
}
public String getSummary() {
StringBuilder sb = new StringBuilder();
sb.append("Quality Gate: ").append(gateName).append("\n");
sb.append("Timestamp: ").append(timestamp).append("\n");
sb.append("Status: ").append(passed ? "PASSED" : "FAILED").append("\n");
if (!violations.isEmpty()) {
sb.append("Violations:\n");
for (String violation : violations) {
sb.append(" - ").append(violation).append("\n");
}
}
return sb.toString();
}
public void addMetric(String name, Object value) {
metrics.put(name, value);
}
}
3 质量门禁执行器
package com.example.qualitygate;
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;
import java.io.*;
public class QualityGateRunner {
private QualityGateConfig config;
private List<CodeMetric> metrics = new ArrayList<>();
public QualityGateRunner(QualityGateConfig config) {
this.config = config;
initializeMetrics();
}
private void initializeMetrics() {
metrics.add(new CodeSizeMetric());
metrics.add(new ComplexityMetric());
metrics.add(new CoverageMetric());
metrics.add(new DuplicationMetric());
metrics.add(new SecurityMetric());
metrics.add(new TestQualityMetric());
}
public QualityGateResult execute(String sourceDirectory, String testDirectory) {
QualityGateResult result = new QualityGateResult();
result.gateName = "Java Code Quality Gate";
result.timestamp = System.currentTimeMillis();
Map<String, Object> analysisResults = analyzeCode(sourceDirectory, testDirectory);
// 执行各项检查
checkCodeSize(analysisResults, result);
checkComplexity(analysisResults, result);
checkCoverage(analysisResults, result);
checkDuplication(analysisResults, result);
checkSecurity(analysisResults, result);
checkTestQuality(analysisResults, result);
return result;
}
private Map<String, Object> analyzeCode(String sourceDir, String testDir) {
Map<String, Object> results = new HashMap<>();
List<Path> javaFiles = collectJavaFiles(sourceDir);
List<Path> testFiles = collectJavaFiles(testDir);
// 收集基本指标
results.put("linesOfCode", calculateLines(javaFiles));
results.put("methods", countMethods(javaFiles));
results.put("classes", countClasses(javaFiles));
results.put("complexity", calculateAverageComplexity(javaFiles));
results.put("testRatio", calculateTestRatio(javaFiles, testFiles));
return results;
}
private List<Path> collectJavaFiles(String directory) {
List<Path> javaFiles = new ArrayList<>();
if (directory == null || directory.isEmpty()) {
return javaFiles;
}
Path root = Paths.get(directory);
if (!Files.exists(root)) {
System.err.println("Directory not found: " + directory);
return javaFiles;
}
try {
Files.walk(root)
.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(javaFiles::add);
} catch (Exception e) {
System.err.println("Error scanning directory: " + directory);
}
return javaFiles;
}
private int calculateLines(List<Path> files) {
int totalLines = 0;
for (Path file : files) {
try {
long count = Files.lines(file).count();
totalLines += count;
} catch (IOException e) {
System.err.println("Error reading file: " + file);
}
}
return totalLines;
}
private int countMethods(List<Path> files) {
int methodCount = 0;
Pattern methodPattern = Pattern.compile(
"(public|private|protected|static|final|synchronized|abstract)\\s+" +
"(<[^>]+>\\s*)?" +
"[\\w\\[\\]\\<\\>\\?]+\\s+" +
"\\w+\\s*\\("
);
for (Path file : files) {
try {
String content = new String(Files.readAllBytes(file));
Matcher matcher = methodPattern.matcher(content);
while (matcher.find()) {
methodCount++;
}
} catch (IOException e) {
// ignore
}
}
return methodCount;
}
private int countClasses(List<Path> files) {
int classCount = 0;
Pattern classPattern = Pattern.compile(
"(public|abstract|final)?\\s*class\\s+\\w+"
);
for (Path file : files) {
try {
String content = new String(Files.readAllBytes(file));
Matcher matcher = classPattern.matcher(content);
while (matcher.find()) {
classCount++;
}
} catch (IOException e) {
// ignore
}
}
return classCount;
}
private double calculateAverageComplexity(List<Path> files) {
int totalComplexity = 0;
int methodCount = 0;
for (Path file : files) {
try {
String content = new String(Files.readAllBytes(file));
methodCount += countMethods(Collections.singletonList(file));
totalComplexity += calculateCyclomaticComplexity(content);
} catch (IOException e) {
// ignore
}
}
return methodCount > 0 ? (double) totalComplexity / methodCount : 0;
}
private int calculateCyclomaticComplexity(String code) {
int complexity = 1;
String[] keywords = {"if", "else if", "for", "while", "case", "catch", "&&", "||", "?"};
for (String keyword : keywords) {
int count = countOccurrences(code, keyword);
complexity += count;
}
return complexity;
}
private int countOccurrences(String text, String word) {
int count = 0;
int index = 0;
while ((index = text.indexOf(word, index)) != -1) {
count++;
index += word.length();
}
return count;
}
private double calculateTestRatio(List<Path> sourceFiles, List<Path> testFiles) {
if (sourceFiles.isEmpty()) {
return 0;
}
return (double) testFiles.size() / sourceFiles.size();
}
// 各检查方法
private void checkCodeSize(Map<String, Object> results, QualityGateResult result) {
int lines = (int) results.get("linesOfCode");
int maxLines = (int) config.getThreshold("linesOfCode.max");
result.addMetric("linesOfCode", lines);
if (lines > maxLines) {
result.addViolation("Lines of code exceed limit: " + lines + " > " + maxLines);
}
}
private void checkComplexity(Map<String, Object> results, QualityGateResult result) {
double complexity = (double) results.get("complexity");
int maxComplexity = (int) config.getThreshold("complexity.max");
result.addMetric("averageComplexity", complexity);
if (complexity > maxComplexity) {
result.addViolation("Average cyclomatic complexity too high: " +
String.format("%.2f", complexity) + " > " + maxComplexity);
}
}
private void checkCoverage(Map<String, Object> results, QualityGateResult result) {
// 简化版本,实际应集成 JaCoCo 等工具
double coverage = calculateMockCoverage();
double minCoverage = (double) config.getThreshold("coverage.min");
result.addMetric("coverage", coverage);
if (coverage < minCoverage) {
result.addViolation("Test coverage too low: " +
String.format("%.2f%%", coverage) + " < " + minCoverage + "%");
}
}
private void checkDuplication(Map<String, Object> results, QualityGateResult result) {
// 简化版本,实际应集成 PMD 或 SonarQube
double duplication = calculateMockDuplication();
double maxDuplication = (double) config.getThreshold("duplication.max");
result.addMetric("duplication", duplication);
if (duplication > maxDuplication) {
result.addViolation("Code duplication too high: " +
String.format("%.2f%%", duplication) + " > " + maxDuplication + "%");
}
}
private void checkSecurity(Map<String, Object> results, QualityGateResult result) {
// 简化版本,实际应集成 FindBugs 或 SpotBugs
int bugs = 0;
int vulnerabilities = 0;
try {
// 模拟安全检查
Pattern dangerous = Pattern.compile("ProcessBuilder|Runtime\\.exec|System\\.getProperty");
// 实际文件扫描逻辑
} catch (Exception e) {
// ignore
}
result.addMetric("bugs", bugs);
result.addMetric("vulnerabilities", vulnerabilities);
if (bugs > (int) config.getThreshold("bugs.max")) {
result.addViolation("Too many bugs found: " + bugs);
}
if (vulnerabilities > (int) config.getThreshold("vulnerabilities.max")) {
result.addViolation("Too many vulnerabilities found: " + vulnerabilities);
}
}
private void checkTestQuality(Map<String, Object> results, QualityGateResult result) {
double testRatio = (double) results.get("testRatio");
double minTestRatio = (double) config.getThreshold("testRatio.min");
result.addMetric("testRatio", testRatio);
if (testRatio < minTestRatio) {
result.addViolation("Test to source ratio too low: " +
String.format("%.2f", testRatio) + " < " + minTestRatio);
}
}
private double calculateMockCoverage() {
// 模拟覆盖率,实际应集成 JaCoCo
return 85.0; // 示例值
}
private double calculateMockDuplication() {
// 模拟重复率,实际应集成 PMD
return 3.5; // 示例值
}
}
4 CI/CD 集成示例
<!-- pom.xml 中的配置 -->
<project>
<properties>
<!-- 质量门禁配置 -->
<quality.gate.max.coverage>80</quality.gate.max.coverage>
<quality.gate.min.complexity>10</quality.gate.min.complexity>
<quality.gate.max.duplication>5</quality.gate.max.duplication>
</properties>
<build>
<plugins>
<!-- Checkstyle 插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<failOnViolation>true</failOnViolation>
<violationSeverity>warning</violationSeverity>
</configuration>
</plugin>
<!-- PMD 插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<failOnViolation>true</failOnViolation>
<rulesets>
<ruleset>rulesets/java/braces.xml</ruleset>
<ruleset>rulesets/java/naming.xml</ruleset>
</rulesets>
</configuration>
</plugin>
<!-- JaCoCo 覆盖率插件 -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<configuration>
<excludes>
<exclude>**/qualitygate/**</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>${quality.gate.max.coverage}</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<!-- SpotBugs 插件 -->
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.2.0</version>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<failOnError>true</failOnError>
</configuration>
</plugin>
</plugins>
</build>
</project>
5 门禁执行工具类
package com.example.qualitygate;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
public class QualityGateExecutor {
public static void main(String[] args) {
if (args.length < 2) {
System.err.println("Usage: QualityGateExecutor <sourceDir> <testDir>");
System.exit(1);
}
String sourceDir = args[0];
String testDir = args[1];
// 1. 加载配置
QualityGateConfig config = QualityGateConfig.defaultConfig();
loadCustomConfig(config);
// 2. 创建执行器
QualityGateRunner runner = new QualityGateRunner(config);
// 3. 执行质量检查
QualityGateResult result = runner.execute(sourceDir, testDir);
// 4. 输出结果
System.out.println(result.getSummary());
// 5. 退出码
if (!result.isPassed() && config.isFailOnError()) {
System.exit(1);
}
}
private static void loadCustomConfig(QualityGateConfig config) {
// 从配置文件或环境变量加载自定义配置
Properties props = new Properties();
try (InputStream in = QualityGateExecutor.class
.getResourceAsStream("/quality-gate.properties")) {
if (in != null) {
props.load(in);
// 解析并覆盖默认配置
props.forEach((key, value) -> {
String metricKey = key.toString().replace("quality.","");
String[] keyParts = metricKey.split("\\.");
if (keyParts.length == 2) {
config.setThreshold(keyParts[0] + "." + keyParts[1],
parseValue(value.toString()));
}
});
}
} catch (IOException e) {
System.err.println("Error loading quality gate configuration: " + e.getMessage());
}
}
private static Object parseValue(String value) {
if (value.contains(".")) {
try {
return Double.parseDouble(value);
} catch (NumberFormatException e) {
return value;
}
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
return Boolean.parseBoolean(value);
}
return value;
}
}
}
6 测试类
package com.example.qualitygate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class QualityGateRunnerTest {
private QualityGateConfig config;
private QualityGateRunner runner;
@BeforeEach
void setUp() {
config = QualityGateConfig.defaultConfig();
runner = new QualityGateRunner(config);
}
@Test
void testPassingQualityGate() {
// 使用高质量代码目录
QualityGateResult result = runner.execute(
"src/test/resources/good-code",
"src/test/resources/good-code-test"
);
assertTrue(result.isPassed(), "Quality gate should pass for good code");
assertTrue(result.getViolations().isEmpty());
}
@Test
void testFailingQualityGate() {
// 使用低质量代码目录
QualityGateResult result = runner.execute(
"src/test/resources/bad-code",
"src/test/resources/bad-code-test"
);
assertFalse(result.isPassed(), "Quality gate should fail for bad code");
assertFalse(result.getViolations().isEmpty());
}
@Test
void testCustomThreshold() {
config.setThreshold("complexity.max", 5); // 更严格的复杂度限制
runner = new QualityGateRunner(config);
QualityGateResult result = runner.execute(
"src/test/resources/complex-code",
"src/test/resources/complex-code-test"
);
assertFalse(result.isPassed());
assertTrue(result.getViolations().stream()
.anyMatch(v -> v.contains("complexity")));
}
@Test
void testEmptySource() {
QualityGateResult result = runner.execute("", "");
assertFalse(result.isPassed());
}
}
配置示例 (quality-gate.properties)
# 质量门禁配置 # 代码规模 quality.linesOfCode.max=2000 quality.classes.max=30 quality.methods.max=50 # 复杂度 quality.complexity.max=10 # 覆盖率 quality.coverage.min=80.0 # 重复率 quality.duplication.max=5.0 # 质量指标 quality.bugs.max=0 quality.vulnerabilities.max=0 quality.codeSmells.max=100 # 测试指标 quality.testRatio.min=0.8 # 其他设置 quality.failOnError=true
CI 集成 (GitLab CI)
# .gitlab-ci.yml
stages:
- build
- test
- quality
- deploy
quality-gate:
stage: quality
script:
- mvn verify
- mvn checkstyle:check
- mvn pmd:check
- mvn spotbugs:check
- java -jar quality-gate.jar ./src ./test
artifacts:
when: always
paths:
- target/surefire-reports/
reports:
junit:
- target/surefire-reports/TEST-*.xml
使用说明
构建和运行
# 编译项目 mvn clean package # 运行质量门禁 java -jar target/quality-gate.jar ./src/main/java ./src/test/java # 执行完整质量检查 mvn verify mvn checkstyle:check mvn pmd:check mvn spotbugs:check
集成到 CI/CD
-
Jenkins Pipeline
pipeline { stages { stage('Quality Gate') { steps { sh 'mvn verify' sh 'mvn checkstyle:check pmd:check spotbugs:check' } } } } -
GitHub Actions
name: Quality Gate on: [push, pull_request] jobs: quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up JDK uses: actions/setup-java@v2 with: java-version: '11' - name: Run quality gate run: | mvn verify mvn checkstyle:check mvn pmd:check
最佳实践
- 分级门禁: 根据项目阶段设置不同严格度的门禁
- 渐进式改进: 逐步提高质量标准
- 持续集成: 将门禁集成到CI流程
- 定制规则: 根据团队情况自定义规则
- 自动修复: 集成自动格式化工具
- 定期审查: 定期审查和更新质量规则
这个案例提供了一个完整的Java代码质量门禁实现,可以根据实际需求进行调整和扩展。