• Swagger文档转Word 文档


    一、前言

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

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

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

    二、思路

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

    • 解析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 ,这部分感觉没什么好说的,直接上代码吧!

    @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, "&");
        }
    }

    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="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

  • 相关阅读:
    对vue中nextTick()的理解及使用场景说明
    微信小程序的视图与渲染
    1分钟了解微信小程
    Idea搭建Spring+SpringMvc+Mybatis框架集成项目
    idea 新建不了servlet文件 方法(1)
    idea使用大全(加载mysql驱动)
    开发文档规范
    如何架构一个框架
    mac os x
    mongodb rockmongo
  • 原文地址:https://www.cnblogs.com/sxw123/p/14069030.html
Copyright © 2020-2023  润新知