用java帮我做一个工具,对于指定的java web目录,识别出API清单(mvc、jax-rs之类的restful api)
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JavaWebAPIScanner {
// MVC注解模式
private static final Pattern[] MVC_PATTERNS = {
Pattern.compile("@RequestMapping\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@GetMapping\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@PostMapping\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@PutMapping\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@DeleteMapping\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@PatchMapping\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@GetMapping\\s*\\(\\s*value\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@PostMapping\\s*\\(\\s*value\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@PutMapping\\s*\\(\\s*value\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@DeleteMapping\\s*\\(\\s*value\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@PatchMapping\\s*\\(\\s*value\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@RequestMapping\\s*\\(\\s*value\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@RequestMapping\\s*\\(\\s*path\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@GetMapping\\s*\\(\\s*path\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@PostMapping\\s*\\(\\s*path\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@PutMapping\\s*\\(\\s*path\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@DeleteMapping\\s*\\(\\s*path\\s*=\\s*[\"']([^\"']+)"),
Pattern.compile("@PatchMapping\\s*\\(\\s*path\\s*=\\s*[\"']([^\"']+)")
};
// JAX-RS注解模式
private static final Pattern[] JAXRS_PATTERNS = {
Pattern.compile("@Path\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@GET[^}]*@Produces\\s*\\(.*?\\)[^}]*@Path\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@POST[^}]*@Produces\\s*\\(.*?\\)[^}]*@Path\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@PUT[^}]*@Produces\\s*\\(.*?\\)[^}]*@Path\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@DELETE[^}]*@Produces\\s*\\(.*?\\)[^}]*@Path\\s*\\(\\s*[\"']([^\"']+)"),
Pattern.compile("@PATCH[^}]*@Produces\\s*\\(.*?\\)[^}]*@Path\\s*\\(\\s*[\"']([^\"']+)")
};
// 类级别注解模式
private static final Pattern[] CLASS_PATTERNS = {
Pattern.compile("@RestController\\s+class\\s+(\\w+)"),
Pattern.compile("@Controller\\s+class\\s+(\\w+)"),
Pattern.compile("@Path\\s*\\(\\s*[\"']([^\"']+)\\s*\\)\\s+public\\s+class\\s+(\\w+)"),
Pattern.compile("@Path\\s*\\(\\s*[\"']([^\"']+)\\s*\\)\\s+class\\s+(\\w+)")
};
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("Usage: java JavaWebAPIScanner <web_directory>");
return;
}
String directoryPath = args[0];
scanDirectory(directoryPath);
}
public static void scanDirectory(String directoryPath) {
Path start = Paths.get(directoryPath);
List<APIInfo> apis = new ArrayList<>();
try {
Files.walk(start)
.filter(path -> path.toString().endsWith(".java"))
.forEach(file -> {
try {
String content = new String(Files.readAllBytes(file));
List<APIInfo> fileApis = extractAPIs(content, file.toString());
apis.addAll(fileApis);
} catch (IOException e) {
System.err.println("Error reading file: " + file + " - " + e.getMessage());
}
});
System.out.println("Found " + apis.size() + " API endpoints:");
System.out.println("=".repeat(80));
for (int i = 0; i < apis.size(); i++) {
APIInfo api = apis.get(i);
System.out.println((i + 1) + ". [" + api.getMethod() + "] " + api.getPath());
System.out.println(" File: " + api.getFile());
System.out.println(" Class: " + api.getClassName());
System.out.println(" Method: " + api.getMethodName());
System.out.println();
}
} catch (IOException e) {
System.err.println("Error walking directory: " + e.getMessage());
}
}
private static List<APIInfo> extractAPIs(String content, String filePath) {
List<APIInfo> apis = new ArrayList<>();
// 提取类级别信息
String className = extractClassName(content);
String classPath = extractClassPath(content);
// 查找所有方法级别的注解
String[] lines = content.split("\\n");
for (int i = 0; i < lines.length; i++) {
String line = lines[i].trim();
// 查找MVC注解
for (Pattern pattern : MVC_PATTERNS) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
String method = extractMethodType(line);
String path = matcher.group(1);
String methodName = extractMethodName(lines, i);
// 构建完整路径
String fullPath = buildFullPath(classPath, path);
apis.add(new APIInfo(method, fullPath, className, methodName, filePath));
}
}
// 查找JAX-RS注解
for (Pattern pattern : JAXRS_PATTERNS) {
Matcher matcher = pattern.matcher(content.substring(0,
Math.min(content.length(), Math.max(0, content.indexOf(line) + line.length() + 500))));
if (matcher.find()) {
String method = extractJaxRsMethodType(matcher.group(0));
String path = matcher.group(1);
String methodName = extractMethodName(lines, i);
// 构建完整路径
String fullPath = buildFullPath(classPath, path);
apis.add(new APIInfo(method, fullPath, className, methodName, filePath));
}
}
}
return apis;
}
private static String extractClassName(String content) {
Pattern pattern = Pattern.compile("public\\s+class\\s+(\\w+)");
Matcher matcher = pattern.matcher(content);
return matcher.find() ? matcher.group(1) : "Unknown";
}
private static String extractClassPath(String content) {
for (Pattern pattern : CLASS_PATTERNS) {
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
// 对于@RestController和@Controller,返回空字符串
if (content.contains("@RestController") || content.contains("@Controller")) {
return "";
}
// 对于@Path,返回匹配的第一组
return matcher.group(1);
}
}
return "";
}
private static String extractMethodType(String line) {
if (line.contains("@GetMapping")) return "GET";
if (line.contains("@PostMapping")) return "POST";
if (line.contains("@PutMapping")) return "PUT";
if (line.contains("@DeleteMapping")) return "DELETE";
if (line.contains("@PatchMapping")) return "PATCH";
if (line.contains("@RequestMapping")) {
// 尝试从method参数中提取HTTP方法
Pattern methodPattern = Pattern.compile("method\\s*=\\s*RequestMethod\\.(\\w+)");
Matcher matcher = methodPattern.matcher(line);
if (matcher.find()) {
return matcher.group(1).toUpperCase();
}
// 默认返回GET(如果未指定method,默认为GET)
return "GET";
}
return "UNKNOWN";
}
private static String extractJaxRsMethodType(String content) {
if (content.contains("@GET")) return "GET";
if (content.contains("@POST")) return "POST";
if (content.contains("@PUT")) return "PUT";
if (content.contains("@DELETE")) return "DELETE";
if (content.contains("@PATCH")) return "PATCH";
return "UNKNOWN";
}
private static String extractMethodName(String[] lines, int index) {
// 向上查找直到找到方法声明
for (int i = index; i >= 0 && i > index - 10; i--) {
String line = lines[i].trim();
// 查找方法签名(包含括号但不以分号结尾)
if (line.matches(".*\\w+\\s+\\w+\\s*\\([^)]*\\)\\s*[^;{]*$")) {
// 提取方法名
Pattern methodPattern = Pattern.compile("\\w+\\s+(\\w+)\\s*\\(");
Matcher matcher = methodPattern.matcher(line);
if (matcher.find()) {
return matcher.group(1);
}
}
// 直接匹配方法定义
Pattern directMethodPattern = Pattern.compile("(?:public|private|protected)?\\s*(?:static)?\\s*\\w+\\s+(\\w+)\\s*\\(.*\\)");
Matcher directMatcher = directMethodPattern.matcher(line);
if (directMatcher.find()) {
return directMatcher.group(1);
}
}
return "unknownMethod";
}
private static String buildFullPath(String classPath, String methodPath) {
StringBuilder fullPath = new StringBuilder();
if (classPath != null && !classPath.isEmpty()) {
if (!classPath.startsWith("/")) {
fullPath.append("/");
}
fullPath.append(classPath);
}
if (methodPath != null && !methodPath.isEmpty()) {
if (!methodPath.startsWith("/")) {
fullPath.append("/");
}
fullPath.append(methodPath);
}
if (fullPath.length() == 0) {
fullPath.append("/");
}
return fullPath.toString();
}
static class APIInfo {
private String method;
private String path;
private String className;
private String methodName;
private String file;
public APIInfo(String method, String path, String className, String methodName, String file) {
this.method = method;
this.path = path;
this.className = className;
this.methodName = methodName;
this.file = file;
}
public String getMethod() { return method; }
public String getPath() { return path; }
public String getClassName() { return className; }
public String getMethodName() { return methodName; }
public String getFile() { return file; }
}
}
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SimpleUrlScanner {
// 类级 Path / RequestMapping
private static final Pattern CLASS_PATH = Pattern.compile(
"@(RequestMapping|Path)\\s*\\(\\s*\"([^\"]+)\"\\s*\\)"
);
// 方法级 Mapping / Path
private static final Pattern METHOD_PATH = Pattern.compile(
"@(RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|Path)\\s*\\(\\s*\"([^\"]+)\"\\s*\\)"
);
// JAX-RS HTTP Method(无路径,也算 URL)
private static final Pattern JAXRS_METHOD = Pattern.compile(
"@(GET|POST|PUT|DELETE|HEAD|OPTIONS)"
);
public static void main(String[] args) throws IOException {
if (args.length == 0) {
System.out.println("Usage: java SimpleUrlScanner <java-source-root>");
return;
}
Path root = Paths.get(args[0]);
Set<String> urls = new LinkedHashSet<>();
Files.walk(root)
.filter(p -> p.toString().endsWith(".java"))
.forEach(p -> scanJavaFile(p, urls));
System.out.println("==== URL LIST ====");
urls.forEach(System.out::println);
}
private static void scanJavaFile(Path javaFile, Set<String> urls) {
try {
List<String> lines = Files.readAllLines(javaFile);
String classPrefix = "";
// 先找类级 Path
for (String line : lines) {
Matcher m = CLASS_PATH.matcher(line);
if (m.find()) {
classPrefix = m.group(2);
break;
}
}
// 再扫方法级
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
Matcher methodPath = METHOD_PATH.matcher(line);
if (methodPath.find()) {
String path = methodPath.group(2);
urls.add(normalize(classPrefix + "/" + path));
continue;
}
// JAX-RS:@GET 这种,没路径也记
Matcher jaxrs = JAXRS_METHOD.matcher(line);
if (jaxrs.find()) {
urls.add(normalize(classPrefix));
}
}
} catch (Exception e) {
System.err.println("Failed to parse: " + javaFile);
}
}
private static String normalize(String path) {
if (path == null || path.isEmpty()) return "/";
return path.replaceAll("//+", "/");
}
}