源码update
All checks were successful
CodeChecker 变更检测 / code-check (push) Successful in 20s

This commit is contained in:
2026-06-09 15:43:04 +08:00
parent c8840e2af0
commit f94c24a0ab
10 changed files with 540 additions and 24 deletions

View File

@@ -0,0 +1,203 @@
package com.codechecker.notify;
import com.codechecker.api.model.ApiChangeKind;
import com.codechecker.api.model.EndpointChangeReport;
import com.codechecker.api.model.ParameterChange;
import com.codechecker.config.DtoOverlapMode;
import com.codechecker.model.ApiEndpoint;
import com.codechecker.model.ClassChangeReport;
import com.codechecker.model.ClassType;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* 按配置过滤 Dto 类变更与 API 参数变更的重叠通知。
*/
public class OverlapNotificationFilter {
public static final class FilterResult {
private final List<ClassChangeReport> classReports;
private final List<EndpointChangeReport> apiReports;
public FilterResult(List<ClassChangeReport> classReports, List<EndpointChangeReport> apiReports) {
this.classReports = classReports;
this.apiReports = apiReports;
}
public List<ClassChangeReport> classReports() {
return classReports;
}
public List<EndpointChangeReport> apiReports() {
return apiReports;
}
}
public static FilterResult apply(List<ClassChangeReport> classReports,
List<EndpointChangeReport> apiReports,
DtoOverlapMode mode) {
if (mode == DtoOverlapMode.BOTH) {
return new FilterResult(classReports, apiReports);
}
Set<OverlapKey> overlapKeys = buildOverlapKeys(classReports);
if (overlapKeys.isEmpty()) {
return new FilterResult(classReports, apiReports);
}
if (mode == DtoOverlapMode.CLASS_ONLY) {
return new FilterResult(classReports, filterApiReports(apiReports, overlapKeys));
}
Set<OverlapKey> apiOverlapKeys = buildApiOverlapKeys(apiReports);
return new FilterResult(filterClassReportsForApiOnly(classReports, apiOverlapKeys), apiReports);
}
private static Set<OverlapKey> buildOverlapKeys(List<ClassChangeReport> classReports) {
Set<OverlapKey> keys = new LinkedHashSet<>();
for (ClassChangeReport report : classReports) {
if (report.getClassType() != ClassType.DTO) {
continue;
}
if (!hasDtoFieldChanges(report)) {
continue;
}
Set<String> dtoNames = dtoNames(report);
for (ApiEndpoint endpoint : report.getInputImpactEndpoints()) {
for (String dtoName : dtoNames) {
keys.add(new OverlapKey(dtoName, endpoint.endpointKey()));
}
}
}
return keys;
}
private static List<EndpointChangeReport> filterApiReports(List<EndpointChangeReport> apiReports,
Set<OverlapKey> overlapKeys) {
List<EndpointChangeReport> kept = new ArrayList<>();
for (EndpointChangeReport report : apiReports) {
if (!matchesOverlap(report, overlapKeys)) {
kept.add(report);
}
}
return kept;
}
private static List<ClassChangeReport> filterClassReportsForApiOnly(List<ClassChangeReport> classReports,
Set<OverlapKey> apiOverlapKeys) {
List<ClassChangeReport> kept = new ArrayList<>();
for (ClassChangeReport report : classReports) {
if (!shouldSuppressClassForApiOnly(report, apiOverlapKeys)) {
kept.add(report);
}
}
return kept;
}
private static Set<OverlapKey> buildApiOverlapKeys(List<EndpointChangeReport> apiReports) {
Set<OverlapKey> keys = new LinkedHashSet<>();
for (EndpointChangeReport report : apiReports) {
if (report.getChangeKind() != ApiChangeKind.PARAM_CHANGED) {
continue;
}
String endpointKey = report.getHttpMethod() + " " + report.getUri();
for (ParameterChange change : report.getParameterChanges()) {
if (!"body".equals(change.getSource())) {
continue;
}
String parentDto = change.getParentDto();
if (parentDto != null && !parentDto.isBlank()) {
keys.add(new OverlapKey(parentDto, endpointKey));
}
}
if (report.isDtoFollowUp()) {
String relatedDto = report.getRelatedDtoClassName();
if (relatedDto != null && !relatedDto.isBlank()) {
keys.add(new OverlapKey(relatedDto, endpointKey));
}
}
}
return keys;
}
private static boolean shouldSuppressClassForApiOnly(ClassChangeReport report,
Set<OverlapKey> apiOverlapKeys) {
if (report.getClassType() != ClassType.DTO || !hasDtoFieldChanges(report)) {
return false;
}
for (ApiEndpoint endpoint : report.getInputImpactEndpoints()) {
for (String dtoName : dtoNames(report)) {
if (apiOverlapKeys.contains(new OverlapKey(dtoName, endpoint.endpointKey()))) {
return true;
}
}
}
return false;
}
private static boolean matchesOverlap(EndpointChangeReport report, Set<OverlapKey> overlapKeys) {
if (report.getChangeKind() != ApiChangeKind.PARAM_CHANGED) {
return false;
}
String endpointKey = report.getHttpMethod() + " " + report.getUri();
for (ParameterChange change : report.getParameterChanges()) {
if (!"body".equals(change.getSource())) {
continue;
}
String parentDto = change.getParentDto();
if (parentDto == null || parentDto.isBlank()) {
continue;
}
if (overlapKeys.contains(new OverlapKey(parentDto, endpointKey))) {
return true;
}
}
if (report.isDtoFollowUp()) {
String relatedDto = report.getRelatedDtoClassName();
if (relatedDto != null && overlapKeys.contains(new OverlapKey(relatedDto, endpointKey))) {
return true;
}
}
return false;
}
private static boolean hasDtoFieldChanges(ClassChangeReport report) {
return !report.getFieldChanges().isEmpty();
}
private static Set<String> dtoNames(ClassChangeReport report) {
Set<String> names = new LinkedHashSet<>();
names.add(report.getClassName());
if (report.getOldClassName() != null && !report.getOldClassName().isBlank()) {
names.add(report.getOldClassName());
}
return names;
}
private static final class OverlapKey {
private final String dtoClassName;
private final String endpointKey;
private OverlapKey(String dtoClassName, String endpointKey) {
this.dtoClassName = dtoClassName;
this.endpointKey = endpointKey;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof OverlapKey)) {
return false;
}
OverlapKey other = (OverlapKey) obj;
return dtoClassName.equals(other.dtoClassName) && endpointKey.equals(other.endpointKey);
}
@Override
public int hashCode() {
return dtoClassName.hashCode() * 31 + endpointKey.hashCode();
}
}
}