1. 程式人生 > 實用技巧 >Swagger文件轉Word 文件

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

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