This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package com.aicheck;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 单个 Controller 接口端点的模型,包含 URI、HTTP 方法及参数列表。
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class ApiEndpoint {
|
||||
|
||||
/** HTTP 方法:GET / POST / PUT / DELETE / PATCH */
|
||||
private String httpMethod;
|
||||
|
||||
/** 完整 URI 路径,如 /api/users/{id} */
|
||||
private String uri;
|
||||
|
||||
/** 所属 Controller 类名 */
|
||||
private String controllerClass;
|
||||
|
||||
/** Java 方法名 */
|
||||
private String methodName;
|
||||
|
||||
/** 源文件相对路径 */
|
||||
private String sourceFile;
|
||||
|
||||
/** 接口参数列表 */
|
||||
private List<ApiParameter> parameters = new ArrayList<>();
|
||||
|
||||
public String getHttpMethod() {
|
||||
return httpMethod;
|
||||
}
|
||||
|
||||
public void setHttpMethod(String httpMethod) {
|
||||
this.httpMethod = httpMethod;
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public void setUri(String uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public String getControllerClass() {
|
||||
return controllerClass;
|
||||
}
|
||||
|
||||
public void setControllerClass(String controllerClass) {
|
||||
this.controllerClass = controllerClass;
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
public void setMethodName(String methodName) {
|
||||
this.methodName = methodName;
|
||||
}
|
||||
|
||||
public String getSourceFile() {
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
public void setSourceFile(String sourceFile) {
|
||||
this.sourceFile = sourceFile;
|
||||
}
|
||||
|
||||
public List<ApiParameter> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public void setParameters(List<ApiParameter> parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一标识,用于跨版本比对接口是否为同一个。
|
||||
* 格式:HTTP_METHOD + 空格 + URI
|
||||
*/
|
||||
public String getEndpointKey() {
|
||||
return httpMethod + " " + uri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.aicheck;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
/**
|
||||
* 单个接口参数的模型,对应 Controller 方法上的一个入参。
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class ApiParameter {
|
||||
|
||||
/** 参数名称(@RequestParam / @PathVariable 的 value,或字段名) */
|
||||
private String name;
|
||||
|
||||
/** Java 类型,如 String、Long、Boolean */
|
||||
private String type;
|
||||
|
||||
/** 是否必填(来自 required 属性或 @NotNull 等,默认 true) */
|
||||
private boolean required = true;
|
||||
|
||||
/** 参数来源:query / path / body / header / form */
|
||||
private String source;
|
||||
|
||||
/** 参数说明(来自 @ApiParam、@Parameter 等注解的 description) */
|
||||
private String description;
|
||||
|
||||
public ApiParameter() {
|
||||
}
|
||||
|
||||
public ApiParameter(String name, String type, boolean required, String source) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.required = required;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public void setRequired(boolean required) {
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
package com.aicheck;
|
||||
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
|
||||
import com.github.javaparser.ast.body.FieldDeclaration;
|
||||
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.body.Parameter;
|
||||
import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
import com.github.javaparser.ast.expr.MemberValuePair;
|
||||
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
|
||||
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
|
||||
import com.github.javaparser.ast.type.ClassOrInterfaceType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 基于 JavaParser 的 Spring Controller AST 解析器。
|
||||
* 扫描指定目录下的 Java 文件,提取带 @RestController / @Controller 注解类中的接口定义。
|
||||
*/
|
||||
public class ControllerAstParser {
|
||||
|
||||
/** Spring 映射注解 -> HTTP 方法 */
|
||||
private static final Set<String> MAPPING_ANNOTATIONS = Set.of(
|
||||
"GetMapping", "PostMapping", "PutMapping", "DeleteMapping", "PatchMapping", "RequestMapping"
|
||||
);
|
||||
|
||||
/** 标识 Controller 的类级别注解 */
|
||||
private static final Set<String> CONTROLLER_ANNOTATIONS = Set.of("RestController", "Controller");
|
||||
|
||||
/**
|
||||
* 解析目录下所有 Java 文件中的 Controller 接口。
|
||||
*
|
||||
* @param rootDir 项目根目录或源码目录
|
||||
* @return 解析出的所有 API 端点列表
|
||||
*/
|
||||
public List<ApiEndpoint> parseDirectory(Path rootDir) throws IOException {
|
||||
List<ApiEndpoint> endpoints = new ArrayList<>();
|
||||
|
||||
if (!Files.exists(rootDir)) {
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
try (Stream<Path> paths = Files.walk(rootDir)) {
|
||||
List<Path> javaFiles = paths
|
||||
.filter(p -> p.toString().endsWith(".java"))
|
||||
.filter(p -> p.toString().contains("Controller"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Path javaFile : javaFiles) {
|
||||
endpoints.addAll(parseFile(javaFile, rootDir));
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个 Java 源文件。
|
||||
*
|
||||
* @param javaFile 源文件路径
|
||||
* @param rootDir 根目录,用于计算相对路径
|
||||
*/
|
||||
public List<ApiEndpoint> parseFile(Path javaFile, Path rootDir) throws IOException {
|
||||
List<ApiEndpoint> endpoints = new ArrayList<>();
|
||||
String source = Files.readString(javaFile);
|
||||
CompilationUnit cu = StaticJavaParser.parse(source);
|
||||
|
||||
String relativePath = rootDir.relativize(javaFile).toString().replace("\\", "/");
|
||||
|
||||
for (ClassOrInterfaceDeclaration clazz : cu.findAll(ClassOrInterfaceDeclaration.class)) {
|
||||
if (!isController(clazz)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String classBasePath = extractClassBasePath(clazz);
|
||||
|
||||
for (MethodDeclaration method : clazz.getMethods()) {
|
||||
Optional<ApiEndpoint> endpointOpt = parseMethod(method, clazz, classBasePath, relativePath, rootDir);
|
||||
endpointOpt.ifPresent(endpoints::add);
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断类是否为 Spring Controller(含 @RestController 或 @Controller)。
|
||||
*/
|
||||
private boolean isController(ClassOrInterfaceDeclaration clazz) {
|
||||
return clazz.getAnnotations().stream()
|
||||
.anyMatch(a -> CONTROLLER_ANNOTATIONS.contains(getSimpleAnnotationName(a)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取类级别 @RequestMapping 的基础路径。
|
||||
*/
|
||||
private String extractClassBasePath(ClassOrInterfaceDeclaration clazz) {
|
||||
for (AnnotationExpr annotation : clazz.getAnnotations()) {
|
||||
if ("RequestMapping".equals(getSimpleAnnotationName(annotation))) {
|
||||
return normalizePath(extractAnnotationStringValue(annotation, "value", "path"));
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个 Controller 方法,若不含映射注解则返回 empty。
|
||||
*/
|
||||
private Optional<ApiEndpoint> parseMethod(
|
||||
MethodDeclaration method,
|
||||
ClassOrInterfaceDeclaration clazz,
|
||||
String classBasePath,
|
||||
String relativePath,
|
||||
Path rootDir) {
|
||||
|
||||
for (AnnotationExpr annotation : method.getAnnotations()) {
|
||||
String annName = getSimpleAnnotationName(annotation);
|
||||
if (!MAPPING_ANNOTATIONS.contains(annName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ApiEndpoint endpoint = new ApiEndpoint();
|
||||
endpoint.setControllerClass(clazz.getNameAsString());
|
||||
endpoint.setMethodName(method.getNameAsString());
|
||||
endpoint.setSourceFile(relativePath);
|
||||
endpoint.setHttpMethod(resolveHttpMethod(annName, annotation));
|
||||
endpoint.setUri(joinPaths(classBasePath, normalizePath(extractAnnotationStringValue(annotation, "value", "path"))));
|
||||
endpoint.setParameters(extractParameters(method, rootDir));
|
||||
|
||||
return Optional.of(endpoint);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从映射注解推断 HTTP 方法。
|
||||
*/
|
||||
private String resolveHttpMethod(String annName, AnnotationExpr annotation) {
|
||||
switch (annName) {
|
||||
case "GetMapping":
|
||||
return "GET";
|
||||
case "PostMapping":
|
||||
return "POST";
|
||||
case "PutMapping":
|
||||
return "PUT";
|
||||
case "DeleteMapping":
|
||||
return "DELETE";
|
||||
case "PatchMapping":
|
||||
return "PATCH";
|
||||
case "RequestMapping":
|
||||
String method = extractAnnotationStringValue(annotation, "method");
|
||||
if (!method.isEmpty()) {
|
||||
return method.replace("RequestMethod.", "").toUpperCase();
|
||||
}
|
||||
return "GET";
|
||||
default:
|
||||
return "GET";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取方法的所有入参(含 @RequestBody DTO 字段展开)。
|
||||
*/
|
||||
private List<ApiParameter> extractParameters(MethodDeclaration method, Path rootDir) {
|
||||
List<ApiParameter> params = new ArrayList<>();
|
||||
|
||||
for (Parameter param : method.getParameters()) {
|
||||
String paramType = param.getType().asString();
|
||||
|
||||
// @RequestBody:尝试展开 DTO 类字段
|
||||
if (hasAnnotation(param, "RequestBody")) {
|
||||
params.addAll(expandDtoFields(paramType, rootDir, "body"));
|
||||
continue;
|
||||
}
|
||||
|
||||
ApiParameter apiParam = new ApiParameter();
|
||||
apiParam.setType(paramType);
|
||||
apiParam.setSource(resolveParameterSource(param));
|
||||
apiParam.setName(resolveParameterName(param));
|
||||
apiParam.setRequired(resolveRequired(param));
|
||||
apiParam.setDescription(extractParamDescription(param));
|
||||
params.add(apiParam);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开 @RequestBody DTO 类的字段为独立参数(便于对比字段增删改)。
|
||||
*/
|
||||
private List<ApiParameter> expandDtoFields(String typeName, Path rootDir, String source) {
|
||||
List<ApiParameter> fields = new ArrayList<>();
|
||||
Optional<Path> dtoFile = findJavaFileBySimpleName(typeName, rootDir);
|
||||
|
||||
if (dtoFile.isEmpty()) {
|
||||
// 找不到 DTO 源文件时,保留整体类型
|
||||
ApiParameter body = new ApiParameter();
|
||||
body.setName(typeName);
|
||||
body.setType(typeName);
|
||||
body.setSource(source);
|
||||
body.setRequired(true);
|
||||
fields.add(body);
|
||||
return fields;
|
||||
}
|
||||
|
||||
try {
|
||||
CompilationUnit cu = StaticJavaParser.parse(dtoFile.get());
|
||||
for (FieldDeclaration field : cu.findAll(FieldDeclaration.class)) {
|
||||
if (field.isStatic()) {
|
||||
continue;
|
||||
}
|
||||
for (var variable : field.getVariables()) {
|
||||
ApiParameter fp = new ApiParameter();
|
||||
fp.setName(variable.getNameAsString());
|
||||
fp.setType(field.getElementType().asString());
|
||||
fp.setSource(source);
|
||||
fp.setRequired(!hasAnnotation(field, "Nullable"));
|
||||
fields.add(fp);
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
// 解析失败时退化为整体 body 参数
|
||||
ApiParameter body = new ApiParameter();
|
||||
body.setName(typeName);
|
||||
body.setType(typeName);
|
||||
body.setSource(source);
|
||||
fields.add(body);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在源码目录中按简单类名查找 Java 文件。
|
||||
*/
|
||||
private Optional<Path> findJavaFileBySimpleName(String typeName, Path rootDir) {
|
||||
String simpleName = typeName.contains(".") ? typeName.substring(typeName.lastIndexOf('.') + 1) : typeName;
|
||||
simpleName = simpleName.replace(">", "").replace("<", "").trim();
|
||||
|
||||
try (Stream<Path> paths = Files.walk(rootDir)) {
|
||||
final String target = simpleName;
|
||||
return paths
|
||||
.filter(p -> p.getFileName().toString().equals(target + ".java"))
|
||||
.findFirst();
|
||||
} catch (IOException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数来源:query / path / header / form / body。
|
||||
*/
|
||||
private String resolveParameterSource(Parameter param) {
|
||||
if (hasAnnotation(param, "PathVariable")) return "path";
|
||||
if (hasAnnotation(param, "RequestHeader")) return "header";
|
||||
if (hasAnnotation(param, "RequestPart")) return "form";
|
||||
if (hasAnnotation(param, "ModelAttribute")) return "form";
|
||||
return "query";
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析参数名称:优先取注解 value/name,否则用变量名。
|
||||
*/
|
||||
private String resolveParameterName(Parameter param) {
|
||||
for (String ann : Arrays.asList("RequestParam", "PathVariable", "RequestHeader", "RequestPart")) {
|
||||
if (hasAnnotation(param, ann)) {
|
||||
Optional<AnnotationExpr> opt = param.getAnnotationByName(ann);
|
||||
if (opt.isPresent()) {
|
||||
String val = extractAnnotationStringValue(opt.get(), "value", "name");
|
||||
if (!val.isEmpty()) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return param.getNameAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析参数是否必填。
|
||||
*/
|
||||
private boolean resolveRequired(Parameter param) {
|
||||
if (hasAnnotation(param, "RequestParam")) {
|
||||
Optional<AnnotationExpr> opt = param.getAnnotationByName("RequestParam");
|
||||
if (opt.isPresent()) {
|
||||
String required = extractAnnotationMemberValue(opt.get(), "required");
|
||||
if ("false".equalsIgnoreCase(required)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (param.getType() instanceof ClassOrInterfaceType) {
|
||||
ClassOrInterfaceType cit = (ClassOrInterfaceType) param.getType();
|
||||
if ("Optional".equals(cit.getNameAsString())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !hasAnnotation(param, "Nullable");
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取 @ApiParam / @Parameter 的 description。
|
||||
*/
|
||||
private String extractParamDescription(Parameter param) {
|
||||
for (String ann : Arrays.asList("ApiParam", "Parameter", "Schema")) {
|
||||
Optional<AnnotationExpr> opt = param.getAnnotationByName(ann);
|
||||
if (opt.isPresent()) {
|
||||
return extractAnnotationStringValue(opt.get(), "description", "value");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasAnnotation(Object node, String simpleName) {
|
||||
if (node instanceof Parameter) {
|
||||
Parameter p = (Parameter) node;
|
||||
return p.getAnnotationByName(simpleName).isPresent();
|
||||
}
|
||||
if (node instanceof FieldDeclaration) {
|
||||
FieldDeclaration f = (FieldDeclaration) node;
|
||||
return f.getAnnotationByName(simpleName).isPresent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解的简单名称(去掉包名)。
|
||||
*/
|
||||
private String getSimpleAnnotationName(AnnotationExpr annotation) {
|
||||
String name = annotation.getNameAsString();
|
||||
int dot = name.lastIndexOf('.');
|
||||
return dot >= 0 ? name.substring(dot + 1) : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从注解中提取字符串属性,支持 value/path/name 等多个候选 key。
|
||||
*/
|
||||
private String extractAnnotationStringValue(AnnotationExpr annotation, String... keys) {
|
||||
Set<String> keySet = new HashSet<>(Arrays.asList(keys));
|
||||
|
||||
if (annotation instanceof SingleMemberAnnotationExpr) {
|
||||
SingleMemberAnnotationExpr single = (SingleMemberAnnotationExpr) annotation;
|
||||
return stripQuotes(single.getMemberValue().toString());
|
||||
}
|
||||
|
||||
if (annotation instanceof NormalAnnotationExpr) {
|
||||
NormalAnnotationExpr normal = (NormalAnnotationExpr) annotation;
|
||||
for (MemberValuePair pair : normal.getPairs()) {
|
||||
if (keySet.contains(pair.getNameAsString())) {
|
||||
return stripQuotes(pair.getValue().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取注解成员的原始字符串值。
|
||||
*/
|
||||
private String extractAnnotationMemberValue(AnnotationExpr annotation, String key) {
|
||||
if (annotation instanceof NormalAnnotationExpr) {
|
||||
NormalAnnotationExpr normal = (NormalAnnotationExpr) annotation;
|
||||
for (MemberValuePair pair : normal.getPairs()) {
|
||||
if (key.equals(pair.getNameAsString())) {
|
||||
return stripQuotes(pair.getValue().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String stripQuotes(String value) {
|
||||
return value.replace("\"", "").replace("'", "").trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接类级别与方法级别的路径。
|
||||
*/
|
||||
private String joinPaths(String base, String methodPath) {
|
||||
String b = normalizePath(base);
|
||||
String m = normalizePath(methodPath);
|
||||
|
||||
if (b.isEmpty()) return m.isEmpty() ? "/" : m;
|
||||
if (m.isEmpty()) return b;
|
||||
|
||||
if (b.endsWith("/") && m.startsWith("/")) {
|
||||
return b + m.substring(1);
|
||||
}
|
||||
if (!b.endsWith("/") && !m.startsWith("/")) {
|
||||
return b + "/" + m;
|
||||
}
|
||||
return b + m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化路径:确保以 / 开头,去除多余斜杠。
|
||||
*/
|
||||
private String normalizePath(String path) {
|
||||
if (path == null || path.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
path = path.trim();
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
return path.replaceAll("/+", "/");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.aicheck;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Java AST 解析器命令行入口。
|
||||
* 用法:java -jar controller-parser.jar <源码目录> [输出JSON文件路径]
|
||||
*
|
||||
* 示例:
|
||||
* java -jar controller-parser.jar ./src/main/java ./endpoints.json
|
||||
*/
|
||||
public class ControllerParserMain {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper()
|
||||
.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
|
||||
/**
|
||||
* 程序入口:解析指定目录并输出 JSON。
|
||||
*
|
||||
* @param args [0]=源码目录, [1]=可选的输出文件路径(默认 stdout)
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
if (args.length < 1) {
|
||||
System.err.println("用法: java -jar controller-parser.jar <源码目录> [输出JSON路径]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
Path sourceDir = Paths.get(args[0]).toAbsolutePath().normalize();
|
||||
ControllerAstParser parser = new ControllerAstParser();
|
||||
List<ApiEndpoint> endpoints = parser.parseDirectory(sourceDir);
|
||||
|
||||
String json = MAPPER.writeValueAsString(endpoints);
|
||||
|
||||
if (args.length >= 2) {
|
||||
Path output = Paths.get(args[1]);
|
||||
MAPPER.writeValue(output.toFile(), endpoints);
|
||||
System.out.println("已解析 " + endpoints.size() + " 个接口,输出至: " + output);
|
||||
} else {
|
||||
System.out.println(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user