GitHub 地址:https://github.com/JMCuixy/swagger2word

原创作品,转载请注明出处:http://www.cnblogs.com/jmcui/p/8298823.html

一、前言

为什么会产生这个需求呢?

我们公司作为乙方,老是被客户追着要一份API文档,当我们把一个 Swagger 文档地址丢给客户的时候。客户还是很不满意,嫌不够正式!!死活坚持要一份 word 文档 。然后领导给了个接口模板,就把这个活交给我了......我去,近10个微服务,几百个接口,这不得要了我的命啊(最后整理出来将近200页的 word 文档)。最后,还是领导有办法:要不我们把Swagger的 json文件转成word文档吧!

一直坚持一句话。作为使用者,人要迁就机器;作为开发者,要机器迁就人。

二、思路

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

1、解析JSON 文件

2、把JSON文件的内容填充进html 的Table中

3、由html直接转成word

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

三、实现

1、封装对象

按照面向对象的思想,一个接口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;
}
}

Table

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

Request

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

Response

2、解析 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, "&");
}
}

TableServiceImpl

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>

json.jsp

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 的请求方式。当时就在想,要是放在这个项目中不是恰到好处?

2018-06-21 整理发布了 1.2 版本,更新说明如下:

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

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

3、修改之前导入data.josn的方式,变成restTemplate.getForObject("SwaggerJson的url地址",Map.class);的动态获取方式。

2019-06-12 整理发布了 1.3 版本,更新说明如下:

1、Spring 框架向 SpringBoot 升级

2、thymeleaf 取代 jsp模板

现在的使用方式也更加简单:

1、修改 application.yml 文件的 swagger.url 为Swagger Json资源的url地址。

2、服务启动后:访问 http://host(主机):port(端口)/toWord,etc:http://127.0.0.1:8080/toWord

3、可以看到网页上生成的类似 word 文档的页面,右键另存为 xxx.doc 文件即可。

2019-08-02 整理发布了 1.4 版本,更新说明如下:

1、取消 HttpClient 的请求方式去获得返回值,改由从 Swagger Json 文件中直接读取

2、针对 application/json 请求方式的入参做渲染

3、对于文字过多导致 HTML table 变形做适配

4、真诚感谢 fpzhan的代码贡献

2019-12-18 整理发布了 1.5 版本,更新说明如下:

  1. 代码梳理和页面美化。
  2. 真诚感谢 kevin4j 的代码贡献。

Swagger文档转Word 文档的更多相关文章

  1. API接口文档中将Swagger文档转Word 文档

    一般的开发工作,尤其是API接口的开发工作,首先要有开发文档,接口说明文档 ok,后来开发完毕了 和页面联调,或者是和第三方联调的时候, 这个时候,SA systeam admin 就会开始直接让开发 ...

  2. 如何转换pdf文档为word文档--先标记下,本周把这个问题知识掌握

    http://developer.51cto.com/art/201803/567539.htm

  3. Swagger文档转Word

    Swagger文档转Word 文档   GitHub 地址:https://github.com/JMCuixy/SwaggerToWord/tree/developer 原创作品,转载请注明出处:h ...

  4. c#生成word文档

    参考:http://blog.163.com/zhouchunping_99/blog/static/7837998820085114394716/ 生成word文档 生成word文档 view pl ...

  5. 基于springboot的freemarker创建指定格式的word文档

    在web或其他应用中,经常我们需要导出或者预览word文档,比较实际的例子有招聘网站上预览或者导出个人简历,使用POI导出excel会非常的方便,但是如果想导出word,由于其格式控制非常复杂,故而使 ...

  6. javadoc导出成word文档

    刚刚上次弄完了一个坑爹的任务,这次我领导又给我一个让人脑瓜子疼的任务了. 基本上客户他在验收我们系统的时候,都会要求我们编写相关的文档,这次也不例外. 只是这次的客户要求我们给出接口文档.不仅是要整个 ...

  7. Java 设置、删除、获取Word文档背景(基于Spire.Cloud.SDK for Java)

    本文介绍使用Spire.Cloud.SDK for Java 提供的BackgroundApi接口来操作Word文档背景的方法,可设置背景,包括设置颜色背景setBackgroundColor().图 ...

  8. 数据库界的Swagger:一键生成数据库文档!

    对于开发的API文档,我们可以通过Swagger等工具来自动生成了.但是对于数据库表结构的文档呢,在实际开发中在开发前我们一般会先设计好表结构,大家讨论一下, 这个时候就很需要有个数据库表结构的文档, ...

  9. C#中5步完成word文档打印的方法

    在日常工作中,我们可能常常需要打印各种文件资料,比如word文档.对于编程员,应用程序中文档的打印是一项非常重要的功能,也一直是一个非常复杂的工作.特别是提到Web打印,这的确会很棘手.一般如果要想选 ...

随机推荐

  1. java枚举细节

     1.在没有枚举之前,我们如果需要一些常量,比如说,我们想用一些常量来代替订单的几种状态,如已下单未付款.已付款未发货.已发货未确认收货.已收货未评价.已评价.我们会定义一个用来装常量的类,比如: p ...

  2. ArcGIS API for JavaScript 4.2学习笔记[19] 搜索小部件——使用更多数据源

    上一篇中提到,空间搜索小部件是Search这个类的实例化,作为视图的ui属性添加进去后,视图就会出现搜索框了. 这节的主体代码和上篇几乎一致,区别就在上篇提及的sources属性. 先看看结果: 由于 ...

  3. Linux第八讲随笔 -tar / 系统启动流程

    linux 第八讲1.tar 参考 作用:压缩和解压文件.tar本身不具有压缩功能.他是调用压缩功能实现的. 语法:tar[必要参数][选择参数][文件] 参数:必要参数有如下: -A 新增压缩文件到 ...

  4. win10大水牛主机插入耳机没有声音

    主机:大水牛,技嘉主板 操作系统:win10 问题:主机前面插入耳机,没有声音,扬声器图标出错 解决 一..插入耳机 二..Realtek高清晰音频管理器 1.打开音频管理器,点击右下角的设置 2.点 ...

  5. Android模拟器

    一.Genymotion 1.下载安装:https://www.genymotion.com/download/  (注:安装前需要先注册) 因为Genymotion运行需要VirtualBox,如果 ...

  6. Jasperreports以及iReport4.5报表PDF导出字体完美解决方案

    在使用Jasperreports以及iReport设计报表时,导出PDF是一个常见的需求.网上解决PDF导出中文显示问题相关的文章很多,无非就是设置控件的pdf font name和pdf encod ...

  7. Layout 不可思议(一)—— CSS 实现自适应的正方形卡片

    最近被一个布局问题给难住了,枉我一向自称掌握最好的前端技能是 CSS,写完博客就得敷脸去 需求是实现一个自适应的正方形卡片,效果如下: 顺便(开个坑)写个系列,总结那些设计精妙的布局结构 本次页面的 ...

  8. umask的作用[转]

    umask的作用 umask 命令允许你设定文件创建时的缺省模式,对应每一类用户(文件属主.同组用户.其他用户)存在一个相应的umask值中的数字.对于文件来说,这一数字的最 大值分别是6.系统不允许 ...

  9. MicroPython-GPRS教程之TPYBoardv702GPRS功能测试

    一.什么是TPYBoardV702 TPYBoardV702是目前市面上唯一支持通信通信功能的MicroPython开发板:支持Python3.0及以上版本直接运行.支持GPS+北斗双模通信.GPRS ...

  10. 机器学习笔记3-Tensorflow简介

    前言 前面两篇主要写了一些机器学习的基础概念,从本篇开始我们来了解下深度学习.深度学习是机器学习的一个子集,是一种特殊的数学模型.同样是从输入到输出,深度学习在这两者之间会有很多层称为"隐层 ...