package com.dsideal.base; import cn.hutool.core.io.FileUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.dsideal.base.Plugin.PostmanDocPlugin; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.jfinal.aop.Before; import com.jfinal.core.Controller; import com.jfinal.ext.interceptor.GET; import com.jfinal.ext.interceptor.POST; import io.github.yedaxia.apidocs.DocContext; import io.github.yedaxia.apidocs.Docs; import io.github.yedaxia.apidocs.DocsConfig; import java.io.File; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; /** * JApiDocs 无需额外注解的 API 文档生成工具 *

* 源码 https://github.com/YeDaxia/JApiDocs * 文档 https://japidocs.agilestudio.cn/#/zh-cn/ * * @author zxd 2022-02-17 */ public class JApiDocsGenerator { private static String getHttpType(Before before) { if (before.value().length > 0) { for (Class interceptClass : before.value()) { if (interceptClass.equals(GET.class)) { return "GET"; } else if (interceptClass.equals(POST.class)) { return "POST"; } } } return "UNKNOWN"; } /** * 获取指定目录下所有JAVA文件 * @param directory 指定目录 * @return 所有Java文件 */ public static List findJavaFiles(File directory) { List javaFiles = new ArrayList<>(); File[] files = directory.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { javaFiles.addAll(findJavaFiles(file)); // 递归遍历子目录 } else if (file.getName().endsWith(".java")) { javaFiles.add(file); // 添加Java文件 } } } return javaFiles; } /** * 功能:获取所有 Controller 名称 * * @param sourceJava JAVA目录 * @return Controller名称集合 */ public static Map getControllMap(String sourceJava) { Map res = new HashMap<>(); List files = findJavaFiles(new File(sourceJava)); for (File file : files) { String className = file.getAbsolutePath().replace("\\", "/").replace(sourceJava + "/", ""); className = className.replace("/", "."); className = className.replace(".java", ""); try { // 去掉.class后缀并加载类 Class cls = Class.forName(className); if (Controller.class.isAssignableFrom(cls) && !Modifier.isAbstract(cls.getModifiers())) { // 获取类上的所有方法 Method[] methods = cls.getDeclaredMethods(); for (Method method : methods) { // 检查方法上是否有@Before注解 var before = method.getAnnotation(Before.class); if (before != null) { String httpType = getHttpType(before); res.put(cls.getSimpleName() + "." + method.getName(), httpType); } } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } return res; } /** * JApiDocs 生成器 * 如果报错,做如下检查: * 1 javadoc @param 后是否有注释 * 2 src.main.java 目录中非 .java 扩展名文件的内容要 // 注释起来 * 3 删除 config.setDocsPath 目录中的文件,再生成试试 *

* 如果生成的 api 文档不是预期的,作如下检查: * 1 必须在 configRoute(Routes me) 中已该方式 me.add("/xx/yy", xx.class, "/"); 定义 Controller * 2 在需要生成 api 的 Controller 中添加 @ApiDoc 注解 * 3 如果要忽略某 action,在 action 上添加 @Ignore * https://jfinal.com/share/2528 *

* 在需要生成Doc文档的Controller类上面加上@ApiDoc注解 *

* https://japidocs.agilestudio.cn/#/zh-cn/ */ public static void main(String[] args) { //可以限制只生成哪个接口,数组内容为空,则表示生成全部 //String[] generateInterfacesList = {"getWxTj"}; String[] generateInterfacesList = {}; String projectPath = System.getProperty("user.dir"); projectPath = projectPath.replace("\\", "/"); String projectName = projectPath.substring(projectPath.lastIndexOf("/") + 1); DocsConfig config = new DocsConfig(); String sourceJava = projectPath + "/src/main/java"; config.setProjectPath(sourceJava); // root project path config.setProjectName(projectName); // project name String version = "v1.0"; config.setApiVersion(version); // api version String docPath = projectPath + "/Doc"; config.setDocsPath(docPath); // api docs target path config.addPlugin(new PostmanDocPlugin()); config.setAutoGenerate(Boolean.FALSE); // auto generate config.setMvcFramework("JFinal"); Docs.buildHtmlDocs(config); // execute to generate // 获取项目路径 Map map = getControllMap(sourceJava); //修正一下 postman 的请求方式 String docFileName = String.format("%s-%s-api-docs.json", DocContext.getDocsConfig().getProjectName(), DocContext.getDocsConfig().getApiVersion()); String jsonPath = docPath + "/" + version + "/" + docFileName; String jsonContent = FileUtil.readUtf8String(jsonPath); JSONObject jo = JSONObject.parseObject(jsonContent); //第一层item for (Object item : jo.getJSONArray("item")) { JSONObject j2 = (JSONObject) item; //Controller类名 String className = j2.getString("name"); //第二层item for (Object o : j2.getJSONArray("item")) { JSONObject j3 = (JSONObject) o; JSONObject jRequest = j3.getJSONObject("request"); String x = jRequest.getJSONObject("url").getString("raw"); x = x.substring(x.lastIndexOf("/") + 1); jRequest.put("method", map.get(className + "." + x)); } } //限制只生成哪个接口 Set _set = new HashSet<>(); Collections.addAll(_set, generateInterfacesList); // 获取JSONArray jo = jo.getJSONArray("item").getJSONObject(0); JSONArray jsonArray = jo.getJSONArray("item"); if (!_set.isEmpty()) { // 遍历JSONArray for (int i = 0; i < jsonArray.size(); i++) { // 获取当前的JSONObject JSONObject obj = jsonArray.getJSONObject(i); JSONObject jRequest = obj.getJSONObject("request"); // 检查条件,例如,如果名字不是"John",则删除这个条目 String x = jRequest.getJSONObject("url").getString("raw"); x = x.substring(x.lastIndexOf("/") + 1); if (!_set.contains(x)) { // 从JSONArray中移除当前条目 jsonArray.remove(i); // 由于移除操作会改变数组的长度和索引,所以i需要减1以保持正确的索引位置 i--; } } // 将修改后的JSONArray重新设置回JSONObject jo.put("item", jsonArray); } //美化JSON格式化保存 Gson gson = new GsonBuilder().setPrettyPrinting().create(); String jsonString = gson.toJson(jo); FileUtil.writeUtf8String(jsonString, jsonPath); System.out.println("恭喜,文档JSON处理完成!"); } }