我们公司作为乙方,总是被客户追着要一份API文档,当我们把一个 Swagger 文档地址丢给客户的时候。
客户还是很不满意,嫌不足正式!

去世活坚持要一份 word 文档 。
然后领导给了个接口模板,就把这个活交给我了……我去,近10个微做事,几百个接口,这不得要了我的命啊(末了整理出来将近200页的 word 文档)。
末了,还是领导有办法:要不我们把Swagger的 json文件转成word文档吧!

一贯坚持一句话。
作为利用者,人要迁就机器;作为开拓者,要机器迁就人。

二、思路

领导供应了一个接口模板,类似下面这样,实在便是一个word的table页。
想到 html 可以转 word ,那么问题就变成了 :

div里的内容替换成jspSwagger文档转Word 文档 NoSQL

解析JSON 文件把JSON文件的内容添补进html 的Table中由html直接转成word

几百个接口,一气呵成!
如下,还有一个大略的示例,便是要求参数 和 返回值 。
怎么处理呢?在程序中写了 HTTP 的要求,封装了须要的参数去实行了一个要求,得到相应的返回值!

三、实现

1、封装工具

按照面向工具的思想,一个接口Table便是一个工具,可变的要求参数和返回参数也封装成一个工具……

Table

public class Table { / 大标题 / private String title; / 小标题 / private String tag; / url / private String url; / 相应参数格式 / private String responseForm; / 要求办法 / private String requestType; / 要求体 / private List<Request> requestList; / 返回体 / private List<Response> responseList; / 要求参数 / private String requestParam; / 返回值 / private String responseParam; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getResponseForm() { return responseForm; } public void setResponseForm(String responseForm) { this.responseForm = responseForm; } public String getRequestType() { return requestType; } public void setRequestType(String requestType) { this.requestType = requestType; } public List<Request> getRequestList() { return requestList; } public void setRequestList(List<Request> requestList) { this.requestList = requestList; } public List<Response> getResponseList() { return responseList; } public void setResponseList(List<Response> responseList) { this.responseList = responseList; } public String getRequestParam() { return requestParam; } public void setRequestParam(String requestParam) { this.requestParam = requestParam; } public String getResponseParam() { return responseParam; } public void setResponseParam(String responseParam) { this.responseParam = responseParam; }}

Request

public class Request { / 要求参数 / private String description; / 参数名 / private String name; / 数据类型 / private String type; / 参数类型 / private String paramType; / 是否必填 / private Boolean require; / 解释 / private String remark; public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public Boolean getRequire() { return require; } public void setRequire(Boolean require) { this.require = require; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public String getParamType() { return paramType; } public void setParamType(String paramType) { this.paramType = paramType; }}

Response

public class Response { / 返回参数 / private String description; / 参数名 / private String name; / 解释 / private String remark; public Response(String description, String name, String remark) { this.description = description; this.name = name; this.remark = remark; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; }}

2、解析 json

先来看看Swagger json文件的格式吧!
须要把稳的是这个 json 文件默认的 host 是没有加 http:// 前缀的,须要我们手动加上,由于程序的HTTP要求不像浏览器一样会自动补上 http:// 的前缀 ……

解析JSON真是一件呆板的事情,大家可以按照自己想要天生模板的样子修正这边的代码……须要提的是,这里有一点让我纠结了好久。
怎么假造接口的要求参数发送HTTP要求以避免不会抛非常呢?末了还是参考了Swagger的办法,即:如果是 String 类型的参数,就把这个参数置为\"大众string\"大众;如果是 Integer 类型的参数,就把这个参数置为 0 ;如果是Double 类型的参数,就置为 0.0 ;如果是其他没办法预见的类型,就全部置为 null;

解析 JSON 用的是Spring推举的 jackson ,这部分觉得没什么好说的,直接上代码吧!

@Servicepublic class TableServiceImpl implements TableService { @Override public List<Table> tableList() { List<Table> list = new LinkedList(); try { ClassLoader classLoader = TableService.class.getClassLoader(); URL resource = classLoader.getResource(\公众data.json\"大众); Map map = new ObjectMapper().readValue(resource, Map.class); //得到host,用于仿照http要求 String host = String.valueOf(map.get(\"大众host\"大众)); //解析paths LinkedHashMap<String, LinkedHashMap> paths = (LinkedHashMap) map.get(\公众paths\"大众); if (paths != null) { Iterator<Map.Entry<String, LinkedHashMap>> iterator = paths.entrySet().iterator(); while (iterator.hasNext()) { Table table = new Table(); List<Request> requestList = new LinkedList<Request>(); String requestType = \"大众\公众; Map.Entry<String, LinkedHashMap> next = iterator.next(); String url = next.getKey();//得到url LinkedHashMap<String, LinkedHashMap> value = next.getValue(); //得到要求办法,输出结果类似为 get/post/delete/put 这样 Set<String> requestTypes = value.keySet(); for (String str : requestTypes) { requestType += str + \"大众/\"大众; } Iterator<Map.Entry<String, LinkedHashMap>> it2 = value.entrySet().iterator(); //解析要求 Map.Entry<String, LinkedHashMap> get = it2.next();//得到get LinkedHashMap getValue = get.getValue(); String title = (String) ((List) getValue.get(\"大众tags\"大众)).get(0);//得到大标题 String tag = String.valueOf(getValue.get(\公众summary\"大众)); //要求体 ArrayList parameters = (ArrayList) getValue.get(\"大众parameters\"大众); if (parameters != null && parameters.size() > 0) { for (int i = 0; i < parameters.size(); i++) { Request request = new Request(); LinkedHashMap<String, Object> param = (LinkedHashMap) parameters.get(i); request.setDescription(String.valueOf(param.get(\"大众description\"大众))); request.setName(String.valueOf(param.get(\"大众name\公众))); request.setType(String.valueOf(param.get(\"大众type\"大众))); request.setParamType(String.valueOf(param.get(\"大众in\"大众))); request.setRequire((Boolean) param.get(\公众required\公众)); requestList.add(request); } } //返回体,比较固定 List<Response> responseList = listResponse(); //仿照一次HTTP要求,封装要求体和返回体,如果是Restful的文档可以再补充 if (requestType.contains(\"大众post\"大众)) { Map<String, String> stringStringMap = toPostBody(requestList); table.setRequestParam(stringStringMap.toString()); String post = NetUtil.post(host + url, stringStringMap); table.setResponseParam(post); } else if (requestType.contains(\公众get\公众)) { String s = toGetHeader(requestList); table.setResponseParam(s); String getStr = NetUtil.get(host + url + s); table.setResponseParam(getStr); } //封装Table table.setTitle(title); table.setUrl(url); table.setTag(tag); table.setResponseForm(\"大众application/json\"大众); table.setRequestType(StringUtils.removeEnd(requestType, \"大众/\公众)); table.setRequestList(requestList); table.setResponseList(responseList); list.add(table); } } return list; } catch (IOException e) { e.printStackTrace(); } return null; } //封装返复书息,可能需求不一样,可以自定义 private List<Response> listResponse() { List<Response> responseList = new LinkedList<Response>(); responseList.add(new Response(\公众受影响的行数\"大众, \"大众counts\公众, null)); responseList.add(new Response(\"大众结果解释信息\公众, \公众msg\"大众, null)); responseList.add(new Response(\"大众是否成功\"大众, \"大众success\"大众, null)); responseList.add(new Response(\"大众返回工具\公众, \"大众data\公众, null)); responseList.add(new Response(\"大众缺点代码\公众, \"大众errCode\公众, null)); return responseList; } //封装post要求体 private Map<String, String> toPostBody(List<Request> list) { Map<String, String> map = new HashMap<>(16); if (list != null && list.size() > 0) { for (Request request : list) { String name = request.getName(); String type = request.getType(); switch (type) { case \"大众string\"大众: map.put(name, \"大众string\"大众); break; case \"大众integer\"大众: map.put(name, \公众0\"大众); break; case \公众double\公众: map.put(name, \公众0.0\公众); break; default: map.put(name, \公众null\"大众); break; } } } return map; } //封装get要求头 private String toGetHeader(List<Request> list) { StringBuffer stringBuffer = new StringBuffer(); if (list != null && list.size() > 0) { for (Request request : list) { String name = request.getName(); String type = request.getType(); switch (type) { case \公众string\"大众: stringBuffer.append(name+\公众&=string\公众); break; case \公众integer\"大众: stringBuffer.append(name+\"大众&=0\公众); break; case \"大众double\"大众: stringBuffer.append(name+\"大众&=0.0\公众); break; default: stringBuffer.append(name+\"大众&=null\"大众); break; } } } String s = stringBuffer.toString(); if (\"大众\公众.equalsIgnoreCase(s)){ return \公众\"大众; } return \"大众?\"大众 + StringUtils.removeStart(s, \"大众&\"大众); }}

3、html 模板

我们须要一个和 Word Table 模板一样的HTML 页面,然后利用JSP的 foreach 遍历后台得到的 List<Table>凑集,一气呵成,天生所有接口……

<%-- text/html:正常的html显示 application/msword:html页面直接转word--%><%@ page contentType=\公众application/msword\"大众 pageEncoding=\公众UTF-8\"大众 language=\"大众java\"大众 %><%--<%@page contentType=\"大众text/html\公众 pageEncoding=\公众UTF-8\"大众 language=\公众java\公众 %>--%><%@ taglib uri=\"大众http://java.sun.com/jsp/jstl/core\"大众 prefix=\"大众c\"大众 %><!DOCTYPE html><html> <head> <title>tool</title> <style type=\"大众text/css\"大众> .bg { background-color: rgb(84, 127, 177); } tr { height: 20px; font-size: 12px; } .specialHeight { height: 40px; } </style> </head> <body> <div style=\公众width:800px; margin: 0 auto\"大众> <c:forEach items=\公众${table}\"大众 var=\"大众t\"大众> <h4>${t.title}</h4> <%--这个是类的解释--%> <h5>${t.tag}</h5> <%--这个是每个要求的解释,方便天生文档后进行整理--%> <table border=\"大众1\"大众 cellspacing=\"大众0\"大众 cellpadding=\"大众0\"大众 width=\公众100%\"大众> <tr class=\"大众bg\公众> <td colspan=\公众6\公众><c:out value=\公众${t.tag}\公众/></td> </tr> <tr> <td>URL</td> <td colspan=\"大众5\公众>${t.url}</td> </tr> <tr> <td>要求办法</td> <td colspan=\"大众5\"大众>${t.requestType}</td> </tr> <tr> <td>返回值类型</td> <td colspan=\"大众5\"大众>${t.responseForm}</td> </tr> <tr class=\公众bg\"大众 align=\"大众center\"大众> <td>要求参数</td> <td>参数名</td> <td>数据类型</td> <td>参数类型</td> <td>是否必填</td> <td>解释</td> </tr> <c:forEach items=\"大众${t.requestList}\"大众 var=\"大众req\"大众> <tr align=\"大众center\"大众> <td>${req.description}</td> <td>${req.name}</td> <td>${req.type}</td> <td>${req.paramType}</td> <td> <c:choose> <c:when test=\"大众${req.require == true}\公众>Y</c:when> <c:otherwise>N</c:otherwise> </c:choose> </td> <td>${remark}</td> </tr> </c:forEach> <tr class=\"大众bg\公众 align=\"大众center\"大众> <td>返回参数</td> <td>参数名</td> <td colspan=\"大众4\"大众>解释</td> </tr> <c:forEach items=\"大众${t.responseList}\"大众 var=\公众res\公众> <tr align=\"大众center\"大众> <td>${res.description}</td> <td>${res.name}</td> <td colspan=\公众4\"大众>${res.remark}</td> </tr> </c:forEach> <tr class=\"大众bg\"大众> <td colspan=\"大众6\"大众>示例</td> </tr> <tr class=\"大众specialHeight\"大众> <td class=\"大众bg\"大众>要求参数</td> <td colspan=\"大众5\"大众>${t.requestParam}</td> </tr> <tr class=\"大众specialHeight\公众> <td class=\"大众bg\公众>返回值</td> <td colspan=\公众5\"大众>${t.responseParam}</td> </tr> </table> <br> </c:forEach> </div> </body></html>

4、效果

把代码运行起来后,访问JSP页面,不会像平常一样看到 HTML 页面,而是直接下载天生一个 文件,按照SpringMVC要求方法命名(这个项目中是getWord文件)。
把这个文件的后缀名改成 .doc 就能看到效果了!
差不多是如下效果:

当然,剩下的事情,就要我们手动去整理掩护了。
比如:把属于同一个类的要求分类整理到一起;把HTTP要求缺点的返回值删除(还无法适配所有的HTTP要求情形);整理掩护效果如下:

四、利用

如果直接采取我的API文档模板的话,只须要将 resources 目录下的 data.json 文件的内容更换本钱身的Swagger Json 文件内容就好。
但是,考虑到我们模板中的返回参数是我们公司一个自定义的工具,以是可能这里还须要大家根据自己的哀求稍作修正,紧张 修正TableServiceImpl 类下的 listResponse() 方法。

须要解释的是,这个项目还没有很好的支持所有的HTTP要求,比如 restful 做事将要求参数放在要求路径中的;比如参数是放在header中的;以及一系列可能没有考虑到的bug……

其余,我以为 TableServiceImpl 还有很大可以改进的地方,代码略显冗余。
之后逐步掩护吧!
当然,很欢迎大家一起来开拓…哈哈

五、结语

一贯以为,IT最迷人的地方便是开源和分享,大家互不相识,纵然没有利益可图,却能为同一个项目,相同的目标 贡献自己的韶光和精力。
想想就不可思议。
写这个博文的目地更多是分享自己的创意和想法,说实话,代码可能写的有点烂。
还请大家不要嫌弃,不吝指教!

六、更新解释

之前看《Spring In Action》的时候,创造了 RestTemplate 这个东西, 作为取代 HttpClients 的要求办法。
当时就在想,假如放在这个项目中不是恰到好处?

更新解释如下:

1、引入了Spring的RestTemplate取代 HttpClients 以支持更多的Restful要求。

2、命名规范以及增加非常处理,对付无法处理的HTTP要求返回空字符串。

3、修正之前导入data.josn的办法,变成restTemplate.getForObject(\"大众SwaggerJson的url地址\"大众,Map.class);的动态获取办法。

现在的利用办法也更加大略:

1、修正resources目录下resources.properties文件的 swaggerUrl 为Swagger Json资源的url地址。

2、做事启动后:访问 http://host(主机):port(端口)/getWord,etc:http://localhost:8080/getWord

3、将天生的getWord文件,增加后缀名 getWord.doc 。

GitHub 地址

https://github.com/JMCuixy/swagger2word

练手项目源码、新技能先容、事理性知识、运用框架知识点讲解、常见笔试口试题剖析,搜索【Java知音】