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 | 
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 |
29 |
30 |
31 |
32 |
36 |
37 |
{{currentApiAction.title}}
38 |
39 |
只支持:
40 | {{m}}
41 |
42 |
接口地址: {{uri}}
43 |
接口返回: {{currentApiAction.returnDesc}}
44 |
45 |
46 |
47 |
48 | 请求参数
49 |
50 |
51 |
52 |
53 |
54 | {{scope.row.require ? '是' : '否'}}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 返回参数
63 |
64 |
65 |
66 |
67 |
68 | {{scope.row.require ? '是' : '否'}}
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | 返回例子
77 |
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 |
22 |
23 |
24 |
25 |
29 |
30 |
{{currentApiAction.title}}
31 |
32 |
只支持:
33 | {{m}}
34 |
35 |
接口地址: {{uri}}
36 |
接口返回: {{currentApiAction.returnDesc}}
37 |
38 |
39 |
40 |
41 | 请求参数
42 |
43 |
44 |
45 |
46 |
47 | {{scope.row.require ? '是' : '否'}}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | 返回参数
56 |
57 |
58 |
59 |
60 |
61 | {{scope.row.require ? '是' : '否'}}
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | 返回例子
70 |
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 |
--------------------------------------------------------------------------------