Files
codeCheck/src/main/java/com/codechecker/api/analyzer/DtoImpactedApiAnalyzer.java
2026-06-09 17:57:52 +08:00

151 lines
6.3 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.codechecker.api.analyzer;
import com.codechecker.analyzer.DtoNestIndex;
import com.codechecker.api.model.ApiChangeKind;
import com.codechecker.api.model.EndpointChangeReport;
import com.codechecker.api.model.EndpointSnapshot;
import com.codechecker.api.model.ParameterChange;
import com.codechecker.api.parser.EndpointSnapshotParser;
import com.codechecker.config.AppConfig;
import com.codechecker.git.GitChangeScanner;
import com.codechecker.model.ApiEndpoint;
import com.codechecker.model.ClassChangeReport;
import com.codechecker.model.ClassType;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 类变更Dto/Vo 嵌套字段)后,对受影响的 Controller 继续 API 参数 diff产出 PARAM_CHANGED 报告。
*/
public class DtoImpactedApiAnalyzer {
private final GitChangeScanner gitScanner;
public DtoImpactedApiAnalyzer(GitChangeScanner gitScanner) {
this.gitScanner = gitScanner;
}
public List<EndpointChangeReport> analyze(Path repoRoot, AppConfig config,
String oldSha, String newSha,
List<ClassChangeReport> classReports,
Set<String> alreadyScannedFiles,
DtoNestIndex nestIndex) throws IOException {
Map<String, Set<String>> controllerToDtos = collectImpactedControllers(classReports, alreadyScannedFiles,
nestIndex);
if (controllerToDtos.isEmpty()) {
return List.of();
}
EndpointSnapshotParser parser = new EndpointSnapshotParser(config.isApiExcludeFrameworkParams());
ParameterDiffEngine parameterDiffEngine = new ParameterDiffEngine(
repoRoot, buildSearchDirs(config), gitScanner, oldSha, newSha, config.getNestMaxDepth());
EndpointDiffEngine endpointDiffEngine = new EndpointDiffEngine(parameterDiffEngine);
List<EndpointSnapshot> oldSnapshots = new ArrayList<>();
List<EndpointSnapshot> newSnapshots = new ArrayList<>();
for (String path : controllerToDtos.keySet()) {
boolean feign = isFeignPath(path, config);
String oldSource = gitScanner.readFileAtCommit(oldSha, path);
String newSource = gitScanner.readFileAtCommit(newSha, path);
oldSnapshots.addAll(parser.parseSource(oldSource, path, feign));
newSnapshots.addAll(parser.parseSource(newSource, path, feign));
}
List<EndpointChangeReport> reports = new ArrayList<>();
for (EndpointChangeReport report : endpointDiffEngine.diff(oldSnapshots, newSnapshots)) {
if (report.getChangeKind() != ApiChangeKind.PARAM_CHANGED || !report.hasParameterChanges()) {
continue;
}
String relatedDto = findRelatedDto(report, controllerToDtos);
if (relatedDto == null) {
continue;
}
reports.add(EndpointChangeReport.dtoFollowUp(report, relatedDto));
}
return reports;
}
private Map<String, Set<String>> collectImpactedControllers(List<ClassChangeReport> classReports,
Set<String> alreadyScannedFiles,
DtoNestIndex nestIndex) {
Map<String, Set<String>> controllerToDtos = new LinkedHashMap<>();
for (ClassChangeReport report : classReports) {
if (report.getFieldChanges().isEmpty()) {
continue;
}
if (report.getClassType() != ClassType.DTO && report.getClassType() != ClassType.VO) {
continue;
}
Set<String> bodyRoots = resolveBodyRoots(report, nestIndex);
if (bodyRoots.isEmpty()) {
continue;
}
for (ApiEndpoint endpoint : report.getInputImpactEndpoints()) {
String controllerFile = endpoint.getSourceFile();
if (alreadyScannedFiles.contains(controllerFile)) {
continue;
}
controllerToDtos.computeIfAbsent(controllerFile, k -> new LinkedHashSet<>()).addAll(bodyRoots);
}
}
return controllerToDtos;
}
private Set<String> resolveBodyRoots(ClassChangeReport report, DtoNestIndex nestIndex) {
if (nestIndex == null) {
Set<String> names = new LinkedHashSet<>();
if (report.getClassName().endsWith("Dto")) {
names.add(report.getClassName());
}
return names;
}
Set<String> roots = new LinkedHashSet<>();
roots.addAll(nestIndex.findRequestBodyRoots(report.getClassName()));
if (report.getOldClassName() != null && !report.getOldClassName().isBlank()) {
roots.addAll(nestIndex.findRequestBodyRoots(report.getOldClassName()));
}
return roots;
}
private String findRelatedDto(EndpointChangeReport report, Map<String, Set<String>> controllerToDtos) {
Set<String> impactedDtos = controllerToDtos.getOrDefault(report.getSourceFile(), Set.of());
for (ParameterChange change : report.getParameterChanges()) {
if (!"body".equals(change.getSource())) {
continue;
}
String parentDto = change.getParentDto();
if (parentDto != null && impactedDtos.contains(parentDto)) {
return parentDto;
}
}
return null;
}
private List<String> buildSearchDirs(AppConfig config) {
List<String> dirs = new ArrayList<>();
dirs.addAll(config.getModelDirs());
dirs.addAll(config.getAllApiScanDirs());
return dirs;
}
private boolean isFeignPath(String path, AppConfig config) {
String normalized = path.replace('\\', '/');
for (String dir : config.getApiFeignScanDirs()) {
String prefix = dir.replace('\\', '/');
if (!prefix.endsWith("/")) {
prefix = prefix + "/";
}
if (normalized.startsWith(prefix)) {
return true;
}
}
return false;
}
}