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