1. 程式人生 > >Spring專案整合apidoc生成api介面文件

Spring專案整合apidoc生成api介面文件

一、背景需求

 JavaWeb/spring專案寫成的api介面,需要自動生成api文件,甚至需要線上測試介面。考慮實現的方案有swagger,apidoc,spring rest docs。在之後的專案都有一一嘗試,最終還是覺得apidoc的方式比較合適,雖然有一些問題(針對線上測試方面),但可以進行定製修復並解決。

二、方案對比

1.現在大家普遍使用的是swagger結合springmvc來生成api介面文件,對比apidoc,swagger有一個明顯的劣勢,便是返回的響應,無法生成文件描述,即無法描述響應體的資料結構,這對前後端對接,或者是與移動端/其他端對接來說,需要耗費更多的交流成本,溝通成本,即不可能每個介面都通過實際呼叫後,看返回實體獲悉響應引數。針對後端改動響應體這種情況,又會導致新的問題存在。

 

2.spring rest docs,這是spring體系裡提供的一種介面生成框架,基於mockmvc編寫單元測試,單元測試通過即可生成可供閱讀的介面文件。這種生成方式需要編寫詳細的測試單元,並且稍微一點出錯便導致編譯不通過,對於程式的嚴謹有一定幫助,但又犧牲一些時間,並且最終生成的文件是基於測試用例資料,沒有類似swagger和apidoc的線上測試功能。

 

3.apidoc,通過註釋,生成介面文件,不像swagger和spring rest docs嵌入在程式碼中,僅僅是通過註釋而已。缺點是線上測試功能有些問題,不支援檔案表單,但這些缺陷都是可以彌補的,可通過再程式設計,重新定製原始碼實現,基於handlebars.js。

三、環境準備

1.安裝node.js,官網:https://nodejs.org/en/點選開啟連結;windows64位下載地址https://nodejs.org/dist/v8.9.4/node-v8.9.4-x64.msi下載

2.安裝apidoc,命令列下,輸入npm install apidoc -g,參考官網:http://apidocjs.com/#install 點選開啟連結

npm install apidoc -g

安裝完畢,可在命令下使用apidoc -h測試是否安裝成功

apidoc -h

3.apidoc指令能成功識別,apidoc環境便已經安裝好了,這時可在專案中使用,所有的程式碼基於註釋即可。

四、整合專案使用

1.專案根路徑下建立apidoc.json檔案,配置好基本的文件資訊。

{
  "name": "API文件",
  "version": "1.0.0",
  "description": "開發技術介面文件",
  "title": "API文件",
  "url" : "http://localhost:8080/test",
  "sampleUrl":"http://localhost:8080/test"
}

如圖

最終可配置apidoc的標題,版本號,描述,全域性url根路徑,測試請求的url根路徑

2.抽象一些通用的返回資訊,自定義一些tag,如我的程式碼:

/**
 * Created by Administrator on 2017/2/16.
 */
public class BaseApi {
    /**
     * @apiDefine error_msg 全域性配置失敗響應資訊
     * @apiError 1001 儲存失敗
     * @apiError 1002 修改失敗
     * @apiError 1003 刪除失敗
     * @apiError 1004 上傳失敗
     * @apiError 1005 註冊失敗
     * @apiError 1101 輸入引數格式不正確
     * @apiError 1102 使用者名稱或者密碼錯誤
     * @apiError 1103 使用者名稱不存在
     * @apiError 1201 傳送手機註冊驗證碼失敗
     * @apiError 1202 使用者註冊失敗
     * @apiError 1203 機構不存在
     * @apiError 1204 註冊驗證碼輸入錯誤
     * @apiError 1205 手機號碼已存在
     * @apiError 1206 使用者名稱已存在
     * @apiError 1207 機構不存在
     * @apiError 1208 手機或者使用者名稱已存在
     * @apiError 4101 token過期
     * @apiError 4102 token簽名錯誤
     * @apiError 4103 無效token
     * @apiError 4104 token格式錯誤
     * @apiError 5000 介面內部錯誤
     * @apiErrorExample 錯誤響應例子:
     *     {
     *       "code": 1101,
     *       "msg": "輸入引數格式不正確",
     *       "res": "",
     *       "timestamp": 1489110927975
     *     }
     *
     */
 
    /**
     * @apiDefine success_msg 全域性配置成功響應資訊
     * @apiSuccess (success 2000) {Date}  timestamp     時間戳
     * @apiSuccess (success 2000) {Integer} code        響應碼
     * @apiSuccess (success 2000) {String}  msg       響應資訊
     * @apiSuccess (success 2000) {Object}  res   響應實體
     */
 
    /**
     * @apiDefine token_msg 全域性配置token鑑權請求頭
     * @apiError 4101 token過期
     * @apiError 4102 token簽名錯誤
     * @apiError 4103 無效token
     * @apiError 4104 token格式錯誤
     * @apiHeader {String}  Authorization 鑑權資訊:為Bearer + "空格" +  {token}
     * @apiHeaderExample {json} 請求頭例子:
     *     {
     *       "Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxNDg5NjAiLCJpYXQiOjE0OTUxNjYyMzgsImV4cCI6MTQ5Nzc1ODIzOH0.Mv8BfTIGxGZ6AGkYqHFTRhp40x5xHV6k7Hpwo6OdgiA"
     *     }
     */
}

抽象一些返回的錯誤程式碼

public enum AttendRestEnum implements RestEnum{
    /**
     * @apiDefine ATTEND_EMPTY_ID
     * @apiError 5001 規則不能為空
     */
    ATTEND_EMPTY_ID(5001,"規則不能為空"),
    /**
     * @apiDefine ATTEND_EMPTY_VALUE
     * @apiError 5002 值不能為空
     */
    ATTEND_EMPTY_VALUE(5002,"值不能為空"),
    /**
     * @apiDefine ATTEND_ERROR_EQUAL_VALUE
     * @apiError 5003 設定引數的個數不一致
     */
    ATTEND_ERROR_EQUAL_VALUE(5003,"設定引數的個數不一致"),
    /**
     * @apiDefine ATTEND_EMPTY_LONGITUDE
     * @apiError 5004 經度不能為空
     */
    ATTEND_EMPTY_LONGITUDE(5004, "經度不能為空"),
    /**
     * @apiDefine ATTEND_EMPTY_LATITUDE
     * @apiError 5005 緯度不能為空
     */
    ATTEND_EMPTY_LATITUDE(5005,"緯度不能為空" ),
    /**
     * @apiDefine ATTEND_EMPTY_DEVICE_SN
     * @apiError 5006 裝置不能為空
     */
    ATTEND_EMPTY_DEVICE_SN(5006,"裝置不能為空" ),
    
    /**
     * @apiDefine ATTEND_EMPTY_ORG
     * @apiError 5007 機構不能為空
     */
    ATTEND_EMPTY_ORG(5007,"機構不能為空"),
    
    /**
     * @apiDefine ATTEND_NOT_FIND_ORG
     * @apiError 5008 機構沒有找到
     */
    ATTEND_NOT_FIND_ORG(5008,"機構沒有找到"),
    
    /**
     * @apiDefine ATTEND_EMPTY_MINUTES
     * @apiError 5009 使用時長不能為空
     */
    ATTEND_EMPTY_MINUTES(5009,"使用時長不能為空"),
    
    /**
     * @apiDefine ATTEND_ERROR_MINUTES
     * @apiError 5010 使用時長不能為負數
     */
    ATTEND_ERROR_MINUTES(5010,"使用時長不能為負數"),
    
    /**
     * @apiDefine ATTEND_ERROR2_MINUTES
     * @apiError 5011 當天使用時長不能大於24小時
     */
    ATTEND_ERROR2_MINUTES(5011,"當天使用時長不能大於24小時")
    
    ;
 
    private  final int code;
    private final String msg;
 
    private AttendRestEnum(int code,String msg){
        this.code = code;
        this.msg = msg;
    }
    @Override
    public int getCode() {
        return this.code;
    }
 
    @Override
    public String getMsg() {
        return this.msg;
    }
}

以上定義了一個常用的並且對於我的專案來說是通用的返回資訊,如token_msg,success_msg,error_msg,下面例子中,一些apiUse用到的是其他的錯誤程式碼,並未一一列舉出來,但可以根據名字想象就是。

3.在介面中使用。

A:get請求例子1

/**
     * @api {get} /rest/area/getAreasByCode 行政區域查詢
     * @apiDescription 根據行政編碼獲取行政區域,0獲取省級行政區域
     * @apiName getAreasByCode
     * @apiGroup area
     * @apiVersion 1.0.0
     *
     * @apiParam {String} code 行政編碼
     *
     * @apiSampleRequest /rest/area/getAreasByCode
     * @apiUse token_msg
     * @apiUse success_msg
     * @apiSuccess (success 2000) {String}   res.id    標識碼
     * @apiSuccess (success 2000) {String}   res.name    行政地區名稱
     * @apiSuccess (success 2000) {String}   res.code    行政編碼
     * @apiSuccess (success 2000) {String}   res.prevCode    上級行政編碼
     * @apiSuccess (success 2000) {String}   res.allName    全稱
     *
     */
    @RequestMapping("/getAreasByCode")
    @ResponseBody
    public RestResponse getAreasByCode(String code){
        return new RestResponse(areaService.findAreaByPrevCode(code));
    }

get請求例子2:

/**
     * @api {get} /rest/role/find 角色列表查詢
     * @apiDescription  綜合角色查詢
     * @apiName find
     * @apiGroup role
     * @apiVersion 1.0.0
     *
     * @apiUse token_msg
     * @apiParam {String}  [page]     當前第幾頁
     * @apiParam {String}  [pageSize]    每頁顯示多少條資料,當該引數為0時表示不分頁,查詢全部
     * @apiParam {String}  [name]    角色名稱
     * @apiParam {String}  [code]       角色程式碼
     *
     * @apiSampleRequest /rest/role/find
     * @apiUse success_msg
     * @apiSuccess (success 2000) {Long}      res.total    總條數
     * @apiSuccess (success 2000) {Array}    res.results    結果集
     * @apiSuccess (success 2000) {String}    res.results.id    角色id
     * @apiSuccess (success 2000) {String}    res.results.name    角色名稱
     * @apiSuccess (success 2000) {String}    res.results.code        角色程式碼
     * @apiSuccess (success 2000) {String}    res.results.remark        角色描述
     * @apiSuccess (success 2000) {String}    res.results.createTime        建立時間
     * @apiSuccess (success 2000) {String}    res.results.updateTime        更新時間
     * @apiSuccess (success 2000) {String}    res.results.sort        排序編號
     * @apiSuccess (success 2000) {String}    res.results.isSuper        是否超級管理員
     */
    @RequestMapping("/find")
    @ResponseBody
    public RestResponse find(String name,String code,String page,String pageSize){
        return new RestResponse(rsp);
    }

get請求例子3:

/**
     * @api {get} /rest/role/get 角色詳情
     * @apiDescription  根據id或者根據code查詢角色
     * @apiName get
     * @apiGroup role
     * @apiVersion 1.0.0
     *
     * @apiUse token_msg
     * @apiParam {String}  [id]     角色id
     * @apiParam {String}  [code]       角色程式碼
     *
     * @apiSampleRequest /rest/role/get
     * @apiUse success_msg
     * @apiSuccess (success 2000) {String}    res.id    角色id
     * @apiSuccess (success 2000) {String}    res.name    角色名稱
     * @apiSuccess (success 2000) {String}    res.code        角色程式碼
     * @apiSuccess (success 2000) {String}    res.remark        角色描述
     * @apiSuccess (success 2000) {String}    res.createTime        建立時間
     * @apiSuccess (success 2000) {String}    res.updateTime        更新時間
     * @apiSuccess (success 2000) {String}    res.sort        排序編號
     * @apiSuccess (success 2000) {String}    res.isSuper        是否超級管理員
     *
     * @apiUse ROLE_UN_EXIST
     */
    @RequestMapping("/get")
    @ResponseBody
    public RestResponse get(String id,String code){
        return rest;
    }

B:POST請求例子1

/**
     * @api {post} /rest/role/create 建立角色
     * @apiDescription  新建角色
     * @apiName create
     * @apiGroup role
     * @apiVersion 1.0.0
     *
     * @apiUse token_msg
     * @apiParam {String}  code 角色程式碼
     * @apiParam {String}  name 角色名稱
     * @apiParam {String}  [remark] 角色描述
     *
     * @apiSampleRequest /rest/role/create
     * @apiUse success_msg
     *
     * @apiUse ROLE_INPUT_NAME_ERROR
     * @apiUse ROLE_INPUT_CODE_ERROR
     * @apiUse ROLE_REPEAT_CODE
     */
    @RequestMapping("/create")
    @ResponseBody
    public RestResponse create(String name,String code,String remark){
        return new RestResponse();
    }

POST請求例子2

/**
     * @api {post} /rest/role/update 修改角色
     * @apiDescription  修改角色
     * @apiName update
     * @apiGroup role
     * @apiVersion 1.0.0
     *
     * @apiUse token_msg
     * @apiParam {String}  id 角色程式碼
     * @apiParam {String}  [name] 角色名稱
     * @apiParam {String}  [code] 角色程式碼
     * @apiParam {String}  [remark] 角色描述
     *
     * @apiSampleRequest /rest/role/update
     * @apiUse success_msg
     *
     * @apiUse ROLE_CANNOTBE_NONE
     * @apiUse ROLE_REPEAT_CODE
     * @apiUse ROLE_UN_EXIST
     * @apiUse ROLE_CANNOT_EDIT
     */
    @RequestMapping("/update")
    @ResponseBody
    public RestResponse update(String id,String name,String code,String remark){
            return new RestResponse();
        }

POST請求例子3

/**
     * @api {post} /rest/role/delete 刪除角色
     * @apiDescription  根據id刪除角色
     * @apiName delete
     * @apiGroup role
     * @apiVersion 1.0.0
     *
     * @apiUse token_msg
     * @apiParam {String}  id 角色id
     *
     * @apiSampleRequest /rest/role/delete
     * @apiUse success_msg
     *
     * @apiUse USER_ROLE_UNEXIST
     * @apiUse ROLE_DELETE_CANNOT_DELETE_DEFAULT
     */
    @RequestMapping("/delete")
    @ResponseBody
    public RestResponse delete(String id){
        return new RestResponse(rest);
    }

POST請求4(表單上傳1)

/**
     * @api {post} /rest/user/updateHxIcon/{userName} 上傳頭像
     * @apiDescription 上傳頭像,{userName}是需要上傳的使用者名稱稱,為地址引數
     * @apiName updateHxIcon
     * @apiGroup user
     * @apiVersion 1.0.0
     *
     * @apiParam {formData} imageFile 頭像檔案
     *
     * @apiSampleRequest /rest/user/updateHxIcon/{userName}
     * @apiUse token_msg
     * @apiUse success_msg
     * @apiSuccess (success 2000) {boolean}   res.result    請求結果
     * @apiSuccess (success 2000) {String}   res.message    請求結果資訊
     * @apiSuccess (success 2000) {String}    res.url        頭像連結
     *
     * @apiUse INPUT_ERROR
     * @apiUse BASE_UPLOAD_FAIL
     * @apiUse USER_UNEXIST
     */
    @RequestMapping(value = "/updateHxIcon/{userName}",method = RequestMethod.POST)
    @ResponseBody
    public RestResponse updateHxIcon(HttpServletRequest request,@PathVariable("userName") String userName,@RequestParam(value = "imageFile", required = true) MultipartFile file){
        return res;
    }

POST請求5(表單上傳2)

/**
     * @api {post} /rest/user/create 新建使用者
     * @apiDescription  新建使用者
     * @apiName create
     * @apiGroup user
     * @apiVersion 1.0.0
     *
     * @apiUse token_msg
     * @apiParam {formData}  [iconFile]     頭像
     * @apiParam {String}  loginName     登入名
     * @apiParam {String}  pwd     密碼
     * @apiParam {String}  orgId     機構id
     * @apiParam {String}  [roleId]     角色id
     * @apiParam {String}  name    使用者名稱
     * @apiParam {String}  [jobCode]    職務 1:科長 2:主任 3:科員 4:。。。
     * @apiParam {String}  [jobType]    職業性質 1:全職 2:兼職
     * @apiParam {String}  [sex]    性別 1:男 2:女
     * @apiParam {String}  phone    手機號
     * @apiParam {String}   idCard   身份證號
     * @apiParam {String}  birthday  出生日期
     * @apiParam {String}  [address]    住址
     * @apiParam  {String} [contactUser] 緊急聯絡人
     * @apiParam {String}  [contactPhone] 緊急聯絡電話
     * @apiParam  {String} [sex]    性別  1:男  2:女
     *
     * @apiSampleRequest /rest/user/create
     * @apiUse success_msg
     *
     * @apiUse USER_EMPTY_NAME
     * @apiUse USER_EMPTY_LOGIN_NAME
     * @apiUse USER_EMPTY_PWD
     * @apiUse USER_EMPTY_ORG
     * @apiUse USER_EMPTY_ROLE
     * @apiUse REGISTER_PHONE_EXIST
     * @apiUse REGISTER_USERNAME_EXIST
     * @apiUse USER_IDCARD_EXIST
     * @apiUse REGISTER_ORG_UNEXIST
     * @apiUse BASE_UPLOAD_FAIL
     * @apiUse BASE_SAVE_FAIL
     * */
    @RequestMapping("/create")
    @ResponseBody
    public RestResponse create(@RequestParam("iconFile") CommonsMultipartFile[] files
                                ,String loginName,String pwd,String orgId,String roleId,String name,
                               String jobCode,String jobType,String sex,String phone,String idCard,String birthday,String address,String contactUser,String contactPhone){
        return new RestResponse(rest);
    }

4.如上已經列舉增刪改查,以及檔案上傳的註釋例子,注意:formData是我自己定製程式碼使用的,原生並沒有提供表單上傳的功能。

 

下面把我的定製過程分享給大家。

在resource裡面新增一個目錄,放置修改的檔案。

(1)如圖所示,我們先在main.js中引入jqury.form.min.js依賴

(2)在index.html模板檔案中,新增支援formData的模板

                {{#if_eq this.type compare="formData"}}
                <input style="padding:0px" id="sample-request-param-field-{{field}}"  name="{{field}}" type="file" placeholder="{{field}}" class="form-control sample-request-param" data-sample-request-param-name="{{field}}" data-sample-request-param-group="sample-request-param-{{@../index}}">
                <div class="input-group-addon">{{{type}}}</div>
                {{else}}
                <input id="sample-request-param-field-{{field}}" type="text" placeholder="{{field}}" class="form-control sample-request-param" data-sample-request-param-name="{{field}}" data-sample-request-param-group="sample-request-param-{{@../index}}">
                <div class="input-group-addon">{{{type}}}</div>
                {{/if_eq}}


其實所有的資源都是使用apidoc -i ./ -o ./src/main/webapp/WEB-INF/doc生成後的檔案,再把原始碼進行修改而已,我們修改的只是在線測試部分的程式碼,所需的只是找準渲染模板所在的位置。
(3)模板修改完成後,讓請求帶上即可,所以修改傳送請求的js檔案程式碼

// send AJAX request, catch success or error callback
      var ajaxRequest = {
          url        : url,
          headers    : header,
          data       : param,
          type       : type.toUpperCase(),
          success    : displaySuccess,
          error      : displayError
      };
      if($root.find("input[type='file']").length == 0) {
          $.ajax(ajaxRequest);
      }else{
          var $ycfm = $($root.find("form")[0]);
          $ycfm.attr("enctype","multipart/form-data");
          $ycfm.ajaxSubmit(ajaxRequest);
      }


(4)定製已經完成。我們只需要將doc-extends的檔案,直接覆蓋回去即可。如我的批處理檔案。docGenerator.bat.

@echo off
call apidoc -i ./ -o ./src/main/webapp/WEB-INF/doc
copy "%~dp0src\main\resources\doc-extends\index.html" "%~dp0src\main\webapp\WEB-INF\doc" /y
copy "%~dp0src\main\resources\doc-extends\main.js" "%~dp0src\main\webapp\WEB-INF\doc" /y
copy "%~dp0src\main\resources\doc-extends\jquery.form.min.js" "%~dp0src\main\webapp\WEB-INF\doc\vendor" /y
copy "%~dp0src\main\resources\doc-extends\send_sample_request.js" "%~dp0src\main\webapp\WEB-INF\doc\utils" /y
copy "%~dp0src\main\resources\doc-extends\favicon.ico" "%~dp0src\main\webapp\WEB-INF\doc\img" /y
pause

即把index.html,main.js,放回生成後的根目錄,jquery.form.min.js放到vendor目錄下,send_sample_request.js放回utils目錄下,favicon.ico放回img目錄下,覆蓋原來的檔案即可,等於是修改了原始碼。

5.在spring專案中開放一個路由,或者將其對映為靜態路徑,xml配置如下

	<mvc:resources mapping="/rest/doc/**" location="/WEB-INF/doc/" cache-period="31536000"/>

這時,只需要將apidoc生成的文件放置在/WEB-INF/doc下,訪問http://localhost:port/contextPath/rest/doc/index.html便可進入介面文件,生成指令為apidoc -i ./ -o ./src/main/webapp/WEB-INF/doc。

springBoot的專案也是同理,把其放置到某個目錄下,然後將該目錄對映為靜態資源,對映一個路徑,訪問該路徑即可。

五、打包專案。

至此,apidoc的程式碼已經寫進註釋裡,要融合進我們的開發裡面,就需要使用指令碼來一步完成,不然的話,就按照基本流程過來。

總共步驟如下

1.開啟cmd,呼叫apidoc的執行程式,生成apidoc文件,apidoc -i ./ -o ./src/main/webapp/WEB-INF/doc

2.將我們修改過的原始檔逐個複製回原本的目錄,覆蓋。

3.專案打包,mvn clean install package

4.部署,訪問http://localhost:port/contextPath/rest/doc/index.html,訪問介面文件。

我寫了一個在window下的批處理檔案。package.bat。程式碼如下。

@echo off
svn revert -R src/main/webapp/WEB-INF/doc
svn update
 
call apidoc -i ./ -o ./src/main/webapp/WEB-INF/doc
copy "%~dp0src\main\resources\doc-extends\index.html" "%~dp0src\main\webapp\WEB-INF\doc" /y
copy "%~dp0src\main\resources\doc-extends\main.js" "%~dp0src\main\webapp\WEB-INF\doc" /y
copy "%~dp0src\main\resources\doc-extends\jquery.form.min.js" "%~dp0src\main\webapp\WEB-INF\doc\vendor" /y
copy "%~dp0src\main\resources\doc-extends\send_sample_request.js" "%~dp0src\main\webapp\WEB-INF\doc\utils" /y
copy "%~dp0src\main\resources\doc-extends\favicon.ico" "%~dp0src\main\webapp\WEB-INF\doc\img" /y
 
call mvn clean install package -Dmaven.test.skip=true
 
for /f "tokens=2,*" %%i in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "Desktop"') do (
set desk=%%j
)
copy "%~dp0target\foreranger.war" "%desk%" /y
pause

與步驟有些不同:svn回滾,然後svn更新,apidoc生成文件,覆蓋修改檔案到apidoc目錄下,打包專案,將打包的war包拷貝到桌面。具體根據自己專案修改批處理檔案,linux系統指令碼自己定製。

 

六、效果圖。


 

七、結束。

這裡沒有講apidoc具體的註釋的使用,但是已經舉了一些例子,並且對原始碼進行了一定的定製,雖然仍然有其不足,但是思路已經為大家打開了,你也可以像我一樣對原始碼進行自己的定製,不過是基於handlebars.js的渲染而已。具體的註釋請參照官網http://apidocjs.com即可。

--------------------- 本文來自 神在異鄉 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/chemphone/article/details/79193466?utm_source=copy