简介
随着移动客户端的流行,后端系统需开放越来越多的 API 来供客户端使用。API文档的编写和管理是一个挑战,随着API不断变化,文档必须及时更新,但编写文档也是个不小的负担。
一个比较好的做法,就是将文档放在代码里,开发人员编写代码时同时修改文档。然后通过工具从代码中抽出文档,并生成方便用户阅读的格式。此类工具早已存在,比如Java中的javadoc。这里我们要介绍一个非常轻量级的,适用于几乎所有流行语言的,针对Restful API的文档自动生成工具-apiDoc。
安装
apiDoc基于NodeJS实现,所以你需先有NodeJS及NPM环境。然后通过:
$ npm install apidoc -g
全局安装apiDoc。之后,你就可以执行下面的命令来生成文档:
$ apidoc -i src -o dest
该命令从当前工作目录的”src”子目录下读取所有代码文件,并从中抽取apiDoc的文档注释,然后生成HTML格式的文档,并保存在”dest”子目录下。执行完后,用户就可以打开”dest”下的”index.html”来阅读文档。
“apidoc”命令支持许多参数,常用的有:
-
-t template
- 根据”template”子目录下的模板生成文档,缺省时采用apiDoc的默认模板。
-
-f ".*\.py$"
- 只解析Python代码。-f后的参数按正则匹配,可以设置多个-f参数。
-
-h
- 显示该命令帮助信息。
配置文件
运行apidoc前,你需先有一个配置文件,放在代码根目录下(如上例中的src目录),取名为”apidoc.json”。下面是一个标准配置文件的例子:
{
"name": "电影网站接口文档",
"version": "0.1.0",
"description": "公司内部文档,请勿外传",
"title": "h5.com",
"url" : "https://movie.52kfw.cn/index.php/api/movie",
"sampleUrl": "https://test.52kfw.cn/index.php/api/movie",
"header": {
"title": "简介",
"filename": "header.md"
},
"footer": {
"title": "底部",
"filename": "footer.md"
},
"template": {
"withCompare": true,
"withGenerator": false
}
}
-
name, version, description -这三个配置项的内容会显示在生成文档的最上方,其中name即整个文档的大标题。
-
title
- 网页的标题,显示在浏览器标签上。
-
url
- 文档中每个API地址的前缀。
-
sampleUrl
- 当有此项时,每个API文档的最后会有Sample Request测试部分。该配置项是测试API地址的前缀。
-
header, footer
- 页面的头和尾。当多个页面都有相同的头和尾时,这个配置项就非常有用。子配置项中
-
title
- 页面左边菜单栏中显示的标题。
-
filename
- 指向页头或页尾的模板文件,apiDoc使用的是Markdown文件。
-
template
- withCompare
- 生成的文档有版本比较功能。后面会介绍。默认为”true”。
- withGenerator
- 生成的文档页尾带有一段文字,表示这个文档是由apiDoc生成的,加上这个代表对作者的尊重吧。默认为”true”。 让我们简单写下页头和页尾的Markdown文件,并将它们同”apidoc.json”放在一起,即”src”目录下。
- withCompare
-
header.md
## 简介
> 本文档仅限内部使用切勿外传。
- email: gogery@163.com
- wx: superve
- footer.md
Copyright © 2017 - 2019 [h5.com](http://www.h5.com/) All Rights Reserved
API文档注释
apidoc 根据我们代码里面特定格式的数据生成文档。
JavaScript 示例:
/**
*
* @api {post} /register 用户注册接口
* @apiName register
* @apiGroup 用户管理
* @apiVersion 0.1.0
* @apiDescription 完成用户注册操作
*
* @apiPermission admin
*
* @apiParam {string} username 用户名
* @apiParam {string} password 密码
* @apiParam {string} email 邮箱
* @apiParam {string} phoneNumber 手机号
*
* @apiSuccess (success-200) {String} code 响应状态码
* @apiSuccess (success-200) {String} msg 响应描述
* @apiSuccess (success-200) {Object} data 返回数据,成功的时候才存在
* @apiSuccess (success-200) {int} data.count 记录总数
* @apiSuccess (success-200) {Object[]} data.list 记录列表
*
*
* @apiSuccessExample {json} 成功注册-示例:
* 成功注册
* HTTP/1.1 200 OK
* {
* code:0,
* msg:'success',
* data:{}
* }
*
* @apiError (failure-500) {String} code 响应状态码
* @apiError (failure-500) {String} msg 提示信息
*
* @apiErrorExample {json} 失败-示例:
* 这是出现错误时返回结果示例
* HTTP/1.1 404 Not Found
* {
* code:1,
* msg:'not found',
* }
*/
function register() {
}
/**
*
* @api {get} /list 用户列表接口
* @apiName useList
* @apiGroup 用户管理
* @apiVersion 0.1.2
* @apiDescription 用户列表最新接口
*
* @apiParam {int} page 页码
* @apiParam {int} size 每页数量
* @apiParam {string} orderBy 排序字段
*
* @apiSuccess (success-200) {String} code 响应状态码
* @apiSuccess (success-200) {String} msg 响应描述
* @apiSuccess (success-200) {Object} data 返回数据,成功的时候才存在
* @apiSuccess (success-200) {int} data.count 记录总数
* @apiSuccess (success-200) {Object[]} data.list 记录列表
*
* @apiSuccessExample Success-Response:
* 这是返回结果示例
* HTTP/1.1 200 OK
* {
* code:0,
* msg:'success',
* data:{}
* }
*
* @apiUse UserNotFoundError
*/
function useList() {
}
可以看到,文档注释主要是由一系列以@符号开头的参数组成:
-
@api {get} /users/:user_id Request User Information
- {get}定义了HTTP请求是GET
- API地址是/users/:user_id
- 文档中API的名称是Request User Information
-
@apiVersion 0.1.0
- API的版本号,默认显示在API名称的右方。该参数可用来在不同的版本之间做比较,后面会介绍
-
@apiName GetUser
- API名称,不影响文档
-
@apiGroup User
- API分组名,文档内容中和菜单栏中同一组的API会在一同显示,方便阅读
-
@apiPermission admin
- API的访问权限,文档中默认会API地址下面显示。没有权限要求的话,此项可以省略。
-
@apiDescription API to get the user information.
- API的详细描述,默认显示在API名称的下方
-
@apiExample Example usage:
- API调用示例,该参数的下一行就是示例的内容,直到有空行结束。可以定义多个@apiExample,默认在文档中会以标签形式列出,标签名就是”Example usage:“。
-
@apiParam {Number} user_id The user’s unique ID.
- API参数字段介绍,”{Number}“定义了字段类型,”user_id”是字段名称,后面则是字段描述。可以定义多个@apiParam字段。
-
@apiSuccess {String} name Name of the User.
- API成功后返回的字段,如同@apiParam,”{String}“定义了字段类型,”name”是返回字段名称,后面则是字段描述。可以定义多个@apiSuccess字段。
-
@apiSuccessExample {json} Success-Response:
- 显示一个API成功返回后Response响应的示例,”{json}“代表响应体是JSON类型。该参数的下行就是响应体内容,直到有空行结束。可以定义多个@apiSuccessExample,默认在文档中会以标签形式列出,标签名就是”Success-Response:“。
-
@apiError UserNotFound User was not found.
- API发生错误后的返回,”UserNotFound”是错误名称,后面则是错误描述。可以定义多个错误返回。
-
@apiErrorExample {json} Error-Response:
- 显示一个API错误返回后Response响应的示例,”{json}“代表响应体是JSON类型。该参数的下行就是响应体内容,直到有空行结束。可以定义多个@apiErrorExample,默认在文档中会以标签形式列出,标签名就是”Error-Response:“。
-
@apiSampleRequest http://localhost:5000/users/:user_id
- 文档提供的API Sample测试的地址。其实在”apidoc.json”中配过”sampleUrl”项后,此参数即可省去,除非这个API的测试URL比较特殊,需特别指定。
参数信息重用
很多时候,不同的API有着相同的文档内容,比如相同的成功响应示例,或者相同的错误返回。当API非常多时,一个变化需要改动多处的API文档,很麻烦。能不能像写C代码一样,定义一个宏,然后在API文档注释中引用这个宏呢?答案是有,在apiDoc中,这叫继承(inherit)。首先,你要在源代码注释中用@apiDefine定义这个宏。
比如,下面的注释,就是把上例中UserNotFound错误及其响应示例定义在一个名叫UserNotFoundError的宏中。此后,所有需要使用这段文档的地方,就通过 @apiUse UserNotFoundError
引入这个宏即可
/**
* @apiDefine UserNotFoundError
*
* @apiError error 这里错误描述
*
* @apiErrorExample {json} 错误示例:
* 当请求错误的时候,出现如下的结果:
* HTTP/1.1 404 Not Found
* {
* code:1,
* msg:'list not found',
* }
*/
/**
*
* @api {post} /cxpt 分类列表
* @apiName categoryList
* @apiGroup Category
* @apiVersion 0.1.0
* @apiDescription 分类列表展示
*
* @apiParam {string} page 页码
* @apiParam {string} size 每页获取数量
*
* @apiSuccess {String} code 返回参数描述,简单描述一下个返回参数
* @apiSuccess {String} msg 返回参数描述,简单描述一下个返回参数
* @apiSuccess {Object} data 返回参数描述,简单描述一下个返回参数
* @apiSuccess {int} data.count 返回参数描述,简单描述一下个返回参数
* @apiSuccess {Object[]} data.list 返回参数描述,简单描述一下个返回参数
* @apiSuccessExample Success-Response:
* 这是返回结果示例
* HTTP/1.1 200 OK
* {
* code:0,
* msg:'success',
* data:{}
* }
*
*
* @apiUse UserNotFoundError
*
*/
function categoryList() {
}
当宏定义比较多时,怎么管理呢?我们可以把宏定义挪到代码以外去。让我们在”src”根目录下建立一个文件”_apidoc.js”。然后把上面UserNotFoundError的定义移到这个文件中。
src/_apidoc.js
/**
* @apiDefine UserNotFoundError
*
* @apiError error 这里错误描述
*
* @apiErrorExample {json} 错误示例:
* 当请求错误的时候,出现如下的结果:
* HTTP/1.1 404 Not Found
* {
* code:1,
* msg:'分类信息不存在',
* }
*/
执行下apidoc命令,看下效果吧。是不是和上例中一样?实际项目中,建议尽可能把所有的宏定义都写在”_apidoc.py”中。还有人会问,那这个文件太大了怎么办?你可以分出多个文件,记得给一个有意义的命名即可。
宏定义中加了@apiVersion版本信息,这说明这段宏只对特定版本有效。 src/_apidoc.js
/**
* @apiDefine UserNotFoundError
*
* @apiVersion 0.1.0
*
* @apiError error 这里错误描述
*
* @apiErrorExample {json} 错误示例:
* 当请求错误的时候,出现如下的结果:
* HTTP/1.1 404 Not Found
* {
* code:1,
* msg:'分类信息不存在',
* }
*/
定义访问权限
上面我们讲到@apiPermission参数可以定义当前API的访问权限。如果文档的读者想了解更多关于这个权限的信息呢?我们可以通过@apiDefine来定义每个权限的详细信息。 src/_pri.js
/**
* @apiDefine admin 管理员权限
*
* 注意该接口的必须要使用管理员的权限才可以使用
*
* @apiVersion 0.1.0
*
*
*/
加上这段定义后,在文档出现”admin”权限名时,后面会有个“i”符号。鼠标点击后就会弹出”admin”的详细信息。
历史版本比较
当API更新时,不用再耗费体力查找到底哪里改动了,直接在文档上比较下就知道了。当在文档注释中定义多个版本后,生成后的文档页面中版本号是一个下拉列表。
点击其中一个历史版本,页面就会出现比较信息,通过高亮,删除线等来告诉你哪里被改动过了。
/**
*
* @api {get} /cxpt 用户列表接口
* @apiName useList
* @apiGroup Users
* @apiVersion 0.1.0
* @apiDescription 简单描述这个API方法吧
*
* @apiParam {int} myparam 参数描述,简单描述一下个参数
*
* @apiSuccess {String} code 返回参数描述,简单描述一下个返回参数
* @apiSuccess {String} msg 返回参数描述,简单描述一下个返回参数
* @apiSuccess {Object} data 返回参数描述,简单描述一下个返回参数
* @apiSuccess {int} data.count 返回参数描述,简单描述一下个返回参数
* @apiSuccess {Object[]} data.list 返回参数描述,简单描述一下个返回参数
* @apiSuccessExample Success-Response:
* 这是返回结果示例
* HTTP/1.1 200 OK
* {
* code:0,
* msg:'success',
* data:{}
* }
*
* @apiError error 这里错误描述
* @apiErrorExample {json} Error-Response:
* 这是出现错误时返回结果示例
* HTTP/1.1 404 Not Found
* {
* code:1,
* msg:'user not found',
* }
*/
/**
*
* @api {get} /cxpt 用户列表接口
* @apiName useList
* @apiGroup Users
* @apiVersion 0.1.1
* @apiDescription 简单描述这个API方法吧
*
* @apiParam {int} page 分页页码,如果不传递,默认为:1
*
* @apiSuccess {String} code 返回参数描述,简单描述一下个返回参数
* @apiSuccess {String} msg 返回参数描述,简单描述一下个返回参数
* @apiSuccess {Object} data 返回参数描述,简单描述一下个返回参数
* @apiSuccess {int} data.count 返回参数描述,简单描述一下个返回参数
* @apiSuccess {Object[]} data.list 返回参数描述,简单描述一下个返回参数
* @apiSuccessExample Success-Response:
* 这是返回结果示例
* HTTP/1.1 200 OK
* {
* code:0,
* msg:'success',
* data:{}
* }
*
* @apiError error 这里错误描述
* @apiErrorExample {json} Error-Response:
* 这是出现错误时返回结果示例
* HTTP/1.1 404 Not Found
* {
* code:1,
* msg:'user not found',
* }
*/
function fn() {
}
怎么来添加这历史版本信息呢。要做的事情很简单,就是每次API文档改动时,把原来的API文档注释复制备份到_apidoc.js中,然后再修改。修改完后别忘了更新版本号就行。
然后运行下,就可以做版本比较了。@apiDefine宏定义也可以有历史版本,要注意,这个版本号是同API注释的版本号相匹配的。
比如,假设我们在上例GET方法”0.0.1”版本注释中加入@apiUse UserNotFoundError,这个时候执行apidoc命令会报错。
因为它找不到”0.0.1”版本的@apiDefine UserNotFoundError定义(前面的例子中我们指定的版本号是@apiVersion 0.1.0)。所以,必须要补上”0.0.1”版的@apiDefine UserNotFoundError命令才能运行成功。
扩展
让apidoc可以输入中文
- 找到自己npm安装后apidoc的路径,并打开api_group.js (node_modules\apidoc\node_modules\apidoc-core\lib\workers\api_group.js)
- 注释掉//group = group.replace(/[^\w]/g, '_')
- 你用来生成文档的源文件必须是UTF-8,不然后生成后代码也是乱码