一、前言

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

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

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

二、思路

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

  • 解析JSON 文件
  • 把JSON文件的内容填充进html 的Table中
  • 由html直接转成word

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

Java html转换成String java将html页面转换为word_json接口文档模板

三、实现

1、封装对象

按照面向对象的思想,一个接口Table就是一个对象,可变的请求参数和返回参数也封装成一个对象……

Java html转换成String java将html页面转换为word_json接口文档模板_02

 Table



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

    publicvoid setTitle(String title) {
        this.title = title;
    }

    public String getTag() {
        return tag;
    }

    publicvoid setTag(String tag) {
        this.tag = tag;
    }

    public String getUrl() {
        return url;
    }

    publicvoid setUrl(String url) {
        this.url = url;
    }

    public String getResponseForm() {
        return responseForm;
    }

    publicvoid setResponseForm(String responseForm) {
        this.responseForm = responseForm;
    }

    public String getRequestType() {
        return requestType;
    }

    publicvoid setRequestType(String requestType) {
        this.requestType = requestType;
    }

    public List<Request> getRequestList() {
        return requestList;
    }

    publicvoid setRequestList(List<Request> requestList) {
        this.requestList = requestList;
    }

    public List<Response> getResponseList() {
        return responseList;
    }

    publicvoid setResponseList(List<Response> responseList) {
        this.responseList = responseList;
    }

    public String getRequestParam() {
        return requestParam;
    }

    publicvoid setRequestParam(String requestParam) {
        this.requestParam = requestParam;
    }

    public String getResponseParam() {
        return responseParam;
    }

    publicvoid setResponseParam(String responseParam) {
        this.responseParam = responseParam;
    }
}



Request



publicclass Request {

    /**
     * 请求参数
     */private String description;

    /**
     * 参数名
     */private String name;

    /**
     * 数据类型
     */private String type;

    /**
     * 参数类型
     */private String paramType;

    /**
     * 是否必填
     */private Boolean require;

    /**
     * 说明
     */private String remark;

    public String getDescription() {
        return description;
    }

    publicvoid setDescription(String description) {
        this.description = description;
    }

    public String getName() {
        return name;
    }

    publicvoid setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    publicvoid setType(String type) {
        this.type = type;
    }

    public Boolean getRequire() {
        return require;
    }

    publicvoid setRequire(Boolean require) {
        this.require = require;
    }

    public String getRemark() {
        return remark;
    }

    publicvoid setRemark(String remark) {
        this.remark = remark;
    }

    public String getParamType() {
        return paramType;
    }

    publicvoid setParamType(String paramType) {
        this.paramType = paramType;
    }
}



Response



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

    publicvoid setDescription(String description) {
        this.description = description;
    }

    public String getName() {
        return name;
    }

    publicvoid setName(String name) {
        this.name = name;
    }

    public String getRemark() {
        return remark;
    }

    publicvoid setRemark(String remark) {
        this.remark = remark;
    }
}



2、解析 json

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

Java html转换成String java将html页面转换为word_Java html转换成String_03

解析JSON真是一件枯燥的工作,大家可以按照自己想要生成模板的样子修改这边的代码……需要提的是,这里有一点让我纠结了好久。怎么伪造接口的请求参数发送HTTP请求以避免不会抛异常呢?最后还是参考了Swagger的方式,即:如果是 String 类型的参数,就把这个参数置为"string";如果是 Integer 类型的参数,就把这个参数置为 0 ;如果是Double 类型的参数,就置为 ;如果是其他没办法预见的类型,就全部置为 null;

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



@Service
publicclass TableServiceImpl implements TableService {

    @Override
    public List<Table> tableList() {
        List<Table> list = new LinkedList();
        try {
            ClassLoader classLoader = TableService.class.getClassLoader();
            URL resource = ("");
            Map map = new ObjectMapper().readValue(resource, Map.class);
            //得到host,用于模拟http请求
            String host = (("host"));
            //解析paths
            LinkedHashMap<String, LinkedHashMap> paths = (LinkedHashMap) ("paths");
            if (paths != null) {
                Iterator<Map.Entry<String, LinkedHashMap>> iterator = paths.entrySet().iterator();
                while (()) {
                    Table table = new Table();
                    List<Request> requestList = new LinkedList<Request>();
                    String requestType = "";

                    Map.Entry<String, LinkedHashMap> next = iterator.next();
                    String url = ();//得到url
                    LinkedHashMap<String, LinkedHashMap> value = ();
                    //得到请求方式,输出结果类似为 get/post/delete/put 这样
                    Set<String> requestTypes = ();
                    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 = ();
                    String title = (String) ((List) ("tags")).get(0);//得到大标题
                    String tag = (("summary"));
                    //请求体
                    ArrayList parameters = (ArrayList) ("parameters");
                    if (parameters != null && () > 0) {
                        for (int i = 0; i < (); i++) {
                            Request request = new Request();
                            LinkedHashMap<String, Object> param = (LinkedHashMap) (i);
                            ((("description")));
                            ((("name")));
                            ((("type")));
                            ((("in")));
                            ((Boolean) ("required"));
                            (request);
                        }
                    }
                    //返回体,比较固定
                    List<Response> responseList = listResponse();
                    //模拟一次HTTP请求,封装请求体和返回体,如果是Restful的文档可以再补充if (("post")) {
                        Map<String, String> stringStringMap = toPostBody(requestList);
                        (());
                        String post = (host + url, stringStringMap);
                        (post);
                    } elseif (("get")) {
                        String s = toGetHeader(requestList);
                        (s);
                        String getStr = (host + url + s);
                        (getStr);
                    }

                    //封装Table                    table.setTitle(title);
                    (url);
                    (tag);
                    ("application/json");
                    ((requestType, "/"));
                    (requestList);
                    (responseList);
                    (table);
                }
            }
            return list;

        } catch (IOException e) {
            ();
        }
        returnnull;
    }


    //封装返回信息,可能需求不一样,可以自定义private List<Response> listResponse() {
        List<Response> responseList = new LinkedList<Response>();
        (new Response("受影响的行数", "counts", null));
        (new Response("结果说明信息", "msg", null));
        (new Response("是否成功", "success", null));
        (new Response("返回对象", "data", null));
        (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 && () > 0) {
            for (Request request : list) {
                String name = ();
                String type = ();
                switch (type) {
                    case "string":
                        (name, "string");
                        break;
                    case "integer":
                        (name, "0");
                        break;
                    case "double":
                        (name, "");
                        break;
                    default:
                        (name, "null");
                        break;
                }
            }
        }
        return map;
    }

    //封装get请求头private String toGetHeader(List<Request> list) {
        StringBuffer stringBuffer = new StringBuffer();
        if (list != null && () > 0) {
            for (Request request : list) {
                String name = ();
                String type = ();
                switch (type) {
                    case "string":
                        (name+"&=string");
                        break;
                    case "integer":
                        (name+"&=0");
                        break;
                    case "double":
                        (name+"&=");
                        break;
                    default:
                        (name+"&=null");
                        break;
                }
            }
        }
        String s = ();
        if ("".equalsIgnoreCase(s)){
            return "";
        }
        return "?" + (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="" 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>${}</h4><%--这个是类的说明--%><h5>${}</h5><%--这个是每个请求的说明,方便生成文档后进行整理--%><table border="1" cellspacing="0" cellpadding="0" width="100%"><tr class="bg"><td colspan="6"><c:out value="${}"/></td></tr><tr><td>URL</td><td colspan="5">${}</td></tr><tr><td>请求方式</td><td colspan="5">${}</td></tr><tr><td>返回值类型</td><td colspan="5">${}</td></tr><tr class="bg" align="center"><td>请求参数</td><td>参数名</td><td>数据类型</td><td>参数类型</td><td>是否必填</td><td>说明</td></tr><c:forEach items="${}" var="req"><tr align="center"><td>${}</td><td>${}</td><td>${}</td><td>${}</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="${}" var="res"><tr align="center"><td>${}</td><td>${}</td><td colspan="4">${}</td></tr></c:forEach><tr class="bg"><td colspan="6">示例</td></tr><tr class="specialHeight"><td class="bg">请求参数</td><td colspan="5">${}</td></tr><tr class="specialHeight"><td class="bg">返回值</td><td colspan="5">${}</td></tr></table><br></c:forEach></div></body></html>



4、效果

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

Java html转换成String java将html页面转换为word_Java html转换成String_04

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

Java html转换成String java将html页面转换为word_json转map_05

四、使用

如果直接采用我的API文档模板的话,只需要将 resources 目录下的 文件的内容替换成自己的Swagger Json 文件内容就好。但是,考虑到我们模板中的返回参数是我们公司一个自定义的对象,所以可能这里还需要大家根据自己的要求稍作修改,主要 修改TableServiceImpl 类下的 listResponse() 方法。

需要说明的是,这个项目还没有很好的支持所有的HTTP请求,比如 restful 服务将请求参数放在请求路径中的;比如参数是放在header中的;以及一系列可能没有考虑到的bug……

另外,我觉得 TableServiceImpl 还有很大可以改善的地方,代码略显冗余。之后慢慢维护吧!当然,很欢迎大家一起来开发…哈哈

五、结语

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

六、更新说明

之前看《Spring In Action》的时候,发现了 RestTemplate 这个东西, 作为取代 HttpClients 的请求方式。当时就在想,要是放在这个项目中不是恰到好处?

更新说明如下:

1、引入了Spring的RestTemplate取代 HttpClients 以支持更多的Restful请求。
2、命名规范以及增加异常处理,对于无法处理的HTTP请求返回空字符串。
3、修改之前导入的方式,变成("SwaggerJson的url地址",);的动态获取方式。

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

1、修改resources目录下文件的 swaggerUrl 为Swagger Json资源的url地址。
2、服务启动后:访问 http://host(主机):port(端口)/getWord,etc:http://localhost:8080/getWord 
3、将生成的getWord文件,增加后缀名 。

GitHub 地址