diff --git a/pom.xml b/pom.xml index d6575c9c..8ca903b4 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,12 @@ cos 2022.2 + + + io.github.yedaxia + japidocs + 1.4.4 + org.apache.pdfbox diff --git a/src/main/java/com/dsideal/QingLong/JApiDocsGenerator.java b/src/main/java/com/dsideal/QingLong/JApiDocsGenerator.java new file mode 100644 index 00000000..ead3684e --- /dev/null +++ b/src/main/java/com/dsideal/QingLong/JApiDocsGenerator.java @@ -0,0 +1,191 @@ +package com.dsideal.QingLong; + +import cn.hutool.core.io.FileUtil; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.dsideal.QingLong.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处理完成!"); + } +} \ No newline at end of file diff --git a/src/main/java/com/dsideal/QingLong/Plugin/PostmanDocPlugin.java b/src/main/java/com/dsideal/QingLong/Plugin/PostmanDocPlugin.java new file mode 100644 index 00000000..999b9c54 --- /dev/null +++ b/src/main/java/com/dsideal/QingLong/Plugin/PostmanDocPlugin.java @@ -0,0 +1,48 @@ +package com.dsideal.QingLong.Plugin; + +import freemarker.template.Template; +import freemarker.template.TemplateException; +import io.github.yedaxia.apidocs.DocContext; +import io.github.yedaxia.apidocs.IPluginSupport; +import io.github.yedaxia.apidocs.Resources; +import io.github.yedaxia.apidocs.Utils; +import io.github.yedaxia.apidocs.parser.ControllerNode; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PostmanDocPlugin implements IPluginSupport { + public PostmanDocPlugin() { + } + + public void execute(List controllerNodeList) { + FileWriter docFileWriter = null; + + try { + Template ctrlTemplate = this.getDocTpl(); + + String docFileName = String.format("%s-%s-api-docs.json", DocContext.getDocsConfig().getProjectName(), DocContext.getDocsConfig().getApiVersion()); + File docFile = new File(DocContext.getDocPath(), docFileName); + docFileWriter = new FileWriter(docFile); + Map data = new HashMap(); + data.put("controllerNodes", controllerNodeList); + data.put("currentApiVersion", DocContext.getCurrentApiVersion()); + data.put("projectName", DocContext.getDocsConfig().getProjectName()); + data.put("i18n", DocContext.getI18n()); + ctrlTemplate.process(data, docFileWriter); + + } catch (IOException | TemplateException var10) { + var10.printStackTrace(); + } finally { + Utils.closeSilently(docFileWriter); + } + } + + private Template getDocTpl() throws IOException { + return Resources.getFreemarkerTemplate("postman-doc.json.ftl"); + } +} \ No newline at end of file diff --git a/src/main/resources/postman-doc.json.ftl b/src/main/resources/postman-doc.json.ftl new file mode 100644 index 00000000..4ce6ceee --- /dev/null +++ b/src/main/resources/postman-doc.json.ftl @@ -0,0 +1,37 @@ +{ +"info": { +"_postman_id": "", +"name": "${projectName}", +"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" +}, +"item": [ +<#list controllerNodes as controller> + { + "name": "${controller.description}", + "item": [ + <#list controller.requestNodes as reqNode> + { + "name": "${reqNode.description}", + "request": { + "url": { + "raw": "{{domain}}${reqNode.url}", + "query": [ + <#if reqNode.paramNodes?size != 0> + <#list reqNode.paramNodes as paramNode> + { + "key": "${paramNode.name}", + "value": "", + "description": "${paramNode.description}" + }<#if paramNode_has_next>, + + + ] + } + } + }<#if reqNode_has_next>, + + ] + }<#if controller_has_next>, + +] +} \ No newline at end of file