写在前面
又想写一个系列文章了,之所以起这么个标题就是完全从我自身实践出发,我个人的感觉就是,学习一个新知识特别是技术类的,如果只是咬文嚼字,从头到尾的撸一遍文档,下来之后还是不会达到应用级别,在尝试的时候还是不断的中断,然后再回头重新看,我觉得效率不高。所以,我一般的做法就是直接上手,毕竟现在的前端技术都差不多,大同小异。框架语法+路由+自身的一些前置约束,随便跑一个demo感觉就差不多可以上手了。至于更深层次的东西,可以写的时候遇到问题了再去深究,这样的话知识点也掌握得更牢固。
当然,每个人的习惯都不同,我只是给这一系列文章搞一个噱头,哈哈?。
什么是apiDoc
容我臭美一下,我看了一些apidoc的文章,个人觉得自己这篇作为入门级应该是最详实的了,无论是文章的结构还是示例代码,希望朋友们耐心看完?
还是正常操作,虽然我说了喜欢边写边学,但是首先还是要打开官网去看看基本的知识的,至少你得知道这东西是干什么的,基于什么,有什么规范等等之类的内容。apiDoc官网介绍了 —— Inline Documentation for RESTful web APIs
。翻译过来就是“以内联文档的形式提供RESTful web APIs”。官网也是话不多说,上来直接就是各种Demo,因为是前端,我只关心JavaScript的了:
其实apidoc支持很多语言,Java、PHP、python和JavaScript等。
/**
* @api {get} /user/:id Request User information
* @apiName GetUser
* @apiGroup User
*
* @apiParam {Number} id Users unique ID.
*
* @apiSuccess {String} firstname Firstname of the User.
* @apiSuccess {String} lastname Lastname of the User.
*/
复制代码
上面就是一个内联的api文档,可以看到,其实apidoc是通过我们在代码里插入一定规范的注解(注释),然后再通过运行相应的命令去解析,帮助我们生成RESTful web APIs。这种方式也就意味着约束条件很多,我们必须严格按照约束条件来做,当然好处也是有的,就是不会出错,约定强也就意味着规范性强。
apidoc的基本注释规范
前面说过了,约束越强,后面写起来其实也就越简单,因为没什么创造性的东西,而apidoc的约束也就是各种@apiParam了。
如上图,其实也没想象的那么多,只要我们把这些全部掌握了,基本就OK了。
边写边学
既然是边写边学,与其他文章不同的地方就在于,不是一点一点的每一个@api按照官方文档翻译一下,而是直接拿来用,逐个理解,从示例中去理解的效率要高的多得多得多。所以我准备是优先写出来一个简易的demo,然后不断的加深,然后把所有的apiParam都过一遍,这篇文章就结束了,伴随着示例代码,大家看着也会舒服。学起来有代码也简单~
第一步 - 安装apidoc
万变不离其宗,你既然要用它肯定得先安装它。
npm install -g apidoc
// 其实我想了一下,安装在每个项目里的devDependencies也是OK的
npm install --save-dev apidoc
复制代码
第二步 - 配置相关文件
因为apidoc最后会给我们一个静态文件,我们可以进行访问,那么关于这个服务的相关配置我们可以通过apidoc.json来进行。
这里有一个前提,就是你得有一个自己的工程,我直接通过node起了一个服务,一个非常简单的小工程,专门用来放这篇文章的Demo,apidoc-demo,喜欢的可以给个?
// apidoc.json
{
"name": "apidoc-demo",
"version": "1.0.0",
"description": "边写边学系列 —— apidoc",
"title": "apidoc-demo",
"url" : "http://localhost:3333",
"preview-url": "http://localhost:3333/apidoc/index.html" //预览服务地址
}
复制代码
name、version、description是基本的配置字段,其他的看自己的方便来配,用不用得上再说。
第三步 - 第一个示例
OK,激动人心的时刻要到来了,边写边学的兴奋之处就在于,你其实还不太理解这个东西的工作原理和过程,通过Demo就把示例跑出来了,这表示你已经可以成功写它了,接下来你只需要深入了解一下就可以完全掌握了~
// 我们在routes/users.js下面写一个api
/**
* @api {get} /users
* @apiDescription 获取用户列表
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "errcode" : 0,
* "message": "",
* "data" : [{
* "name" : "userName",
* "email" : "userEmail"
* }]
* }
* @apiSampleRequest http://localhost:3333/users
* @apiVersion 1.0.0
*/
router.get('/', function(req, res, next) {
res.json({
errcode: 0,
message: '',
data: [
{
name: 'luffy',
email: 'luffy@163.com'
}, {
name: 'naruto',
email: 'naruto@126.com'
}
]
});
});
复制代码
上面我们写好了一个获取用户列表的api,然后我们来生成api文档。
这里有两个前置条件要说明一下
- 第一个,我们在
pulic/
目录下新建apidoc
文件夹 - 第二个,我们运行
apidoc -i routes/ -o public/apidoc/
命令
解释一下,我们声称文档的命令是
apidoc -i routes/ -o public/apidoc/
,熟悉node的同学应该都清楚,routes就是后端路由,也就是api的位置,apidoc监听的是routes/
目录的所有文件,然后输出到public/apidoc/
目录中,这里其实随意,你输出到哪里都可以,因为它的输出就是一套静态文件,带样式的html。那么既然是node服务,我起的静态服务器就是public,我将生成的文件放到public/apidoc/
文件夹下,项目启动其实服务也就可以被访问了,一举两得,很方便。所以上面apidoc.json我写了preview-url: http://localhost:3333/apidoc/index.html
。
因为命令很长,方便日后封装一下:
// package.json
...
"scripts": {
"start": "DEBUG=apidoc-demo:* nodemon ./bin/www",
+ "apidoc": "apidoc -i routes/ -o public/apidoc/"
}
...
复制代码
我们运行yarn apidoc
,控制台会输出如下内容表示已经完成
并且/punlic/apidoc/
目录内也出现了apidoc为我们生成的内容:
然后我们启动服务yarn start
,访问http://localhost:3333/apidoc/index.html
。
-
@api - 定义这是一个apidoc的api
用法:`@api {method} path [title]` Required,这是必须的,每一个apidoc的API文档必须拥有此ziduan 复制代码
-
@apiDescription - 这个api的描述
用法:@apiDescription text 描述这个api是干什么的 复制代码
-
@apiSuccessExample - 成功示例
用法: @apiSuccessExample [{type}] [title] example 响应成功的返回示例 复制代码
-
@apiSampleRequest - 请求地址
用法: @apiSampleRequest url 用来点击发送示例请求的地址 复制代码
-
@apiVersion - api版本号
用法: @apiVersion version 当前api的版本号 复制代码
第四步 - 进阶学习
上面第一个例子相信大家都跑成功了,而且应该也掌握了几个基本的字段意义以及如何使用。接下来我们就进阶,扩展一下api参数来丰富我们的文档。首先我们来看看那么一大串是什么个东东,看起来真是丑啊。。。
扩展功能 —— 分组 + 响应参数
读一下,大概意思就是接口的位置了,不过这么长确实有点不美观了,按照我们的习惯,/routes/users.js应该就是专门为User来提供接口的,并且按照以往接口规范,接口也是应该分组的。所以我们来了,扩展接口功能 —— 分组。
- 分组 - @apiGroup
/** * @api {get} /users * @apiDescription 获取用户列表 + * @apiGroup User * @apiSuccessExample {json} Success-Response: * HTTP/1.1 200 OK * { * "errcode" : 0, * "message": "", * "data" : [{ * "name" : "userName", * "email" : "userEmail" * }] * } * @apiSampleRequest http://localhost:3333/users * @apiVersion 1.0.0 */ 复制代码
同时,我们在上面可以看到,成功时候的返回,但是返回的数据说明没有,继续扩展,返回数据格式及说明扩展接口功能 - 响应数据规范
- 响应 - @apiSuccess
/** * @api {get} /users * @apiDescription 获取用户列表 * @apiName GetUserList * @apiGroup User + * @apiSuccess {array} data 响应数据 + * @apiSuccess {string} message 响应消息 + * @apiSuccess {number} errcode 错误码(自己定义,0为无错误) * @apiSuccessExample {json} Success-Response: * HTTP/1.1 200 OK * { * "errcode" : 0, * "message": "", * "data" : [{ * "name" : "userName", * "email" : "userEmail" * }] * } * @apiSampleRequest http://localhost:3333/users * @apiVersion 1.0.0 */ 复制代码
OK,增加完了,我们再来重新生成一下文档并重启服务yarn apidoc && yarn start
。
可以看到,我们扩展的功能按照预期都出现了,至于这两个的更全面使用,比如响应参数的default-value等等给你们扩展空间,去官网看吧~
扩展功能 - 错误响应 + 携带参数
上面的示例我们看到了,扩展了两个功能,可以说很接近一个完整的api文档了,不过既然有successExample,那么也就应该有errorExample,因为毕竟不是所有请求都能成功,也存在失败的响应嘛。
还有就是,第一个接口我们获取的是所有用户列表,直接GET /users
就OK了,那么问题来了,如果有参数该怎么办呢,参数应该如何定义呢。所以接下来就写一个带参数的接口,我们来把功能继续完善。
/**
* @api {get} /users/:id GetUserInfoById
* @apiDescription 获取用户列表
* @apiName GetUserById
* @apiGroup User
* @apiParam {Number} id Users unique ID.
* @apiSuccess {Object} data 响应数据
* @apiSuccess {String} message 响应消息
* @apiSuccess {Number} errcode 错误码(自己定义,0为无错误)
* @apiSuccessExample {Json} Success-Response:
* HTTP/1.1 200 OK
* {
* "errcode" : 0,
* "message": "",
* "data" : {
* "id": 0,
* "name" : "userName",
* "email" : "userEmail"
* }
* }
* @apiError {4XX} UserNotFound The <code>id</code> of the User was not found.
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 404 Not Found
* {
* "error": "UserNotFound"
* }
* @apiSampleRequest http://localhost:3333/users/:id
* @apiVersion 1.0.0
*/
router.get('/:id', function(req, res, next) {
const { id } = req.params;
if (!userData.some(item => item.id === parseInt(id, 10))) {
// 不存在直接404
return res.status(404).json({
errcode: 404,
message: `The ${id} of users was not found!`,
data: {}
});
}
// 存在
res.json({
errcode: 0,
message: '',
data: userData.find(item => item.id === parseInt(id, 10))
})
});
复制代码
代码虽然有点长,不过大部分都是apidoc的注释,我们可以看到,增加了两个注解,一个@apiParam
一个@apiErrorExample
,也很简单,一个是参数,一个是错误响应示例。
-
@apiParam - 参数
用法:@apiParam [(group)] [{type}] [field=defaultValue] [description] 请求参数,类型,可以包括默认值和参数描述 复制代码
-
@apiError - 定义错误信息
用法:@apiError [(group)] [{type}] field [description] 定义错误类型,如 {4XX}错误,{5XX}错误 复制代码
-
@apiErrorExample - 错误响应示例
用法: @apiErrorExample [{type}] [title] example 错误响应的示例 复制代码
我们来看一下实际效果:
我们发送一个错误请求,因为3是找不到的,所以返回的是404 Error。成功请求如下:
高级处理 - @apiParam既可作为param又可以作为query
这里算是一个小tip吧,也算是踩坑过程经历到的一个东西,感觉挺有意思,可以这么用。就是我在使用的时候一直有个疑惑,为什么注解里只有@apiParam而没有@apiQuery呢,因为实际场景中还是有很多query形式的api的,但是说实话真没发现,虽然RESTful形式的api放在param里也可以,不过还是很疑惑。(如果有大牛给我解释一下还是很感激的)
// @apiParam -> @apiQuery
上面那个是个假命题,也就是还是@qpiParam注解,但是实际上是可以当作query来去做的。还是以查用户内容作为示例
/**
* @api {get} /users/:id GetUserInfoById
* @apiDescription 获取用户列表
* @apiParam {Number} id
* ...
* @apiSampleRequest http://localhost:3333/users
* @apiVersion 1.0.0
*/
复制代码
我们在上面这个地方;
将原来的: @apiSampleRequest: http://localhost:3333/users/:id
改成 => @apiSampleRequest: http://localhost:3333/users
然后也会产生一个param为id字段,此时我们填写进去id,在后台就会将这个param转换为query的形式
复制代码
【注】:官方文档并没有说这么用,只是我使用起来发现表现是一致的。
扩展功能 - @apiDefine抽离公共块 + @apiUse使用公共块
到上面为止,注解基本已经可以使用的差不多了,但是有一个问题,如果想写的很全,每一个api上方的注释会超级的长,怎么办呢?这就用到了扩展功能 - @apiDefine和@apiUse
- @apiDefine - 定义代码块
用法:@apiDefine name [title] [description] 定义公共代码块,然后可以通过@apiUse使用 复制代码
- @apiUse - 使用预先定义的代码块
用法:@apiUse name 使用@apiDefine定义好的代码块 复制代码
我们还是举例说明,比如上面可以抽离的部分,很明显,成功返回字段是可以抽离的,因为成功一定会返回两个字段errcode,message
,data字段由于返回内容类型不确定不是很好确定,所以不做抽离。所以就抽离一个成功返回字段的代码块来使用。
/**
* @apiDefine CommonSuccess 成功响应字段公共部分
* @apiSuccess {Number} errcode The success res code.
* @apiSuccess {Strng} message The res message.
*/
// 在下面使用
/**
* @api {get} /users GetUserList
* @apiDescription 获取用户列表
* @apiName GetUserList
* @apiGroup User
+ * @apiUse CommonSuccess
* @apiSuccess {Array} data 响应数据
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "errcode" : 0,
* "message": "",
* "data" : [{
* "id": 0,
* "name" : "userName",
* "email" : "userEmail"
* }]
* }
* @apiSampleRequest http://localhost:3333/users
* @apiVersion 1.0.0
*/
复制代码
其他注解说明
除了上面的注解之外,还剩下的一些其他可能会用到的注解,这里顺便也说一下。
- @apiHeader - 头部字段(类似param,只不过是放在req的头部)
用法:@apiHeader [(group)] [{type}] [field=defaultValue] [description] 放在req的头部,一般是用来进行校验,如jwt 复制代码
比如,我在获取所有用户列表的接口里要求头部必须有authorization字段。
/**
* @api {get} /users GetUserList
* @apiDescription 获取用户列表
* @apiName GetUserList
* @apiGroup User
+ * @apiHeader {String} Authorization 用户权限验证码
* @apiUse CommonSuccess
* @apiSuccess {Array} data 响应数据
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "errcode" : 0,
* "message": "",
* "data" : [{
* "id": 0,
* "name" : "userName",
* "email" : "userEmail"
* }]
* }
+ * @apiUse InvalidToken
* @apiSampleRequest http://localhost:3333/users
* @apiVersion 1.0.0
*/
复制代码
如图所示,在模拟请求的同时会要求将token和header放进去。
- @apiPermission - 有权限的api
用法:@apiPermission name 比如,某些api要求必须管理员才能访问,或者要求头部必须anthorization等。 复制代码
同样,我们让获取用户列表增加permission提示:
// 第一步,定义一个token
/**
* @apiDefine token 需要验证用户权限
* 需要在header中加入Authorization字段进行用户权限验证
*/
// 第二步,使用@apiPermission
@apiPermission token
复制代码
- @apiIgnore - 忽略某些API,比如某些未完成的方法
用法: @apiIgnore [hint] 比如某些方法未完成不想暴露给外面,就是用这个注解 复制代码
我们直接在代码里新写一个api然后标记为@apiIgnore:
/**
* @apiIgnore 没写完的POST USER
* @api {post} /users
*/
router.post('/users', function (req, res, next) {
console.log('没写完的POST USER');
});
复制代码
如图所示可以看到,我们的文档仍然是只有两个api,这个post api确实被ignore了。
抽离公共代码块
这里就不是一个注解了,就是我认为项目规范里值得提一点的地方,就是说其实我们抽离出来的代码块是可以统一管理的,而不必要每个文件都单独管理。单独抽离出来我统一放到了/routes/apidoc/common.js
里面,然后其他路由文件正常使用就可以,看起来项目整体就更规范了。仁者见仁智者见智,当项目庞大的时候,还可以将响应再单独封装,这就看自己的需要了~
扩展插件 - vscode-apidoc-snippets
总结
最后,强调一下,其实我是为了写这篇文章随便起了一个node服务,其实这个服务不适合很是,或者说这个场景不是很合适,为什么呢?因为node+渲染引擎的这种开发模式前后端本来就一个人来写,而且接口与页面耦合的很严重,一个人去写其实可能来说没有必要需要api文档或者说场景不是很合适。我倒是觉得很适合前后端分离,node端作为后端,虽然也可能是一个人去写但是可能分离的比较彻底~
这个系列的第一篇文章,写得还算比较流畅,最主要的是我确实是一边写代码一边学习apidoc一边写文章,三位一体感觉学的还是挺深刻的。与其说是一篇文章,更不如说是一个记录过程,不过这个过程我觉得可以让很多小白少走很多弯路,至少有完整的示例代码,有详细的学习过程,循序渐进,还是比较适合新手的~
apidoc-demo代码地址,各位看官,有任何意见都可以提,希望多多关注多多喜欢。