├── .gitignore ├── 1.0版本目标.md ├── LICENSE ├── README.md ├── doc ├── 1.jpg ├── 2.jpg └── 3.jpg ├── pom.xml ├── samples ├── sample-base │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── treeleafj │ │ └── xdoc │ │ └── test │ │ └── vo │ │ ├── Account.java │ │ ├── AccountEx.java │ │ └── User.java ├── sample-jfinal │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── treeleafj │ │ │ │ └── xdoc │ │ │ │ └── test │ │ │ │ ├── JFinalTestApplication.java │ │ │ │ └── controller │ │ │ │ ├── AccountController.java │ │ │ │ ├── CommController.java │ │ │ │ └── UserController.java │ │ ├── resources │ │ │ ├── application.txt │ │ │ ├── logback.xml │ │ │ └── undertow.txt │ │ └── webapp │ │ │ ├── WEB-INF │ │ │ └── web.xml │ │ │ └── index.jsp │ │ └── test │ │ └── java │ │ └── org │ │ └── treeleafj │ │ └── xdoc │ │ └── test │ │ └── XDocJfinalTest.java └── sample-springboot │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── treeleafj │ │ │ └── xdoc │ │ │ └── test │ │ │ ├── SpringBootTestApplication.java │ │ │ └── controller │ │ │ ├── AccountController.java │ │ │ ├── CommController.java │ │ │ └── UserController.java │ └── resources │ │ ├── application.yml │ │ └── logback.xml │ └── test │ └── java │ └── org │ └── treeleafj │ └── xdoc │ ├── resolver │ └── javaparser │ │ └── converter │ │ └── ParamTagConverterTest.java │ └── test │ └── XDocSpringTest.java ├── spring-boot-starter-xDoc ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── treeleafj │ │ └── xdoc │ │ └── boot │ │ ├── EnableXDoc.java │ │ ├── XDocConfiguration.java │ │ ├── XDocProperties.java │ │ └── XDocSpringController.java │ └── resources │ └── static │ └── xdoc │ ├── element-ui.css │ ├── element-ui.js │ ├── fonts │ └── element-icons.woff │ ├── index.html │ └── vue.min.js ├── xDoc-core ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── treeleafj │ └── xdoc │ ├── XDoc.java │ ├── format │ ├── Format.java │ └── http │ │ ├── HtmlForamt.java │ │ ├── MarkdownFormat.java │ │ ├── VelocityTemplater.java │ │ ├── html.vm │ │ └── markdown.vm │ ├── framework │ ├── AbstractHttpFramework.java │ └── Framework.java │ ├── model │ ├── ApiAction.java │ ├── ApiDoc.java │ ├── ApiModule.java │ ├── FieldInfo.java │ ├── ObjectInfo.java │ └── http │ │ ├── HttpApiAction.java │ │ └── HttpParam.java │ ├── resolver │ ├── DocTagResolver.java │ ├── IgnoreApi.java │ ├── JavaSourceFileManager.java │ └── javaparser │ │ ├── JavaParserDocTagResolver.java │ │ └── converter │ │ ├── DefaultJavaParserTagConverterImpl.java │ │ ├── JavaParserTagConverter.java │ │ ├── JavaParserTagConverterRegistrar.java │ │ ├── ParamObjTagConverter.java │ │ ├── ParamTagConverter.java │ │ ├── RespTagConverter.java │ │ └── SeeTagConverter.java │ ├── tag │ ├── DocTag.java │ ├── DocTagImpl.java │ ├── ParamObjTagImpl.java │ ├── ParamTagImpl.java │ ├── RespTagImpl.java │ └── SeeTagImpl.java │ └── utils │ ├── CommentUtils.java │ ├── Constant.java │ ├── JavaFileUtils.java │ ├── JsonUtils.java │ └── TagUtils.java ├── xDoc-jfinal ├── pom.xml └── src │ └── main │ └── java │ ├── com │ └── jfinal │ │ └── core │ │ └── ConfigGetter.java │ └── org │ └── treeleafj │ └── xdoc │ └── jfinal │ ├── JfinalHttpFramework.java │ └── XDocJfinalController.java ├── xDoc-spring ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── treeleafj │ └── xdoc │ └── spring │ └── framework │ └── SpringWebHttpFramework.java └── xDoc-view └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | target 4 | /.idea 5 | .settings 6 | .classpath 7 | .project 8 | .metadata 9 | node_modules -------------------------------------------------------------------------------- /1.0版本目标.md: -------------------------------------------------------------------------------- 1 | # 1.去掉sun doc这种依赖jar包比较大的解析方式 OK 2 | # 2.优化界面,不再使用vue,采用单纯的js,同时提供基础说明页面,以及提供一个重新生成文档的按钮 3 | # 3.语义支持英文 OK 4 | # 4.@param 入参支持标注类型 OK 5 | # 5.支持更多spring mvc的注释 OK 6 | # 6.重构代码,让扩展更容易,并提供扩展文档 7 | # 8.支持@see标签于@resp标签混合使用 OK 8 | # 9.@see读取的能支持到父类的属性 OK 9 | # 10.增加排序功能 10 | # 11.增加概述页面 11 | # 12.增加功能类似于@see标签的@paramObj注释,省掉些@param 12 | # 13.增加排除参数用的注释,用于配合@see合@paramObj -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 leaf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## XDoc讨论QQ群:820854691 2 | 3 | # ❦ XDoc 基于Java注释的接口文档工具 4 | 5 | - **基于java注释生成接口文档-对代码无侵入,无需注解,纯代码注释** 6 | - **支持SpringWeb, SpringBoot, JFinal** 7 | - **文档输出格式支持markdown和离线/在线html等** 8 | 9 | ### 为何使用XDoc? 10 | - **减少外部接口文档的另外编写,在编码过程就一起完成,减少外部维护工作量** 11 | - **修改代码时,少掉翻看外部接口文档的过程,直接看代码注释** 12 | - **接口文档同版本管理一起,可方便回退以及多环境多版本** 13 | - **难道标准的注释不应该作为企业项目的必须规范吗?如果你的规范里没有接口这块的,那么,还不补上?如果补上的还能帮你生成文档,何乐而不为?** 14 | 15 | ### 如何使用? 16 | #### 1.以SpringBoot为例: 17 | ```xml 18 | 19 | 20 | com.github.treeleafj 21 | spring-boot-starter-xDoc 22 | 1.1.0 23 | 24 | ``` 25 | 26 | ```java 27 | @EnableXDoc //<--- 加上此注解以便启用XDOC在线HTML文档 28 | @SpringBootApplication 29 | public class TestApplication { 30 | 31 | public static void main(String[] args) { 32 | SpringApplication.run(TestApplication.class, args); 33 | } 34 | } 35 | ``` 36 | 37 | ``` 38 | #在application.properties配置项目源码的位置,直接在项目里启动时,如果是单模块的maven项目,默认可以不配置 39 | xdoc.enable=true #是否启动XDoc,默认是true,生产环境建议改为false 40 | xdoc.sourcePath=F:/java/project/xDoc/samples/sample-springboot/src/main/java #源码路径,多个路径时用英文逗号隔开 41 | xdoc.title=用户中心接口文档 #用于配置文档页面标题 42 | xdoc.version=1.0 #标识接口文档的版本号 43 | ``` 44 | 45 | **以上准备配置就都做好了** 46 | 47 | **接下来,我们只需要像往常一样写个Controller,并写好注释:** 48 | ```java 49 | /** 50 | * 用户模块 51 | * 52 | * @author treeleaf 53 | * @date 2017-03-03 10:11 54 | */ 55 | @Controller 56 | @RequestMapping("api/user") 57 | public class UserController { 58 | 59 | /** 60 | * 登录 61 | * 62 | * @param username 用户名|必填 63 | * @param password 密码 64 | * @return 当前登录用户的基本信息 65 | * @resp code 返回码(0000表示登录成功,其它表示失败)|string|必填 66 | * @resp msg 登录信息|string 67 | * @resp username 登录成功后返回的用户名|string 68 | */ 69 | @ResponseBody 70 | @PostMapping("login") 71 | public Map login(String username, String password) { 72 | Map model = new HashMap<>(); 73 | model.put("code", "0000"); 74 | model.put("msg", "登录成功"); 75 | model.put("username", username); 76 | return model; 77 | } 78 | 79 | 80 | /** 81 | * 用户注册 82 | * 83 | * @param user :username 用户名|必填 84 | * @param user :password 密码 85 | * @return 注册后生成的用户的基本信息 86 | * @respbody {"id":"123","password":"123456","username":"admin"} 87 | * @see User 88 | */ 89 | @ResponseBody 90 | @RequestMapping(value = "register", method = {RequestMethod.POST, RequestMethod.PUT}) 91 | User register(User user) { 92 | user.setId(UUID.randomUUID().toString()); 93 | return user; 94 | } 95 | } 96 | ``` 97 | 98 | **写完之后,直接启动项目, 敲入地址: http://localhost:8080/xdoc/index.html** 99 | ![demo](https://images.gitee.com/uploads/images/2019/1208/163306_ee88c3c1_12124.png "1.jpg") 100 | 101 | ### 2.如果想生成离线文档怎么办? 102 | **支持html:** 103 | 104 | ```java 105 | /** 106 | * 生成离线的HTML格式的接口文档 107 | */ 108 | @Test 109 | public void buildHtml() throws Exception { 110 | /**注意!!!路径必须是要能扫描到源码工程的路径,执行生成的文件打开没有接口目录,说明没扫描到,请优先确认自己传入的路径是否正确!!!*/ 111 | FileOutputStream out = new FileOutputStream(new File(userDir, "api.html")); 112 | XDoc xDoc = new XDoc(new File("F:/java/project/xDoc/samples/sample-springboot/src/main/java"), new SpringWebHttpFramework()); 113 | xDoc.build(out, new HtmlForamt()); 114 | } 115 | ``` 116 | 117 | **也支持markdown:** 118 | ```java 119 | /** 120 | * 生成离线的Markdown格式的接口文档 121 | */ 122 | @Test 123 | public void buildMarkdown() { 124 | /**注意!!!路径必须是要能扫描到源码工程的路径,执行生成的markdown如果没有接口内容,说明没扫描到,请优先确认自己传入的路径是否正确!!!*/ 125 | 126 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 127 | XDoc xDoc = new XDoc(new File("F:/java/project/xDoc/samples/sample-springboot/src/main/java"), new SpringWebHttpFramework()); 128 | 129 | xDoc.build(out, new MarkdownFormat()); 130 | 131 | System.out.println(out.toString()); 132 | } 133 | ``` 134 | 135 | #### 如果不是SpringBoot,只是单纯的SpringWeb,或者是JFinal, 如何使用请参考samples目录下demo 136 | 137 | ### 现有注释标签用法: 138 | - @title 139 | 接口标题,如果不加这个,默认读的是接口注释上第一行的描述内容 140 | - @param 141 | 接口入参, 格式为: "参数名 参数描述|(参数类型)|(是否必填)" 142 | 其中"参数类型"可不填,默认是`String`, "是否必填"可不填,默认为非必填, "是否必填"的取值有`必填(Y)`,`非必填(N)`,具体常用的格式如下: 143 | username 用户名 144 | username 用户名|必填 `或者` username 用户名|Y 145 | username 用户名|非必填 `或者` username 用户名|N 146 | username 用户名|String 147 | username 用户名|String|必填 148 | 149 | 针对IDEA的在使用Java自身的@param注释注解时,如果上面的参数名在当前方法入参上是没有的,是会提示错误的,为了解决这种问题,XDoc支持在注释的参数名称前面加上`冒号:`来避开IDEA的检测,如: 150 | :username 用户名 `或者` user :username 用户名 151 | 152 | - @paramObj 153 | 当觉得入参本身就在一个Dto中,但是要一个个@param去加会比较麻烦时,可以用@paramObj指定入参的Dto对象,用法同@see,但是@paramObj支持一个接口方法出现多个,同时,@param与@paramObj混用,@paramObj对象中的某个属性名与@param的参数名冲突时,会优先以@param的为准, 使用可参考samples中的AccountController.java 154 | 155 | - @resp 156 | 指定返回的参数,格式同@param 157 | 158 | - @respbody 159 | 指定返回数据的demo,暂只支持对json数据进行格式化,仅用于展示,使用可参考samples中的UserController.java 160 | 161 | - @see 162 | 指定返回的出参对象,类似@paramObj,不过一个是入参,一个是出参,一个方法只能出现一个@see,同时,跟@resp混用时有属性名冲突,以@resp的为准, 使用可参考samples中的AccountController.java 163 | 164 | - @return 165 | 返回信息的描述,内容为纯文本,仅用于展示 166 | 167 | - @IgnoreApi 168 | 这个是注解,不是放在注释上的,用于标注哪些接口不需要生成接口文档 -------------------------------------------------------------------------------- /doc/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeleafj/xDoc/df60c2ebc1d428aa4bb6db0adb6b2285f7277e82/doc/1.jpg -------------------------------------------------------------------------------- /doc/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeleafj/xDoc/df60c2ebc1d428aa4bb6db0adb6b2285f7277e82/doc/2.jpg -------------------------------------------------------------------------------- /doc/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeleafj/xDoc/df60c2ebc1d428aa4bb6db0adb6b2285f7277e82/doc/3.jpg -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | 1.7 9 | UTF-8 10 | 11 | 12 | com.github.treeleafj 13 | xDoc 14 | pom 15 | 1.1.0 16 | 17 | 18 | xDoc-core 19 | xDoc-spring 20 | spring-boot-starter-xDoc 21 | xDoc-jfinal 22 | xDoc-view 23 | 24 | 25 | ${project.artifactId} 26 | XDoc 27 | https://github.com/treeleafj/xDoc 28 | 29 | 30 | 31 | MIT License 32 | https://github.com/treeleafj/xDoc/blob/master/LICENSE 33 | 34 | 35 | 36 | 37 | 38 | leaf 39 | treeleafj@outlook.com 40 | treeleafj 41 | https://github.com/treeleafj/xDoc 42 | 43 | 44 | 45 | 46 | scm:git:https://github.com/treeleafj/xDoc.git 47 | scm:git:https://github.com/treeleafj/xDoc.git 48 | https://github.com/treeleafj/xDoc 49 | 50 | 51 | 52 | 53 | ossrh 54 | https://oss.sonatype.org/content/repositories/snapshots 55 | 56 | 57 | ossrh 58 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 59 | 60 | 61 | 62 | 63 | 64 | 65 | jdk-1.8 66 | 67 | 1.8 68 | 69 | 70 | 71 | central 72 | central 73 | http://maven.aliyun.com/nexus/content/groups/public/ 74 | 75 | true 76 | 77 | 78 | false 79 | 80 | 81 | 82 | 83 | 84 | central 85 | central 86 | http://maven.aliyun.com/nexus/content/groups/public/ 87 | 88 | true 89 | 90 | 91 | false 92 | 93 | 94 | 95 | 96 | 97 | 98 | release 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-javadoc-plugin 105 | 2.9.1 106 | 107 | UTF-8 108 | UTF-8 109 | 110 | 111 | 112 | package 113 | 114 | jar 115 | 116 | 117 | UTF-8 118 | -Xdoclint:none 119 | 120 | 121 | 122 | 123 | 124 | 125 | org.apache.maven.plugins 126 | maven-gpg-plugin 127 | 1.6 128 | 129 | 130 | sign-artifacts 131 | verify 132 | 133 | sign 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | junit 146 | junit 147 | 4.12 148 | test 149 | 150 | 151 | 152 | 153 | org.apache.commons 154 | commons-lang3 155 | 3.4 156 | 157 | 158 | 159 | commons-beanutils 160 | commons-beanutils 161 | 1.9.3 162 | 163 | 164 | 165 | commons-io 166 | commons-io 167 | 2.5 168 | 169 | 170 | 171 | 172 | org.projectlombok 173 | lombok 174 | 1.16.18 175 | provided 176 | 177 | 178 | 179 | org.slf4j 180 | slf4j-api 181 | 1.7.25 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | src/main/java 190 | 191 | **/*.vm 192 | 193 | false 194 | 195 | 196 | src/main/resources 197 | 198 | **/*.* 199 | 200 | false 201 | 202 | 203 | 204 | 205 | 206 | org.apache.maven.plugins 207 | maven-compiler-plugin 208 | 3.6.1 209 | 210 | 8 211 | 8 212 | UTF-8 213 | 214 | 215 | 216 | 217 | org.apache.maven.plugins 218 | maven-source-plugin 219 | 3.0.1 220 | 221 | 222 | attach-sources 223 | 224 | jar-no-fork 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /samples/sample-base/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | com.github.treeleafj 9 | 1.1.0 10 | sample-base 11 | 12 | 13 | 14 | 15 | 16 | org.projectlombok 17 | lombok 18 | 1.16.18 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.apache.maven.plugins 27 | maven-compiler-plugin 28 | 3.6.1 29 | 30 | 8 31 | 8 32 | UTF-8 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /samples/sample-base/src/main/java/org/treeleafj/xdoc/test/vo/Account.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 用户账户 7 | * @author leaf 8 | * @date 2017-03-10 10:43 9 | */ 10 | @Data 11 | public class Account { 12 | 13 | /** 14 | * 账户ID,跟用户ID一致 15 | */ 16 | private String id; 17 | 18 | /** 19 | * 用户余额 20 | */ 21 | private Double balance; 22 | 23 | /** 24 | * 用户积分 25 | */ 26 | private Integer score; 27 | } 28 | -------------------------------------------------------------------------------- /samples/sample-base/src/main/java/org/treeleafj/xdoc/test/vo/AccountEx.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * Created by leaf on 2018/6/22. 9 | */ 10 | @Data 11 | public class AccountEx extends Account { 12 | 13 | /** 14 | * 创建时间 15 | */ 16 | private Date createdtime; 17 | 18 | /** 19 | * 等级,数字越高级别越大 20 | */ 21 | private Integer level; 22 | 23 | /** 24 | * 重写父类的注释,新的含义是:已消费的积分 25 | */ 26 | private Integer score; 27 | } 28 | -------------------------------------------------------------------------------- /samples/sample-base/src/main/java/org/treeleafj/xdoc/test/vo/User.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test.vo; 2 | 3 | /** 4 | * 用户 5 | * 6 | * @author leaf 7 | * @date 2017-03-03 10:13 8 | */ 9 | public class User { 10 | 11 | /** 12 | * 用户ID 13 | */ 14 | private String id; 15 | 16 | /** 17 | * 用户名 18 | */ 19 | private String username; 20 | 21 | /** 22 | * 密码 23 | */ 24 | private String password; 25 | 26 | public String getId() { 27 | return id; 28 | } 29 | 30 | public User setId(String id) { 31 | this.id = id; 32 | return this; 33 | } 34 | 35 | public String getUsername() { 36 | return username; 37 | } 38 | 39 | public User setUsername(String username) { 40 | this.username = username; 41 | return this; 42 | } 43 | 44 | public String getPassword() { 45 | return password; 46 | } 47 | 48 | public User setPassword(String password) { 49 | this.password = password; 50 | return this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /samples/sample-jfinal/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | com.github.treeleafj 9 | 1.1.0 10 | sample-jfinal 11 | war 12 | 13 | smaple-jfinal Maven Webapp 14 | 15 | 16 | UTF-8 17 | 1.7 18 | 1.7 19 | 20 | 21 | 22 | 23 | 24 | com.github.treeleafj 25 | sample-base 26 | ${project.version} 27 | 28 | 29 | 30 | com.github.treeleafj 31 | xDoc-jfinal 32 | 1.1.0 33 | 34 | 35 | org.apache.velocity 36 | velocity 37 | 38 | 39 | 40 | 41 | 42 | org.slf4j 43 | slf4j-api 44 | 1.7.25 45 | 46 | 47 | 48 | com.jfinal 49 | jfinal 50 | 4.1 51 | 52 | 53 | 54 | com.jfinal 55 | jfinal-undertow 56 | 1.6 57 | 58 | 59 | 60 | junit 61 | junit 62 | 4.12 63 | test 64 | 65 | 66 | 67 | ch.qos.logback 68 | logback-classic 69 | 1.2.3 70 | compile 71 | 72 | 73 | 74 | org.apache.logging.log4j 75 | log4j-to-slf4j 76 | 2.11.2 77 | compile 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-compiler-plugin 88 | 3.6.1 89 | 90 | 8 91 | 8 92 | UTF-8 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /samples/sample-jfinal/src/main/java/org/treeleafj/xdoc/test/JFinalTestApplication.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test; 2 | 3 | import com.jfinal.config.*; 4 | import com.jfinal.json.JacksonFactory; 5 | import com.jfinal.kit.PropKit; 6 | import com.jfinal.server.undertow.UndertowServer; 7 | import com.jfinal.template.Engine; 8 | import org.treeleafj.xdoc.jfinal.XDocJfinalController; 9 | import org.treeleafj.xdoc.test.controller.AccountController; 10 | import org.treeleafj.xdoc.test.controller.CommController; 11 | import org.treeleafj.xdoc.test.controller.UserController; 12 | 13 | public class JFinalTestApplication extends JFinalConfig { 14 | 15 | 16 | public static void main(String[] args) { 17 | UndertowServer.start(JFinalTestApplication.class, 8081, true); 18 | } 19 | 20 | @Override 21 | public void configConstant(Constants constants) { 22 | constants.setJsonFactory(new JacksonFactory()); 23 | constants.setDevMode(true); 24 | 25 | PropKit.use("application.txt"); 26 | } 27 | 28 | @Override 29 | public void configRoute(Routes routes) { 30 | //启用xDoc 31 | routes.add("/xdoc", XDocJfinalController.class); 32 | //添加测试用的类 33 | routes.add("/account", AccountController.class); 34 | routes.add("/comm", CommController.class); 35 | routes.add("/user", UserController.class); 36 | } 37 | 38 | @Override 39 | public void configEngine(Engine engine) { 40 | 41 | } 42 | 43 | @Override 44 | public void configPlugin(Plugins plugins) { 45 | 46 | } 47 | 48 | @Override 49 | public void configInterceptor(Interceptors interceptors) { 50 | 51 | } 52 | 53 | @Override 54 | public void configHandler(Handlers handlers) { 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples/sample-jfinal/src/main/java/org/treeleafj/xdoc/test/controller/AccountController.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test.controller; 2 | 3 | import com.jfinal.core.Controller; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.treeleafj.xdoc.test.vo.Account; 7 | 8 | import java.util.UUID; 9 | 10 | /** 11 | * 用户账户模块 12 | * 13 | * @author leaf 14 | * @date 2017-03-10 10:43 15 | */ 16 | public class AccountController extends Controller { 17 | 18 | private Logger log = LoggerFactory.getLogger(AccountController.class); 19 | 20 | /** 21 | * 获取当前登录用户的账户资产信息,用户不存在会返回code为9999的错误信息,见:https://github.com/treeleafj/xDoc 22 | * 23 | * @param type 账户类型(1-普通账户)|必填 24 | * @param balance 重写@paramObj中AccountEx的balance的注释 25 | * @paramObj AccountEx 26 | * @return 用户的资产 27 | * @title 查询用户资产 28 | * @resp balance 账户余额|double 29 | */ 30 | public void info() { 31 | String type = this.get("type"); 32 | Account account = new Account(); 33 | account.setId(UUID.randomUUID().toString()); 34 | account.setBalance(100D); 35 | account.setScore(666666); 36 | this.renderJson(account); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/sample-jfinal/src/main/java/org/treeleafj/xdoc/test/controller/CommController.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test.controller; 2 | 3 | import com.jfinal.core.Controller; 4 | 5 | /** 6 | * 通用接口 7 | *

8 | * Created by leaf on 2017/6/1. 9 | */ 10 | public class CommController extends Controller { 11 | 12 | /** 13 | * 首页 14 | * 15 | * @return 首页页面 16 | */ 17 | public void index() { 18 | this.renderText("首页"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/sample-jfinal/src/main/java/org/treeleafj/xdoc/test/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test.controller; 2 | 3 | import com.jfinal.core.Controller; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.treeleafj.xdoc.test.vo.User; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.UUID; 11 | 12 | /** 13 | * 用户模块 14 | * 15 | * @author leaf 16 | * @date 2017-03-03 10:11 17 | */ 18 | public class UserController extends Controller { 19 | 20 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 21 | 22 | /** 23 | * 登录 24 | * 25 | * @param :username 用户名|必填 26 | * @param :password 密码 27 | * @return 当前登录用户的基本信息 28 | * @resp code 返回码(0000表示登录成功,其它表示失败)|string|必填 29 | * @resp msg 登录信息|string 30 | * @resp username 登录成功后返回的用户名|string 31 | */ 32 | public void login() { 33 | 34 | String username = this.get("username"); 35 | String password = this.get("password"); 36 | 37 | logger.info("username={}, passowrd={}", username, password); 38 | 39 | Map model = new HashMap<>(); 40 | model.put("code", "0000"); 41 | model.put("msg", "登录成功"); 42 | model.put("username", username); 43 | this.renderJson(model); 44 | } 45 | 46 | 47 | /** 48 | * 用户注册 49 | * 50 | * @param :username 用户名|必填 51 | * @param :password 密码 52 | * @return 注册后生成的用户的基本信息 53 | * @respbody {"id":"123","password":"123456","username":"admin"} 54 | * @title 注册 55 | * @see User 56 | * @resp score 分数 57 | */ 58 | public void register() { 59 | User user = getModel(User.class); 60 | user.setId(UUID.randomUUID().toString()); 61 | this.renderJson(user); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /samples/sample-jfinal/src/main/resources/application.txt: -------------------------------------------------------------------------------- 1 | xdoc.enable=true 2 | ## 该路径可能在不同的环境启动时应用的相对路径是不同的,请设置成绝对路径 3 | xdoc.sourcePath=samples/sample-jfinal/src/main/java,samples/sample-base/src/main/java 4 | xdoc.version=1.0 5 | xdoc.title=Jfinal文档测试 -------------------------------------------------------------------------------- /samples/sample-jfinal/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36}:%L - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/sample-jfinal/src/main/resources/undertow.txt: -------------------------------------------------------------------------------- 1 | undertow.resourcePath=sample-jfinal/src/main/webapp,classpath:static -------------------------------------------------------------------------------- /samples/sample-jfinal/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Archetype Created Web Application 7 | 8 | -------------------------------------------------------------------------------- /samples/sample-jfinal/src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello World!

4 | 5 | 6 | -------------------------------------------------------------------------------- /samples/sample-jfinal/src/test/java/org/treeleafj/xdoc/test/XDocJfinalTest.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test; 2 | 3 | import org.junit.Test; 4 | import org.treeleafj.xdoc.XDoc; 5 | import org.treeleafj.xdoc.format.http.HtmlForamt; 6 | import org.treeleafj.xdoc.jfinal.JfinalHttpFramework; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.File; 10 | import java.io.FileOutputStream; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | public class XDocJfinalTest { 15 | 16 | @Test 17 | public void buildMarkdown() { 18 | //生成离线的Markdown格式的接口文档 19 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 20 | String rootDir = System.getProperty("user.dir"); 21 | XDoc xDoc = new XDoc(new File(rootDir + "/src/main/java/org/treeleafj"), new JfinalHttpFramework()); 22 | // xDoc.build(out, new MarkdownFormat()); 23 | 24 | System.out.println(out.toString()); 25 | } 26 | 27 | @Test 28 | public void buildHtml() throws Exception { 29 | //生成离线的HTML格式的接口文档 30 | String userDir = System.getProperty("user.dir"); 31 | FileOutputStream out = new FileOutputStream(new File(userDir, "api.html")); 32 | File srcDir1 = new File(userDir + "/src/main/java/org/treeleafj"); 33 | File srcDir2 = new File(userDir + "/../sample-base/src/main/java/org/treeleafj"); 34 | List list = Arrays.asList(srcDir1, srcDir2); 35 | XDoc xDoc = new XDoc(list, new JfinalHttpFramework()); 36 | xDoc.build(out, new HtmlForamt()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/sample-springboot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | org.springframework.boot 10 | spring-boot-starter-parent 11 | 2.1.7.RELEASE 12 | 13 | 14 | com.github.treeleafj 15 | sample-springboot 16 | 1.1.0 17 | 18 | 19 | 20 | 21 | com.github.treeleafj 22 | sample-base 23 | ${project.version} 24 | 25 | 26 | 27 | com.github.treeleafj 28 | spring-boot-starter-xDoc 29 | 1.1.0 30 | 31 | 32 | 33 | junit 34 | junit 35 | 4.12 36 | test 37 | 38 | 39 | 40 | org.apache.velocity 41 | velocity 42 | 1.7 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-deploy-plugin 56 | 2.8.2 57 | 58 | true 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /samples/sample-springboot/src/main/java/org/treeleafj/xdoc/test/SpringBootTestApplication.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.treeleafj.xdoc.boot.EnableXDoc; 6 | 7 | /** 8 | * @author leaf 9 | * @date 2017-03-09 15:46 10 | */ 11 | @EnableXDoc 12 | @SpringBootApplication 13 | public class SpringBootTestApplication { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(SpringBootTestApplication.class, args); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/sample-springboot/src/main/java/org/treeleafj/xdoc/test/controller/AccountController.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test.controller; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.RequestHeader; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | import org.treeleafj.xdoc.test.vo.Account; 14 | 15 | import javax.servlet.ServletOutputStream; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.io.File; 18 | import java.io.FileInputStream; 19 | import java.io.IOException; 20 | import java.util.Date; 21 | import java.util.UUID; 22 | import java.util.concurrent.Callable; 23 | 24 | /** 25 | * 用户账户模块 26 | * 27 | * @author leaf 28 | * @date 2017-03-10 10:43 29 | */ 30 | @Controller 31 | @RequestMapping("api/account") 32 | public class AccountController { 33 | 34 | private Logger log = LoggerFactory.getLogger(AccountController.class); 35 | 36 | /** 37 | * 获取当前登录用户的账户资产信息,用户不存在会返回code为9999的错误信息,见:https://github.com/treeleafj/xDoc 38 | * 39 | * @param type 账户类型(1-普通账户)|必填 40 | * @param balance 重写@paramObj中AccountEx的balance的注释 41 | * @paramObj AccountEx 42 | * @return 用户的资产 43 | * @title 查询用户资产 44 | * @resp balance 账户余额|double 45 | * @see org.treeleafj.xdoc.test.vo.AccountEx 46 | */ 47 | @ResponseBody 48 | @RequestMapping(value = "info", method = RequestMethod.POST) 49 | Account info(String type) { 50 | Account account = new Account(); 51 | account.setId(UUID.randomUUID().toString()); 52 | account.setBalance(100D); 53 | account.setScore(666666); 54 | return account; 55 | } 56 | 57 | /** 58 | * 文件/图片获取 59 | * 60 | * @param id 文件Id|必填 61 | * @param type 业务类型,1-用户头像,2-绘画完成的作品,3-心情,4-图案,5-关于我们logo|必填 62 | * @return 文件(如果文件不存在, http response status会返回404) 63 | * @throws IOException 64 | */ 65 | @RequestMapping("get") 66 | public Callable get(String id, String type, HttpServletResponse resp, @RequestHeader HttpHeaders httpHeaders) { 67 | 68 | if (id.contains("..") || StringUtils.isBlank(id)) { 69 | log.warn("文件ID非法:" + id); 70 | resp.setStatus(404); 71 | return null; 72 | } 73 | 74 | //实现304缓存 75 | String ifModifiedSince = httpHeaders.getFirst("If-Modified-Since"); 76 | if (StringUtils.isNotBlank(ifModifiedSince)) { 77 | resp.setStatus(304); 78 | return null; 79 | } 80 | 81 | resp.setHeader("Last-Modified", new Date().toString()); 82 | 83 | String path = "/"; 84 | File file = new File(path + "/" + type + "/" + id); 85 | if (!file.exists()) { 86 | resp.setStatus(404); 87 | return null; 88 | } 89 | 90 | Callable callback = () -> { 91 | ServletOutputStream out = null; 92 | FileInputStream in = null; 93 | try { 94 | out = resp.getOutputStream(); 95 | in = new FileInputStream(file); 96 | IOUtils.copy(in, out); 97 | } catch (IOException e) { 98 | log.info("输出文件错误:{}", id, e); 99 | } finally { 100 | IOUtils.closeQuietly(out); 101 | IOUtils.closeQuietly(in); 102 | } 103 | return ""; 104 | }; 105 | return callback; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /samples/sample-springboot/src/main/java/org/treeleafj/xdoc/test/controller/CommController.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | /** 7 | * 通用接口 8 | *

9 | * Created by leaf on 2017/6/1. 10 | */ 11 | @Controller 12 | public class CommController { 13 | 14 | /** 15 | * 首页 16 | * 17 | * @return 首页页面 18 | */ 19 | @RequestMapping("index") 20 | public String index() { 21 | return ""; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/sample-springboot/src/main/java/org/treeleafj/xdoc/test/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.*; 5 | import org.springframework.web.multipart.MultipartFile; 6 | import org.treeleafj.xdoc.test.vo.User; 7 | 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.UUID; 12 | 13 | /** 14 | * 用户模块 15 | * 16 | * @author leaf 17 | * @date 2017-03-03 10:11 18 | */ 19 | @Controller 20 | @RequestMapping("user") 21 | public class UserController { 22 | 23 | /** 24 | * 登录 25 | * 26 | * @param username 用户名|必填 27 | * @param password 密码 28 | * @return 当前登录用户的基本信息 29 | * @resp code 返回码(0000表示登录成功,其它表示失败)|string|必填 30 | * @resp msg 登录信息|string 31 | * @resp username 登录成功后返回的用户名|string 32 | */ 33 | @ResponseBody 34 | @PostMapping("login") 35 | public Map login(String username, String password) { 36 | Map model = new HashMap<>(); 37 | model.put("code", "0000"); 38 | model.put("msg", "登录成功"); 39 | model.put("username", username); 40 | return model; 41 | } 42 | 43 | 44 | /** 45 | * 用户注册 46 | * 47 | * @param user :username 用户名|必填 48 | * @param user :password 密码 49 | * @return 注册后生成的用户的基本信息 50 | * @respbody {"id":"123","password":"123456","username":"admin"} 51 | * @title 注册 52 | * @see User 53 | * @resp score 分数 54 | */ 55 | @ResponseBody 56 | @RequestMapping(value = "register", method = {RequestMethod.POST, RequestMethod.PUT}) 57 | User register(org.treeleafj.xdoc.test.vo.User user, @RequestParam(value = "abc", required = false)List list) { 58 | user.setId(UUID.randomUUID().toString()); 59 | return user; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /samples/sample-springboot/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | xdoc: 2 | enable: true 3 | title: 测试文档 4 | ## 该路径可能在不同的环境启动时应用的相对路径是不同的,请设置成绝对路径 5 | sourcePath: samples/sample-springboot/src/main/java,samples/sample-base/src/main/java -------------------------------------------------------------------------------- /samples/sample-springboot/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36}:%L - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/sample-springboot/src/test/java/org/treeleafj/xdoc/resolver/javaparser/converter/ParamTagConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver.javaparser.converter; 2 | 3 | import org.junit.Test; 4 | import org.treeleafj.xdoc.utils.JsonUtils; 5 | 6 | /** 7 | * Created by leaf on 2018/3/8. 8 | */ 9 | public class ParamTagConverterTest { 10 | 11 | @Test 12 | public void converter() throws Exception { 13 | ParamTagConverter converter = new ParamTagConverter(); 14 | // String o = "@param type 账户类型(1-普通账户)|必填"; 15 | String o = "@param user :username 账户名称|必填"; 16 | System.out.println(JsonUtils.toJson(converter.converter("@param username 账户名称"))); 17 | System.out.println(JsonUtils.toJson(converter.converter("@param username 账户名称|必填"))); 18 | System.out.println(JsonUtils.toJson(converter.converter("@param user :username 账户名称|必填"))); 19 | System.out.println(JsonUtils.toJson(converter.converter("@param user :username 账户名称|Y"))); 20 | System.out.println(JsonUtils.toJson(converter.converter("@param username 账户名称|Boolean|必填"))); 21 | System.out.println(JsonUtils.toJson(converter.converter("@param user :username 账户名称|N"))); 22 | System.out.println(JsonUtils.toJson(converter.converter("@param user :username 账户名称|Y"))); 23 | System.out.println(JsonUtils.toJson(converter.converter("@param user :username 账户名称|string"))); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /samples/sample-springboot/src/test/java/org/treeleafj/xdoc/test/XDocSpringTest.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.test; 2 | 3 | import org.junit.Test; 4 | import org.treeleafj.xdoc.XDoc; 5 | import org.treeleafj.xdoc.format.http.HtmlForamt; 6 | import org.treeleafj.xdoc.format.http.MarkdownFormat; 7 | import org.treeleafj.xdoc.spring.framework.SpringWebHttpFramework; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.File; 11 | import java.io.FileOutputStream; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | /** 16 | * Created by leaf on 2017/3/3 003. 17 | */ 18 | public class XDocSpringTest { 19 | 20 | @Test 21 | public void buildMarkdown() { 22 | //生成离线的Markdown格式的接口文档 23 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 24 | String userDir = System.getProperty("user.dir"); 25 | File srcDir1 = new File(userDir + "/src/main/java/org/treeleafj"); 26 | File srcDir2 = new File(userDir + "/../sample-base/src/main/java/org/treeleafj"); 27 | List list = Arrays.asList(srcDir1, srcDir2); 28 | XDoc xDoc = new XDoc(list, new SpringWebHttpFramework()); 29 | xDoc.build(out, new MarkdownFormat()); 30 | 31 | System.out.println(out.toString()); 32 | } 33 | 34 | @Test 35 | public void buildHtml() throws Exception { 36 | //生成离线的HTML格式的接口文档 37 | String userDir = System.getProperty("user.dir"); 38 | FileOutputStream out = new FileOutputStream(new File(userDir, "api.html")); 39 | File srcDir1 = new File(userDir + "/src/main/java/org/treeleafj"); 40 | File srcDir2 = new File(userDir + "/../sample-base/src/main/java/org/treeleafj"); 41 | List list = Arrays.asList(srcDir1, srcDir2); 42 | XDoc xDoc = new XDoc(list, new SpringWebHttpFramework()); 43 | xDoc.build(out, new HtmlForamt()); 44 | } 45 | } -------------------------------------------------------------------------------- /spring-boot-starter-xDoc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | xDoc 7 | com.github.treeleafj 8 | 1.1.0 9 | 10 | 4.0.0 11 | 12 | spring-boot-starter-xDoc 13 | 14 | 15 | 16 | com.github.treeleafj 17 | xDoc-spring 18 | ${project.version} 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /spring-boot-starter-xDoc/src/main/java/org/treeleafj/xdoc/boot/EnableXDoc.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.boot; 2 | 3 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 4 | import org.springframework.context.annotation.Import; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * Created by leaf on 2017/3/9 009. 13 | */ 14 | @Target({ElementType.TYPE}) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Import(XDocConfiguration.class) 17 | @EnableConfigurationProperties(XDocProperties.class) 18 | public @interface EnableXDoc { 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-starter-xDoc/src/main/java/org/treeleafj/xdoc/boot/XDocConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.boot; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | import org.springframework.context.annotation.Bean; 5 | 6 | /** 7 | * @author leaf 8 | * @date 2017-03-09 15:29 9 | */ 10 | public class XDocConfiguration { 11 | 12 | @Bean 13 | @ConditionalOnProperty(prefix = "xdoc", name = "enable", matchIfMissing = true) 14 | public XDocSpringController xDocController() { 15 | return new XDocSpringController(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spring-boot-starter-xDoc/src/main/java/org/treeleafj/xdoc/boot/XDocProperties.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.boot; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | 6 | /** 7 | * @author leaf 8 | * @date 2017-03-09 15:43 9 | */ 10 | @Data 11 | @ConfigurationProperties("xdoc") 12 | public class XDocProperties { 13 | 14 | /** 15 | * 是否启动XDOC,此值便于在生产等环境启动程序时增加参数进行控制 16 | */ 17 | private boolean enable = true; 18 | 19 | /** 20 | * 界面标题描述 21 | */ 22 | private String title = "XDoc 接口文档"; 23 | 24 | /** 25 | * 源码相对路径(支持多个,用英文逗号隔开) 26 | */ 27 | private String sourcePath; 28 | 29 | /** 30 | * 文档版本号 31 | */ 32 | private String version; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /spring-boot-starter-xDoc/src/main/java/org/treeleafj/xdoc/boot/XDocSpringController.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.boot; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.ResponseBody; 10 | import org.treeleafj.xdoc.XDoc; 11 | import org.treeleafj.xdoc.model.ApiDoc; 12 | import org.treeleafj.xdoc.spring.framework.SpringWebHttpFramework; 13 | import org.treeleafj.xdoc.utils.JsonUtils; 14 | 15 | import javax.annotation.PostConstruct; 16 | import java.io.File; 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | 22 | /** 23 | * XDoc的Spring Web入口 24 | * 25 | * @author leaf 26 | * @date 2017-03-09 15:36 27 | */ 28 | @RequestMapping("xdoc") 29 | public class XDocSpringController { 30 | 31 | private Logger logger = LoggerFactory.getLogger(XDocSpringController.class); 32 | 33 | @Autowired 34 | private XDocProperties xDocProperties; 35 | 36 | private static ApiDoc apiDoc; 37 | 38 | @PostConstruct 39 | public void _init() { 40 | //使用多线程异步去初始化,尽量不阻塞系统启动 41 | try { 42 | Thread thread = new Thread(this::init); 43 | thread.start(); 44 | } catch (Exception e) { 45 | logger.error("start up XDoc error", e); 46 | } 47 | } 48 | 49 | public void init() { 50 | if (!xDocProperties.isEnable()) { 51 | return; 52 | } 53 | 54 | String path = xDocProperties.getSourcePath(); 55 | 56 | if (StringUtils.isBlank(path)) { 57 | path = ".";//默认为当前目录 58 | } 59 | 60 | List paths = Arrays.asList(path.split(",")); 61 | 62 | List srcDirs = new ArrayList<>(paths.size()); 63 | List srcDirPaths = new ArrayList<>(paths.size()); 64 | 65 | try { 66 | for (String s : paths) { 67 | File dir = new File(s); 68 | srcDirs.add(dir); 69 | srcDirPaths.add(dir.getCanonicalPath()); 70 | } 71 | } catch (Exception e) { 72 | logger.error("获取源码目录路径错误", e); 73 | return; 74 | } 75 | 76 | logger.debug("starting XDoc, source path:{}", srcDirPaths); 77 | 78 | XDoc xDoc = new XDoc(srcDirs, new SpringWebHttpFramework()); 79 | 80 | try { 81 | apiDoc = xDoc.resolve(); 82 | HashMap properties = new HashMap<>(); 83 | properties.put("version", xDocProperties.getVersion()); 84 | properties.put("title", xDocProperties.getTitle()); 85 | apiDoc.setProperties(properties); 86 | 87 | logger.info("start up XDoc"); 88 | } catch (Exception e) { 89 | logger.error("start up XDoc error", e); 90 | } 91 | 92 | } 93 | 94 | /** 95 | * 跳转到xdoc接口文档首页 96 | */ 97 | @GetMapping 98 | public String index() { 99 | return "redirect:index.html"; 100 | } 101 | 102 | /** 103 | * 获取所有文档api 104 | * 105 | * @return 系统所有文档接口的数据(json格式) 106 | */ 107 | @ResponseBody 108 | @RequestMapping("apis") 109 | public Object apis() { 110 | return JsonUtils.toJson(apiDoc); 111 | } 112 | 113 | /** 114 | * 重新构建文档 115 | * 116 | * @return 文档页面 117 | */ 118 | @GetMapping({"rebuild", "rebuild.html"}) 119 | public String rebuild() { 120 | init(); 121 | return "redirect:index.html"; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /spring-boot-starter-xDoc/src/main/resources/static/xdoc/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeleafj/xDoc/df60c2ebc1d428aa4bb6db0adb6b2285f7277e82/spring-boot-starter-xDoc/src/main/resources/static/xdoc/fonts/element-icons.woff -------------------------------------------------------------------------------- /spring-boot-starter-xDoc/src/main/resources/static/xdoc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 接口文档 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 | 16 |
17 | 18 |

{{title}}

19 | 20 | 21 | 22 | 23 | 重新生成文档 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 | 35 |
36 |
37 |

{{currentApiAction.title}}

38 |

描述: {{currentApiAction.comment}}

39 |

只支持: 40 | {{m}} 41 |

42 |

接口地址: {{uri}}

43 |

接口返回: {{currentApiAction.returnDesc}}

44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 78 |
{{reverseRespbody}}
79 |
80 |
81 |
82 | 83 |
84 |

测试

85 |
86 |
87 | 88 | 89 | 90 | 91 | {{m}} 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 测试 101 | 重置 102 | 103 | 104 | 105 |

返回内容

106 |
107 |
108 | 109 |
110 |
111 |
112 |
113 | 114 | 183 | 454 | 455 | 456 | 561 | 562 | 563 | -------------------------------------------------------------------------------- /xDoc-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | xDoc 7 | com.github.treeleafj 8 | 1.1.0 9 | 10 | 4.0.0 11 | 12 | xDoc-core 13 | 14 | 15 | 16 | com.github.javaparser 17 | javaparser-core 18 | 3.14.16 19 | 20 | 21 | 22 | com.fasterxml.jackson.core 23 | jackson-databind 24 | 2.10.0 25 | 26 | 27 | 28 | 29 | org.apache.velocity 30 | velocity 31 | 1.7 32 | true 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/XDoc.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc; 2 | 3 | import lombok.Setter; 4 | import org.apache.commons.io.IOUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.treeleafj.xdoc.format.Format; 8 | import org.treeleafj.xdoc.framework.Framework; 9 | import org.treeleafj.xdoc.model.ApiDoc; 10 | import org.treeleafj.xdoc.model.ApiModule; 11 | import org.treeleafj.xdoc.resolver.DocTagResolver; 12 | import org.treeleafj.xdoc.resolver.JavaSourceFileManager; 13 | import org.treeleafj.xdoc.resolver.javaparser.JavaParserDocTagResolver; 14 | 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.io.OutputStream; 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | /** 24 | * XDoc主入口,核心处理从这里开始 25 | * 26 | * @author leaf 27 | * @date 2017-03-03 16:25 28 | */ 29 | public class XDoc { 30 | 31 | private static final String CHARSET = "utf-8"; 32 | 33 | private Logger logger = LoggerFactory.getLogger(getClass()); 34 | 35 | /** 36 | * 源码路径 37 | */ 38 | private List srcDirs; 39 | 40 | /** 41 | * api框架类型 42 | */ 43 | @Setter 44 | private Framework framework; 45 | 46 | /** 47 | * 默认的java注释解析器实现 48 | *

49 | * 备注:基于sun doc的解析方式已经废弃,若需要请参考v1.0之前的版本 50 | * 51 | * @see org.treeleafj.xdoc.resolver.javaparser.JavaParserDocTagResolver 52 | */ 53 | @Setter 54 | private DocTagResolver docTagResolver = new JavaParserDocTagResolver(); 55 | 56 | /** 57 | * 构建XDoc对象 58 | * 59 | * @param srcDir 源码目录路径 60 | */ 61 | public XDoc(File srcDir, Framework framework) { 62 | this(Arrays.asList(srcDir), framework); 63 | } 64 | 65 | 66 | /** 67 | * 构建XDoc对象 68 | * 69 | * @param srcDirs 源码目录路径,支持多个 70 | */ 71 | public XDoc(List srcDirs, Framework framework) { 72 | this.srcDirs = srcDirs; 73 | this.framework = framework; 74 | } 75 | 76 | /** 77 | * 解析源码并返回对应的接口数据 78 | * 79 | * @return API接口数据 80 | */ 81 | public ApiDoc resolve() { 82 | List files = new ArrayList<>(); 83 | for (File dir : this.srcDirs) { 84 | 85 | if (!dir.exists()) { 86 | logger.error("源码路径[{}]不存在", dir.getAbsolutePath()); 87 | continue; 88 | } 89 | 90 | if (!dir.isDirectory()) { 91 | logger.error("源码路径[{}]不是一个目录", dir.getAbsolutePath()); 92 | continue; 93 | } 94 | 95 | logger.info("开始解析源码路径:{}", dir.getAbsolutePath()); 96 | files.addAll(JavaSourceFileManager.getInstance().getAllJavaFiles(dir)); 97 | } 98 | 99 | List apiModules = this.docTagResolver.resolve(files, framework); 100 | 101 | if (framework != null && apiModules != null) { 102 | apiModules = framework.extend(apiModules); 103 | } 104 | return new ApiDoc(apiModules); 105 | } 106 | 107 | /** 108 | * 构建接口文档 109 | * 110 | * @param out 输出位置 111 | * @param format 文档格式 112 | */ 113 | public void build(OutputStream out, Format format) { 114 | this.build(out, format, null); 115 | } 116 | 117 | /** 118 | * 构建接口文档 119 | * 120 | * @param out 输出位置 121 | * @param format 文档格式 122 | * @param properties 文档属性 123 | */ 124 | public void build(OutputStream out, Format format, Map properties) { 125 | ApiDoc apiDoc = this.resolve(); 126 | if (properties != null) { 127 | apiDoc.getProperties().putAll(properties); 128 | } 129 | 130 | if (apiDoc.getApiModules() != null && out != null && format != null) { 131 | String s = format.format(apiDoc); 132 | try { 133 | IOUtils.write(s, out, CHARSET); 134 | } catch (IOException e) { 135 | logger.error("接口文档写入文件失败", e); 136 | } finally { 137 | IOUtils.closeQuietly(out); 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/format/Format.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.format; 2 | 3 | import org.treeleafj.xdoc.model.ApiDoc; 4 | 5 | /** 6 | * 文档输出格式 7 | *

8 | * Created by leaf on 2018/6/22. 9 | */ 10 | public interface Format { 11 | 12 | String format(ApiDoc apiDoc); 13 | } 14 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/format/http/HtmlForamt.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.format.http; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.treeleafj.xdoc.format.Format; 6 | import org.treeleafj.xdoc.model.ApiDoc; 7 | import org.treeleafj.xdoc.utils.JsonUtils; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by leaf on 2017/3/18 0018. 16 | */ 17 | public class HtmlForamt implements Format { 18 | 19 | @Override 20 | public String format(ApiDoc apiDoc) { 21 | InputStream in = HtmlForamt.class.getResourceAsStream("html.vm"); 22 | if (in != null) { 23 | try { 24 | String s = IOUtils.toString(in, "utf-8"); 25 | 26 | Map model = new HashMap<>(); 27 | model.put("title", StringUtils.defaultString((String) apiDoc.getProperties().get("title"), "接口文档")); 28 | model.put("apiModules", apiDoc.getApiModules()); 29 | 30 | return s.replace("_apis_json", JsonUtils.toJson(model)); 31 | } catch (IOException e) { 32 | throw new RuntimeException(e); 33 | } finally { 34 | IOUtils.closeQuietly(in); 35 | } 36 | } 37 | return ""; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/format/http/MarkdownFormat.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.format.http; 2 | 3 | import org.apache.commons.beanutils.PropertyUtils; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.treeleafj.xdoc.format.Format; 8 | import org.treeleafj.xdoc.model.*; 9 | import org.treeleafj.xdoc.model.http.HttpApiAction; 10 | import org.treeleafj.xdoc.model.http.HttpParam; 11 | import org.treeleafj.xdoc.utils.JsonUtils; 12 | 13 | import java.util.HashSet; 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | /** 18 | * Created by leaf on 2017/3/4. 19 | */ 20 | public class MarkdownFormat implements Format { 21 | 22 | private Logger log = LoggerFactory.getLogger(getClass()); 23 | 24 | private VelocityTemplater templater = new VelocityTemplater("org/treeleafj/xdoc/format/http/markdown.vm"); 25 | 26 | @Override 27 | public String format(ApiDoc apiDoc) { 28 | StringBuilder sb = new StringBuilder(); 29 | for (ApiModule apiModule : apiDoc.getApiModules()) { 30 | sb.append(format(apiModule)).append("\n\n"); 31 | } 32 | return sb.toString(); 33 | } 34 | 35 | private String format(ApiModule apiModule) { 36 | 37 | for (ApiAction apiAction : apiModule.getApiActions()) { 38 | HttpApiAction saa = (HttpApiAction) apiAction; 39 | if (saa.isJson() && StringUtils.isNotBlank(saa.getRespbody())) { 40 | saa.setRespbody(JsonUtils.formatJson(saa.getRespbody())); 41 | } 42 | 43 | ObjectInfo returnObj = saa.getReturnObj(); 44 | if (returnObj != null && returnObj.getFieldInfos() != null) { 45 | //将@resp标签跟@return标签中重复的属性进行去重,以@resp的为准 46 | Set paramNames = new HashSet<>(); 47 | for (HttpParam param : saa.getRespParam()) { 48 | paramNames.add(param.getParamName()); 49 | } 50 | 51 | for (FieldInfo fieldInfo : returnObj.getFieldInfos()) { 52 | if (paramNames.contains(fieldInfo.getName())) { 53 | continue; 54 | } 55 | HttpParam param = toHttpParam(fieldInfo); 56 | saa.getRespParam().add(param); 57 | } 58 | } 59 | 60 | if (saa.getParamObjs().size() > 0) { 61 | //将@param跟@paramObj标签中重复的属性进行去重,以@param中的为准 62 | Set paramNames = new HashSet<>(); 63 | for (HttpParam param : saa.getParams()) { 64 | paramNames.add(param.getParamName()); 65 | } 66 | 67 | for (ObjectInfo paramObj : saa.getParamObjs()) { 68 | for (FieldInfo fieldInfo : paramObj.getFieldInfos()) { 69 | if (paramNames.contains(fieldInfo.getName())) { 70 | continue; 71 | } 72 | HttpParam param = toHttpParam(fieldInfo); 73 | saa.getParams().add(param); 74 | } 75 | } 76 | } 77 | } 78 | 79 | try { 80 | Map map = PropertyUtils.describe(apiModule); 81 | return templater.parse(map); 82 | } catch (Exception e) { 83 | log.error("输出markdown文档格式失败", e); 84 | } 85 | return null; 86 | } 87 | 88 | private HttpParam toHttpParam(FieldInfo fieldInfo) { 89 | HttpParam param = new HttpParam(); 90 | param.setParamType(fieldInfo.getSimpleTypeName()); 91 | param.setParamDesc(fieldInfo.getComment()); 92 | param.setParamName(fieldInfo.getName()); 93 | param.setRequire(fieldInfo.isRequire()); 94 | return param; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/format/http/VelocityTemplater.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.format.http; 2 | 3 | import org.apache.velocity.Template; 4 | import org.apache.velocity.VelocityContext; 5 | import org.apache.velocity.app.Velocity; 6 | import org.apache.velocity.app.VelocityEngine; 7 | 8 | import java.io.StringWriter; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created by leaf on 2017/3/4. 13 | */ 14 | public class VelocityTemplater { 15 | 16 | public static final String ENCODING = "UTF-8"; 17 | 18 | private static VelocityEngine ve = new VelocityEngine(); 19 | 20 | static { 21 | //设置模板加载路径,这里设置的是class下 22 | ve.setProperty(Velocity.RESOURCE_LOADER, "class"); 23 | ve.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); 24 | ve.setProperty(VelocityEngine.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.NullLogChute"); 25 | ve.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_CACHE, false); 26 | // ve.setProperty("file.resource.loader.modificationCheckInterval", 10); 27 | ve.init(); 28 | } 29 | 30 | private String path; 31 | 32 | public VelocityTemplater(String path) { 33 | this.path = path; 34 | } 35 | 36 | public String parse(Map param) { 37 | 38 | // Template template = ve.getTemplate("com/jleaf/test/netty/template/" + name + ".vm", ENCODING); 39 | Template template = ve.getTemplate(path, ENCODING); 40 | 41 | VelocityContext velocityContext = new VelocityContext(); 42 | 43 | for (Map.Entry entry : param.entrySet()) { 44 | velocityContext.put(entry.getKey(), entry.getValue()); 45 | } 46 | 47 | StringWriter sw = new StringWriter(); 48 | template.merge(velocityContext, sw); 49 | 50 | return sw.toString(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/format/http/html.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 接口文档 6 | 7 | 8 | 9 | 10 | 11 |

12 |
13 | 14 |

{{title}}

15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 | 28 |
29 |
30 |

{{currentApiAction.title}}

31 |

描述: {{currentApiAction.comment}}

32 |

只支持: 33 | {{m}} 34 |

35 |

接口地址: {{uri}}

36 |

接口返回: {{currentApiAction.returnDesc}}

37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 71 |
{{reverseRespbody}}
72 |
73 |
74 |
75 | 76 |
77 |

测试

78 |
79 |
80 | 81 | 82 | 83 | 84 | {{m}} 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 测试 94 | 重置 95 | 96 | 97 | 98 |

返回内容

99 |
100 |
101 | 102 |
103 |
104 |
105 |
106 | 107 | 110 | 179 | 444 | 445 | 446 | 551 | 552 | 553 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/format/http/markdown.vm: -------------------------------------------------------------------------------- 1 | # $comment 2 | 3 | #foreach( $apiAction in $apiActions) 4 | 5 | --- 6 | # $apiAction.title 7 | > $apiAction.comment 8 | 9 | #if($uris.size() > 0) 10 | 地址: $uris.get(0)/$apiAction.uris.get(0) 11 | #else 12 | 地址: $apiAction.uris.get(0) 13 | #end 14 | #if($apiAction.methods.size() > 0) 15 | 支持方式: $apiAction.methods 16 | #else 17 | 支持方式: 所有 18 | #end 19 | 20 | **参数:** 21 | 22 | 参数名|类型|是否必填|描述 23 | -----|------|------ 24 | #foreach($p in $apiAction.params) 25 | $p.paramName|$p.paramType|#if($p.require) 是 #else 否 #end|$p.paramDesc 26 | #end 27 | 28 | **返回:** 29 | $apiAction.returnDesc 30 | 31 | 参数名|类型|是否必填|描述 32 | -----|------|------ 33 | #foreach($p in $apiAction.respParam) 34 | $p.paramName|$p.paramType|#if($p.require) 是 #else 否 #end|$p.paramDesc 35 | #end 36 | 37 | #if($apiAction.respbody) 38 | 返回: 39 | ```json 40 | $apiAction.respbody 41 | ``` 42 | #end 43 | #end -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/framework/AbstractHttpFramework.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.framework; 2 | 3 | import org.apache.commons.beanutils.BeanUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.treeleafj.xdoc.model.ApiAction; 7 | import org.treeleafj.xdoc.model.ApiModule; 8 | import org.treeleafj.xdoc.model.ObjectInfo; 9 | import org.treeleafj.xdoc.model.http.HttpApiAction; 10 | import org.treeleafj.xdoc.model.http.HttpParam; 11 | import org.treeleafj.xdoc.tag.*; 12 | import org.treeleafj.xdoc.utils.TagUtils; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * 提供获取通用注释上的信息 19 | */ 20 | public abstract class AbstractHttpFramework implements Framework { 21 | 22 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 23 | 24 | @Override 25 | public List extend(List apiModules) { 26 | for (ApiModule apiModule : apiModules) { 27 | for (int i = 0; i < apiModule.getApiActions().size(); i++) { 28 | ApiAction oldApiAction = apiModule.getApiActions().get(i); 29 | HttpApiAction newApiAction = new HttpApiAction(); 30 | try { 31 | BeanUtils.copyProperties(newApiAction, oldApiAction); 32 | } catch (Exception e) { 33 | logger.error("copy ApiAction to HttpApiAction properties error", e); 34 | return new ArrayList<>(0); 35 | } 36 | 37 | newApiAction.setTitle(this.getTitile(newApiAction)); 38 | newApiAction.setRespbody(this.getRespbody(newApiAction)); 39 | newApiAction.setParams(this.getParams(newApiAction)); 40 | newApiAction.setRespParam(this.getResp(newApiAction)); 41 | newApiAction.setReturnObj(this.getSeeObj(newApiAction)); 42 | newApiAction.setReturnDesc(this.getReturnDesc(newApiAction)); 43 | newApiAction.setParamObjs(this.getParamObjs(newApiAction)); 44 | 45 | apiModule.getApiActions().set(i, newApiAction); 46 | } 47 | } 48 | 49 | return apiModules; 50 | } 51 | 52 | /** 53 | * 获取@title上的信息 54 | */ 55 | protected String getTitile(ApiAction aa) { 56 | DocTag titleTag = TagUtils.findTag(aa.getDocTags(), "@title"); 57 | if (titleTag != null) { 58 | return (String) titleTag.getValues(); 59 | } else { 60 | return aa.getComment(); 61 | } 62 | } 63 | 64 | /** 65 | * 获取@respbody上的信息 66 | */ 67 | protected String getRespbody(ApiAction aa) { 68 | DocTag respbodyTag = TagUtils.findTag(aa.getDocTags(), "@respbody"); 69 | if (respbodyTag != null) { 70 | return (String) respbodyTag.getValues(); 71 | } 72 | return null; 73 | } 74 | 75 | 76 | /** 77 | * 获取@param注释上的信息 78 | */ 79 | protected List getParams(ApiAction aa) { 80 | List tags = TagUtils.findTags(aa.getDocTags(), "@param"); 81 | List paramInfos = new ArrayList<>(tags.size()); 82 | for (Object tag : tags) { 83 | ParamTagImpl paramTag = (ParamTagImpl) tag; 84 | HttpParam paramInfo = new HttpParam(); 85 | paramInfo.setParamName(paramTag.getParamName()); 86 | paramInfo.setParamDesc(paramTag.getParamDesc()); 87 | paramInfo.setParamType(paramTag.getParamType()); 88 | paramInfo.setRequire(paramTag.isRequire()); 89 | paramInfos.add(paramInfo); 90 | } 91 | return paramInfos; 92 | } 93 | 94 | /** 95 | * 获取@resp注释上的信息 96 | */ 97 | protected List getResp(ApiAction aa) { 98 | List tags = TagUtils.findTags(aa.getDocTags(), "@resp"); 99 | List list = new ArrayList(tags.size()); 100 | for (DocTag tag : tags) { 101 | RespTagImpl respTag = (RespTagImpl) tag; 102 | HttpParam paramInfo = new HttpParam(); 103 | paramInfo.setParamName(respTag.getParamName()); 104 | paramInfo.setRequire(respTag.isRequire()); 105 | paramInfo.setParamDesc(respTag.getParamDesc()); 106 | paramInfo.setParamType(respTag.getParamType()); 107 | list.add(paramInfo); 108 | } 109 | return list; 110 | } 111 | 112 | /** 113 | * 获取@return注释上的描述语 114 | */ 115 | protected String getReturnDesc(ApiAction aa) { 116 | DocTag tag = TagUtils.findTag(aa.getDocTags(), "@return"); 117 | return tag != null ? tag.getValues().toString() : null; 118 | } 119 | 120 | /** 121 | * 获取@see注释上的对象 122 | */ 123 | protected ObjectInfo getSeeObj(ApiAction aa) { 124 | SeeTagImpl tag = (SeeTagImpl) TagUtils.findTag(aa.getDocTags(), "@see"); 125 | return tag != null ? tag.getValues() : null; 126 | } 127 | 128 | /** 129 | * 获取@paramObj注解上的对象 130 | */ 131 | private List getParamObjs(HttpApiAction aa) { 132 | List tags = TagUtils.findTags(aa.getDocTags(), "@paramObj"); 133 | List paramObjs = new ArrayList<>(tags.size()); 134 | for (DocTag tag : tags) { 135 | ParamObjTagImpl paramObjTag = (ParamObjTagImpl) tag; 136 | paramObjs.add(paramObjTag.getValues()); 137 | } 138 | return paramObjs; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/framework/Framework.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.framework; 2 | 3 | import org.treeleafj.xdoc.model.ApiModule; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 抽象各种API框架的特性,用于在基于xDoc-core渲染出来的ApiModule基础中,进行再度包装 9 | *

10 | * 11 | * @author leaf 12 | * @date 2018/6/22 13 | */ 14 | public interface Framework { 15 | 16 | /** 17 | * 扩展API数据 18 | * 19 | * @param apiModules 原始基本的Api数据 20 | * @return 扩展后的api数据 21 | */ 22 | List extend(List apiModules); 23 | 24 | /** 25 | * 判断该类是否适合该框架 26 | * 27 | * @param classz 扫描到的类 28 | * @return 是支持 29 | */ 30 | boolean support(Class classz); 31 | } 32 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/model/ApiAction.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.Data; 5 | import org.treeleafj.xdoc.tag.DocTag; 6 | 7 | import java.lang.reflect.Method; 8 | import java.util.List; 9 | 10 | /** 11 | * 接口信息,一个接口类里面会有多个接口,每个接口都抽象成ApiAction 12 | * 13 | * @author leaf 14 | * @date 2017-03-03 11:09 15 | */ 16 | @Data 17 | public class ApiAction { 18 | 19 | /** 20 | * 展示用的标题 21 | */ 22 | private String title; 23 | 24 | /** 25 | * 接口方法名称 26 | */ 27 | private String name; 28 | 29 | /** 30 | * 接口方法 31 | */ 32 | @JsonIgnore 33 | private Method method; 34 | 35 | /** 36 | * 接口的描述 37 | */ 38 | private String comment; 39 | 40 | /** 41 | * 方法上标注的注解 42 | */ 43 | private List docTags; 44 | } 45 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/model/ApiDoc.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by leaf on 2018/6/22. 11 | */ 12 | @Data 13 | public class ApiDoc { 14 | 15 | /** 16 | * 附带的属性 17 | */ 18 | private Map properties = new HashMap<>(); 19 | 20 | /** 21 | * 所有API模块 22 | */ 23 | private List apiModules; 24 | 25 | public ApiDoc(List apiModules) { 26 | this.apiModules = apiModules; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/model/ApiModule.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.Data; 5 | 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | /** 10 | * 接口业务模块,一个接口类为一个模块 11 | * 12 | * @author leaf 13 | * @date 2017-03-03 10:32 14 | */ 15 | @Data 16 | public class ApiModule { 17 | 18 | /** 19 | * 源码在哪个类 20 | */ 21 | @JsonIgnore 22 | private transient Class type; 23 | 24 | /** 25 | * 业务模块的描述 26 | */ 27 | private String comment; 28 | 29 | /** 30 | * 此业务模块下有哪些接口 31 | */ 32 | private List apiActions = new LinkedList<>(); 33 | } 34 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/model/FieldInfo.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author leaf 9 | * @date 2017-03-03 12:14 10 | */ 11 | @Data 12 | public class FieldInfo { 13 | 14 | private String name; 15 | 16 | private Class type; 17 | 18 | private String simpleTypeName; 19 | 20 | private String comment; 21 | 22 | /** 23 | * 是否必填,默认false 24 | */ 25 | private boolean require; 26 | 27 | private List fieldInfos; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/model/ObjectInfo.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | /** 9 | * 针对@see标签指向的类具体信息 10 | * 11 | * @author leaf 12 | * @date 2017-03-03 12:14 13 | */ 14 | @Data 15 | public class ObjectInfo { 16 | 17 | /** 18 | * 源码在哪个类 19 | */ 20 | private Class type; 21 | 22 | /** 23 | * 上面的注释 24 | */ 25 | private String comment; 26 | 27 | /** 28 | * 对象的属性 29 | */ 30 | private List fieldInfos = new LinkedList<>(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/model/http/HttpApiAction.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.model.http; 2 | 3 | import lombok.Data; 4 | import org.treeleafj.xdoc.model.ApiAction; 5 | import org.treeleafj.xdoc.model.ObjectInfo; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by leaf on 2017/3/4. 12 | */ 13 | @Data 14 | public class HttpApiAction extends ApiAction { 15 | 16 | /** 17 | * 访问的uri地址 18 | */ 19 | private List uris; 20 | 21 | /** 22 | * 允许的访问方法:POST,GET,DELETE,PUT等, 如果没有,则无限制 23 | */ 24 | private List methods; 25 | 26 | /** 27 | * 入参 28 | */ 29 | private List params = new ArrayList<>(0); 30 | 31 | /** 32 | * 请求参数对象 33 | */ 34 | private List paramObjs = new ArrayList<>(0); 35 | 36 | /** 37 | * 返回对象 38 | */ 39 | private ObjectInfo returnObj; 40 | 41 | /** 42 | * 出参 43 | */ 44 | private List respParam = new ArrayList<>(0); 45 | 46 | /** 47 | * 返回描述 48 | */ 49 | private String returnDesc; 50 | 51 | /** 52 | * 返回的数据 53 | */ 54 | private String respbody; 55 | 56 | /** 57 | * 是否返回json 58 | */ 59 | private boolean json; 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/model/http/HttpParam.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.model.http; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * Created by leaf on 2017/3/12 0012. 7 | */ 8 | @Data 9 | public class HttpParam { 10 | 11 | /** 12 | * 参数名 13 | */ 14 | private String paramName; 15 | 16 | /** 17 | * 参数描述 18 | */ 19 | private String paramDesc; 20 | 21 | /** 22 | * 是否必填,默认false 23 | */ 24 | private boolean require; 25 | 26 | /** 27 | * 类型 28 | */ 29 | private String paramType; 30 | } 31 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/DocTagResolver.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver; 2 | 3 | import org.treeleafj.xdoc.framework.Framework; 4 | import org.treeleafj.xdoc.model.ApiModule; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 注释解析器接口,所有的解析器实现都要继承此接口 10 | *

11 | * 现有的实现有基于开源的javaparser. 12 | * 而sun javadoc这种已经废弃掉了 13 | *

14 | * 15 | * @author leaf 16 | * @date 2017/4/1 0001 17 | */ 18 | public interface DocTagResolver { 19 | 20 | /** 21 | * 执行解析 22 | * 23 | * @param files 要解析的所有java源代码文件的绝对路径 24 | * @param framework api文档所属框架 25 | */ 26 | List resolve(List files, Framework framework); 27 | } 28 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/IgnoreApi.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 忽略生成接口文档,标注了该注解的接口,将不会生成接口文档 10 | *

11 | * Created by leaf on 2018/2/28. 12 | */ 13 | @Target({ElementType.TYPE, ElementType.METHOD}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface IgnoreApi { 16 | } 17 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/JavaSourceFileManager.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * 类源码路径映射管理器 11 | */ 12 | public class JavaSourceFileManager { 13 | 14 | private static final JavaSourceFileManager instance = new JavaSourceFileManager(); 15 | 16 | private static Map classPath = new HashMap<>(); 17 | 18 | public static JavaSourceFileManager getInstance() { 19 | return instance; 20 | } 21 | 22 | 23 | /** 24 | * 递归获取指定目录下面所有的Java文件,包括子目录中的 25 | * 26 | * @param file 文件目录 27 | * @return 所有java文件 28 | */ 29 | public List getAllJavaFiles(File file) { 30 | if (!file.exists()) { 31 | return new ArrayList(0); 32 | } 33 | 34 | if (file.isFile()) { 35 | if (file.getName().lastIndexOf(".java") > 0) { 36 | List list = new ArrayList(1); 37 | list.add(file.getAbsolutePath()); 38 | return list; 39 | } else { 40 | return new ArrayList(0); 41 | } 42 | } 43 | 44 | List list = new ArrayList(); 45 | if (file.isDirectory()) { 46 | File[] files = file.listFiles(); 47 | if (files != null) { 48 | for (File f : files) { 49 | list.addAll(getAllJavaFiles(f)); 50 | } 51 | } 52 | } 53 | return list; 54 | } 55 | 56 | /** 57 | * 将指定类名与对应的类源码文件路径存放进来 58 | * 59 | * @param name 类名称 60 | * @param path 类源码文件路径 61 | */ 62 | public void put(String name, String path) { 63 | classPath.put(name, path); 64 | } 65 | 66 | /** 67 | * 获取指定类名所对应的类源码文件路径 68 | * 69 | * @param name 类名 70 | * @return 源码文件路径, 如果不存在则返回null 71 | */ 72 | public String getPath(String name) { 73 | return classPath.get(name); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/javaparser/JavaParserDocTagResolver.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver.javaparser; 2 | 3 | import com.github.javaparser.JavaParser; 4 | import com.github.javaparser.ParseResult; 5 | import com.github.javaparser.ast.CompilationUnit; 6 | import com.github.javaparser.ast.body.MethodDeclaration; 7 | import com.github.javaparser.ast.body.Parameter; 8 | import com.github.javaparser.ast.body.TypeDeclaration; 9 | import com.github.javaparser.ast.type.Type; 10 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.treeleafj.xdoc.framework.Framework; 15 | import org.treeleafj.xdoc.model.ApiAction; 16 | import org.treeleafj.xdoc.model.ApiModule; 17 | import org.treeleafj.xdoc.resolver.DocTagResolver; 18 | import org.treeleafj.xdoc.resolver.IgnoreApi; 19 | import org.treeleafj.xdoc.resolver.JavaSourceFileManager; 20 | import org.treeleafj.xdoc.resolver.javaparser.converter.JavaParserTagConverter; 21 | import org.treeleafj.xdoc.resolver.javaparser.converter.JavaParserTagConverterRegistrar; 22 | import org.treeleafj.xdoc.tag.DocTag; 23 | import org.treeleafj.xdoc.utils.CommentUtils; 24 | 25 | import java.io.FileInputStream; 26 | import java.lang.reflect.Method; 27 | import java.util.ArrayList; 28 | import java.util.LinkedList; 29 | import java.util.List; 30 | import java.util.Optional; 31 | 32 | /** 33 | * 基于开源JavaParser实现的解析 34 | *

35 | * 36 | * @author leaf 37 | * @date 2017/4/1 0001 38 | */ 39 | public class JavaParserDocTagResolver implements DocTagResolver { 40 | 41 | private Logger log = LoggerFactory.getLogger(JavaParserDocTagResolver.class); 42 | 43 | private JavaParser javaParser = new JavaParser(); 44 | 45 | @Override 46 | public List resolve(List files, Framework framework) { 47 | 48 | //先缓存类文件信息 49 | for (String file : files) { 50 | try (FileInputStream in = new FileInputStream(file)) { 51 | Optional optional = javaParser.parse(in).getResult(); 52 | if (!optional.isPresent()) { 53 | continue; 54 | } 55 | 56 | CompilationUnit cu = optional.get(); 57 | if (cu.getTypes().size() <= 0) { 58 | continue; 59 | } 60 | 61 | TypeDeclaration typeDeclaration = cu.getTypes().get(0); 62 | final Class moduleType = Class.forName(cu.getPackageDeclaration().get().getNameAsString() + "." + typeDeclaration.getNameAsString()); 63 | IgnoreApi ignoreApi = moduleType.getAnnotation(IgnoreApi.class); 64 | if (ignoreApi == null) { 65 | //缓存"包名+类名"跟对应的.java文件的位置映射关系 66 | JavaSourceFileManager.getInstance().put(moduleType.getName(), file); 67 | //缓存"类名"跟对应的.java文件的位置映射关系 68 | JavaSourceFileManager.getInstance().put(moduleType.getSimpleName(), file); 69 | } 70 | } catch (Exception e) { 71 | log.warn("读取文件失败:{}, {}", file, e.getMessage()); 72 | } 73 | } 74 | 75 | List apiModules = new LinkedList<>(); 76 | 77 | for (String file : files) { 78 | try (FileInputStream in = new FileInputStream(file)) { 79 | 80 | Optional optional = javaParser.parse(in).getResult(); 81 | if (!optional.isPresent()) { 82 | continue; 83 | } 84 | 85 | CompilationUnit cu = optional.get(); 86 | if (cu.getTypes().size() <= 0) { 87 | continue; 88 | } 89 | 90 | TypeDeclaration typeDeclaration = cu.getTypes().get(0); 91 | final Class moduleType = Class.forName(cu.getPackageDeclaration().get().getNameAsString() + "." + typeDeclaration.getNameAsString()); 92 | 93 | 94 | if (!framework.support(moduleType)) { 95 | continue; 96 | } 97 | 98 | IgnoreApi ignoreApi = moduleType.getAnnotation(IgnoreApi.class); 99 | if (ignoreApi != null) { 100 | continue; 101 | } 102 | 103 | final ApiModule apiModule = new ApiModule(); 104 | apiModule.setType(moduleType); 105 | if (typeDeclaration.getComment().isPresent()) { 106 | String commentText = CommentUtils.parseCommentText(typeDeclaration.getComment().get().getContent()); 107 | commentText = commentText.split("\n")[0].split("\r")[0]; 108 | apiModule.setComment(commentText); 109 | } 110 | 111 | new VoidVisitorAdapter() { 112 | @Override 113 | public void visit(MethodDeclaration m, Void arg) { 114 | Method method = parseToMenthod(moduleType, m); 115 | if (method == null) { 116 | log.warn("查找不到方法:{}.{}", moduleType.getSimpleName(), m.getNameAsString()); 117 | return; 118 | } 119 | 120 | IgnoreApi ignoreApi = method.getAnnotation(IgnoreApi.class); 121 | if (ignoreApi != null || !m.getComment().isPresent()) { 122 | return; 123 | } 124 | 125 | List comments = CommentUtils.asCommentList(StringUtils.defaultIfBlank(m.getComment().get().getContent(), "")); 126 | List docTagList = new ArrayList<>(comments.size()); 127 | 128 | for (int i = 0; i < comments.size(); i++) { 129 | String c = comments.get(i); 130 | String tagType = CommentUtils.getTagType(c); 131 | if (StringUtils.isBlank(tagType)) { 132 | continue; 133 | } 134 | JavaParserTagConverter converter = JavaParserTagConverterRegistrar.getInstance().getConverter(tagType); 135 | DocTag docTag = converter.converter(c); 136 | if (docTag != null) { 137 | docTagList.add(docTag); 138 | } else { 139 | log.warn("识别不了:{}", c); 140 | } 141 | } 142 | 143 | ApiAction apiAction = new ApiAction(); 144 | if (m.getComment().isPresent()) { 145 | apiAction.setComment(CommentUtils.parseCommentText(m.getComment().get().getContent())); 146 | } 147 | apiAction.setName(m.getNameAsString()); 148 | apiAction.setDocTags(docTagList); 149 | apiAction.setMethod(method); 150 | apiModule.getApiActions().add(apiAction); 151 | 152 | super.visit(m, arg); 153 | } 154 | }.visit(cu, null); 155 | 156 | apiModules.add(apiModule); 157 | 158 | } catch (Exception e) { 159 | log.warn("解析{}失败:{}", file, e.getMessage()); 160 | continue; 161 | } 162 | } 163 | return apiModules; 164 | } 165 | 166 | /** 167 | * 获取指定方法的所有入参类型,便于反射 168 | * 169 | * @param declaration 170 | * @return 171 | */ 172 | private static Method parseToMenthod(Class type, MethodDeclaration declaration) { 173 | List parameters = declaration.getParameters(); 174 | parameters = parameters == null ? new ArrayList(0) : parameters; 175 | Method[] methods = type.getDeclaredMethods(); 176 | for (Method m : methods) { 177 | if (!m.getName().equals(declaration.getNameAsString())) { 178 | continue; 179 | } 180 | if (m.getParameterTypes().length != parameters.size()) { 181 | continue; 182 | } 183 | 184 | boolean b = true; 185 | 186 | for (int j = 0; j < m.getParameterTypes().length; j++) { 187 | Class paramClass = m.getParameterTypes()[j]; 188 | Type ptype = parameters.get(j).getType(); 189 | if (ptype == null) { 190 | continue; 191 | } 192 | String paranTypeName = ptype.toString(); 193 | int index = paranTypeName.lastIndexOf("."); 194 | if (index > 0) { 195 | paranTypeName = paranTypeName.substring(index + 1); 196 | } 197 | //处理泛型 198 | index = paranTypeName.indexOf("<"); 199 | if (index > 0) { 200 | paranTypeName = paranTypeName.substring(0, index); 201 | } 202 | 203 | if (!paramClass.getSimpleName().equals(paranTypeName)) { 204 | b = false; 205 | break; 206 | } 207 | } 208 | if (b) { 209 | return m; 210 | } 211 | } 212 | return null; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/javaparser/converter/DefaultJavaParserTagConverterImpl.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver.javaparser.converter; 2 | 3 | import org.treeleafj.xdoc.tag.DocTag; 4 | import org.treeleafj.xdoc.tag.DocTagImpl; 5 | import org.treeleafj.xdoc.utils.CommentUtils; 6 | 7 | /** 8 | * 基于JavaParser包的默认注释解析转换器 9 | * 10 | * @author leaf 11 | * @date 2017/3/4 12 | */ 13 | public class DefaultJavaParserTagConverterImpl implements JavaParserTagConverter { 14 | 15 | @Override 16 | public DocTag converter(String comment) { 17 | String tagType = CommentUtils.getTagType(comment); 18 | String coment = comment.substring(tagType.length()).trim(); 19 | return new DocTagImpl(tagType, coment); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/javaparser/converter/JavaParserTagConverter.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver.javaparser.converter; 2 | 3 | import org.treeleafj.xdoc.tag.DocTag; 4 | 5 | /** 6 | * 针对JavaParser语法解析包解析出来的文本转换器,负责将文本转转DocTag 7 | * 8 | * @author leaf 9 | * @date 2017/4/3 0003 10 | */ 11 | public interface JavaParserTagConverter { 12 | 13 | /** 14 | * 将指定的文本转义为DocTag 15 | * 16 | * @param o 文本 17 | * @return DocTag对象 18 | */ 19 | DocTag converter(T o); 20 | } 21 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/javaparser/converter/JavaParserTagConverterRegistrar.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver.javaparser.converter; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * JavaParserTagConverter注册器,注册管理这些转换器 8 | * 9 | * @author leaf 10 | * @date 2017/4/3 0003 11 | */ 12 | public class JavaParserTagConverterRegistrar { 13 | 14 | private static JavaParserTagConverterRegistrar INSTANCE = new JavaParserTagConverterRegistrar(); 15 | 16 | /** 17 | * 所有转存器的存储在这里面 18 | */ 19 | private Map registrar = new HashMap<>(3); 20 | 21 | /** 22 | * 默认解析转换器 23 | */ 24 | private JavaParserTagConverter defaultTagConverter = new DefaultJavaParserTagConverterImpl(); 25 | 26 | private JavaParserTagConverterRegistrar() { 27 | //注册特殊标签的转换器,其它默认使用DefaultJavaParserTagConverterImpl 28 | registerConverter("@param", new ParamTagConverter()); 29 | registerConverter("@see", new SeeTagConverter()); 30 | registerConverter("@resp", new RespTagConverter()); 31 | registerConverter("@paramObj", new ParamObjTagConverter()); 32 | } 33 | 34 | /** 35 | * 注册转换器 36 | * 37 | * @param tagName 要解析转换的标签 38 | * @param tagConverter 转换器 39 | */ 40 | public void registerConverter(String tagName, JavaParserTagConverter tagConverter) { 41 | registrar.put(tagName, tagConverter); 42 | } 43 | 44 | /** 45 | * 获取标签转换器,如果没有特殊定制的,则返回默认的转换器DefaultJavaParserTagConverterImpl 46 | * 47 | * @param tagName 要转换的标签名称 48 | * @return 匹配到的标签转换器 49 | */ 50 | public JavaParserTagConverter getConverter(String tagName) { 51 | for (Map.Entry entry : registrar.entrySet()) { 52 | if (entry.getKey().equalsIgnoreCase(tagName)) { 53 | return entry.getValue(); 54 | } 55 | } 56 | return defaultTagConverter; 57 | } 58 | 59 | /** 60 | * 获取该注册器的单例对象 61 | */ 62 | public static JavaParserTagConverterRegistrar getInstance() { 63 | return INSTANCE; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/javaparser/converter/ParamObjTagConverter.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver.javaparser.converter; 2 | 3 | import org.treeleafj.xdoc.tag.DocTag; 4 | import org.treeleafj.xdoc.tag.ParamObjTagImpl; 5 | import org.treeleafj.xdoc.tag.SeeTagImpl; 6 | 7 | public class ParamObjTagConverter extends SeeTagConverter { 8 | 9 | @Override 10 | public DocTag converter(String comment) { 11 | SeeTagImpl seeTag = (SeeTagImpl) super.converter(comment); 12 | if (seeTag != null) { 13 | return new ParamObjTagImpl(seeTag.getTagName(), seeTag.getValues()); 14 | } 15 | return null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/javaparser/converter/ParamTagConverter.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver.javaparser.converter; 2 | 3 | import org.treeleafj.xdoc.tag.DocTag; 4 | import org.treeleafj.xdoc.tag.ParamTagImpl; 5 | import org.treeleafj.xdoc.utils.Constant; 6 | 7 | /** 8 | * 针对@param的转换器 9 | * @author leaf 10 | * @date 2017/3/4 11 | */ 12 | public class ParamTagConverter extends DefaultJavaParserTagConverterImpl { 13 | 14 | @Override 15 | public DocTag converter(String comment) { 16 | DocTag docTag = super.converter(comment); 17 | String val = (String) docTag.getValues(); 18 | String[] array = val.split("[ \t]+"); 19 | String paramName = null; 20 | String paramDesc = ""; 21 | String paramType = "String"; 22 | boolean require = false; 23 | //解析 "user :username 用户名|必填" 这种注释内容 24 | //或者 "username 用户名|必填" 这种注释内容 25 | //或者 "username 用户名|String|必填" 这种注释内容 26 | //上面的"必填"两个字也可以换成英文的"Y" 27 | 28 | if (array.length > 0) { 29 | //先将第一个认为是参数名称 30 | paramName = array[0]; 31 | if (array.length > 1) { 32 | 33 | int start = 1; 34 | if (array[1].startsWith(":") && array[1].length() > 1) { 35 | //获取 :username这种类型的参数名称 36 | paramName = array[1].substring(1); 37 | start = 2; 38 | } 39 | 40 | StringBuilder sb = new StringBuilder(); 41 | for (int i = start; i < array.length; i++) { 42 | sb.append(array[i]).append(' '); 43 | } 44 | paramDesc = sb.toString(); 45 | } 46 | } 47 | 48 | String[] descs = paramDesc.split("\\|"); 49 | if (descs.length > 0) { 50 | paramDesc = descs[0]; 51 | if (descs.length > 2) { 52 | paramType = descs[1]; 53 | String requireString = descs[descs.length - 1].trim(); 54 | require = Constant.YES_ZH.equals(requireString) || Constant.YES_EN.equalsIgnoreCase(requireString); 55 | } else if (descs.length == 2) { 56 | String requireString = descs[1].trim(); 57 | require = Constant.YES_ZH.equals(requireString) || Constant.YES_EN.equalsIgnoreCase(requireString); 58 | 59 | //如果最后一个不是是否必填的描述,则认为是类型描述 60 | if (!require && !(Constant.NOT_EN.equalsIgnoreCase(requireString) || Constant.NOT_ZH.equals(requireString))) { 61 | paramType = requireString; 62 | } 63 | } 64 | } 65 | 66 | return new ParamTagImpl(docTag.getTagName(), paramName, paramDesc, paramType, require); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/javaparser/converter/RespTagConverter.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver.javaparser.converter; 2 | 3 | import org.treeleafj.xdoc.tag.DocTag; 4 | import org.treeleafj.xdoc.tag.ParamTagImpl; 5 | import org.treeleafj.xdoc.tag.RespTagImpl; 6 | 7 | /** 8 | * 针对@resp的转换器 9 | * @author leaf 10 | * @date 2017/3/12 0012 11 | */ 12 | public class RespTagConverter extends ParamTagConverter { 13 | 14 | @Override 15 | public DocTag converter(String comment) { 16 | ParamTagImpl paramTag = (ParamTagImpl) super.converter(comment); 17 | RespTagImpl respTag = new RespTagImpl(paramTag.getTagName(), paramTag.getParamName(), paramTag.getParamDesc(), 18 | paramTag.getParamType(), paramTag.isRequire()); 19 | return respTag; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/resolver/javaparser/converter/SeeTagConverter.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.resolver.javaparser.converter; 2 | 3 | import com.github.javaparser.JavaParser; 4 | import com.github.javaparser.ParseResult; 5 | import com.github.javaparser.ast.CompilationUnit; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.treeleafj.xdoc.model.FieldInfo; 10 | import org.treeleafj.xdoc.model.ObjectInfo; 11 | import org.treeleafj.xdoc.resolver.JavaSourceFileManager; 12 | import org.treeleafj.xdoc.tag.DocTag; 13 | import org.treeleafj.xdoc.tag.SeeTagImpl; 14 | import org.treeleafj.xdoc.utils.CommentUtils; 15 | import org.treeleafj.xdoc.utils.JavaFileUtils; 16 | 17 | import java.io.FileInputStream; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.Optional; 21 | 22 | /** 23 | * 针对@see的转换器 24 | * 25 | * @author leaf 26 | * @date 2017/3/4 27 | */ 28 | public class SeeTagConverter extends DefaultJavaParserTagConverterImpl { 29 | 30 | private Logger log = LoggerFactory.getLogger(this.getClass()); 31 | 32 | private JavaParser javaParser = new JavaParser(); 33 | 34 | @Override 35 | public DocTag converter(String comment) { 36 | DocTag docTag = super.converter(comment); 37 | 38 | String path = JavaSourceFileManager.getInstance().getPath((String) docTag.getValues()); 39 | if (StringUtils.isBlank(path)) { 40 | return null; 41 | } 42 | 43 | Class returnClassz; 44 | CompilationUnit cu; 45 | try (FileInputStream in = new FileInputStream(path)) { 46 | Optional optional = javaParser.parse(in).getResult(); 47 | if (!optional.isPresent()) { 48 | return null; 49 | } 50 | cu = optional.get(); 51 | if (cu.getTypes().size() <= 0) { 52 | return null; 53 | } 54 | returnClassz = Class.forName(cu.getPackageDeclaration().get().getNameAsString() + "." + cu.getTypes().get(0).getNameAsString()); 55 | 56 | } catch (Exception e) { 57 | log.warn("读取java原文件失败:{}", path, e.getMessage()); 58 | return null; 59 | } 60 | 61 | String text = cu.getComment().isPresent() ? CommentUtils.parseCommentText(cu.getComment().get().getContent()) : ""; 62 | 63 | Map commentMap = JavaFileUtils.analysisFieldComments(returnClassz); 64 | List fields = JavaFileUtils.analysisFields(returnClassz, commentMap); 65 | 66 | ObjectInfo objectInfo = new ObjectInfo(); 67 | objectInfo.setType(returnClassz); 68 | objectInfo.setFieldInfos(fields); 69 | objectInfo.setComment(text); 70 | return new SeeTagImpl(docTag.getTagName(), objectInfo); 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/tag/DocTag.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.tag; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 针对注释标签 7 | * 8 | * Created by leaf on 2017/3/4. 9 | */ 10 | public abstract class DocTag { 11 | 12 | /** 13 | * 标签名称 14 | */ 15 | @Getter 16 | private String tagName; 17 | 18 | public DocTag(String tagName) { 19 | this.tagName = tagName; 20 | } 21 | 22 | public abstract T getValues(); 23 | } 24 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/tag/DocTagImpl.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.tag; 2 | 3 | /** 4 | * 简单文本型注释标签实现 5 | *

6 | * Created by leaf on 2017/3/4. 7 | */ 8 | public class DocTagImpl extends DocTag { 9 | 10 | private String value; 11 | 12 | public DocTagImpl(String tagName, String value) { 13 | super(tagName); 14 | this.value = value; 15 | } 16 | 17 | @Override 18 | public String getValues() { 19 | return value; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/tag/ParamObjTagImpl.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.tag; 2 | 3 | import org.treeleafj.xdoc.model.ObjectInfo; 4 | 5 | public class ParamObjTagImpl extends DocTag { 6 | 7 | private ObjectInfo objectInfo; 8 | 9 | public ParamObjTagImpl(String tagName, ObjectInfo objectInfo) { 10 | super(tagName); 11 | this.objectInfo = objectInfo; 12 | } 13 | 14 | @Override 15 | public ObjectInfo getValues() { 16 | return objectInfo; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/tag/ParamTagImpl.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.tag; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 对@Param注释的封装 7 | *

8 | * Created by leaf on 2017/3/4. 9 | */ 10 | @Data 11 | public class ParamTagImpl extends DocTag { 12 | 13 | /** 14 | * 参数名 15 | */ 16 | private String paramName; 17 | 18 | /** 19 | * 参数描述 20 | */ 21 | private String paramDesc; 22 | 23 | /** 24 | * 是否必填,默认false 25 | */ 26 | private boolean require; 27 | 28 | /** 29 | * 参数类型 30 | */ 31 | private String paramType; 32 | 33 | public ParamTagImpl(String tagName, String paramName, String paramDesc, String paramType, boolean require) { 34 | super(tagName); 35 | this.paramName = paramName; 36 | this.paramDesc = paramDesc; 37 | this.paramType = paramType; 38 | this.require = require; 39 | } 40 | 41 | @Override 42 | public String getValues() { 43 | return paramName + " " + this.paramDesc; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/tag/RespTagImpl.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.tag; 2 | 3 | import lombok.Data; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.treeleafj.xdoc.utils.Constant; 6 | 7 | /** 8 | * 针对@Resp注释的内容封装 9 | * 10 | * Created by leaf on 2017/3/12 0012. 11 | */ 12 | @Data 13 | public class RespTagImpl extends DocTag { 14 | 15 | /** 16 | * 参数名 17 | */ 18 | private String paramName; 19 | 20 | /** 21 | * 参数描述 22 | */ 23 | private String paramDesc; 24 | 25 | /** 26 | * 是否必填,默认false 27 | */ 28 | private boolean require; 29 | 30 | /** 31 | * 类型 32 | */ 33 | private String paramType; 34 | 35 | public RespTagImpl(String tagName, String paramName, String paramDesc, String paramType, boolean require) { 36 | super(tagName); 37 | this.paramName = paramName; 38 | this.paramDesc = paramDesc; 39 | this.paramType = paramType; 40 | this.require = require; 41 | } 42 | 43 | @Override 44 | public String getValues() { 45 | String s = paramName + " " + paramDesc; 46 | if (StringUtils.isNotBlank(paramType)) { 47 | s += " " + paramType; 48 | } 49 | s += " " + (require ? Constant.YES_ZH : Constant.NOT_ZH); 50 | return s; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/tag/SeeTagImpl.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.tag; 2 | 3 | import org.treeleafj.xdoc.model.ObjectInfo; 4 | 5 | /** 6 | * 针对@see注释标签进行封装,返回@see上注释的类信息 7 | *

8 | * Created by leaf on 2017/3/4. 9 | */ 10 | public class SeeTagImpl extends DocTag { 11 | 12 | private ObjectInfo objectInfo; 13 | 14 | public SeeTagImpl(String tagName, ObjectInfo objectInfo) { 15 | super(tagName); 16 | this.objectInfo = objectInfo; 17 | } 18 | 19 | @Override 20 | public ObjectInfo getValues() { 21 | return objectInfo; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/utils/CommentUtils.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.utils; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * 注释文本工具 12 | * 13 | * @author leaf 14 | * @date 2017/4/3 0003 15 | */ 16 | public class CommentUtils { 17 | 18 | private static Pattern TAG_NAME_COMPILE = Pattern.compile("^@[\\w]+[\\t ]"); 19 | 20 | /** 21 | * 获取注释的类型 22 | * 23 | * @param comment 注释文本 24 | * @return @see @param @resp @return等 25 | */ 26 | public static String getTagType(String comment) { 27 | Matcher m = TAG_NAME_COMPILE.matcher(comment); 28 | if (m.find()) { 29 | return m.group().trim(); 30 | } else { 31 | return null; 32 | } 33 | } 34 | 35 | /** 36 | * 解析基本的文本注释 37 | * 38 | * @param comment 注释文本 39 | */ 40 | public static String parseCommentText(String comment) { 41 | List comments = asCommentList(comment); 42 | for (String s : comments) { 43 | if (!s.startsWith("@")) { 44 | return s; 45 | } 46 | } 47 | return ""; 48 | } 49 | 50 | /** 51 | * 将注释转为多行文本 52 | * 53 | * @param comment 注释文本 54 | */ 55 | public static List asCommentList(String comment) { 56 | comment = comment.replaceAll("\\*", "").trim(); 57 | String[] array = comment.split("\n"); 58 | List comments = new ArrayList(array.length); 59 | int index = 0; 60 | StringBuilder sb = new StringBuilder(); 61 | for (; index < array.length; index++) { 62 | String c = array[index].trim(); 63 | 64 | if (StringUtils.isBlank(c)) { 65 | continue; 66 | } 67 | 68 | String tagType = CommentUtils.getTagType(c); 69 | if (StringUtils.isBlank(tagType)) { 70 | sb.append(c); 71 | sb.append("\n"); 72 | } else { 73 | break; 74 | } 75 | } 76 | 77 | if (sb.length() > 0) { 78 | sb.deleteCharAt(sb.length() - 1); 79 | comments.add(sb.toString()); 80 | } 81 | 82 | for (int i = index; i < array.length; i++) { 83 | String c = array[i].trim(); 84 | if (StringUtils.isNotBlank(c)) { 85 | comments.add(c); 86 | } 87 | } 88 | return comments; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/utils/Constant.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.utils; 2 | 3 | /** 4 | * Created by leaf on 2018/6/22. 5 | */ 6 | public class Constant { 7 | 8 | public static final String NOT_EN = "N"; 9 | public static final String NOT_ZH = "非必填"; 10 | public static final String YES_EN = "Y"; 11 | public static final String YES_ZH = "必填"; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/utils/JavaFileUtils.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.utils; 2 | 3 | import com.github.javaparser.JavaParser; 4 | import com.github.javaparser.ast.CompilationUnit; 5 | import com.github.javaparser.ast.body.FieldDeclaration; 6 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter; 7 | import org.apache.commons.beanutils.PropertyUtils; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.treeleafj.xdoc.model.FieldInfo; 12 | import org.treeleafj.xdoc.resolver.JavaSourceFileManager; 13 | 14 | import java.beans.PropertyDescriptor; 15 | import java.io.File; 16 | import java.io.FileInputStream; 17 | import java.util.*; 18 | 19 | /** 20 | * Java文件工具 21 | * 22 | * 提供解析源码中的类属性和注释等功能 23 | * 24 | * @author leaf 25 | * @date 2017-03-03 16:52 26 | */ 27 | public class JavaFileUtils { 28 | 29 | private static Logger logger = LoggerFactory.getLogger(JavaFileUtils.class); 30 | 31 | private static JavaParser javaParser = new JavaParser(); 32 | 33 | public static Map analysisFieldComments(Class classz) { 34 | 35 | final Map commentMap = new HashMap(10); 36 | 37 | List classes = new LinkedList<>(); 38 | 39 | Class nowClass = classz; 40 | 41 | //获取所有的属性注释(包括父类的) 42 | while (true) { 43 | classes.add(0, nowClass); 44 | if (Object.class.equals(nowClass) || Object.class.equals(nowClass.getSuperclass())) { 45 | break; 46 | } 47 | nowClass = nowClass.getSuperclass(); 48 | } 49 | 50 | //反方向循环,子类属性注释覆盖父类属性 51 | for (Class clz : classes) { 52 | String path = JavaSourceFileManager.getInstance().getPath(clz.getSimpleName()); 53 | if (StringUtils.isBlank(path)) { 54 | continue; 55 | } 56 | try (FileInputStream in = new FileInputStream(path)) { 57 | CompilationUnit cu = javaParser.parse(in).getResult().get(); 58 | 59 | new VoidVisitorAdapter() { 60 | @Override 61 | public void visit(FieldDeclaration n, Void arg) { 62 | String name = n.getVariable(0).getName().asString(); 63 | 64 | String comment = ""; 65 | if (n.getComment().isPresent()) { 66 | comment = n.getComment().get().getContent(); 67 | } 68 | 69 | if (name.contains("=")) { 70 | name = name.substring(0, name.indexOf("=")).trim(); 71 | } 72 | 73 | commentMap.put(name, CommentUtils.parseCommentText(comment)); 74 | } 75 | }.visit(cu, null); 76 | 77 | } catch (Exception e) { 78 | logger.warn("读取java原文件失败:{}", path, e.getMessage(), e); 79 | } 80 | } 81 | 82 | return commentMap; 83 | } 84 | 85 | public static List analysisFields(Class classz, Map commentMap) { 86 | PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(classz); 87 | 88 | 89 | List fields = new ArrayList<>(); 90 | 91 | for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { 92 | //排除掉class属性 93 | if ("class".equals(propertyDescriptor.getName())) { 94 | continue; 95 | } 96 | 97 | FieldInfo field = new FieldInfo(); 98 | field.setType(propertyDescriptor.getPropertyType()); 99 | field.setSimpleTypeName(propertyDescriptor.getPropertyType().getSimpleName()); 100 | field.setName(propertyDescriptor.getName()); 101 | String comment = commentMap.get(propertyDescriptor.getName()); 102 | if (StringUtils.isBlank(comment)) { 103 | field.setComment(""); 104 | field.setRequire(false); 105 | fields.add(field); 106 | } else { 107 | boolean require = false; 108 | if (comment.contains("|")) { 109 | int endIndex = comment.lastIndexOf("|" + Constant.YES_ZH); 110 | if (endIndex < 0) { 111 | endIndex = comment.lastIndexOf("|" + Constant.YES_EN); 112 | } 113 | require = endIndex > 0; 114 | 115 | if (require) { 116 | comment = comment.substring(0, endIndex); 117 | } 118 | } 119 | 120 | field.setComment(comment); 121 | field.setRequire(require); 122 | fields.add(field); 123 | } 124 | } 125 | return fields; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/utils/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.utils; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.apache.commons.lang3.StringUtils; 7 | 8 | import java.text.SimpleDateFormat; 9 | 10 | /** 11 | * Created by leaf on 2018/3/8. 12 | */ 13 | public class JsonUtils { 14 | 15 | private static ObjectMapper objectMapper = new ObjectMapper(); 16 | 17 | static { 18 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 19 | objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); 20 | } 21 | 22 | /** 23 | * 将一个对象转为json字符窜 24 | */ 25 | public static String toJson(Object obj) { 26 | try { 27 | return objectMapper.writeValueAsString(obj); 28 | } catch (JsonProcessingException e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | 33 | 34 | /** 35 | * 格式化json字符串 36 | * 37 | * @param jsonStr 未格式化前的JSON窜 38 | * @return 格式化好的JSON窜 39 | */ 40 | public static String formatJson(String jsonStr) { 41 | if (StringUtils.isBlank(jsonStr)) { 42 | return StringUtils.EMPTY; 43 | } 44 | StringBuilder sb = new StringBuilder(); 45 | char last; 46 | char current = '\0'; 47 | int indent = 0; 48 | for (int i = 0; i < jsonStr.length(); i++) { 49 | last = current; 50 | current = jsonStr.charAt(i); 51 | switch (current) { 52 | case '{': 53 | case '[': 54 | sb.append(current); 55 | sb.append('\n'); 56 | indent++; 57 | addIndentBlank(sb, indent); 58 | break; 59 | case '}': 60 | case ']': 61 | sb.append('\n'); 62 | indent--; 63 | addIndentBlank(sb, indent); 64 | sb.append(current); 65 | break; 66 | case ',': 67 | sb.append(current); 68 | if (last != '\\') { 69 | sb.append('\n'); 70 | addIndentBlank(sb, indent); 71 | } 72 | break; 73 | default: 74 | sb.append(current); 75 | } 76 | } 77 | 78 | return sb.toString(); 79 | } 80 | 81 | /** 82 | * 添加space 83 | * 84 | * @param sb 要追加空格的字符串 85 | * @param indent 追加的空格数 86 | */ 87 | private static void addIndentBlank(StringBuilder sb, int indent) { 88 | for (int i = 0; i < indent; i++) { 89 | sb.append('\t'); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /xDoc-core/src/main/java/org/treeleafj/xdoc/utils/TagUtils.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.utils; 2 | 3 | import org.treeleafj.xdoc.tag.DocTag; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * DocTag工具 10 | * 11 | * @author leaf 12 | * @date 2018/9/30 13 | */ 14 | public class TagUtils { 15 | 16 | /** 17 | * 查找List里面tag名称符合的第一个Tag信息 18 | * 19 | * @param list Tag集合 20 | * @param name DocTag.name, 如@return 21 | * @return 符合的第一个Tag信息, 如果没有则返回null 22 | */ 23 | public static DocTag findTag(List list, String name) { 24 | for (DocTag docTag : list) { 25 | if (docTag.getTagName().equals(name)) { 26 | return docTag; 27 | } 28 | } 29 | return null; 30 | } 31 | 32 | /** 33 | * 查找List里面tag名称符合的多个Tag信息 34 | * 35 | * @param list Tag集合 36 | * @param name DocTag.name, 如@param 37 | * @return 符合的所有Tag信息, 如果没有则返回size=0的List 38 | */ 39 | public static List findTags(List list, String name) { 40 | List docTags = new ArrayList<>(); 41 | for (DocTag docTag : list) { 42 | if (docTag.getTagName().equals(name)) { 43 | docTags.add(docTag); 44 | } 45 | } 46 | return docTags; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /xDoc-jfinal/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | xDoc 7 | com.github.treeleafj 8 | 1.1.0 9 | 10 | 4.0.0 11 | 12 | xDoc-jfinal 13 | 14 | 15 | 16 | 17 | 18 | com.jfinal 19 | jfinal 20 | 4.1 21 | 22 | 23 | 24 | com.github.treeleafj 25 | xDoc-core 26 | ${project.version} 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /xDoc-jfinal/src/main/java/com/jfinal/core/ConfigGetter.java: -------------------------------------------------------------------------------- 1 | package com.jfinal.core; 2 | 3 | import com.jfinal.config.Routes; 4 | 5 | public class ConfigGetter { 6 | 7 | public static Routes getRoutes() { 8 | return Config.getRoutes(); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /xDoc-jfinal/src/main/java/org/treeleafj/xdoc/jfinal/JfinalHttpFramework.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.jfinal; 2 | 3 | import com.jfinal.config.Routes; 4 | import com.jfinal.core.ConfigGetter; 5 | import com.jfinal.core.Controller; 6 | import org.treeleafj.xdoc.framework.AbstractHttpFramework; 7 | import org.treeleafj.xdoc.model.ApiModule; 8 | import org.treeleafj.xdoc.model.http.HttpApiAction; 9 | 10 | import java.util.*; 11 | 12 | /** 13 | * 基于JFinal框架,扩展api接口数据 14 | * 15 | * @auth leaf 16 | * @date 2019/10/20 17 | */ 18 | public class JfinalHttpFramework extends AbstractHttpFramework { 19 | 20 | @Override 21 | public boolean support(Class classz) { 22 | return Controller.class.isAssignableFrom(classz); 23 | } 24 | 25 | @Override 26 | public List extend(List apiModules) { 27 | 28 | apiModules = super.extend(apiModules); 29 | 30 | 31 | Routes routes = ConfigGetter.getRoutes(); 32 | List routeItemList = routes.getRouteItemList(); 33 | 34 | Map controllerMap = new HashMap<>(); 35 | for (Routes.Route route : routeItemList) { 36 | controllerMap.put(route.getControllerClass(), route.getControllerKey()); 37 | } 38 | 39 | for (ApiModule apiModule : apiModules) { 40 | for (int i = 0; i < apiModule.getApiActions().size(); i++) { 41 | HttpApiAction apiAction = (HttpApiAction) apiModule.getApiActions().get(i); 42 | apiAction.setJson(false);//TODO 该属性需要去掉 43 | apiAction.setUris(Arrays.asList(controllerMap.get(apiModule.getType()) + "/" + apiAction.getMethod().getName())); 44 | apiAction.setMethods(Arrays.asList("ALL")); 45 | } 46 | } 47 | 48 | return apiModules; 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /xDoc-jfinal/src/main/java/org/treeleafj/xdoc/jfinal/XDocJfinalController.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.jfinal; 2 | 3 | import com.jfinal.core.Controller; 4 | import com.jfinal.kit.PropKit; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.treeleafj.xdoc.XDoc; 9 | import org.treeleafj.xdoc.format.http.HtmlForamt; 10 | import org.treeleafj.xdoc.model.ApiDoc; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.File; 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | 19 | /** 20 | * xDoc-jfinal 入口 21 | */ 22 | public class XDocJfinalController extends Controller { 23 | 24 | 25 | private Logger logger = LoggerFactory.getLogger(XDocJfinalController.class); 26 | 27 | private static String html; 28 | 29 | private static ApiDoc apiDoc; 30 | 31 | public XDocJfinalController() { 32 | boolean enable = PropKit.getBoolean("xdoc.enable", true); 33 | 34 | if (!enable || apiDoc != null) { 35 | return; 36 | } 37 | 38 | synchronized (XDocJfinalController.class) { 39 | if (apiDoc != null) { 40 | return; 41 | } 42 | init(); 43 | } 44 | 45 | } 46 | 47 | /** 48 | * 访问接口文档首页 49 | */ 50 | public void index() { 51 | renderHtml(html); 52 | } 53 | 54 | /** 55 | * 获取所有文档api 56 | * 57 | * @return 系统所有文档接口的数据(json格式) 58 | */ 59 | public void apis() { 60 | renderJson(this.apiDoc); 61 | } 62 | 63 | /** 64 | * 重新构建文档 65 | * 66 | * @return 文档页面 67 | */ 68 | public void rebuild() { 69 | init(); 70 | redirect("index"); 71 | } 72 | 73 | /** 74 | * 初始化Xdoc文档内容 75 | */ 76 | private void init() { 77 | 78 | String path = PropKit.get("xdoc.sourcePath"); 79 | String version = PropKit.get("xdoc.version"); 80 | String title = PropKit.get("xdoc.title"); 81 | 82 | if (StringUtils.isBlank(path)) { 83 | path = ".";//默认为当前目录 84 | } 85 | 86 | List paths = Arrays.asList(path.split(",")); 87 | 88 | List srcDirs = new ArrayList<>(paths.size()); 89 | List srcDirPaths = new ArrayList<>(paths.size()); 90 | 91 | try { 92 | for (String s : paths) { 93 | File dir = new File(s); 94 | srcDirs.add(dir); 95 | srcDirPaths.add(dir.getCanonicalPath()); 96 | } 97 | } catch (Exception e) { 98 | logger.error("获取源码目录路径错误", e); 99 | return; 100 | } 101 | 102 | logger.debug("starting XDoc, source path:{}", srcDirPaths); 103 | 104 | XDoc xDoc = new XDoc(srcDirs, new JfinalHttpFramework()); 105 | 106 | try { 107 | apiDoc = xDoc.resolve(); 108 | HashMap properties = new HashMap<>(); 109 | properties.put("version", version); 110 | properties.put("title", title); 111 | apiDoc.setProperties(properties); 112 | 113 | //生成接口文档的html 114 | try(ByteArrayOutputStream out = new ByteArrayOutputStream()) { 115 | xDoc.build(out, new HtmlForamt()); 116 | html = out.toString("UTF-8"); 117 | } catch (Exception e) { 118 | logger.error("生成html文档失败"); 119 | } 120 | 121 | } catch (Exception e) { 122 | logger.error("start up XDoc error", e); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /xDoc-spring/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | xDoc 7 | com.github.treeleafj 8 | 1.1.0 9 | 10 | 4.0.0 11 | 12 | xDoc-spring 13 | 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-dependencies 19 | 2.1.7.RELEASE 20 | pom 21 | import 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | com.github.treeleafj 35 | xDoc-core 36 | ${project.version} 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /xDoc-spring/src/main/java/org/treeleafj/xdoc/spring/framework/SpringWebHttpFramework.java: -------------------------------------------------------------------------------- 1 | package org.treeleafj.xdoc.spring.framework; 2 | 3 | import org.apache.commons.beanutils.BeanUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.*; 8 | import org.treeleafj.xdoc.framework.AbstractHttpFramework; 9 | import org.treeleafj.xdoc.model.ApiAction; 10 | import org.treeleafj.xdoc.model.ApiModule; 11 | import org.treeleafj.xdoc.model.http.HttpApiAction; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * 基于spirng web框架,扩展api接口数据 18 | *

19 | * 20 | * @author leaf 21 | * @date 2018/6/22 22 | */ 23 | public class SpringWebHttpFramework extends AbstractHttpFramework { 24 | 25 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 26 | 27 | @Override 28 | public boolean support(Class classz) { 29 | return classz.getAnnotation(Controller.class) != null 30 | || classz.getAnnotation(RestController.class) != null; 31 | } 32 | 33 | @Override 34 | public List extend(List apiModules) { 35 | apiModules = super.extend(apiModules); 36 | 37 | List newApiModules = new ArrayList<>(); 38 | 39 | for (ApiModule apiModule : apiModules) { 40 | 41 | ApiModule newApiModule = new ApiModule(); 42 | newApiModule.setComment(apiModule.getComment()); 43 | newApiModule.setType(apiModule.getType()); 44 | boolean isjson = this.isJson(apiModule.getType()); 45 | 46 | for (ApiAction apiAction : apiModule.getApiActions()) { 47 | HttpApiAction saa = this.buildSpringApiAction(newApiModule, apiAction, isjson); 48 | if (saa != null) { 49 | newApiModule.getApiActions().add(saa); 50 | } 51 | } 52 | 53 | newApiModules.add(newApiModule); 54 | } 55 | return newApiModules; 56 | } 57 | 58 | /** 59 | * 构建基于spring web的接口 60 | * 61 | * @param apiAction 请求的Action信息 62 | * @param isjson 是否json 63 | * @return 封装后的机遇SpringWeb的Action信息 64 | */ 65 | private HttpApiAction buildSpringApiAction(ApiModule apiModule, ApiAction apiAction, boolean isjson) { 66 | 67 | 68 | HttpApiAction saa = new HttpApiAction(); 69 | 70 | try { 71 | BeanUtils.copyProperties(saa, apiAction); 72 | } catch (Exception e) { 73 | logger.error("copy ApiAction to HttpApiAction properties error", e); 74 | return null; 75 | } 76 | 77 | 78 | if (isjson || apiAction.getMethod().getAnnotation(ResponseBody.class) != null) { 79 | saa.setJson(true); 80 | } 81 | 82 | boolean isMappingMethod = this.setUrisAndMethods(apiModule, apiAction, saa); 83 | 84 | if (!isMappingMethod) { 85 | return null; 86 | } 87 | 88 | return saa; 89 | } 90 | 91 | /** 92 | * 设置请求地址和请求方法 93 | */ 94 | private boolean setUrisAndMethods(ApiModule apiModule, ApiAction apiAction, HttpApiAction saa) { 95 | RequestMapping classRequestMappingAnno = apiModule.getType().getAnnotation(RequestMapping.class); 96 | String[] parentPath = new String[0]; 97 | if (classRequestMappingAnno != null) { 98 | parentPath = classRequestMappingAnno.value(); 99 | } 100 | 101 | 102 | RequestMapping methodRequestMappingAnno = apiAction.getMethod().getAnnotation(RequestMapping.class); 103 | if (methodRequestMappingAnno != null) { 104 | saa.setUris(this.getUris(parentPath, methodRequestMappingAnno.value())); 105 | saa.setMethods(this.getMethods(methodRequestMappingAnno.method())); 106 | return true; 107 | } 108 | 109 | PostMapping postMapping = apiAction.getMethod().getAnnotation(PostMapping.class); 110 | if (postMapping != null) { 111 | saa.setUris(this.getUris(parentPath, postMapping.value())); 112 | saa.setMethods(this.getMethods(RequestMethod.POST)); 113 | return true; 114 | } 115 | 116 | GetMapping getMapping = apiAction.getMethod().getAnnotation(GetMapping.class); 117 | if (getMapping != null) { 118 | saa.setUris(this.getUris(parentPath, getMapping.value())); 119 | saa.setMethods(this.getMethods(RequestMethod.GET)); 120 | return true; 121 | } 122 | 123 | PutMapping putMapping = apiAction.getMethod().getAnnotation(PutMapping.class); 124 | if (putMapping != null) { 125 | saa.setUris(this.getUris(parentPath, putMapping.value())); 126 | saa.setMethods(this.getMethods(RequestMethod.PUT)); 127 | return true; 128 | } 129 | 130 | DeleteMapping deleteMapping = apiAction.getMethod().getAnnotation(DeleteMapping.class); 131 | if (deleteMapping != null) { 132 | saa.setUris(this.getUris(parentPath, deleteMapping.value())); 133 | saa.setMethods(this.getMethods(RequestMethod.DELETE)); 134 | return true; 135 | } 136 | 137 | PatchMapping patchMapping = apiAction.getMethod().getAnnotation(PatchMapping.class); 138 | if (patchMapping != null) { 139 | saa.setUris(this.getUris(parentPath, patchMapping.value())); 140 | saa.setMethods(this.getMethods(RequestMethod.PATCH)); 141 | return true; 142 | } 143 | return false; 144 | } 145 | 146 | /** 147 | * 获取接口的uri 148 | */ 149 | protected List getUris(String[] parentPaths, String[] values) { 150 | 151 | if (parentPaths.length == 0) { 152 | parentPaths = new String[]{""}; 153 | } 154 | 155 | List uris = new ArrayList<>(1); 156 | for (String parentPath : parentPaths) { 157 | for (String value : values) { 158 | String uri; 159 | if (parentPath.endsWith("/") && value.startsWith("/")) { 160 | uri = parentPath.substring(0, parentPath.length() - 1) + value; 161 | } else if (parentPath.length() > 0 && !parentPath.endsWith("/") && !value.startsWith("/")) { 162 | uri = parentPath + '/' + value; 163 | } else { 164 | uri = parentPath + value; 165 | } 166 | uris.add(uri); 167 | } 168 | } 169 | 170 | 171 | return uris; 172 | } 173 | 174 | /** 175 | * 获取接口上允许的访问方式 176 | */ 177 | protected List getMethods(RequestMethod... methods) { 178 | List methodStrs = new ArrayList<>(); 179 | for (RequestMethod requestMethod : methods) { 180 | methodStrs.add(requestMethod.name()); 181 | } 182 | return methodStrs; 183 | } 184 | 185 | /** 186 | * 判断整个类里的所有接口是否都返回json 187 | */ 188 | protected boolean isJson(Class classz) { 189 | RestController restControllerAnno = classz.getAnnotation(RestController.class); 190 | ResponseBody responseBody = classz.getAnnotation(ResponseBody.class); 191 | return responseBody != null || restControllerAnno != null; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /xDoc-view/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | xDoc 7 | com.github.treeleafj 8 | 1.1.0 9 | 10 | 4.0.0 11 | 12 | xDoc-view 13 | 14 | 15 | --------------------------------------------------------------------------------