后端问:作为前端如何实现自动生成接口文档
前言,众所周知,后端的接口文档一般使用 swagger/openapi,而其他的一般接口文档也基本是使用 openapi 来进行解析的。例如 redoc(20.8k star) 和 knife4j(3.5k star) 等.
作为一个通用的解决方案,前端社区自然也有对应的实现。
如何实现 swagger 文档界面
swagger-ui-express 是一个 express 插件,可以在 express 中实现 swagger-ui。然后传入 openapi 来渲染接口文档。
const express = require("express");
const app = express();
const swaggerUi = require("swagger-ui-express");
const swaggerDocument = require("./swagger.json");
var options = {
explorer: true,
};
app.use(
"/api-docs",
swaggerUi.serve,
swaggerUi.setup(swaggerDocument, options)
);
从上面的示例可以看到 swagger.json 即为接口文档的描述数据。在这种情况下我们需要准备已经有的 json 数据方可显示文档。
如何生成文档描述数据
我们可以安排 swagger-jsdoc 这个库,来实现在代码中写接口注释,即可生成文档数据。
// Initialize swagger-jsdoc -> returns validated swagger spec in json format
const swaggerSpec = swaggerJSDoc(options);
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
注释示例:
/**
* @openapi
* /:
* get:
* description: Welcome to swagger-jsdoc!
* responses:
* 200:
* description: Returns a mysterious string.
*/
app.get("/", (req, res) => {
res.send("Hello World!");
});
文档不想写,注释不想写,就想生成接口文档
在之前的示例中,虽然我们可以通过写 jsdoc 形式的注释来实现也写接口文档。但问题是,我都能在注释里把文档写清楚了,就不叫“生成文档”了,而叫收集文档片段,而且在代码里写文档,似乎也不太优雅。
要实现注释写不想写,就能实现接口文档的生成,我们首先要让某个工具能理解我们代码的意图。如何理解?
- 方法 1 通过人工智能去理解代码意图然后生成文档
- 方法 2 通过代码钩子或埋点获取意图生成文档
很显然,方法 1 不在我们的实现范畴,因为它涉及大模型、数据安全、本地化等各种问题。而方法 2 则是我们能去研究的方案,例如我们使用 vue 开发项目时,vue-devtool 插件就能看到 vue 相关的状态,这就是在 vue 的实现中有为插件开放获取状态的接口。如果没有开放,也可以通过拦截、代理等方法获取运行状态。
分析接口意图
对于原生的 express 产生的接口,我们可以通过 app.routes
app._router.stack
express.Router()
router.stack
等 api 获取到当前所使用的接口。但我们这里使用 mockm,接口是直接声明在对象中的,就更简单了许多。
api: {
"/a": 111,
"/b": 222,
'get /name': `张三`,
'/status/:code' (req, res) {
res.json({statusCode: req.params.code})
},
},
观察上面的 api 对象,例如 "/a": 111
实现的接口是无论通过什么方法请求 /a 这个接口,都返回数字 111,而 'get /name': '张三',
则是通过 get 请求 /name 时返回字符串 张三
。
以下是转换 api 为 openApi json 的实现:
toOpenApi(serverRouterList) {
const mapObj = {
all(item) {
return [
`delete`,
`get`,
`patch`,
`post`,
`put`,
].map(method => {
return {
...item,
method,
tags: item.tags || [`all-method`],
}
})
},
}
serverRouterList = serverRouterList.map(item => {
const fn = mapObj[item.method]
return fn ? fn(item) : [item]
}).flat()
return serverRouterList
}
serverRouterList 是格式化 api 后面的数据,包含 method route action alias 等,其中要生成接口文档,只需要有 method route 就足够了。
一个只包含此接口的 openApi json 生成后如下:
{
"openapi": "3.0.0",
"servers": [
{
"url": "/"
}
],
"tags": [],
"paths": {
"/a": {
"get": {
"parameters": [],
"responses": {}
}
}
}
}
看起来是简单吧?
然后把它交给 swagger-ui 即可渲染出文档,展现和使用都没有问题:
如何为接口分组、添加字段说明、数据验证等功能?
接口分组比较简单,直接为接口设置标签就行。由于在 mockm 中可以直接写字面量即可生成 api,但我们要为 api 添加额外说明的时候,就不能简单的使用字面量了。
为了兼容 mockm 的已有实现,我们扩展一个叫 side 的函数,用于收集额外信息和返回字面量。
"post /api/login": side({
tags: [`admin`],
summary: `根据用户名获取 token`,
schema: {
body: joi.object({
username: joi.string()
.default(`李蕾`)
.required()
.description(`用户名`),
}).description(`用户信息`),
},
action (req, res) {
const { username } = req.body
res.json({
status: 200,
message: `欢迎 ${username}, 登录成功`,
token: `tokentoken`,
});
}
}),
可以看到,我们在 side 函数中,为 api 进行了分组,并且为 api 添加了描述,然后为 api 的每个字段添加了说明,是否必填,数字类型是什么等。
这时候可能有一个疑问,在前面提到了使用注释编写接口文档信息,而这里则是使用代码编写接口信息,他们之前的区别在哪里?
- 区别 1 使用注释编写时没有语法提示、代码完成、代码高亮等功能,十分不方便
- 区别 2 使用注释编写时,实际上是在写原始 openapi yaml 格式的数据,这要求你对 openapi 规范有相当的了解和熟悉
- 区别 3 使用代码编写时,顺便可以完成业务上的其他功能,而注释和业务是割裂的,我们都知道业务更新但注释没更新的那种噩梦
观察上面的代码,我们使用 joi 来进行数据描述,与此同时 joi 也可以为我们实现数据的校验。
以下是文档生成效果,可以看到数据描述、默认值(示例值)、类型、是否必填等都可以正常显示:
以下是接口校验功能的效果,我们在接口中声明了 username 为 string,但我们尝试传入数字 111,结果按预期运行,自动提示了哪个字段错误了,为什么错:
功能插件化
对于使用 mockm 的群体中,有一些朋友可能并不想去真的用它来实现 api ,更别说还要为 api 生成文档,所以把此功能抽离为插件,当有一天 mockm 的功能繁复时,可以拆分为插件包单独在其他仓库进行维护:
module.exports = async (util) => {
const joi = await util.tool.generate.initPackge(`joi`);
return {
plugin: [util.plugin.validate, util.plugin.apiDoc],
};
};
游乐场
安装和初始化 mockm
yarn add mockm
npx mm --config
编写接口
在 mm.config.js 中录入以下信息
module.exports = async (util) => {
const joi = await util.tool.generate.initPackge(`joi`);
return {
plugin: [util.plugin.validate, util.plugin.apiDoc],
api: {
"post /api/login": util.side({
tags: [`admin`],
summary: `根据用户名获取 token`,
schema: {
body: joi
.object({
username: joi
.string()
.default(`李蕾`)
.required()
.description(`用户名`),
})
.description(`用户信息`),
},
async action(req, res) {
const { username } = req.body;
res.json({
status: 200,
message: `欢迎 ${username}, 登录成功`,
token: `tokentoken`,
});
},
}),
},
};
};
查看接口文档
浏览器打开 http://127.0.0.1:9000/doc
即可
一个基于 express 的框架。它可以快速生成 api 以及创造数据,开箱即用,便于部署。
github 地址:https://github.com/wll8/mockm,求 star 。