This commit is contained in:
@@ -51,7 +51,7 @@ WeComNotifier ────────► ApiChangeNotifier(新
|
|||||||
**方法指纹建议**(用于跨 commit 匹配同一接口):
|
**方法指纹建议**(用于跨 commit 匹配同一接口):
|
||||||
|
|
||||||
```
|
```
|
||||||
controller源文件 + 方法名 + 参数类型签名
|
controller源文件 + 方法名 + 参数槽位(如 0:query,1:body;不含类型与绑定名)
|
||||||
```
|
```
|
||||||
|
|
||||||
仅 URI 匹配在「改路径」场景会失效,需指纹辅助。
|
仅 URI 匹配在「改路径」场景会失效,需指纹辅助。
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
1. 解析旧/新 commit 下同一 Controller 源码 AST
|
1. 解析旧/新 commit 下同一 Controller 源码 AST
|
||||||
2. 提取每个方法的 `httpMethod` + `uri`(已有 `EndpointParser` 逻辑)
|
2. 提取每个方法的 `httpMethod` + `uri`(已有 `EndpointParser` 逻辑)
|
||||||
3. 用**方法指纹**(类文件 + 方法名 + 参数类型签名)匹配新旧接口
|
3. 用**方法指纹**(类文件 + 方法名 + 参数槽位,如 `0:query,1:query`;不含类型与绑定名)匹配新旧接口
|
||||||
4. 指纹相同且 URI 不同 → **修改路径**
|
4. 指纹相同且 URI 不同 → **修改路径**
|
||||||
5. 仅旧有新无 → **删除**;仅新有旧无 → **新增**
|
5. 仅旧有新无 → **删除**;仅新有旧无 → **新增**
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,20 @@ import com.codechecker.model.FieldInfo;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接口入参 diff(普通参数 + RequestBody 嵌套 Dto 字段),与类变更 FieldDiffEngine 解耦。
|
* 接口入参 diff(普通参数 + RequestBody 嵌套 Dto 字段)。
|
||||||
|
*
|
||||||
|
* path/query 规则:
|
||||||
|
* - 形参名+类型相同,仅绑定名变 → 重命名
|
||||||
|
* - 形参名+绑定名相同,仅类型变 → 类型变更
|
||||||
|
* - 仅形参名变(绑定名不变)→ 不通知
|
||||||
|
* - 类型与绑定名同时变,或三者都变 → 先删除后新增
|
||||||
*/
|
*/
|
||||||
public class ParameterDiffEngine {
|
public class ParameterDiffEngine {
|
||||||
private final NestedDtoFieldParser nestedDtoFieldParser;
|
private final NestedDtoFieldParser nestedDtoFieldParser;
|
||||||
@@ -28,38 +36,143 @@ public class ParameterDiffEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<ParameterChange> diff(EndpointSnapshot oldSnap, EndpointSnapshot newSnap) throws IOException {
|
public List<ParameterChange> diff(EndpointSnapshot oldSnap, EndpointSnapshot newSnap) throws IOException {
|
||||||
Map<String, MethodParameterSnapshot> oldParams = toParamMap(oldSnap);
|
List<ParameterChange> changes = new ArrayList<>();
|
||||||
Map<String, MethodParameterSnapshot> newParams = toParamMap(newSnap);
|
changes.addAll(diffBodyParams(oldSnap, newSnap));
|
||||||
|
changes.addAll(diffBindingParams(oldSnap, newSnap));
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ParameterChange> diffBodyParams(EndpointSnapshot oldSnap, EndpointSnapshot newSnap)
|
||||||
|
throws IOException {
|
||||||
|
Map<String, MethodParameterSnapshot> oldParams = filterBySource(oldSnap, "body");
|
||||||
|
Map<String, MethodParameterSnapshot> newParams = filterBySource(newSnap, "body");
|
||||||
List<ParameterChange> changes = new ArrayList<>();
|
List<ParameterChange> changes = new ArrayList<>();
|
||||||
|
|
||||||
for (Map.Entry<String, MethodParameterSnapshot> entry : newParams.entrySet()) {
|
for (Map.Entry<String, MethodParameterSnapshot> entry : newParams.entrySet()) {
|
||||||
MethodParameterSnapshot oldParam = oldParams.get(entry.getKey());
|
MethodParameterSnapshot oldParam = oldParams.get(entry.getKey());
|
||||||
MethodParameterSnapshot newParam = entry.getValue();
|
MethodParameterSnapshot newParam = entry.getValue();
|
||||||
if (oldParam == null) {
|
if (oldParam == null) {
|
||||||
changes.addAll(addedChanges(newParam));
|
changes.addAll(addedBodyChanges(newParam));
|
||||||
} else if ("body".equals(newParam.getSource())) {
|
} else {
|
||||||
changes.addAll(diffBodyDto(oldParam, newParam));
|
changes.addAll(diffBodyDto(oldParam, newParam));
|
||||||
} else if (!oldParam.getType().equals(newParam.getType())) {
|
|
||||||
changes.add(ParameterChange.modified(
|
|
||||||
newParam.getName(),
|
|
||||||
newParam.getType(),
|
|
||||||
newParam.getDescription(),
|
|
||||||
oldParam.getType() + " → " + newParam.getType(),
|
|
||||||
newParam.getSource(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Map.Entry<String, MethodParameterSnapshot> entry : oldParams.entrySet()) {
|
for (Map.Entry<String, MethodParameterSnapshot> entry : oldParams.entrySet()) {
|
||||||
if (!newParams.containsKey(entry.getKey())) {
|
if (!newParams.containsKey(entry.getKey())) {
|
||||||
changes.addAll(removedChanges(entry.getValue()));
|
changes.addAll(removedBodyChanges(entry.getValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<ParameterChange> diffBindingParams(EndpointSnapshot oldSnap, EndpointSnapshot newSnap) {
|
||||||
|
Map<String, MethodParameterSnapshot> oldParams = filterBindingParams(oldSnap);
|
||||||
|
Map<String, MethodParameterSnapshot> newParams = filterBindingParams(newSnap);
|
||||||
|
List<ParameterChange> changes = new ArrayList<>();
|
||||||
|
|
||||||
|
List<MethodParameterSnapshot> unmatchedOld = new ArrayList<>();
|
||||||
|
List<MethodParameterSnapshot> unmatchedNew = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, MethodParameterSnapshot> entry : newParams.entrySet()) {
|
||||||
|
MethodParameterSnapshot oldParam = oldParams.get(entry.getKey());
|
||||||
|
MethodParameterSnapshot newParam = entry.getValue();
|
||||||
|
if (oldParam == null) {
|
||||||
|
unmatchedNew.add(newParam);
|
||||||
|
} else if (!oldParam.getType().equals(newParam.getType())) {
|
||||||
|
changes.add(ParameterChange.modified(
|
||||||
|
newParam.displayName(),
|
||||||
|
newParam.getType(),
|
||||||
|
newParam.getDescription(),
|
||||||
|
oldParam.getType() + " → " + newParam.getType(),
|
||||||
|
newParam.getSource(),
|
||||||
|
null, null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, MethodParameterSnapshot> entry : oldParams.entrySet()) {
|
||||||
|
if (!newParams.containsKey(entry.getKey())) {
|
||||||
|
unmatchedOld.add(entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pairRenamedBindingParams(unmatchedOld, unmatchedNew, changes);
|
||||||
|
|
||||||
|
for (MethodParameterSnapshot removed : unmatchedOld) {
|
||||||
|
changes.add(ParameterChange.removed(
|
||||||
|
removed.displayName(), removed.getType(), removed.getDescription(),
|
||||||
|
removed.getSource(), null, null, null));
|
||||||
|
}
|
||||||
|
for (MethodParameterSnapshot added : unmatchedNew) {
|
||||||
|
changes.add(ParameterChange.added(
|
||||||
|
added.displayName(), added.getType(), added.getDescription(),
|
||||||
|
added.getSource(), null, null, null));
|
||||||
|
}
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 形参名+类型相同,仅绑定名变化 → 重命名 */
|
||||||
|
private void pairRenamedBindingParams(List<MethodParameterSnapshot> unmatchedOld,
|
||||||
|
List<MethodParameterSnapshot> unmatchedNew,
|
||||||
|
List<ParameterChange> changes) {
|
||||||
|
Set<MethodParameterSnapshot> pairedOld = new HashSet<>();
|
||||||
|
Set<MethodParameterSnapshot> pairedNew = new HashSet<>();
|
||||||
|
|
||||||
|
for (MethodParameterSnapshot oldParam : unmatchedOld) {
|
||||||
|
if (pairedOld.contains(oldParam)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (MethodParameterSnapshot newParam : unmatchedNew) {
|
||||||
|
if (pairedNew.contains(newParam)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!oldParam.getSource().equals(newParam.getSource())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!oldParam.getName().equals(newParam.getName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!oldParam.getType().equals(newParam.getType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
changes.add(ParameterChange.renamed(
|
||||||
|
oldParam.getBindingName(),
|
||||||
|
newParam.getBindingName(),
|
||||||
|
newParam.getType(),
|
||||||
|
newParam.getDescription(),
|
||||||
|
newParam.getSource(),
|
||||||
|
null, null, null));
|
||||||
|
pairedOld.add(oldParam);
|
||||||
|
pairedNew.add(newParam);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unmatchedOld.removeIf(pairedOld::contains);
|
||||||
|
unmatchedNew.removeIf(pairedNew::contains);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, MethodParameterSnapshot> filterBindingParams(EndpointSnapshot snap) {
|
||||||
|
Map<String, MethodParameterSnapshot> map = new LinkedHashMap<>();
|
||||||
|
for (MethodParameterSnapshot p : snap.getParameters()) {
|
||||||
|
if (isBindingParam(p)) {
|
||||||
|
map.put(p.identityKey(), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, MethodParameterSnapshot> filterBySource(EndpointSnapshot snap, String source) {
|
||||||
|
Map<String, MethodParameterSnapshot> map = new LinkedHashMap<>();
|
||||||
|
for (MethodParameterSnapshot p : snap.getParameters()) {
|
||||||
|
if (source.equals(p.getSource())) {
|
||||||
|
map.put(p.identityKey(), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBindingParam(MethodParameterSnapshot param) {
|
||||||
|
return "path".equals(param.getSource()) || "query".equals(param.getSource());
|
||||||
|
}
|
||||||
|
|
||||||
private List<ParameterChange> diffBodyDto(MethodParameterSnapshot oldParam,
|
private List<ParameterChange> diffBodyDto(MethodParameterSnapshot oldParam,
|
||||||
MethodParameterSnapshot newParam) throws IOException {
|
MethodParameterSnapshot newParam) throws IOException {
|
||||||
List<NestedFieldInfo> oldFields = nestedDtoFieldParser.parseNestedFields(oldParam.getDtoClassName());
|
List<NestedFieldInfo> oldFields = nestedDtoFieldParser.parseNestedFields(oldParam.getDtoClassName());
|
||||||
@@ -91,45 +204,29 @@ public class ParameterDiffEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ParameterChange> addedChanges(MethodParameterSnapshot param) throws IOException {
|
private List<ParameterChange> addedBodyChanges(MethodParameterSnapshot param) throws IOException {
|
||||||
if ("body".equals(param.getSource())) {
|
List<ParameterChange> list = new ArrayList<>();
|
||||||
List<ParameterChange> list = new ArrayList<>();
|
for (NestedFieldInfo field : nestedDtoFieldParser.parseNestedFields(param.getDtoClassName())) {
|
||||||
for (NestedFieldInfo field : nestedDtoFieldParser.parseNestedFields(param.getDtoClassName())) {
|
list.add(ParameterChange.added(field.getPath(), field.getType(), field.getDescription(),
|
||||||
list.add(ParameterChange.added(field.getPath(), field.getType(), field.getDescription(),
|
"body", param.getName(), param.getDtoClassName(), field.getPath()));
|
||||||
"body", param.getName(), param.getDtoClassName(), field.getPath()));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
return List.of(ParameterChange.added(param.getName(), param.getType(), param.getDescription(),
|
|
||||||
param.getSource(), null, null, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ParameterChange> removedChanges(MethodParameterSnapshot param) throws IOException {
|
|
||||||
if ("body".equals(param.getSource())) {
|
|
||||||
List<ParameterChange> list = new ArrayList<>();
|
|
||||||
for (NestedFieldInfo field : nestedDtoFieldParser.parseNestedFields(param.getDtoClassName())) {
|
|
||||||
list.add(ParameterChange.removed(field.getPath(), field.getType(), field.getDescription(),
|
|
||||||
"body", param.getName(), param.getDtoClassName(), field.getPath()));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
return List.of(ParameterChange.removed(param.getName(), param.getType(), param.getDescription(),
|
|
||||||
param.getSource(), null, null, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, MethodParameterSnapshot> toParamMap(EndpointSnapshot snap) {
|
|
||||||
Map<String, MethodParameterSnapshot> map = new LinkedHashMap<>();
|
|
||||||
for (MethodParameterSnapshot p : snap.getParameters()) {
|
|
||||||
map.put(p.identityKey(), p);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<FieldInfo> toFieldInfo(List<NestedFieldInfo> nested) {
|
|
||||||
List<FieldInfo> list = new ArrayList<>();
|
|
||||||
for (NestedFieldInfo info : nested) {
|
|
||||||
list.add(new FieldInfo(info.getPath(), info.getType(), info.getDescription()));
|
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<ParameterChange> removedBodyChanges(MethodParameterSnapshot param) throws IOException {
|
||||||
|
List<ParameterChange> list = new ArrayList<>();
|
||||||
|
for (NestedFieldInfo field : nestedDtoFieldParser.parseNestedFields(param.getDtoClassName())) {
|
||||||
|
list.add(ParameterChange.removed(field.getPath(), field.getType(), field.getDescription(),
|
||||||
|
"body", param.getName(), param.getDtoClassName(), field.getPath()));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FieldInfo> toFieldInfo(List<NestedFieldInfo> nested) {
|
||||||
|
List<FieldInfo> result = new ArrayList<>();
|
||||||
|
for (NestedFieldInfo info : nested) {
|
||||||
|
result.add(new FieldInfo(info.getPath(), info.getType(), info.getDescription()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.codechecker.api.model;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单个 HTTP/Feign 接口快照。
|
* 单个 HTTP/Feign 接口快照。
|
||||||
@@ -32,9 +31,13 @@ public class EndpointSnapshot {
|
|||||||
|
|
||||||
public static String buildFingerprint(String sourceFile, String methodName,
|
public static String buildFingerprint(String sourceFile, String methodName,
|
||||||
List<MethodParameterSnapshot> parameters) {
|
List<MethodParameterSnapshot> parameters) {
|
||||||
String sig = parameters.stream()
|
StringBuilder sig = new StringBuilder();
|
||||||
.map(p -> p.getType())
|
for (int i = 0; i < parameters.size(); i++) {
|
||||||
.collect(Collectors.joining(","));
|
if (i > 0) {
|
||||||
|
sig.append(',');
|
||||||
|
}
|
||||||
|
sig.append(parameters.get(i).fingerprintSlotKey(i));
|
||||||
|
}
|
||||||
return sourceFile + "#" + methodName + "#" + sig;
|
return sourceFile + "#" + methodName + "#" + sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,17 @@ package com.codechecker.api.model;
|
|||||||
*/
|
*/
|
||||||
public class MethodParameterSnapshot {
|
public class MethodParameterSnapshot {
|
||||||
private final String name;
|
private final String name;
|
||||||
|
private final String bindingName;
|
||||||
private final String type;
|
private final String type;
|
||||||
private final String source;
|
private final String source;
|
||||||
private final boolean required;
|
private final boolean required;
|
||||||
private final String description;
|
private final String description;
|
||||||
private final String dtoClassName;
|
private final String dtoClassName;
|
||||||
|
|
||||||
public MethodParameterSnapshot(String name, String type, String source,
|
public MethodParameterSnapshot(String name, String bindingName, String type, String source,
|
||||||
boolean required, String description, String dtoClassName) {
|
boolean required, String description, String dtoClassName) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.bindingName = bindingName == null || bindingName.isBlank() ? name : bindingName;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.required = required;
|
this.required = required;
|
||||||
@@ -25,6 +27,11 @@ public class MethodParameterSnapshot {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 对外绑定名(@PathVariable / @RequestParam 的 value/name,缺省为形参名) */
|
||||||
|
public String getBindingName() {
|
||||||
|
return bindingName;
|
||||||
|
}
|
||||||
|
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -46,7 +53,24 @@ public class MethodParameterSnapshot {
|
|||||||
return dtoClassName;
|
return dtoClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 接口指纹槽位:序号 + 参数来源,不含类型与绑定名,避免类型/绑定变更导致误配对 */
|
||||||
|
public String fingerprintSlotKey(int index) {
|
||||||
|
return index + ":" + source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** path/query 按绑定名匹配,避免仅 Java 形参重命名误报 */
|
||||||
public String identityKey() {
|
public String identityKey() {
|
||||||
|
if ("path".equals(source) || "query".equals(source)) {
|
||||||
|
return source + ":" + bindingName;
|
||||||
|
}
|
||||||
return source + ":" + name;
|
return source + ":" + name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 通知展示名:path/query 展示绑定名 */
|
||||||
|
public String displayName() {
|
||||||
|
if ("path".equals(source) || "query".equals(source)) {
|
||||||
|
return bindingName;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,6 +205,9 @@ public class ApiChangeNotifier {
|
|||||||
case RENAMED:
|
case RENAMED:
|
||||||
tag = MarkdownStyles.colorWarning("[重命名]");
|
tag = MarkdownStyles.colorWarning("[重命名]");
|
||||||
break;
|
break;
|
||||||
|
case MODIFIED:
|
||||||
|
tag = MarkdownStyles.colorWarning(change.isBodyField() ? "[修改]" : "[类型变更]");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
tag = MarkdownStyles.colorWarning("[修改]");
|
tag = MarkdownStyles.colorWarning("[修改]");
|
||||||
break;
|
break;
|
||||||
@@ -214,19 +217,23 @@ public class ApiChangeNotifier {
|
|||||||
? MarkdownStyles.colorComment("(无说明)")
|
? MarkdownStyles.colorComment("(无说明)")
|
||||||
: MarkdownStyles.colorComment(change.getDescription());
|
: MarkdownStyles.colorComment(change.getDescription());
|
||||||
StringBuilder line = new StringBuilder();
|
StringBuilder line = new StringBuilder();
|
||||||
line.append(MarkdownStyles.quoteLine(tag + " " + name + " 说明: " + desc));
|
|
||||||
if (change.getChangeType() == ParameterChange.ChangeType.RENAMED) {
|
if (change.getChangeType() == ParameterChange.ChangeType.RENAMED) {
|
||||||
line = new StringBuilder();
|
line.append(tag).append(" ")
|
||||||
line.append(MarkdownStyles.quoteLine(tag + " "
|
.append(MarkdownStyles.colorComment(MarkdownStyles.safe(change.getOldName()))).append(" → ")
|
||||||
+ MarkdownStyles.colorComment(MarkdownStyles.safe(change.getOldName())) + " → "
|
.append(MarkdownStyles.colorInfo(MarkdownStyles.safe(change.getParamName())))
|
||||||
+ MarkdownStyles.colorInfo(MarkdownStyles.safe(change.getParamName()))
|
.append(" 说明: ").append(desc);
|
||||||
+ " 说明: " + desc));
|
} else {
|
||||||
|
line.append(tag).append(" ").append(name).append(" 说明: ").append(desc);
|
||||||
}
|
}
|
||||||
|
appendParameterType(line, change);
|
||||||
|
return MarkdownStyles.quoteLine(line.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendParameterType(StringBuilder line, ParameterChange change) {
|
||||||
String typePart = resolveTypePart(change);
|
String typePart = resolveTypePart(change);
|
||||||
if (!typePart.isBlank()) {
|
if (!typePart.isBlank()) {
|
||||||
return line + "\n" + MarkdownStyles.quoteKv("类型", typePart);
|
line.append(" 类型: ").append(typePart);
|
||||||
}
|
}
|
||||||
return line.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatUriWithMethod(String httpMethod, String uri, boolean isNew) {
|
private String formatUriWithMethod(String httpMethod, String uri, boolean isNew) {
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ public class EndpointSnapshotParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<MethodParameterSnapshot> extractParameters(MethodDeclaration method) {
|
private List<MethodParameterSnapshot> extractParameters(MethodDeclaration method) {
|
||||||
|
Map<String, String> paramDescriptions = MethodParamJavadocExtractor.extract(method);
|
||||||
List<MethodParameterSnapshot> params = new ArrayList<>();
|
List<MethodParameterSnapshot> params = new ArrayList<>();
|
||||||
for (Parameter parameter : method.getParameters()) {
|
for (Parameter parameter : method.getParameters()) {
|
||||||
String typeName = TypeNameUtils.typeToString(parameter.getType());
|
String typeName = TypeNameUtils.typeToString(parameter.getType());
|
||||||
@@ -114,20 +115,43 @@ public class EndpointSnapshotParser {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String source = resolveParamSource(parameter);
|
String source = resolveParamSource(parameter);
|
||||||
|
String paramName = parameter.getNameAsString();
|
||||||
|
String bindingName = resolveBindingName(parameter, source, paramName);
|
||||||
boolean required = resolveRequired(parameter, source);
|
boolean required = resolveRequired(parameter, source);
|
||||||
String dtoName = "body".equals(source) ? simple : "";
|
String dtoName = "body".equals(source) ? simple : "";
|
||||||
|
String description = paramDescriptions.getOrDefault(paramName, "");
|
||||||
params.add(new MethodParameterSnapshot(
|
params.add(new MethodParameterSnapshot(
|
||||||
parameter.getNameAsString(),
|
paramName,
|
||||||
|
bindingName,
|
||||||
typeName,
|
typeName,
|
||||||
source,
|
source,
|
||||||
required,
|
required,
|
||||||
"",
|
description,
|
||||||
dtoName
|
dtoName
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveBindingName(Parameter parameter, String source, String paramName) {
|
||||||
|
if (!"path".equals(source) && !"query".equals(source)) {
|
||||||
|
return paramName;
|
||||||
|
}
|
||||||
|
String annName = "path".equals(source) ? "PathVariable" : "RequestParam";
|
||||||
|
for (AnnotationExpr ann : parameter.getAnnotations()) {
|
||||||
|
if (!annName.equals(ann.getNameAsString())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<String> bindings = readStringArray(ann, "value", "name");
|
||||||
|
for (String binding : bindings) {
|
||||||
|
if (binding != null && !binding.isBlank()) {
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paramName;
|
||||||
|
}
|
||||||
|
|
||||||
private String resolveParamSource(Parameter parameter) {
|
private String resolveParamSource(Parameter parameter) {
|
||||||
for (AnnotationExpr ann : parameter.getAnnotations()) {
|
for (AnnotationExpr ann : parameter.getAnnotations()) {
|
||||||
String name = ann.getNameAsString();
|
String name = ann.getNameAsString();
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.codechecker.api.parser;
|
||||||
|
|
||||||
|
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||||
|
import com.github.javaparser.ast.comments.JavadocComment;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从方法 Javadoc 的 @param 标签提取形参说明(按 Java 形参名匹配)。
|
||||||
|
*/
|
||||||
|
public final class MethodParamJavadocExtractor {
|
||||||
|
private static final Pattern PARAM_TAG = Pattern.compile(
|
||||||
|
"@param\\s+(\\w+)\\s+(.+?)(?=\\s*@param\\s+|\\s*@return\\s+|\\s*@throws\\s+|\\s*@see\\s+|\\s*\\*/|$)",
|
||||||
|
Pattern.DOTALL);
|
||||||
|
|
||||||
|
private MethodParamJavadocExtractor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> extract(MethodDeclaration method) {
|
||||||
|
Map<String, String> descriptions = new HashMap<>();
|
||||||
|
if (method == null) {
|
||||||
|
return descriptions;
|
||||||
|
}
|
||||||
|
Optional<JavadocComment> javadoc = method.getJavadocComment();
|
||||||
|
if (javadoc.isEmpty()) {
|
||||||
|
return descriptions;
|
||||||
|
}
|
||||||
|
String raw = javadoc.get().getContent();
|
||||||
|
if (raw == null || raw.isBlank()) {
|
||||||
|
return descriptions;
|
||||||
|
}
|
||||||
|
String normalized = raw.replace('\r', '\n');
|
||||||
|
Matcher matcher = PARAM_TAG.matcher(normalized);
|
||||||
|
while (matcher.find()) {
|
||||||
|
String paramName = matcher.group(1).trim();
|
||||||
|
String desc = cleanDescription(matcher.group(2));
|
||||||
|
if (!paramName.isBlank()) {
|
||||||
|
descriptions.put(paramName, desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return descriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String cleanDescription(String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String line : text.split("\n")) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (trimmed.startsWith("*")) {
|
||||||
|
trimmed = trimmed.substring(1).trim();
|
||||||
|
}
|
||||||
|
if (!trimmed.isEmpty()) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user