GitHub 地址:https://github.com/JMCuixy/swagger2wordcss
原创做品,转载请注明出处:http://www.cnblogs.com/jmcui/p/8298823.htmlhtml
1、前言
为何会产生这个需求呢?java
咱们公司做为乙方,总是被客户追着要一份API文档,当咱们把一个 Swagger 文档地址丢给客户的时候。客户仍是很不满意,嫌不够正式!!死活坚持要一份 word 文档 。而后领导给了个接口模板,就把这个活交给我了......我去,近10个微服务,几百个接口,这不得要了个人命啊(最后整理出来将近200页的 word 文档)。最后,仍是领导有办法:要不咱们把Swagger的 json文件转成word文档吧!git
一直坚持一句话。做为使用者,人要迁就机器;做为开发者,要机器迁就人。github
2、思路
领导提供了一个接口模板,相似下面这样,其实就是一个word的table页。想到 html 能够转 word ,那么问题就变成了 :json
一、解析JSON 文件浏览器
二、把JSON文件的内容填充进html 的Table中restful
三、由html直接转成wordapp
几百个接口,一鼓作气!以下,还有一个简单的示例,就是请求参数 和 返回值 。怎么处理呢?在程序中写了 HTTP 的请求,封装了须要的参数去执行了一个请求,获得相应的返回值!框架
3、实现
一、封装对象
按照面向对象的思想,一个接口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; } }


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; } }


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; } }
二、解析 json
先来看看Swagger json文件的格式吧!须要注意的是这个 json 文件默认的 host 是没有加 http:// 前缀的,须要咱们手动加上,由于程序的HTTP请求不像浏览器同样会自动补上 http:// 的前缀 ......
解析JSON真是一件枯燥的工做,你们能够按照本身想要生成模板的样子修改这边的代码......须要提的是,这里有一点让我纠结了很久。怎么伪造接口的请求参数发送HTTP请求以免不会抛异常呢?最后仍是参考了Swagger的方式,即:若是是 String 类型的参数,就把这个参数置为"string";若是是 Integer 类型的参数,就把这个参数置为 0 ;若是是Double 类型的参数,就置为 0.0 ;若是是其余没办法预见的类型,就所有置为 null;
解析 JSON 用的是Spring推荐的 jackson ,这部分感受没什么好说的,直接上代码吧!


@Service public 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, "&"); } }
三、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>
四、效果
把代码运行起来后,访问JSP页面,不会像日常同样看到 HTML 页面,而是直接下载生成一个 文件,按照SpringMVC请求方法命名(这个项目中是getWord文件)。把这个文件的后缀名改为 .doc 就能看到效果了!差很少是以下效果:
固然,剩下的工做,就要咱们手动去整理维护了。好比:把属于同一个类的请求分类整理到一块儿;把HTTP请求错误的返回值删除(还没法适配全部的HTTP请求状况);整理维护效果以下:
4、使用
若是直接采用个人API文档模板的话,只须要将 resources 目录下的 data.json 文件的内容替换成本身的Swagger Json 文件内容就好。可是,考虑到咱们模板中的返回参数是咱们公司一个自定义的对象,因此可能这里还须要你们根据本身的要求稍做修改,主要 修改TableServiceImpl 类下的 listResponse() 方法。
须要说明的是,这个项目尚未很好的支持全部的HTTP请求,好比 restful 服务将请求参数放在请求路径中的;好比参数是放在header中的;以及一系列可能没有考虑到的bug......
另外,我以为 TableServiceImpl 还有很大能够改善的地方,代码略显冗余。以后慢慢维护吧!固然,很欢迎你们一块儿来开发...哈哈
5、结语
一直以为,IT最迷人的地方就是开源和分享,你们互不相识,即便没有利益可图,却能为同一个项目,相同的目标 贡献本身的时间和精力。想一想就难以想象。写这个博文的目地更可能是分享本身的创意和想法,说实话,代码可能写的有点烂。还请你们不要嫌弃,不吝指教!
6、更新说明
以前看《Spring In Action》的时候,发现了 RestTemplate 这个东西, 做为取代 HttpClients 的请求方式。当时就在想,要是放在这个项目中不是恰到好处?
2018-06-21 整理发布了 1.2 版本,更新说明以下:
一、引入了Spring的RestTemplate取代 HttpClients 以支持更多的Restful请求。
二、命名规范以及增长异常处理,对于没法处理的HTTP请求返回空字符串。
三、修改以前导入data.josn的方式,变成restTemplate.getForObject("SwaggerJson的url地址",Map.class);的动态获取方式。
2019-06-12 整理发布了 1.3 版本,更新说明以下:
一、Spring 框架向 SpringBoot 升级
二、thymeleaf 取代 jsp模板
如今的使用方式也更加简单:
一、修改 application.yml 文件的 swagger.url 为Swagger Json资源的url地址。
二、服务启动后:访问 http://host(主机):port(端口)/toWord,etc:http://127.0.0.1:8080/toWord
三、能够看到网页上生成的相似 word 文档的页面,右键另存为 xxx.doc 文件便可。
2019-08-02 整理发布了 1.4 版本,更新说明以下:
一、取消 HttpClient 的请求方式去得到返回值,改由从 Swagger Json 文件中直接读取
二、针对 application/json 请求方式的入参作渲染
三、对于文字过多致使 HTML table 变形作适配
四、真诚感谢 fpzhan 的代码贡献
2019-12-18 整理发布了 1.5 版本,更新说明以下:
- 代码梳理和页面美化。
- 真诚感谢 kevin4j 的代码贡献。