commit 9f4ee124f2bb9b7d0c364f68f50c0aabc9e5a7e1 Author: oppofind <836575280@qq.com> Date: Tue Sep 10 09:39:04 2019 +0800 init smart-doc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59d72b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +#Compiled class file +*.class + +.idea +*.iml +target + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear +*.zip +*.tar.gz +*.rar +*.jar +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* diff --git a/README.md b/README.md new file mode 100644 index 0000000..a14a0a0 --- /dev/null +++ b/README.md @@ -0,0 +1,213 @@ + +smart-doc是一个java restful api文档生成工具,smart-doc颠覆了传统类似swagger这种大量采用注解侵入来生成文档的实现方法。 +smart-doc完全基于接口源码分析来生成接口文档,完全做到零注解侵入,你只需要按照java标准注释的写就能得到一个标准的markdown接口文档。 +如果你已经厌倦了swagger等文档工具的注解和强侵入污染,那请拥抱smart-doc吧! + +**重点** smart-doc已成功被开源中国收录,并且被一加、iflytek等知名公司采用 + +# smart-doc和其他工具对比 + +没有对比就没有伤害,如果不是为了解决问题又何必发明新的轮子。 + +# smart-doc使用 +## 1.导入smart-doc工具的依赖包 +``` + + com.github.shalousun + smart-doc + 1.6 + test + +``` +## 2.编写一个单元测试类 + +妈妈再也不用担心我不会用了,So easy! + +``` +/** + * Description: + * ApiDoc测试 + * + * @author yu 2018/06/11. + */ +public class ApiDocTest { + + /** + * 简单型接口,不需要指定请求头,并且项目是maven的. + * + */ + @Test + public void testBuilderControllersApiSimple(){ + //将生成的文档输出到d:\md目录下,严格模式下api-doc会检测Controller的接口注释 + ApiDocBuilder.builderControllersApi("d:\\md",true); + } + + /** + * 包括设置请求头,缺失注释的字段批量在文档生成期使用定义好的注释 + */ + @Test + public void testBuilderControllersApi() { + ApiConfig config = new ApiConfig(); + config.setStrict(true); + config.setAllInOne(true);//true则将所有接口合并到一个AllInOne中markdown中,错误码合并到最后 + config.setOutPath("d:\\md"); + // @since 1.2,如果不配置该选项,则默认匹配全部的controller, + // 如果需要配置有多个controller可以使用逗号隔开 + config.setPackageFilters("com.power.doc.controller.app"); + //默认是src/main/java,maven项目可以不写 + config.setSourcePaths( + SourcePath.path().setDesc("本项目代码").setPath("src/test/java"), + SourcePath.path().setPath("E:\\Test\\Mybatis-PageHelper-master\\src\\main\\java"), + SourcePath.path().setDesc("加载项目外代码").setPath("E:\\ApplicationPower\\ApplicationPower\\Common-util\\src\\main\\java") + ); + + //设置请求头,如果没有请求头,可以不用设置 + config.setRequestHeaders( + ApiReqHeader.header().setName("access_token").setType("string").setDesc("Basic auth credentials"), + ApiReqHeader.header().setName("user_uuid").setType("string").setDesc("User Uuid key") + ); + //对于外部jar的类,api-doc目前无法自动获取注释, + //如果有这种场景,则自己添加字段和注释,api-doc后期遇到同名字段则直接给相应字段加注释 + config.setCustomResponseFields( + CustomRespField.field().setName("success").setDesc("成功返回true,失败返回false"), + CustomRespField.field().setName("message").setDesc("接口响应信息"), + CustomRespField.field().setName("data").setDesc("接口响应数据"), + CustomRespField.field().setName("code").setValue("00000").setDesc("响应代码") + ); + + //设置项目错误码列表,设置自动生成错误列表 + List errorCodeList = new ArrayList<>(); + for(ErrorCodeEnum codeEnum:ErrorCodeEnum.values()){ + ApiErrorCode errorCode = new ApiErrorCode(); + errorCode.setValue(codeEnum.getValue()).setDesc(codeEnum.getDesc()); + errorCodeList.add(errorCode); + } + //不是必须 + config.setErrorCodes(errorCodeList); + + ApiDocBuilder.builderControllersApi(config); + } + +} +``` + + +# smart-doc的缺点 + +万物有痕,所以美! + + +## 1.泛型推导不完美 + +目前api-doc在泛型的字段推导上是不完美的。例如: + +**样例1**: +``` +public class Teacher { + private T data; + + private Object data3 + + private K data1; + + private M data2; + + /** + * 年龄 + */ + private int age; +} +``` +介于当前的推导算法。泛型字段属性的顺序和Teacher泛型参数顺序一致,否则api-doc推导的json数据会出现张冠李戴的情况。当然你只要调整一下属性的顺序就可以了。 + +**样例2** +``` +public class Teacher { + private T data; + + private Object data3 + + private K data1; + + private M data2; + + /** + * 年龄 + */ + private int age; +} +``` +上面这种在泛型属性中突然插入了一个Object的属性,因为泛型本身在编译后会被擦除,因此这种在中间插入的情况会导致目前算法推导出错,同样实现数据张冠李戴,因此推荐如果前面泛型的字段的前面字段不属于基本类型(String,int,Double等)的都移动到泛型后面去。 + + +## 2.form data数据请求处理不完美 + +这并不是smart-doc不具备能力来生成处理form data请求式接口文档,而是目前作者不知道怎么来做这个模板,尤其是请求示例数据的模板怎么提供。但是api-doc还是提供了基本类型请求参数的文档生成。 + +所以真诚的希望您可以提供一个form data请求式的模板,然后smart-doc在后续的版本更新中完善form data请求参数的处理。 +## 3. 对于继承的属性为推导到文档 +由于api-doc目前采用qdoc来实现了代码的加载,由于qdoc在这方面没有提供直接支持,并且在泛型处理上也太好,考虑后期会去使用其他框架来作为api-doc +的底层实现,因此这项先搁浅,如果使用者遇到这样问题可以定义一个返回包装类解决。 + +## api-doc 测试demo + +https://github.com/shalousun/api-doc-test +## 效果图[markdown截图] +### 接口头部效果图 +![输入图片说明](https://images.gitee.com/uploads/images/2018/0905/173104_abcf4345_144669.png "1.png") +### 请求参数示例效果图 +![请求参数示例](https://images.gitee.com/uploads/images/2018/0905/172510_853735b9_144669.png "2.png") +### 响应参数示例效果图 +![响应参数示例](https://images.gitee.com/uploads/images/2018/0905/172538_1918820c_144669.png "3.png") + + + +## smart-doc版本 +版本小于1.0都属于使用版,正式1.0起始发布将会等到文中提到的问题解决后才发布。 +### 版本号:0.1 +- 更新日期:2018-06-25 +- 更新内容: + 1. 手册将api-doc发布到中央仓库 +### 版本号:0.2 +- 更新日期:2018-07-07 +- 更新内容: + 1. 修改api-doc泛型推导的bug. +#### 版本号:0.3 +- 更新日期:2018-07-10 +- 更新内容: + 1. api-doc增加对jackson和fastjson注解的支持,可根据注解定义来生成返回信息。 +#### 版本号:0.4 +- 更新日期:2018-07-11 +- 更新内容: + 1. 修改api-doc对类继承属性的支持。 +#### 版本号:0.5 +- 更新日期:2018-08-04 +- 更新内容: + 1. 修改api-doc对各种字段的解析错误bug。 +#### 版本号:0.5 +- 更新日期:2018-08-23 +- 更新内容: + 1. 将api-doc重命名为smart-doc并发布到中央仓库 +#### 版本号:1.0 +- 更新日期:2018-08-25 +- 更新内容: + 1. smart-doc增加将所有文档导出归档到一个markdown中件的功能 + 2. 参考阿里开发手册将直接提升到1.0,之前的版本主要是个人内部测试 +#### 版本号:1.1 +- 更新日期:2018-08-30 +- 更新内容: + 1. 修改PostMapping和GetMapping value为空报错的bug + 2. 增强时间字段的mock数据创建 + 3. 修改smart-doc解析自引用对象出错的bug +#### 版本号:1.2 +- 更新日期:2018-09-04 +- 更新内容: + 1. 根据用户反馈增加controller报名过滤功能,该功能为可选项 +#### 版本号:1.3 +- 更新日期:2018-09-15 +- 更新内容: + 1. 增加PutMapping和DeleteMapping支持 + 2. 添加字符串date和Date类型时间的模拟值生成 +# 问题反馈 +目前已经能够支持许多复杂和返回类型和类结构推导,但是由于目前做采用的底层工具处理能力不是很好,所以有些功能做的不够彻底。 +当然基于目前的版如果在使用中发现问题欢迎及时的提出反馈的建议。 diff --git a/doc/List.md b/doc/List.md new file mode 100644 index 0000000..af7d825 --- /dev/null +++ b/doc/List.md @@ -0,0 +1,136 @@ +关于list结构的返回json数据测试 + +# List结构 + +api-doc对于List中返回基础数据类型都是支持的 +``` +/** + * List + * + * @return + */ +@GetMapping(value = "listString") +public List testList() { + return null; +} +``` +api-doc生成的响应数据 +``` +[ "ivvqah","isrz5x"] +``` +# List>结构 + +``` +/** + * + * @return + */ +@GetMapping(value = "/map/Primitive") +public List> testMap() { + return null; +} +``` +api-doc生成的响应数据 +``` +[{ + "mapKey1": "o9mibj", + "mapKey2": "3dnnrn" +}] +``` + +# List>结构 + +``` +@GetMapping(value = "/map/Primitive") +public List> testMap() { + return null; +} + +``` +相应数据省略 + +# 测试List结构 + +``` +/** + * 测试List结构 + * @return + */ +@GetMapping(value = "/map/Primitive") +public List testMap() { + return null; +} +``` + +# List>结构 +``` +/** + * 测试List>结构 + * @return + */ +@GetMapping(value = "/map/Primitive") +public List> testMap() { + return null; +} +``` +# List>>超复杂结构 +``` +/** + * 测试List>>超复杂结构 + * @return + */ +@GetMapping(value = "/map/Primitive") +public List>> testMap() { + return null; +} +``` +api-doc自动返回的数据 +``` +[{ + "mapKey": { + "data": { + "userName": "lxh2yi", + "userAddress": "6jfp3h", + "userAge": 741 + }, + "data1": { + "userName": "1wp54g", + "userAddress": "8ul6m4", + "userAge": 550 + }, + "age": 10 + } +}] +``` +# List,List,List>>超复杂结构 + +``` +/** + * List,List,List>> + * @return + */ +@GetMapping(value = "listString") +public List,List,List>> testListString(){ + return null; +} +``` +# 其他复杂结构 +``` +/** + * List,List,List>> + * + * @return + */ +@GetMapping(value = "listString") +public List,User,User>> testListString() { + return null; +} + +@GetMapping(value = "listString") +public List,Teacher,Teacher>> testListString() { + return null; +} +``` + +**注意:** api-doc为了传入的复杂泛型结构数据,做了许多情况的测试,目前基本能兼容系统开发中95%以上的List返回接口, +也提供了一些能够处理的很复杂的泛型结构,但是这种复杂的泛型结构在开发中是不被推荐的。 \ No newline at end of file diff --git a/doc/Map.md b/doc/Map.md new file mode 100644 index 0000000..f485a70 --- /dev/null +++ b/doc/Map.md @@ -0,0 +1,153 @@ +Api-doc对于api中map结构数据的json化处理多组测试用例,对于map返回的json结构, +目前基本仅仅支持,String类型的key。 + +**基础数据类型:** json支持的基本java数据类型(不包含byte,包含String) + +# map使用基础数据类型 + +``` +/** + * 测试map使用基础数据类型 + * @return + */ +@GetMapping(value = "/map/Primitive") +public Map testMap() { + return null; +} +``` +api-doc 生成的json: +``` +{ + "mapKey1": 721, + "mapKey2": 280 +} +``` +# map使用Object + +因为api-doc使用的是无侵入静态分析生成api文档,因此对于直接使用Object做map value的接口,api-doc无法准确的生成json。 +所以api-doc返回是会在默认json中加一段警告,使用者需要自己去修改返回数据,或者是使用显示的类型数据结构。 +``` +/** + * 测试map使用基础数据类型 + * @return + */ +@GetMapping(value = "/map/Primitive") +public Map testMap() { + return null; +} +``` +api-doc 生成的json: +``` +{ + "mapKey": { + "waring": "You may use java.util.Object for Map value;Api-doc can't be handle." + } +} +``` +# map中属于自己定义的简单数据结构 +User对象的属性仅仅是基本数据类型 +``` +/** + * 测试map使用自定义数据结构 + * @return + */ +@GetMapping(value = "/map/Primitive") +public Map testMap() { + return null; +} +``` +api-doc 生成的json: +``` +{ + "mapKey": { + "userName": "7t2ccy", + "userAddress": "3ipy7g", + "userAge": 280 + } +} +``` +# map中属于自己定义的复杂数据结构 +Student对象的属性有基本类型又有User类型和Map类型的属性。 +``` +/** + * 测试map使用自定义数据结构 + * @return + */ +@GetMapping(value = "/map/Primitive") +public Map testMap() { + return null; +} +``` +api-doc 生成的json: +``` +{ + "mapKey": { + "stuName": "9cwzml", + "stuAge": 792, + "stuAddress": "rdfmtx", + "user": { + "userName": "fjglql", + "userAddress": "yy6vkf", + "userAge": 398 + }, + "userMap": { + "mapKey": { + "userName": "paw90w", + "userAddress": "mnmz42", + "userAge": 937 + } + }, + "user1": { + "userName": "rr3v6g", + "userAddress": "rbeorq", + "userAge": 399 + } + } +} +``` + +# Map>复杂结构 + +``` +{ + "mapKey":{ + "data":{ + "userName":"tumrit", + "userAddress":"v8fvdi", + "userAge":465 + }, + "data1":{ + "userName":"f7wbwk", + "userAddress":"brdh8j", + "userAge":345 + }, + "age":194 + } +} +``` +# Map,N>超复杂结构 + +``` +/** + * Map,N>超复杂结构 + * @return + */ +@GetMapping(value = "/map/Primitive") +public Map,User>> testMap() { + return null; +} +``` +# Map其他复杂结构 + +对于map的key采用多泛型的情况,目前api-doc也是支持的。 +``` +/** + * Map,N>超复杂结构 + * @return + */ +public Map,Map,Map>> testMap() { + return null; +} +``` +**注意:** api-doc为了传入的复杂泛型结构数据,做了许多情况的测试,目前基本能兼容系统开发中95%以上的Map返回接口, +也提供了一些能够处理的很复杂的泛型结构,但是这种复杂的泛型结构在开发中是不被推荐的。 \ No newline at end of file diff --git a/doc/error.md b/doc/error.md new file mode 100644 index 0000000..9ca3c5b --- /dev/null +++ b/doc/error.md @@ -0,0 +1,31 @@ +api-doc对Spring mvc或者SpringBoot应用的Controller接口返回做了一些强制规约,一旦在代码中使用 +这些被api-doc不推荐的接口返回类型,api-doc将会直接报错。 + +# 违反规约的实例 + +## 直接返回Object + +``` +/** + * 返回object + * @return + */ +@GetMapping("/test/Object") +public Object getMe(){ + return null; +} +``` +报错提示:Please do not return java.lang.Object directly in api interface. + +## 将非String对象作为Map的key,然后将map作为接口中返回 + +``` +/** + * 测试object的作为map的key + * @return + */ +@GetMapping("/test/map") +public Map objectMap(){ + return null; +} +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c5c245e --- /dev/null +++ b/pom.xml @@ -0,0 +1,141 @@ + + + com.github.shalousun + 4.0.0 + smart-doc + jar + 1.6.1 + + smart-doc + https://github.com/shalousun/ApplicationPower.git + ApplicationPower smart-doc + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + https://github.com/shalousun/ApplicationPower.git + scm:https://github.com/shalousun/ApplicationPower.git + scm:https://github.com/shalousun/ApplicationPower.git + + + + shalousun + 836575280@qq.com + https://github.com/shalousun + + + + UTF-8 + + + + junit + junit + 4.12 + test + + + com.ibeetl + beetl + 3.0.11.RELEASE + + + com.thoughtworks.qdox + qdox + 2.0-M10 + + + com.github.javafaker + javafaker + 1.0.1 + + + com.github.shalousun + common-util + 1.8.4 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.0 + + 1.8 + 1.8 + UTF-8 + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0 + + + package + + jar + + + + + + smart-doc + + + + release + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + oss + https://oss.sonatype.org/content/repositories/snapshots/ + + + oss + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + diff --git a/screen/1.png b/screen/1.png new file mode 100644 index 0000000..9853630 Binary files /dev/null and b/screen/1.png differ diff --git a/screen/2.png b/screen/2.png new file mode 100644 index 0000000..70811a2 Binary files /dev/null and b/screen/2.png differ diff --git a/screen/3.png b/screen/3.png new file mode 100644 index 0000000..78fc5b5 Binary files /dev/null and b/screen/3.png differ diff --git a/src/main/java/com/power/doc/builder/ApiDocBuilder.java b/src/main/java/com/power/doc/builder/ApiDocBuilder.java new file mode 100644 index 0000000..4ec8f57 --- /dev/null +++ b/src/main/java/com/power/doc/builder/ApiDocBuilder.java @@ -0,0 +1,116 @@ +package com.power.doc.builder; + +import com.power.common.util.CollectionUtil; +import com.power.common.util.DateTimeUtil; +import com.power.common.util.FileUtil; +import com.power.common.util.StringUtil; +import com.power.doc.model.ApiConfig; +import com.power.doc.model.ApiDoc; +import com.power.doc.model.ApiErrorCode; +import com.power.doc.utils.BeetlTemplateUtil; + +import org.beetl.core.Template; + +import java.util.List; + +public class ApiDocBuilder { + + + public static final String FILE_SEPARATOR = System.getProperty("file.separator"); + + /** + * 生成所有controller的api文档 + * + * @param outPath 代码输出路径 + * @param isStrict 是否启用严格模式 + */ + public static void builderControllersApi(String outPath, boolean isStrict) { + SourceBuilder sourceBuilder = new SourceBuilder(isStrict); + List apiDocList = sourceBuilder.getControllerApiData(); + buildApiDoc(apiDocList, outPath); + } + + /** + * @param config 配置 + */ + public static void builderControllersApi(ApiConfig config) { + if (null == config) { + throw new NullPointerException("ApiConfig can't be null"); + } + if (StringUtil.isEmpty(config.getOutPath())) { + throw new RuntimeException("doc output path can't be null or empty"); + } + SourceBuilder sourceBuilder = new SourceBuilder(config); + List apiDocList = sourceBuilder.getControllerApiData(); + if (config.isAllInOne()) { + buildAllInOne(apiDocList, config); + } else { + buildApiDoc(apiDocList, config.getOutPath()); + buildErrorCodeDoc(config.getErrorCodes(), config.getOutPath()); + } + } + + /** + * 生成单个controller的api文档 + * + * @param outPath 代码输出路径 + * @param controllerName controller 名称 + */ + public static void buildSingleControllerApi(String outPath, String controllerName) { + FileUtil.mkdirs(outPath); + SourceBuilder sourceBuilder = new SourceBuilder(true); + ApiDoc doc = sourceBuilder.getSingleControllerApiData(controllerName); + Template mapper = BeetlTemplateUtil.getByName("ApiDoc.btl"); + mapper.binding("desc", doc.getDesc()); + mapper.binding("name", doc.getName()); + mapper.binding("list", doc.getList());//类名 + FileUtil.writeFileNotAppend(mapper.render(), outPath + FILE_SEPARATOR + doc.getName() + "Api.md"); + } + + /** + * 公共生成controller api 文档 + * + * @param apiDocList + * @param outPath + */ + private static void buildApiDoc(List apiDocList, String outPath) { + FileUtil.mkdirs(outPath); + for (ApiDoc doc : apiDocList) { + Template mapper = BeetlTemplateUtil.getByName("ApiDoc.btl"); + mapper.binding("desc", doc.getDesc()); + mapper.binding("name", doc.getName()); + mapper.binding("list", doc.getList());//类名 + FileUtil.nioWriteFile(mapper.render(), outPath + FILE_SEPARATOR + doc.getName() + "Api.md"); + } + } + + /** + * 合并所有接口文档到一个文档中 + * + * @param apiDocList + */ + private static void buildAllInOne(List apiDocList, ApiConfig config) { + String outPath = config.getOutPath(); + FileUtil.mkdirs(outPath); + Template tpl = BeetlTemplateUtil.getByName("AllInOne.btl"); + tpl.binding("apiDocList", apiDocList); + tpl.binding("errorCodeList", config.getErrorCodes()); + tpl.binding("revisionLogList", config.getRevisionLogs()); + String version = DateTimeUtil.long2Str(System.currentTimeMillis(), "yyyyMMddHHmm"); + FileUtil.nioWriteFile(tpl.render(), outPath + FILE_SEPARATOR + "AllInOne-V" + version + ".md"); + } + + /** + * 构建错误码列表 + * + * @param errorCodeList 错误列表 + * @param outPath + */ + private static void buildErrorCodeDoc(List errorCodeList, String outPath) { + if (CollectionUtil.isNotEmpty(errorCodeList)) { + Template mapper = BeetlTemplateUtil.getByName("ErrorCodeList.btl"); + mapper.binding("list", errorCodeList);//类名 + FileUtil.nioWriteFile(mapper.render(), outPath + FILE_SEPARATOR + "ErrorCodeList.md"); + } + } +} diff --git a/src/main/java/com/power/doc/builder/SourceBuilder.java b/src/main/java/com/power/doc/builder/SourceBuilder.java new file mode 100644 index 0000000..13733cf --- /dev/null +++ b/src/main/java/com/power/doc/builder/SourceBuilder.java @@ -0,0 +1,1121 @@ +package com.power.doc.builder; + +import com.power.common.util.CollectionUtil; +import com.power.common.util.JsonFormatUtil; +import com.power.common.util.StringUtil; +import com.power.doc.constants.GlobalConstants; +import com.power.doc.model.*; +import com.power.doc.utils.DocClassUtil; +import com.power.doc.utils.DocUtil; +import com.thoughtworks.qdox.JavaProjectBuilder; +import com.thoughtworks.qdox.model.*; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.util.*; + +public class SourceBuilder { + + private static final String IGNORE_TAG = "ignore"; + + private static final String GET_MAPPING = "GetMapping"; + + private static final String GET_MAPPING_FULLY = "org.springframework.web.bind.annotation.GetMapping"; + + private static final String POST_MAPPING = "PostMapping"; + + private static final String POST_MAPPING_FULLY = "org.springframework.web.bind.annotation.PostMapping"; + + private static final String PUT_MAPPING = "PutMapping"; + + private static final String PUT_MAPPING_FULLY = "org.springframework.web.bind.annotation.PutMapping"; + + private static final String DELETE_MAPPING = "DeleteMapping"; + + private static final String DELETE_MAPPING_FULLY = "org.springframework.web.bind.annotation.DeleteMapping"; + + private static final String REQUEST_MAPPING = "RequestMapping"; + + private static final String REQUEST_MAPPING_FULLY = "org.springframework.web.bind.annotation.RequestMapping"; + + private static final String REQUEST_BODY = "RequestBody"; + + private static final String REQUEST_BODY_FULLY = "org.springframework.web.bind.annotation.RequestBody"; + + private static final String REQUEST_PARAM = "RequestParam"; + + private static final String JSON_CONTENT_TYPE = "application/json; charset=utf-8"; + + private static final String MAP_CLASS = "java.util.Map"; + + + public Map javaFilesMap = new HashMap<>(); + + private JavaProjectBuilder builder; + + private Collection javaClasses; + + private boolean isStrict;//严格模式 + + private String packageMatch; + + private List headers; + + private String appUrl; + + + public Map fieldMap = new HashMap<>(); + + /** + * if isStrict value is true,it while check all method + * + * @param isStrict strict flag + */ + public SourceBuilder(boolean isStrict) { + loadJavaFiles(null); + this.isStrict = isStrict; + } + + /** + * use custom config + * + * @param config config + */ + public SourceBuilder(ApiConfig config) { + if (null == config) { + throw new NullPointerException("ApiConfig can't be null."); + } + + if (StringUtil.isEmpty(config.getServerUrl())) { + this.appUrl = "http://{server}"; + } else { + this.appUrl = config.getServerUrl(); + } + + this.packageMatch = config.getPackageFilters(); + this.isStrict = config.isStrict(); + loadJavaFiles(config.getSourcePaths()); + this.headers = config.getRequestHeaders(); + if (CollectionUtil.isNotEmpty(config.getCustomResponseFields())) { + for (CustomRespField field : config.getCustomResponseFields()) { + fieldMap.put(field.getName(), field); + } + } + } + + /** + * 加载项目的源代码 + * + * @param paths list of SourcePath + */ + private void loadJavaFiles(List paths) { + JavaProjectBuilder builder = new JavaProjectBuilder(); + if (CollectionUtil.isEmpty(paths)) { + builder.addSourceTree(new File("src/main/java")); + } else { + for (SourcePath path : paths) { + if (null == path) { + continue; + } + String strPath = path.getPath(); + if (StringUtil.isNotEmpty(strPath)) { + strPath = strPath.replace("\\", "/"); + builder.addSourceTree(new File(strPath)); + } + } + } + this.builder = builder; + this.javaClasses = builder.getClasses(); + for (JavaClass cls : javaClasses) { + javaFilesMap.put(cls.getFullyQualifiedName(), cls); + } + } + + /** + * 检测controller上的注解 + * + * @param cls + * @return + */ + private boolean checkController(JavaClass cls) { + List classAnnotations = cls.getAnnotations(); + for (JavaAnnotation annotation : classAnnotations) { + String annotationName = annotation.getType().getName(); + if ("Controller".equals(annotationName) || "RestController".equals(annotationName) + || GlobalConstants.REST_CONTROLLER_FULLY.equals(annotationName) + || GlobalConstants.CONTROLLER_FULLY.equals(annotationName) + ) { + return true; + } + } + return false; + } + + /** + * 检查是否是rest controller + * + * @param cls The JavaClass object + * @return boolean + */ + private boolean isRestController(JavaClass cls) { + List classAnnotations = cls.getAnnotations(); + for (JavaAnnotation annotation : classAnnotations) { + String annotationName = annotation.getType().getName(); + if ("RestController".equals(annotationName)) { + return true; + } + } + return false; + } + + public List getControllerApiData() { + List apiDocList = new ArrayList<>(); + for (JavaClass cls : javaClasses) { + if (checkController(cls)) { + String controllerName = cls.getName(); + if (StringUtil.isNotEmpty(packageMatch)) { + if (DocUtil.isMatch(packageMatch, cls.getCanonicalName())) { + List apiMethodDocs = buildControllerMethod(cls); + ApiDoc apiDoc = new ApiDoc(); + apiDoc.setDesc(cls.getComment()); + apiDoc.setName(controllerName); + apiDoc.setList(apiMethodDocs); + apiDocList.add(apiDoc); + } + } else { + List apiMethodDocs = buildControllerMethod(cls); + ApiDoc apiDoc = new ApiDoc(); + apiDoc.setName(controllerName); + apiDoc.setDesc(cls.getComment()); + apiDoc.setList(apiMethodDocs); + apiDocList.add(apiDoc); + } + } + } + return apiDocList; + } + + + /** + * 包括包名 + * + * @param controller controller的名称 + * @return ApiDoc + */ + public ApiDoc getSingleControllerApiData(String controller) { + if (!javaFilesMap.containsKey(controller)) { + throw new RuntimeException("Unable to find " + controller + " in your project"); + } + JavaClass cls = builder.getClassByName(controller); + if (checkController(cls)) { + String controllerName = cls.getName(); + List apiMethodDocs = buildControllerMethod(cls); + ApiDoc apiDoc = new ApiDoc(); + apiDoc.setList(apiMethodDocs); + apiDoc.setName(controllerName); + return apiDoc; + } else { + throw new RuntimeException(controller + " is not a Controller in your project"); + } + } + + public List buildControllerMethod(final JavaClass cls) { + List classAnnotations = cls.getAnnotations(); + String baseUrl = null; + for (JavaAnnotation annotation : classAnnotations) { + String annotationName = annotation.getType().getName(); + if (REQUEST_MAPPING.equals(annotationName) || REQUEST_MAPPING_FULLY.equals(annotationName)) { + baseUrl = annotation.getNamedParameter("value").toString(); + baseUrl = baseUrl.replaceAll("\"", ""); + } + } + List methods = cls.getMethods(); + List methodDocList = new ArrayList<>(methods.size()); + for (JavaMethod method : methods) { + if (StringUtil.isEmpty(method.getComment()) && isStrict) { + throw new RuntimeException("Unable to find comment for method " + method.getName() + " in " + cls.getCanonicalName()); + } + ApiMethodDoc apiMethodDoc = new ApiMethodDoc(); + apiMethodDoc.setDesc(method.getComment()); + List annotations = method.getAnnotations(); + String url = null; + String methodType = null; + int methodCounter = 0; + for (JavaAnnotation annotation : annotations) { + String annotationName = annotation.getType().getName(); + if (REQUEST_MAPPING.equals(annotationName) || REQUEST_MAPPING_FULLY.equals(annotationName)) { + if (null == annotation.getNamedParameter("value")) { + url = "/"; + } else { + url = annotation.getNamedParameter("value").toString(); + } + if (null != annotation.getNamedParameter("method")) { + methodType = annotation.getNamedParameter("method").toString(); + if ("RequestMethod.POST".equals(methodType)) { + methodType = "POST"; + } else if ("RequestMethod.GET".equals(methodType)) { + methodType = "GET"; + } else if ("RequestMethod.PUT".equals(methodType)) { + methodType = "PUT"; + } else if ("RequestMethod.DELETE".equals(methodType)) { + methodType = "DELETE"; + } else { + methodType = "GET"; + } + } else { + methodType = "GET"; + } + methodCounter++; + } else if (GET_MAPPING.equals(annotationName) || GET_MAPPING_FULLY.equals(annotationName)) { + if (null == annotation.getNamedParameter("value")) { + url = "/"; + } else { + url = annotation.getNamedParameter("value").toString(); + } + methodType = "GET"; + methodCounter++; + } else if (POST_MAPPING.equals(annotationName) || POST_MAPPING_FULLY.equals(annotationName)) { + if (null == annotation.getNamedParameter("value")) { + url = "/"; + } else { + url = annotation.getNamedParameter("value").toString(); + } + methodType = "POST"; + methodCounter++; + } else if (PUT_MAPPING.equals(annotationName) || PUT_MAPPING_FULLY.equals(annotationName)) { + if (null == annotation.getNamedParameter("value")) { + url = "/"; + } else { + url = annotation.getNamedParameter("value").toString(); + } + methodType = "PUT"; + methodCounter++; + } else if (DELETE_MAPPING.equals(annotationName) || DELETE_MAPPING_FULLY.equals(annotationName)) { + if (null == annotation.getNamedParameter("value")) { + url = "/"; + } else { + url = annotation.getNamedParameter("value").toString(); + } + methodType = "DELETE"; + methodCounter++; + } + } + if (methodCounter > 0) { +// if ("void".equals(method.getReturnType().getFullyQualifiedName())) { +// throw new RuntimeException(method.getName() + " method in " + cls.getCanonicalName() + " can't be return type 'void'"); +// } + if (null != method.getTagByName(IGNORE_TAG)) { + continue; + } + url = url.replaceAll("\"", "").trim(); + apiMethodDoc.setType(methodType); + if (StringUtil.isNotEmpty(baseUrl)) { + baseUrl = StringUtils.equals("/", baseUrl.subSequence(0, 1)) ? baseUrl : "/" + baseUrl; + apiMethodDoc.setUrl(this.appUrl + (baseUrl + "/" + url).replace("//", "/")); + } else { + url = StringUtils.equals("/", url.subSequence(0, 1)) ? url : "/" + url; + apiMethodDoc.setUrl(this.appUrl + (url).replace("//", "/")); + } + String comment = getCommentTag(method, "param", cls.getCanonicalName()); + apiMethodDoc.setRequestParams(comment); + String requestJson = buildReqJson(method, apiMethodDoc); + apiMethodDoc.setRequestUsage(JsonFormatUtil.formatJson(requestJson)); + + apiMethodDoc.setResponseUsage(buildReturnJson(method, this.fieldMap)); + + String str = buildMethodReturn(method, cls.getGenericFullyQualifiedName()); + apiMethodDoc.setResponseParams(str); + apiMethodDoc.setHeaders(createHeaders(this.headers)); + methodDocList.add(apiMethodDoc); + } + } + return methodDocList; + + } + + /** + * create request headers + * + * @param headers Api request headers + * @return headers + */ + private String createHeaders(List headers) { + StringBuilder builder = new StringBuilder(); + if (CollectionUtil.isEmpty(headers)) { + headers = new ArrayList<>(0); + } + for (ApiReqHeader header : headers) { + builder.append(header.getName()).append("|") + .append(header.getType()).append("|") + .append(header.getDesc()).append("\n"); + } + return builder.toString(); + } + + private String buildMethodReturn(JavaMethod method, String controllerName) { + String returnType = method.getReturnType().getGenericCanonicalName(); + String typeName = method.getReturnType().getFullyQualifiedName(); + if (DocClassUtil.isMvcIgnoreParams(typeName)) { + if ("org.springframework.web.servlet.ModelAndView".equals(typeName)) { + return null; + } else { + throw new RuntimeException("smart-doc can't support " + typeName + " as method return in " + controllerName); + } + } + if (DocClassUtil.isPrimitive(typeName)) { + return primitiveReturnRespComment(DocClassUtil.processTypeNameForParams(typeName)); + } + if (DocClassUtil.isCollection(typeName)) { + if (returnType.contains("<")) { + String gicName = returnType.substring(returnType.indexOf("<") + 1, returnType.lastIndexOf(">")); + if (DocClassUtil.isPrimitive(gicName)) { + return primitiveReturnRespComment("array of " + DocClassUtil.processTypeNameForParams(gicName)); + } + String param = buildParams(gicName, "", 0, null, fieldMap, true); + return param; + } else { + return null; + } + } + if (DocClassUtil.isMap(typeName)) { + String[] keyValue = DocClassUtil.getMapKeyValueType(returnType); + if (keyValue.length == 0) { + return null; + } + if (DocClassUtil.isPrimitive(keyValue[1])) { + return primitiveReturnRespComment("key value"); + } + String param = buildParams(keyValue[1], "", 0, null, fieldMap, true); + return param; + } + if (StringUtil.isNotEmpty(returnType)) { + String param = buildParams(returnType, "", 0, null, fieldMap, true); + return param; + } + return null; + } + + /** + * @param className class name + * @param pre pre + * @param i counter + * @param isRequired required flag + * @param responseFieldMap response map + * @param isResp response flag + * @return params + */ + private String buildParams(String className, String pre, int i, String isRequired, + Map responseFieldMap, boolean isResp) { + if (StringUtil.isEmpty(className)) { + throw new RuntimeException("Class name can't be null or empty."); + } + StringBuilder params0 = new StringBuilder(); + String simpleName = DocClassUtil.getSimpleName(className); + + + String[] globGicName = DocClassUtil.getSimpleGicName(className); + JavaClass cls = builder.getClassByName(simpleName); + List fields = getFields(cls, 0); + int n = 0; + if (DocClassUtil.isPrimitive(simpleName)) { + params0.append(primitiveReturnRespComment(DocClassUtil.processTypeNameForParams(simpleName))); + } else if (DocClassUtil.isCollection(simpleName) || DocClassUtil.isArray(simpleName)) { + if (!DocClassUtil.isCollection(globGicName[0])) { + String gicName = globGicName[0]; + if (DocClassUtil.isArray(gicName)) { + gicName = gicName.substring(0, gicName.indexOf("[")); + } + params0.append(buildParams(gicName, pre, i + 1, isRequired, responseFieldMap, isResp)); + } + } else if (DocClassUtil.isMap(simpleName)) { + if (globGicName.length == 2) { + params0.append(buildParams(globGicName[1], pre, i + 1, isRequired, responseFieldMap, isResp)); + } + } else if ("java.lang.Object".equals(className)) { + params0.append(pre + "any object|object|"); + if (StringUtil.isEmpty(isRequired)) { + params0.append("any object.").append("\n"); + } else { + params0.append("any object.").append("|").append(false).append("\n"); + } + } else { + out: + for (JavaField field : fields) { + String fieldName = field.getName(); + if (!"serialVersionUID".equals(fieldName)) { + + String typeSimpleName = field.getType().getSimpleName(); + String subTypeName = field.getType().getFullyQualifiedName(); + String fieldGicName = field.getType().getGenericCanonicalName(); + List javaAnnotations = field.getAnnotations(); + + List paramTags = field.getTags(); + if (!isResp) { + pre: + for (DocletTag docletTag : paramTags) { + if (DocClassUtil.isIgnoreTag(docletTag.getName())) { + continue out; + } + } + } + String strRequired = "false"; + int annotationCounter = 0; + an: + for (JavaAnnotation annotation : javaAnnotations) { + String annotionName = annotation.getType().getSimpleName(); + if ("JsonIgnore".equals(annotionName) && isResp) { + continue out; + } else if ("JSONField".equals(annotionName) && isResp) { + if (null != annotation.getProperty("serialize")) { + if ("false".equals(annotation.getProperty("serialize").toString())) { + continue out; + } + } else if (null != annotation.getProperty("name")) { + fieldName = annotation.getProperty("name").toString().replace("\"", ""); + } + } else if ("JsonProperty".equals(annotionName) && isResp) { + if (null != annotation.getProperty("value")) { + fieldName = annotation.getProperty("value").toString().replace("\"", ""); + } + } else if (DocClassUtil.isJSR303Required(annotionName)) { + strRequired = "true"; + annotationCounter++; + break an; + } + } + if (annotationCounter < 1) { + doc: + for (DocletTag docletTag : paramTags) { + if (DocClassUtil.isRequiredTag(docletTag.getName())) { + strRequired = "true"; + break doc; + } + } + } + //cover comment + CustomRespField customResponseField = responseFieldMap.get(field.getName()); + String comment; + if (null != customResponseField && StringUtil.isNotEmpty(customResponseField.getDesc())) { + comment = customResponseField.getDesc(); + } else { + comment = field.getComment(); + } + if (DocClassUtil.isPrimitive(subTypeName)) { + params0.append(pre); + params0.append(fieldName).append("|") + .append(DocClassUtil.processTypeNameForParams(typeSimpleName.toLowerCase())).append("|"); + + if (StringUtil.isNotEmpty(comment)) { + if (StringUtil.isEmpty(isRequired)) { + params0.append(comment).append("\n"); + } else { + params0.append(comment).append("|").append(strRequired).append("\n"); + } + } else { + if (StringUtil.isEmpty(isRequired)) { + params0.append("No comments found.").append("\n"); + } else { + params0.append("No comments found.").append("|").append(strRequired).append("\n"); + } + } + } else { + params0.append(pre); + params0.append(fieldName).append("|") + .append(DocClassUtil.processTypeNameForParams(typeSimpleName.toLowerCase())).append("|"); + if (StringUtil.isNotEmpty(comment)) { + if (StringUtil.isEmpty(isRequired)) { + params0.append(comment).append("\n"); + } else { + params0.append(comment).append("|").append(strRequired).append("\n"); + } + } else { + if (StringUtil.isEmpty(isRequired)) { + params0.append("No comments found.").append("\n"); + } else { + params0.append("No comments found|").append(strRequired).append("\n"); + } + + } + StringBuilder preBuilder = new StringBuilder(); + for (int j = 0; j < i; j++) { + preBuilder.append("     "); + } + preBuilder.append("└─"); + if (DocClassUtil.isMap(subTypeName)) { + String gNameTemp = field.getType().getGenericCanonicalName(); + if ("java.util.Map".equals(gNameTemp)) { + params0.append(preBuilder + "any object|object|any object\n"); + continue; + } + String valType = DocClassUtil.getMapKeyValueType(gNameTemp)[1]; + if (!DocClassUtil.isPrimitive(valType)) { + if (valType.length() == 1) { + String gicName = (n < globGicName.length) ? globGicName[n] : globGicName[globGicName.length - 1]; + if (!DocClassUtil.isPrimitive(gicName) && !simpleName.equals(gicName)) { + params0.append(buildParams(gicName, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } else { + params0.append(buildParams(valType, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } + } else if (DocClassUtil.isCollection(subTypeName)) { + String gNameTemp = field.getType().getGenericCanonicalName(); + String[] gNameArr = DocClassUtil.getSimpleGicName(gNameTemp); + if (gNameArr.length == 0) { + continue out; + } + String gName = DocClassUtil.getSimpleGicName(gNameTemp)[0]; + if (!DocClassUtil.isPrimitive(gName)) { + if (!simpleName.equals(gName) && !gName.contains(simpleName)) { + if (gName.length() == 1) { + int len = globGicName.length; + if (len > 0) { + String gicName = (n < len) ? globGicName[n] : globGicName[len - 1]; + if (!DocClassUtil.isPrimitive(gicName) && !simpleName.equals(gicName)) { + params0.append(buildParams(gicName, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } + } else { + params0.append(buildParams(gName, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } + } + } else if (subTypeName.length() == 1 || "java.lang.Object".equals(subTypeName)) { + if (!simpleName.equals(className)) { + if (n < globGicName.length) { + String gicName = globGicName[n]; + String simple = DocClassUtil.getSimpleName(gicName); + if (DocClassUtil.isPrimitive(simple)) { + //do nothing + } else if (gicName.contains("<")) { + if (DocClassUtil.isCollection(simple)) { + String gName = DocClassUtil.getSimpleGicName(gicName)[0]; + if (!DocClassUtil.isPrimitive(gName)) { + params0.append(buildParams(gName, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } else if (DocClassUtil.isMap(simple)) { + String valType = DocClassUtil.getMapKeyValueType(gicName)[1]; + if (!DocClassUtil.isPrimitive(valType)) { + params0.append(buildParams(valType, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } else { + params0.append(buildParams(gicName, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } else { + params0.append(buildParams(gicName, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } else { + params0.append(buildParams(subTypeName, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } + n++; + } else if (DocClassUtil.isArray(subTypeName)) { + fieldGicName = fieldGicName.substring(0, fieldGicName.indexOf("[")); + if (className.equals(fieldGicName)) { + //do nothing + } else if (!DocClassUtil.isPrimitive(fieldGicName)) { + params0.append(buildParams(fieldGicName, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } else if (simpleName.equals(subTypeName)) { + //do nothing + } else { + params0.append(buildParams(fieldGicName, preBuilder.toString(), i + 1, isRequired, responseFieldMap, isResp)); + } + } + } + } + } + + return params0.toString(); + + } + + + private String primitiveReturnRespComment(String typeName) { + StringBuilder comments = new StringBuilder(); + comments.append("no param name|") + .append(typeName).append("|") + .append("The interface directly returns the ") + .append(typeName).append(" type value.\n"); + return comments.toString(); + } + + /** + * 构建返回的json + * + * @param method The JavaMethod object + * @return String + */ + private String buildReturnJson(JavaMethod method, Map responseFieldMap) { + if ("void".equals(method.getReturnType().getFullyQualifiedName())) { + return "this api return nothing."; + } + String returnType = method.getReturnType().getGenericCanonicalName(); + String typeName = method.getReturnType().getFullyQualifiedName(); + return JsonFormatUtil.formatJson(buildJson(typeName, returnType, responseFieldMap, true)); + } + + /** + * @param typeName type name + * @param genericCanonicalName genericCanonicalName + * @param responseFieldMap map of response fields data + * @param isResp response flag + * @return String + */ + private String buildJson(String typeName, String genericCanonicalName, Map responseFieldMap, boolean isResp) { + if (DocClassUtil.isMvcIgnoreParams(typeName)) { + if ("org.springframework.web.servlet.ModelAndView".equals(typeName)) { + return "forward or redirect to a page view."; + } else { + return "error restful return."; + } + } + if (DocClassUtil.isPrimitive(typeName)) { + return DocUtil.jsonValueByType(typeName); + } + StringBuilder data0 = new StringBuilder(); + JavaClass cls = builder.getClassByName(typeName); + data0.append("{"); + String[] globGicName = DocClassUtil.getSimpleGicName(genericCanonicalName); + StringBuilder data = new StringBuilder(); + if (DocClassUtil.isCollection(typeName) || DocClassUtil.isArray(typeName)) { + data.append("["); + if (globGicName.length == 0) { + data.append("{\"object\":\"any object\"}"); + data.append("]"); + return data.toString(); + } + String gNameTemp = globGicName[0]; + String gName = DocClassUtil.isArray(typeName) ? gNameTemp.substring(0, gNameTemp.indexOf("[")) : globGicName[0]; + if ("java.lang.Object".equals(gName)) { + data.append("{\"waring\":\"You may use java.util.Object instead of display generics in the List\"}"); + } else if (DocClassUtil.isPrimitive(gName)) { + data.append(DocUtil.jsonValueByType(gName)).append(","); + data.append(DocUtil.jsonValueByType(gName)); + } else if (gName.contains("<")) { + String simple = DocClassUtil.getSimpleName(gName); + String json = buildJson(simple, gName, responseFieldMap, isResp); + data.append(json); + } else if (DocClassUtil.isCollection(gName)) { + data.append("\"any object\""); + } else { + String json = buildJson(gName, gName, responseFieldMap, isResp); + data.append(json); + } + data.append("]"); + return data.toString(); + } else if (DocClassUtil.isMap(typeName)) { + String gNameTemp = genericCanonicalName; + String[] getKeyValType = DocClassUtil.getMapKeyValueType(gNameTemp); + if (getKeyValType.length == 0) { + data.append("{\"mapKey\":{}}"); + return data.toString(); + } + if (!"java.lang.String".equals(getKeyValType[0])) { + throw new RuntimeException("Map's key can only use String for json,but you use " + getKeyValType[0]); + } + String gicName = gNameTemp.substring(gNameTemp.indexOf(",") + 1, gNameTemp.lastIndexOf(">")); + if ("java.lang.Object".equals(gicName)) { + data.append("{").append("\"mapKey\":").append("{\"waring\":\"You may use java.util.Object for Map value; smart-doc can't be handle.\"}").append("}"); + } else if (DocClassUtil.isPrimitive(gicName)) { + data.append("{").append("\"mapKey1\":").append(DocUtil.jsonValueByType(gicName)).append(","); + data.append("\"mapKey2\":").append(DocUtil.jsonValueByType(gicName)).append("}"); + } else if (gicName.contains("<")) { + String simple = DocClassUtil.getSimpleName(gicName); + String json = buildJson(simple, gicName, responseFieldMap, isResp); + data.append("{").append("\"mapKey\":").append(json).append("}"); + } else { + data.append("{").append("\"mapKey\":").append(buildJson(gicName, gNameTemp, responseFieldMap, isResp)).append("}"); + } + return data.toString(); + } else if ("java.lang.Object".equals(typeName)) { + if ("java.lang.Object".equals(typeName)) { + data.append("{\"object\":\" any object\"},"); + // throw new RuntimeException("Please do not return java.lang.Object directly in api interface."); + } + } else { + List fields = getFields(cls, 0); + int i = 0; + out: + for (JavaField field : fields) { + String fieldName = field.getName(); + if (!"serialVersionUID".equals(fieldName)) { + List paramTags = field.getTags(); + if (!isResp) { + pre: + for (DocletTag docletTag : paramTags) { + if (DocClassUtil.isIgnoreTag(docletTag.getName())) { + continue out; + } + } + } + List annotations = field.getAnnotations(); + for (JavaAnnotation annotation : annotations) { + String annotationName = annotation.getType().getSimpleName(); + if ("JsonIgnore".equals(annotationName) && isResp) { + continue out; + } else if ("JSONField".equals(annotationName) && isResp) { + if (null != annotation.getProperty("serialize")) { + if ("false".equals(annotation.getProperty("serialize").toString())) { + continue out; + } + } else if (null != annotation.getProperty("name")) { + fieldName = annotation.getProperty("name").toString().replace("\"", ""); + } + } else if ("JsonProperty".equals(annotationName) && isResp) { + if (null != annotation.getProperty("value")) { + fieldName = annotation.getProperty("value").toString().replace("\"", ""); + } + } + } + String typeSimpleName = field.getType().getSimpleName(); + String subTypeName = field.getType().getFullyQualifiedName(); + String fieldGicName = field.getType().getGenericCanonicalName(); + data0.append("\"").append(fieldName).append("\":"); + if (DocClassUtil.isPrimitive(typeSimpleName)) { + CustomRespField customResponseField = responseFieldMap.get(fieldName); + if (null != customResponseField) { + Object val = customResponseField.getValue(); + if (null != val) { + if ("String".equals(typeSimpleName)) { + data0.append("\"").append(val).append("\","); + } else { + data0.append(val).append(","); + } + } else { + data0.append(DocUtil.getValByTypeAndFieldName(typeSimpleName, field.getName())).append(","); + } + } else { + data0.append(DocUtil.getValByTypeAndFieldName(typeSimpleName, field.getName())).append(","); + } + } else { + if (DocClassUtil.isCollection(subTypeName) || DocClassUtil.isArray(subTypeName)) { + fieldGicName = DocClassUtil.isArray(subTypeName) ? fieldGicName.substring(0, fieldGicName.indexOf("[")) : fieldGicName; + if (DocClassUtil.getSimpleGicName(fieldGicName).length == 0) { + data0.append("{\"object\":\"any object\"},"); + continue out; + } + String gicName = DocClassUtil.getSimpleGicName(fieldGicName)[0]; + + if ("java.lang.String".equals(gicName)) { + data0.append("[").append("\"").append(buildJson(gicName, fieldGicName, responseFieldMap, isResp)).append("\"]").append(","); + } else if ("java.util.List".equals(gicName)) { + data0.append("{\"object\":\"any object\"},"); + } else if (gicName.length() == 1) { + if (globGicName.length == 0) { + data0.append("{\"object\":\"any object\"},"); + continue out; + } + String gicName1 = (i < globGicName.length) ? globGicName[i] : globGicName[globGicName.length - 1]; + if ("java.lang.String".equals(gicName1)) { + data0.append("[").append("\"").append(buildJson(gicName1, gicName1, responseFieldMap, isResp)).append("\"]").append(","); + } else { + if (!typeName.equals(gicName1)) { + data0.append("[").append(buildJson(DocClassUtil.getSimpleName(gicName1), gicName1, responseFieldMap, isResp)).append("]").append(","); + } else { + data0.append("[{\"$ref\":\"..\"}]").append(","); + } + + } + } else { + if (!typeName.equals(gicName) && !gicName.contains(typeName)) { + if (MAP_CLASS.equals(gicName)) { + data0.append("[{\"mapKey\":{}}],"); + continue out; + } + data0.append("[").append(buildJson(gicName, fieldGicName, responseFieldMap, isResp)).append("]").append(","); + } else { + data0.append("[{\"$ref\":\"..\"}]").append(","); + } + } + } else if (DocClassUtil.isMap(subTypeName)) { + if ("java.util.Map".equals(subTypeName)) { + data0.append("{").append("\"mapKey\":{}},"); + continue out; + } + String gicName = fieldGicName.substring(fieldGicName.indexOf(",") + 1, fieldGicName.indexOf(">")); + if (gicName.length() == 1) { + String gicName1 = (i < globGicName.length) ? globGicName[i] : globGicName[globGicName.length - 1]; + if ("java.lang.String".equals(gicName1)) { + data0.append("{").append("\"mapKey\":\"").append(buildJson(gicName1, gicName1, responseFieldMap, isResp)).append("\"},"); + } else { + if (!typeName.equals(gicName1)) { + data0.append("{").append("\"mapKey\":").append(buildJson(DocClassUtil.getSimpleName(gicName1), gicName1, responseFieldMap, isResp)).append("},"); + } else { + data0.append("{\"mapKey\":{}},"); + } + } + } else { + data0.append("{").append("\"mapKey\":").append(buildJson(gicName, fieldGicName, responseFieldMap, isResp)).append("},"); + + } + } else if (subTypeName.length() == 1) { + if (!typeName.equals(genericCanonicalName)) { + String gicName = globGicName[i]; + if (gicName.contains("<")) { + String simple = DocClassUtil.getSimpleName(gicName); + data0.append(buildJson(simple, gicName, responseFieldMap, isResp)).append(","); + } else { + if (DocClassUtil.isPrimitive(gicName)) { + data0.append(DocUtil.jsonValueByType(gicName)).append(","); + } else { + data0.append(buildJson(gicName, gicName, responseFieldMap, isResp)).append(","); + } + } + } else { + data0.append("{\"waring\":\"You may have used non-display generics.\"},"); + } + i++; + } else if ("java.lang.Object".equals(subTypeName)) { + if (i < globGicName.length) { + String gicName = globGicName[i]; + if (!typeName.equals(genericCanonicalName)) { + if (DocClassUtil.isPrimitive(gicName)) { + data0.append("\"").append(buildJson(gicName, genericCanonicalName, responseFieldMap, isResp)).append("\","); + } else { + data0.append(buildJson(gicName, gicName, responseFieldMap, isResp)).append(","); + } + } else { + data0.append("{\"waring\":\"You may have used non-display generics.\"},"); + } + } else { + data0.append("{\"waring\":\"You may have used non-display generics.\"},"); + } + } else if (typeName.equals(subTypeName)) { + data0.append("{\"$ref\":\"...\"}").append(","); + } else { + // + data0.append(buildJson(subTypeName, fieldGicName, responseFieldMap, isResp)).append(","); + } + } + } + } + } + if (data0.toString().contains(",")) { + data0.deleteCharAt(data0.lastIndexOf(",")); + } + + data0.append("}"); + return data0.toString(); + } + + private String buildReqJson(JavaMethod method, ApiMethodDoc apiMethodDoc) { + List parameterList = method.getParameters(); + for (JavaParameter parameter : parameterList) { + JavaType javaType = parameter.getType(); + String simpleTypeName = javaType.getValue(); + String gicTypeName = javaType.getGenericCanonicalName(); + String typeName = javaType.getFullyQualifiedName(); + String paraName = parameter.getName(); + if (!DocClassUtil.isMvcIgnoreParams(typeName)) { + List annotations = parameter.getAnnotations(); + int requestBodyCounter = 0; + for (JavaAnnotation annotation : annotations) { + String annotationName = annotation.getType().getSimpleName(); + if (REQUEST_BODY.equals(annotationName) || REQUEST_BODY_FULLY.equals(annotationName)) { + requestBodyCounter++; + apiMethodDoc.setContentType(JSON_CONTENT_TYPE); + if (DocClassUtil.isPrimitive(simpleTypeName)) { + StringBuilder builder = new StringBuilder(); + builder.append("{\"") + .append(paraName) + .append("\":") + .append(DocUtil.jsonValueByType(simpleTypeName)) + .append("}"); + return builder.toString(); + } else { + return buildJson(typeName, gicTypeName, this.fieldMap, false); + } + + } + + } + if (requestBodyCounter < 1) { + //not json + return "smart-doc currently cannot provide examples of parameters for the RequestParam request mode."; + + } + + } + } + return "No request parameters are required."; + } + + /** + * Get tag + * + * @param javaMethod The JavaMethod method + * @param tagName The doc tag name + * @param className The class name + * @return String + */ + private String getCommentTag(final JavaMethod javaMethod, final String tagName, final String className) { + // + Map responseFieldMap = new HashMap<>(); + List paramTags = javaMethod.getTagsByName(tagName); + Map paramTagMap = new HashMap<>(); + for (DocletTag docletTag : paramTags) { + String value = docletTag.getValue(); + if (StringUtil.isEmpty(value)) { + throw new RuntimeException("ERROR: #" + javaMethod.getName() + + "() - bad @param javadoc from " + className); + } + String pName; + String pValue; + int idx = value.indexOf("\n"); + //如果存在换行 + if (idx > -1) { + pName = value.substring(0, idx); + pValue = value.substring(idx + 1); + } else { + pName = (value.indexOf(" ") > -1) ? value.substring(0, value.indexOf(" ")) : value; + pValue = value.indexOf(" ") > -1 ? value.substring(value.indexOf(' ') + 1) : "No comments found."; + } + paramTagMap.put(pName, pValue); + } + + List parameterList = javaMethod.getParameters(); + if (parameterList.size() > 0) { + StringBuilder params = new StringBuilder(); + int requestBodyCounter = 0; + StringBuilder reqBodyParams = new StringBuilder(); + StringBuilder reqParam = new StringBuilder(); + out: + for (JavaParameter parameter : parameterList) { + String paramName = parameter.getName(); + String typeName = parameter.getType().getGenericCanonicalName(); + String simpleName = parameter.getType().getValue().toLowerCase(); + String fullTypeName = parameter.getType().getFullyQualifiedName(); + if (!DocClassUtil.isMvcIgnoreParams(typeName)) { + if (!paramTagMap.containsKey(paramName) && DocClassUtil.isPrimitive(fullTypeName) && isStrict) { + throw new RuntimeException("ERROR: Unable to find javadoc @param for actual param \"" + + paramName + "\" in method " + javaMethod.getName() + " from " + className); + } + String comment = paramTagMap.get(paramName); + if (StringUtil.isEmpty(comment)) { + comment = "No comments found."; + } + List annotations = parameter.getAnnotations(); + if (annotations.size() == 0) { + //default set required is true + if (DocClassUtil.isCollection(fullTypeName) || DocClassUtil.isArray(fullTypeName)) { + String[] gicNameArr = DocClassUtil.getSimpleGicName(typeName); + String gicName = gicNameArr[0]; + if (DocClassUtil.isArray(gicName)) { + gicName = gicName.substring(0, gicName.indexOf("[")); + } + String typeTemp = ""; + if (DocClassUtil.isPrimitive(gicName)) { + typeTemp = " of " + DocClassUtil.processTypeNameForParams(gicName); + reqParam.append(paramName).append("|") + .append(DocClassUtil.processTypeNameForParams(simpleName)).append(typeTemp).append("|") + .append(comment).append("|true\n"); + } else { + reqParam.append(paramName).append("|") + .append(DocClassUtil.processTypeNameForParams(simpleName)).append(typeTemp).append("|") + .append(comment).append("|true\n"); + String strPrams = buildParams(gicNameArr[0], "└─", 1, "true", responseFieldMap, false); + reqParam.append(strPrams); + } + + } else if (DocClassUtil.isPrimitive(simpleName)) { + reqParam.append(paramName).append("|") + .append(DocClassUtil.processTypeNameForParams(simpleName)).append("|") + .append(comment).append("|true\n"); + } else if ("java.util.Map".equals(typeName)) { + reqParam.append(paramName).append("|") + .append("map").append("|") + .append(comment).append("|true\n"); + } else { + reqParam.append(buildParams(fullTypeName, "", 0, "true", responseFieldMap, false)); + } + + } + for (JavaAnnotation annotation : annotations) { + String required = "true"; + if (null != annotation.getProperty("required")) { + required = annotation.getProperty("required").toString(); + } + String annotationName = annotation.getType().getName(); + if (REQUEST_BODY.equals(annotationName)) { + if (requestBodyCounter > 0) { + throw new RuntimeException("You have use @RequestBody Passing multiple variables for method " + + javaMethod.getName() + " in " + className + ",@RequestBody annotation could only bind one variables."); + } + if (DocClassUtil.isPrimitive(fullTypeName)) { + reqBodyParams.append(paramName).append("|") + .append(DocClassUtil.processTypeNameForParams(simpleName)).append("|") + .append(comment).append("|").append(required).append("\n"); + } else { + if (DocClassUtil.isCollection(fullTypeName) || DocClassUtil.isArray(fullTypeName)) { + String[] gicNameArr = DocClassUtil.getSimpleGicName(typeName); + String gicName = gicNameArr[0]; + if (DocClassUtil.isArray(gicName)) { + gicName = gicName.substring(0, gicName.indexOf("[")); + } + if (DocClassUtil.isPrimitive(gicName)) { + reqBodyParams.append(paramName).append("|") + .append(DocClassUtil.processTypeNameForParams(simpleName)).append("|") + .append(comment).append("|").append(required).append("\n"); + } else { + String strPrams = buildParams(gicNameArr[0], "", 0, "true", responseFieldMap, false); + reqBodyParams.append(strPrams); + } + + } else if (DocClassUtil.isMap(fullTypeName)) { + if ("java.util.Map".equals(typeName)) { + reqParam.append(paramName).append("|") + .append("map").append("|") + .append(comment).append("|").append(required).append("\n"); + continue out; + } + String[] gicNameArr = DocClassUtil.getSimpleGicName(typeName); + String strPrams = buildParams(gicNameArr[1], "", 0, "true", responseFieldMap, false); + reqBodyParams.append(strPrams); + } else { + reqBodyParams.append(buildParams(typeName, "", 0, "true", responseFieldMap, false)); + } + } + requestBodyCounter++; + } else { + reqParam.append(paramName).append("|") + .append(DocClassUtil.processTypeNameForParams(simpleName)).append("|") + .append(comment).append("|") + .append(required).append("\n"); + } + } + + } + } + if (requestBodyCounter > 0) { + params.append(reqBodyParams); + return params.toString(); + } + params.append(reqParam); + return params.toString(); + } + return null; + } + + /** + * Get fields + * + * @param cls1 The JavaClass object + * @param i Recursive counter + * @return list of JavaField + */ + private List getFields(JavaClass cls1, int i) { + List fieldList = new ArrayList<>(); + if (null == cls1) { + return fieldList; + } else if ("Object".equals(cls1.getSimpleName()) || "Timestamp".equals(cls1.getSimpleName()) || + "Date".equals(cls1.getSimpleName()) || "Locale".equals(cls1.getSimpleName())) { + return fieldList; + } else { + JavaClass pcls = cls1.getSuperJavaClass(); + fieldList.addAll(getFields(pcls, i)); + fieldList.addAll(cls1.getFields()); + } + return fieldList; + } +} diff --git a/src/main/java/com/power/doc/constants/GlobalConstants.java b/src/main/java/com/power/doc/constants/GlobalConstants.java new file mode 100644 index 0000000..72e9022 --- /dev/null +++ b/src/main/java/com/power/doc/constants/GlobalConstants.java @@ -0,0 +1,17 @@ +package com.power.doc.constants; + +/** + * @author yu 2018/12/15. + */ +public class GlobalConstants { + + /** + * controller注解全名称 + */ + public static final String CONTROLLER_FULLY = "org.springframework.stereotype.Controller"; + + /** + * rest controller注解全名称 + */ + public static final String REST_CONTROLLER_FULLY = "org.springframework.web.bind.annotation.RestController"; +} diff --git a/src/main/java/com/power/doc/model/ApiConfig.java b/src/main/java/com/power/doc/model/ApiConfig.java new file mode 100644 index 0000000..3a463a4 --- /dev/null +++ b/src/main/java/com/power/doc/model/ApiConfig.java @@ -0,0 +1,149 @@ +package com.power.doc.model; + +import com.power.common.util.CollectionUtil; + +import java.util.List; + +/** + * Description: + * Api配置 + * + * @author yu 2018/06/18. + */ +public class ApiConfig { + + /** + * 应用请求base路径 + */ + private String serverUrl; + + /** + * 是否采用严格模式 + */ + private boolean isStrict; + + /** + * 是否将markdown全输出合并到一个文件 + */ + private boolean allInOne; + + /** + * 输出路径 + */ + private String outPath; + + + /** + * source path + */ + private List sourcePaths; + + /** + * 请求头 + */ + private List requestHeaders; + + /** + * 自定义字段 + */ + private List customResponseFields; + + /** + * 错误码code列表 + * @return + */ + + private List errorCodes; + + /** + * controller包过滤 + */ + private String packageFilters; + + /** + * 接口变更日志 + */ + private List revisionLogs; + + public String getServerUrl() { + return serverUrl; + } + + public void setServerUrl(String serverUrl) { + this.serverUrl = serverUrl; + } + + public boolean isStrict() { + return isStrict; + } + + public void setStrict(boolean strict) { + isStrict = strict; + } + + public String getOutPath() { + return outPath; + } + + public void setOutPath(String outPath) { + this.outPath = outPath; + } + + public List getRequestHeaders() { + return requestHeaders; + } + + public void setRequestHeaders(ApiReqHeader... requestHeaders) { + this.requestHeaders = CollectionUtil.asList(requestHeaders); + } + + public List getCustomResponseFields() { + return customResponseFields; + } + + public void setCustomResponseFields(CustomRespField... customResponseFields) { + this.customResponseFields = CollectionUtil.asList(customResponseFields); + } + + + public List getErrorCodes() { + return errorCodes; + } + + public void setErrorCodes(List errorCodes) { + this.errorCodes = errorCodes; + } + + public List getSourcePaths() { + return sourcePaths; + } + + public void setSourcePaths(SourcePath... sourcePaths) { + this.sourcePaths = CollectionUtil.asList(sourcePaths); + } + + public boolean isAllInOne() { + return allInOne; + } + + public void setAllInOne(boolean allInOne) { + this.allInOne = allInOne; + } + + public String getPackageFilters() { + return packageFilters; + } + + public void setPackageFilters(String packageFilters) { + this.packageFilters = packageFilters; + } + + public void setRevisionLogs(RevisionLog... revisionLogs){ + this.revisionLogs = CollectionUtil.asList(revisionLogs); + } + + public List getRevisionLogs() { + return revisionLogs; + } + +} diff --git a/src/main/java/com/power/doc/model/ApiDoc.java b/src/main/java/com/power/doc/model/ApiDoc.java new file mode 100644 index 0000000..462cb87 --- /dev/null +++ b/src/main/java/com/power/doc/model/ApiDoc.java @@ -0,0 +1,48 @@ +package com.power.doc.model; + +import java.util.List; + +public class ApiDoc { + + + /** + * 类名 + */ + private String name; + + /** + * 方法文档列表 + */ + private List list; + + /** + * 类注解描述 + */ + private String desc; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + +} diff --git a/src/main/java/com/power/doc/model/ApiErrorCode.java b/src/main/java/com/power/doc/model/ApiErrorCode.java new file mode 100644 index 0000000..d23de3e --- /dev/null +++ b/src/main/java/com/power/doc/model/ApiErrorCode.java @@ -0,0 +1,39 @@ +package com.power.doc.model; + +/** + * Description: + * restful api错误码 + * + * @author yu 2018/06/25. + */ +public class ApiErrorCode { + + /** + * 错误码 + */ + private String value; + + /** + * 错误描述 + */ + private String desc; + + + public String getValue() { + return value; + } + + public ApiErrorCode setValue(String value) { + this.value = value; + return this; + } + + public String getDesc() { + return desc; + } + + public ApiErrorCode setDesc(String desc) { + this.desc = desc; + return this; + } +} diff --git a/src/main/java/com/power/doc/model/ApiMethodDoc.java b/src/main/java/com/power/doc/model/ApiMethodDoc.java new file mode 100644 index 0000000..50b0e60 --- /dev/null +++ b/src/main/java/com/power/doc/model/ApiMethodDoc.java @@ -0,0 +1,100 @@ +package com.power.doc.model; + +import java.io.Serializable; + +/** + * api文档 + */ +public class ApiMethodDoc implements Serializable { + + private String desc; + + private String url; + + private String type; + + private String headers; + + private String contentType = "application/x-www-form-urlencoded"; + + private String requestParams; + + private String requestUsage; + + private String responseUsage; + + private String responseParams; + + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRequestParams() { + return requestParams; + } + + public void setRequestParams(String requestParams) { + this.requestParams = requestParams; + } + + public String getResponseUsage() { + return responseUsage; + } + + public void setResponseUsage(String responseUsage) { + this.responseUsage = responseUsage; + } + + public String getResponseParams() { + return responseParams; + } + + public void setResponseParams(String responseParams) { + this.responseParams = responseParams; + } + + public String getRequestUsage() { + return requestUsage; + } + + public void setRequestUsage(String requestUsage) { + this.requestUsage = requestUsage; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getHeaders() { + return headers; + } + + public void setHeaders(String headers) { + this.headers = headers; + } +} diff --git a/src/main/java/com/power/doc/model/ApiReqHeader.java b/src/main/java/com/power/doc/model/ApiReqHeader.java new file mode 100644 index 0000000..fa15cd8 --- /dev/null +++ b/src/main/java/com/power/doc/model/ApiReqHeader.java @@ -0,0 +1,55 @@ +package com.power.doc.model; + +/** + * Description: + * 请求头 + * + * @author yu 2018/06/18. + */ +public class ApiReqHeader { + + /** + * 请求头的名称 + */ + private String name; + + /** + * 请求头类型 + */ + private String type; + /** + * 请求头描述 + */ + private String desc; + + public static ApiReqHeader header(){ + return new ApiReqHeader(); + } + + public String getName() { + return name; + } + + public ApiReqHeader setName(String name) { + this.name = name; + return this; + } + + public String getType() { + return type; + } + + public ApiReqHeader setType(String type) { + this.type = type; + return this; + } + + public String getDesc() { + return desc; + } + + public ApiReqHeader setDesc(String desc) { + this.desc = desc; + return this; + } +} diff --git a/src/main/java/com/power/doc/model/CustomRespField.java b/src/main/java/com/power/doc/model/CustomRespField.java new file mode 100644 index 0000000..4525d78 --- /dev/null +++ b/src/main/java/com/power/doc/model/CustomRespField.java @@ -0,0 +1,70 @@ +package com.power.doc.model; + +/** + * Description: + * Api 自动义字段修正 + * + * @author yu 2018/06/18. + */ +public class CustomRespField { + + /** + * 字段名 + */ + private String name; + + /** + * 字段描述 + */ + private String desc; + + /** + * 字段隶属类 + */ + private String ownerClassName; + + /** + * 默认值 + */ + private Object value; + + public static CustomRespField field(){ + return new CustomRespField(); + } + + public String getName() { + return name; + } + + public CustomRespField setName(String name) { + this.name = name; + return this; + } + + public String getDesc() { + return desc; + } + + public CustomRespField setDesc(String desc) { + this.desc = desc; + return this; + } + + public String getOwnerClassName() { + return ownerClassName; + } + + public CustomRespField setOwnerClassName(String ownerClassName) { + this.ownerClassName = ownerClassName; + return this; + } + + public Object getValue() { + return value; + } + + public CustomRespField setValue(Object value) { + this.value = value; + return this; + } +} diff --git a/src/main/java/com/power/doc/model/RevisionLog.java b/src/main/java/com/power/doc/model/RevisionLog.java new file mode 100644 index 0000000..6289f03 --- /dev/null +++ b/src/main/java/com/power/doc/model/RevisionLog.java @@ -0,0 +1,84 @@ +package com.power.doc.model; + +/** + * 接口文档修订日志 + * @author yolanda0608 2018/12/15 + */ +public class RevisionLog { + + /** + * 修订版本 + */ + private String version; + + /** + * 状态 + */ + private String status; + + /** + * 作者 + */ + private String author; + + /** + * 修订时间 + */ + private String revisionTime; + + /** + * 备注 + */ + private String remarks; + + + + public String getVersion() { + return version; + } + + public RevisionLog setVersion(String version) { + this.version = version; + return this; + } + + public String getStatus() { + return status; + } + + public RevisionLog setStatus(String status) { + this.status = status; + return this; + } + + public String getAuthor() { + return author; + } + + public RevisionLog setAuthor(String author) { + this.author = author; + return this; + } + + public String getRevisionTime() { + return revisionTime; + } + + public RevisionLog setRevisionTime(String revisionTime) { + this.revisionTime = revisionTime; + return this; + } + + public String getRemarks() { + return remarks; + } + + public RevisionLog setRemarks(String remarks) { + this.remarks = remarks; + return this; + } + + public static RevisionLog getLog(){ + return new RevisionLog(); + } +} diff --git a/src/main/java/com/power/doc/model/SourcePath.java b/src/main/java/com/power/doc/model/SourcePath.java new file mode 100644 index 0000000..fa697b2 --- /dev/null +++ b/src/main/java/com/power/doc/model/SourcePath.java @@ -0,0 +1,39 @@ +package com.power.doc.model; + +/** + * @author yu 2018/7/14. + */ +public class SourcePath { + + /** + * Source path + */ + private String path; + + /** + * path description + */ + private String desc; + + public static SourcePath path() { + return new SourcePath(); + } + + public String getPath() { + return path; + } + + public SourcePath setPath(String path) { + this.path = path; + return this; + } + + public String getDesc() { + return desc; + } + + public SourcePath setDesc(String desc) { + this.desc = desc; + return this; + } +} diff --git a/src/main/java/com/power/doc/utils/BeetlTemplateUtil.java b/src/main/java/com/power/doc/utils/BeetlTemplateUtil.java new file mode 100644 index 0000000..38e84f7 --- /dev/null +++ b/src/main/java/com/power/doc/utils/BeetlTemplateUtil.java @@ -0,0 +1,69 @@ +package com.power.doc.utils; + +import com.power.common.util.FileUtil; +import org.beetl.core.Configuration; +import org.beetl.core.GroupTemplate; +import org.beetl.core.Template; +import org.beetl.core.resource.ClasspathResourceLoader; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * 获取模板 + * + * @author sunyu on 2016/12/6. + */ +public class BeetlTemplateUtil { + public static Template getByName(String templateName) { + try { + ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader("/template/"); + Configuration cfg = Configuration.defaultConfiguration(); + GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); + return gt.getTemplate(templateName); + } catch (IOException e) { + throw new RuntimeException("获取模板异常"); + } + } + + /** + * + * @param path path + * @param params params + * @return map + */ + public static Map getTemplatesRendered(String path, Map params) { + Map templateMap = new HashMap<>(); + File[] files = FileUtil.getResourceFolderFiles(path); + GroupTemplate gt = getGroupTemplate(path); + for (File f : files) { + if (f.isFile()) { + String fileName = f.getName(); + Template tp = gt.getTemplate(fileName); + if (null != params) { + tp.binding(params); + } + templateMap.put(fileName, tp.render()); + } + } + return templateMap; + } + + /** + * + * @param path + * @return + */ + private static GroupTemplate getGroupTemplate(String path) { + try { + ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader("/"+path+"/"); + Configuration cfg = Configuration.defaultConfiguration(); + GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); + return gt; + } catch (IOException e) { + throw new RuntimeException("获取模板异常"); + } + } +} diff --git a/src/main/java/com/power/doc/utils/DocClassUtil.java b/src/main/java/com/power/doc/utils/DocClassUtil.java new file mode 100644 index 0000000..7543752 --- /dev/null +++ b/src/main/java/com/power/doc/utils/DocClassUtil.java @@ -0,0 +1,390 @@ +package com.power.doc.utils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Description: + * class的工具 + * + * @author yu 2018//14. + */ +public class DocClassUtil { + + /** + * Check if it is the basic data type of json data + * + * @param type0 java class name + * @return boolean + */ + public static boolean isPrimitive(String type0) { + String type = type0.contains("java.lang") ? type0.substring(type0.lastIndexOf(".") + 1, type0.length()) : type0; + type = type.toLowerCase(); + switch (type) { + case "integer": + return true; + case "int": + return true; + case "long": + return true; + case "double": + return true; + case "float": + return true; + case "short": + return true; + case "bigdecimal": + return true; + case "char": + return true; + case "string": + return true; + case "boolean": + return true; + case "byte": + return true; + case "java.sql.timestamp": + return true; + case "java.util.date": + return true; + case "java.time.localdatetime": + return true; + case "localdatetime": + return true; + case "localdate": + return true; + case "java.time.localdate": + return true; + case "java.math.bigdecimal": + return true; + case "java.math.biginteger": + return true; + default: + return false; + } + } + + /** + * get class names by generic class name + * + * @param returnType generic class name + * @return array of string + */ + public static String[] getSimpleGicName(String returnType) { + if (returnType.contains("<")) { + String pre = returnType.substring(0, returnType.indexOf("<")); + if (DocClassUtil.isMap(pre)) { + return getMapKeyValueType(returnType); + } + String type = returnType.substring(returnType.indexOf("<") + 1, returnType.lastIndexOf(">")); + if (DocClassUtil.isCollection(pre)) { + return type.split(" "); + } + String[] arr = type.split(","); + return classNameFix(arr); + } else { + return returnType.split(" "); + } + } + + /** + * Get a simple type name from a generic class name + * + * @param gicName Generic class name + * @return String + */ + public static String getSimpleName(String gicName) { + if (gicName.contains("<")) { + return gicName.substring(0, gicName.indexOf("<")); + } else { + return gicName; + } + } + + /** + * Automatic repair of generic split class names + * + * @param arr arr of class name + * @return array of String + */ + private static String[] classNameFix(String[] arr) { + List classes = new ArrayList<>(); + List indexList = new ArrayList<>(); + int globIndex = 0; + for (int i = 0; i < arr.length; i++) { + if (classes.size() > 0) { + int index = classes.size() - 1; + if (!DocUtil.isClassName(classes.get(index))) { + globIndex = globIndex + 1; + if (globIndex < arr.length) { + indexList.add(globIndex); + String className = classes.get(index) + "," + arr[globIndex]; + classes.set(index, className); + } + + } else { + globIndex = globIndex + 1; + if (globIndex < arr.length) { + if (DocUtil.isClassName(arr[globIndex])) { + indexList.add(globIndex); + classes.add(arr[globIndex]); + } else { + if (!indexList.contains(globIndex) && !indexList.contains(globIndex + 1)) { + indexList.add(globIndex); + classes.add(arr[globIndex] + "," + arr[globIndex + 1]); + globIndex = globIndex + 1; + indexList.add(globIndex); + } + } + } + } + } else { + if (DocUtil.isClassName(arr[i])) { + indexList.add(i); + classes.add(arr[i]); + } else { + if (!indexList.contains(i) && !indexList.contains(i + 1)) { + globIndex = i + 1; + classes.add(arr[i] + "," + arr[globIndex]); + indexList.add(i); + indexList.add(i + 1); + } + } + } + } + return classes.toArray(new String[classes.size()]); + } + + /** + * get map key and value type name populate into array. + * + * @param gName generic class name + * @return array of string + */ + public static String[] getMapKeyValueType(String gName) { + if(gName.contains("<")){ + String[] arr = new String[2]; + String key = gName.substring(gName.indexOf("<") + 1, gName.indexOf(",")); + String value = gName.substring(gName.indexOf(",") + 1, gName.lastIndexOf(">")); + arr[0] = key; + arr[1] = value; + return arr; + }else { + return new String[0]; + } + + } + + /** + * Convert the parameter types exported to the api document + * + * @param javaTypeName java simple typeName + * @return String + */ + public static String processTypeNameForParams(String javaTypeName) { + if (javaTypeName.length() == 1) { + return "object"; + } + if(javaTypeName.contains("[]")){ + return "array"; + } + switch (javaTypeName) { + case "java.lang.String": + return "string"; + case "string": + return "string"; + case "char": + return "char"; + case "java.util.List": + return "array"; + case "list": + return "array"; + case "java.lang.Integer": + return "int"; + case "integer": + return "int"; + case "int": + return "int"; + case "short": + return "int"; + case "java.lang.Short": + return "int"; + case "double": + return "number"; + case "java.lang.Long": + return "number"; + case "long": + return "number"; + case "java.lang.Float": + return "number"; + case "bigdecimal": + return "number"; + case "biginteger": + return "number"; + case "float": + return "number"; + case "java.lang.Boolean": + return "boolean"; + case "boolean": + return "boolean"; + case "java.util.Byte": + return "string"; + case "byte": + return "string"; + case "map": + return "map"; + case "date": + return "string"; + case "localdatetime": + return "string"; + case "localdate": + return "string"; + default: + return "object"; + } + + } + + /** + * validate java collection + * + * @param type java typeName + * @return boolean + */ + public static boolean isCollection(String type) { + switch (type) { + case "java.util.List": + return true; + case "java.util.LinkedList": + return true; + case "java.util.ArrayList": + return true; + case "java.util.Set": + return true; + case "java.util.TreeSet": + return true; + case "java.util.HashSet": + return true; + case "java.util.SortedSet": + return true; + case "java.util.Collection": + return true; + case "java.util.ArrayDeque": + return true; + case "java.util.PriorityQueue": + return true; + default: + return false; + } + } + + /** + * Check if it is an map + * + * @param type java type + * @return boolean + */ + public static boolean isMap(String type) { + switch (type) { + case "java.util.Map": + return true; + case "java.util.SortedMap": + return true; + case "java.util.TreeMap": + return true; + case "java.util.LinkedHashMap": + return true; + case "java.util.HashMap": + return true; + case "java.util.concurrent.ConcurrentHashMap": + return true; + case "java.util.Properties": + return true; + case "java.util.Hashtable": + return true; + default: + return false; + } + } + + /** + * check array + * @param type type name + * @return boolean + */ + public static boolean isArray(String type){ + return type.contains("[]"); + } + + /** + * check JSR303 + * @param annotationSimpleName annotation name + * @return boolean + */ + public static boolean isJSR303Required(String annotationSimpleName) { + switch (annotationSimpleName) { + case "NotNull": + return true; + case "NotEmpty": + return true; + case "NotBlank": + return true; + case "Required": + return true; + default: + return false; + } + } + + /** + * custom tag + * @param tagName custom field tag + * @return boolean + */ + public static boolean isRequiredTag(String tagName){ + switch (tagName) { + case "required": + return true; + default: + return false; + } + } + + /** + * ignore tag request field + * @param tagName custom field tag + * @return boolean + */ + public static boolean isIgnoreTag(String tagName){ + switch (tagName) { + case "ignore": + return true; + default: + return false; + } + } + + /** + * ignore param of spring mvc + * @param paramType param type name + * @return boolean + */ + public static boolean isMvcIgnoreParams(String paramType){ + switch (paramType){ + case "org.springframework.ui.Model": + return true; + case "org.springframework.ui.ModelMap": + return true; + case "org.springframework.web.servlet.ModelAndView": + return true; + case "org.springframework.validation.BindingResult" : + return true; + case "javax.servlet.http.HttpServletRequest": + return true; + case "javax.servlet.http.HttpServletResponse": + return true; + default: + return false; + } + } +} diff --git a/src/main/java/com/power/doc/utils/DocUtil.java b/src/main/java/com/power/doc/utils/DocUtil.java new file mode 100644 index 0000000..7f7fdad --- /dev/null +++ b/src/main/java/com/power/doc/utils/DocUtil.java @@ -0,0 +1,159 @@ +package com.power.doc.utils; + +import com.github.javafaker.Faker; +import com.power.common.util.DateTimeUtil; +import com.power.common.util.IDCardUtil; +import com.power.common.util.RandomUtil; +import com.power.common.util.StringUtil; + +import java.util.*; +import java.util.regex.Pattern; + +/** + * Description: + * DocUtil + * + * @author yu 2018/06/11. + */ +public class DocUtil { + + private static Faker faker = new Faker(new Locale("zh-CN")); + + private static Faker enFaker = new Faker(new Locale("en-US")); + + private static Map fieldValue = new LinkedHashMap<>(); + + static { + fieldValue.put("uuid-string", UUID.randomUUID().toString()); + fieldValue.put("uid",UUID.randomUUID().toString()); + fieldValue.put("nickname-string",enFaker.name().username()); + fieldValue.put("name-string",faker.name().username()); + fieldValue.put("url-string",faker.internet().url()); + fieldValue.put("username-string",faker.name().username()); + fieldValue.put("age-int",String.valueOf(RandomUtil.randomInt(0,70))); + fieldValue.put("age-integer",String.valueOf(RandomUtil.randomInt(0,70))); + fieldValue.put("email-string",faker.internet().emailAddress()); + fieldValue.put("domain-string",faker.internet().domainName()); + fieldValue.put("phone-string",faker.phoneNumber().cellPhone()); + fieldValue.put("mobile-string",faker.phoneNumber().cellPhone()); + fieldValue.put("telephone-string",faker.phoneNumber().phoneNumber()); + fieldValue.put("address-string",faker.address().fullAddress().replace(",",",")); + fieldValue.put("ip-string",faker.internet().ipV4Address()); + fieldValue.put("ipv4-string",faker.internet().ipV4Address()); + fieldValue.put("ipv6-string",faker.internet().ipV6Address()); + fieldValue.put("company-string",faker.company().name()); + fieldValue.put("timestamp-long",String.valueOf(System.currentTimeMillis())); + fieldValue.put("timestamp-string",DateTimeUtil.dateToStr(new Date(),DateTimeUtil.DATE_FORMAT_SECOND)); + fieldValue.put("time-long",String.valueOf(System.currentTimeMillis())); + fieldValue.put("time-string",DateTimeUtil.dateToStr(new Date(),DateTimeUtil.DATE_FORMAT_SECOND)); + fieldValue.put("birthday-string", DateTimeUtil.dateToStr(new Date(),DateTimeUtil.DATE_FORMAT_DAY)); + fieldValue.put("birthday-long",String.valueOf(System.currentTimeMillis())); + fieldValue.put("code-string",String.valueOf(RandomUtil.randomInt(100,99999))); + fieldValue.put("message-string","success,fail".split(",")[RandomUtil.randomInt(0,1)]); + fieldValue.put("date-string",DateTimeUtil.dateToStr(new Date(),DateTimeUtil.DATE_FORMAT_DAY)); + fieldValue.put("date-date",DateTimeUtil.dateToStr(new Date(),DateTimeUtil.DATE_FORMAT_DAY)); + fieldValue.put("state-int",String.valueOf(RandomUtil.randomInt(0,10))); + fieldValue.put("state-integer",String.valueOf(RandomUtil.randomInt(0,10))); + fieldValue.put("flag-int",String.valueOf(RandomUtil.randomInt(0,10))); + fieldValue.put("flag-integer",String.valueOf(RandomUtil.randomInt(0,10))); + fieldValue.put("flag-boolean","true"); + fieldValue.put("flag-Boolean","false"); + fieldValue.put("idcard-string", IDCardUtil.getIdCard()); + fieldValue.put("sex-int",String.valueOf(RandomUtil.randomInt(0,1))); + fieldValue.put("sex-integer",String.valueOf(RandomUtil.randomInt(0,1))); + fieldValue.put("gender-int",String.valueOf(RandomUtil.randomInt(0,1))); + fieldValue.put("gender-integer",String.valueOf(RandomUtil.randomInt(0,1))); + + } + /** + * 随机生成json值 + * @param type0 type name + * @return string + */ + public static String jsonValueByType(String type0){ + String type = type0.contains(".")?type0.substring(type0.lastIndexOf(".")+1,type0.length()):type0; + String value = RandomUtil.randomValueByType(type); + if("Integer".equals(type)||"int".equals(type)||"Long".equals(type)||"long".equals(type) + ||"Double".equals(type)||"double".equals(type)|| "Float".equals(type)||"float".equals(type)|| + "BigDecimal".equals(type)||"boolean".equals(type)||"Boolean".equals(type)|| + "Short".equals(type)||"BigInteger".equals(type)){ + return value; + }else{ + StringBuilder builder = new StringBuilder(); + builder.append("\"").append(value).append("\""); + return builder.toString(); + } + } + + /** + * 根据字段字段名和type生成字段值 + * @param type0 类型 + * @param filedName 字段名称 + * @return string + */ + public static String getValByTypeAndFieldName(String type0,String filedName){ + String type = type0.contains("java.lang")?type0.substring(type0.lastIndexOf(".")+1,type0.length()):type0; + String key = filedName.toLowerCase()+"-"+type.toLowerCase(); + String value = null; + for(Map.Entry entry:fieldValue.entrySet()){ + if(key.contains(entry.getKey())){ + value = entry.getValue(); + break; + } + } + if(null == value){ + return jsonValueByType(type0); + }else{ + if("string".equals(type.toLowerCase())){ + StringBuilder builder = new StringBuilder(); + builder.append("\"").append(value).append("\""); + return builder.toString(); + }else{ + return value; + } + } + } + + /** + * 是否是合法的java类名称 + * @param className class nem + * @return boolean + */ + public static boolean isClassName(String className){ + if(StringUtil.isEmpty(className)){ + return false; + } + if(className.contains("<")&&!className.contains(">")){ + return false; + }else if(className.contains(">")&&!className.contains("<")){ + return false; + }else{ + return true; + } + } + + /** + * match controller package + * @param packageFilters package filter + * @param controllerName controller name + * @return boolean + */ + public static boolean isMatch(String packageFilters,String controllerName){ + if(StringUtil.isNotEmpty(packageFilters)){ + String[] patterns = packageFilters.split(","); + for (String str : patterns) { + if (str.endsWith("*")) { + String name = str.substring(0, str.length() - 2); + if (controllerName.contains(name)) { + return true; + } + } else { + if(controllerName.contains(str)){ + return true; + } + } + } + } + return false; + } +} diff --git a/src/main/java/com/power/doc/utils/PathUtil.java b/src/main/java/com/power/doc/utils/PathUtil.java new file mode 100644 index 0000000..e3fece7 --- /dev/null +++ b/src/main/java/com/power/doc/utils/PathUtil.java @@ -0,0 +1,26 @@ +package com.power.doc.utils; + +import com.power.common.util.StringUtil; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; + +public class PathUtil { + + /** + * 获取java类名 + * @param parentDir parent dir + * @param className 类名 + * @return string + */ + public static String javaFilePath(String parentDir, String className) { + if (StringUtil.isEmpty(parentDir)) { + parentDir = "java.io.tmpdir"; + } + if (!StringUtils.endsWith(parentDir, File.separator)) { + parentDir += File.separator; + } + className = className.replaceAll("\\.", "\\" + File.separator); + return parentDir + className+".java"; + } +} diff --git a/src/main/resources/beetl.properties b/src/main/resources/beetl.properties new file mode 100644 index 0000000..1a1b75d --- /dev/null +++ b/src/main/resources/beetl.properties @@ -0,0 +1,2 @@ +DELIMITER_STATEMENT_START=<% +DELIMITER_STATEMENT_END=%> \ No newline at end of file diff --git a/src/main/resources/template/AllInOne.btl b/src/main/resources/template/AllInOne.btl new file mode 100644 index 0000000..60bad96 --- /dev/null +++ b/src/main/resources/template/AllInOne.btl @@ -0,0 +1,75 @@ +<%if(isNotEmpty(revisionLogList)){%> +版本 | 时间 | 状态 | 作者 | 备注 +------|--------|-----|------| ------- +<% +for(revisionLog in revisionLogList){ +%> +${revisionLog.version}|${revisionLog.revisionTime}|${revisionLog.status}|${revisionLog.author}|${revisionLog.remarks} +<%}%> + +<%}%> + + +<% +for(api in apiDocList){ +%> +# ${api.desc} +<% +for(doc in api.list){ +%> +## ${doc.desc} +**URL:** ${doc.url} + +**Type:** ${doc.type} + +**Content-Type:** ${doc.contentType} + +<%if(isNotEmpty(doc.headers)){%> +**Request-headers:** + +Name | Type|Description +---|---|--- +${doc.headers} +<%}%> + +<%if(isNotEmpty(doc.requestParams)){%> +**Request-parameters:** + +Parameter | Type|Description|Required +---|---|---|--- +${doc.requestParams} +<%}%> + +<%if(isNotEmpty(doc.requestUsage)){%> +**Request-example:** +``` +${doc.requestUsage} +``` +<%}%> +<%if(isNotEmpty(doc.responseParams)){%> +**Response-fields:** + +Field | Type|Description +---|---|--- +${doc.responseParams} +<%}%> + +<%if(isNotEmpty(doc.responseUsage)){%> +**Response-example:** +``` +${doc.responseUsage} +``` +<%}%> + +<% } %> +<% } %> +<%if(isNotEmpty(errorCodeList)){%> +# Error code list +Error code |Description +---|--- +<% +for(error in errorCodeList){ +%> +${error.value}|${error.desc} +<%}%> +<%}%> \ No newline at end of file diff --git a/src/main/resources/template/ApiDoc.btl b/src/main/resources/template/ApiDoc.btl new file mode 100644 index 0000000..b49f3b4 --- /dev/null +++ b/src/main/resources/template/ApiDoc.btl @@ -0,0 +1,50 @@ + +# ${desc} +<% +for(doc in list){ +%> +## ${doc.desc} +**URL:** ${doc.url} + +**Type:** ${doc.type} + +**Content-Type:** ${doc.contentType} + +<%if(isNotEmpty(doc.headers)){%> +**Request-headers:** + +Name | Type|Description +---|---|--- +${doc.headers} +<%}%> + +<%if(isNotEmpty(doc.requestParams)){%> +**Request-parameters:** + +Parameter | Type|Description|Required +---|---|---|--- +${doc.requestParams} +<%}%> + +<%if(isNotEmpty(doc.requestUsage)){%> +**Request-example:** +``` +${doc.requestUsage} +``` +<%}%> +<%if(isNotEmpty(doc.responseParams)){%> +**Response-fields:** + +Field | Type|Description +---|---|--- +${doc.responseParams} +<%}%> + +<%if(isNotEmpty(doc.responseUsage)){%> +**Response-example:** +``` +${doc.responseUsage} +``` +<%}%> + +<% } %> diff --git a/src/main/resources/template/ErrorCodeList.btl b/src/main/resources/template/ErrorCodeList.btl new file mode 100644 index 0000000..e8071f1 --- /dev/null +++ b/src/main/resources/template/ErrorCodeList.btl @@ -0,0 +1,10 @@ + +# Error code list + +Error code |Description +---|--- +<% +for(error in list){ +%> +${error.value}|${error.desc} +<%}%> \ No newline at end of file diff --git a/src/test/java/com/power/doc/ApiDocTest.java b/src/test/java/com/power/doc/ApiDocTest.java new file mode 100644 index 0000000..e6c65fd --- /dev/null +++ b/src/test/java/com/power/doc/ApiDocTest.java @@ -0,0 +1,71 @@ +package com.power.doc; + +import com.power.common.util.DateTimeUtil; +import com.power.doc.builder.ApiDocBuilder; +import com.power.doc.model.*; +import org.junit.Test; + +/** + * Description: + * ApiDoc测试 + * + * @author yu 2018/06/11. + */ +public class ApiDocTest { + + /** + * 简单型接口,不需要指定请求头,并且项目是maven的. + */ +/* + @Test + public void testBuilderControllersApiSimple() { + //将生成的文档输出到d:\md目录下,严格模式下api-doc会检测Controller的接口注释 + ApiDocBuilder.builderControllersApi("d:\\md",true); + } +*/ + + /** + * 包括设置请求头,缺失注释的字段批量在文档生成期使用定义好的注释 + */ + @Test + public void testBuilderControllersApi() { + ApiConfig config = new ApiConfig(); + config.setServerUrl("http://localhost:8080"); + //config.setStrict(true); + + config.setAllInOne(true); + config.setOutPath("d:\\md2"); + //不指定SourcePaths默认加载代码为项目src/main/java下的 + config.setSourcePaths( + //SourcePath.path().setDesc("本项目代码").setPath("src/test/java") + SourcePath.path().setPath("F:\\Personal\\project\\smart\\src\\main\\java") + //SourcePath.path().setDesc("加载项目外代码").setPath("E:\\ApplicationPower\\ApplicationPower\\Common-util\\src\\main\\java") + ); + //设置请求头,如果没有请求头,可以不用设置 + /* config.setRequestHeaders( + ApiReqHeader.header().setName("access_token").setType("string").setDesc("Basic auth credentials"), + ApiReqHeader.header().setName("user_uuid").setType("string").setDesc("User Uuid key") + );*/ + //对于外部jar的类,api-doc目前无法自动获取注释, + //如果有这种场景,则自己添加字段和注释,api-doc后期遇到同名字段则直接给相应字段加注释 + /* config.setCustomResponseFields( +// CustomRespField.field().setName("success").setDesc("成功返回true,失败返回false"), +// CustomRespField.field().setName("message").setDesc("接口响应信息"), +// CustomRespField.field().setName("data").setDesc("接口响应数据"), + CustomRespField.field().setName("code").setValue("00000") + //.setDesc("响应代码") + );*/ + + //非必须只有当setAllInOne设置为true时文档变更记录才生效,https://gitee.com/sunyurepository/ApplicationPower/issues/IPS4O + config.setRevisionLogs( + RevisionLog.getLog().setRevisionTime("2018/12/15").setAuthor("chen").setRemarks("测试").setStatus("创建").setVersion("V1.0"), + RevisionLog.getLog().setRevisionTime("2018/12/16").setAuthor("chen2").setRemarks("测试2").setStatus("修改").setVersion("V2.0") + ); + + long start = System.currentTimeMillis(); + ApiDocBuilder.builderControllersApi(config); + long end = System.currentTimeMillis(); + DateTimeUtil.printRunTime(end, start); + } + +} diff --git a/src/test/java/com/power/doc/Main.java b/src/test/java/com/power/doc/Main.java new file mode 100644 index 0000000..e6b36c1 --- /dev/null +++ b/src/test/java/com/power/doc/Main.java @@ -0,0 +1,46 @@ +package com.power.doc; + +import com.power.common.model.CommonResult; + +import java.io.BufferedReader; +import java.io.InputStreamReader; + +public class Main { + + public static void exeCmd(String commandStr) { + BufferedReader br = null; + try { + Process p = Runtime.getRuntime().exec(commandStr); + br = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line = null; + StringBuilder sb = new StringBuilder(); + while ((line = br.readLine()) != null) { + sb.append(line + "\n"); + } + System.out.println(sb.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + finally + { + if (br != null) + { + try { + br.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + public static void main(String[] args) throws Exception { + Class cls = CommonResult.class; + System.out.println("path:"+cls.getResource("")); + String path = cls.getResource("").getPath(); + String commandStr = String.format("javap -classpath %s -private CommonResult",path); + String cmd = "java -jar d:/procyon-decompiler-0.5.30.jar D:/ProgramFiles/mvnrepository/repository/com/boco/sp/Common-util/1.0-SNAPSHOT/Common-util-1.0-20180105.062727-5.jar -o out"; + //String commandStr = "ipconfig"; + Main.exeCmd(cmd); + } +} diff --git a/src/test/java/com/power/doc/util/DocClassUtilTest.java b/src/test/java/com/power/doc/util/DocClassUtilTest.java new file mode 100644 index 0000000..377129a --- /dev/null +++ b/src/test/java/com/power/doc/util/DocClassUtilTest.java @@ -0,0 +1,27 @@ +package com.power.doc.util; + +import com.power.doc.utils.DocClassUtil; +import org.junit.Test; + +/** + * Description: + * DocUtil单元测试 + * + * @author yu 2018/06/16. + */ +public class DocClassUtilTest { + + @Test + public void testGetSimpleGicName(){ + char me = 'k'; + String className = "com.power.doc.controller.Teacher,com.power.doc.controller.Teacher,com.power.doc.controller.Teacher>"; + String[] arr = DocClassUtil.getSimpleGicName(className); +// System.out.println("arr:"+ JSON.toJSONString(arr)); + } + + @Test + public void testIsPrimitive(){ + String typeName = "java.time.LocalDateTime"; + System.out.println(DocClassUtil.isPrimitive(typeName)); + } +} diff --git a/src/test/java/com/power/doc/util/DocUtilTest.java b/src/test/java/com/power/doc/util/DocUtilTest.java new file mode 100644 index 0000000..a007609 --- /dev/null +++ b/src/test/java/com/power/doc/util/DocUtilTest.java @@ -0,0 +1,16 @@ +package com.power.doc.util; + +import com.power.doc.utils.DocUtil; +import org.junit.Test; + +/** + * @author yu 2018/12/10. + */ +public class DocUtilTest { + + @Test + public void test(){ + String str = DocUtil.getValByTypeAndFieldName("LocalDateTime","createTime"); + System.out.println(str); + } +}