前言,衆所周知,後端的接口文檔一般使用 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 。