Swagger文檔轉Word 文檔

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

二、解析 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

三、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

 四、效果

把代碼運行起來後,訪問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 版本,更新說明以下:  

  1. 代碼梳理和頁面美化。
  2. 真誠感謝 kevin4j 的代碼貢獻。
相關文章
相關標籤/搜索