後端問:作爲前端如何實現自動生成接口文檔

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

image.png

serverRouterList 是格式化 api 後面的數據,包含 method route action alias 等,其中要生成接口文檔,只需要有 method route 就足夠了。

一個只包含此接口的 openApi json 生成後如下:

{
  "openapi": "3.0.0",
  "servers": [
    {
      "url": "/"
    }
  ],
  "tags": [],
  "paths": {
    "/a": {
      "get": {
        "parameters": [],
        "responses": {}
      }
    }
  }
}

看起來是簡單吧?

然後把它交給 swagger-ui 即可渲染出文檔,展現和使用都沒有問題:

2023-08-16-11-54-08.png

如何爲接口分組、添加字段說明、數據驗證等功能?

接口分組比較簡單,直接爲接口設置標籤就行。由於在 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 也可以爲我們實現數據的校驗。

以下是文檔生成效果,可以看到數據描述、默認值(示例值)、類型、是否必填等都可以正常顯示:

2023-08-16-12-09-42.png

以下是接口校驗功能的效果,我們在接口中聲明瞭 username 爲 string,但我們嘗試傳入數字 111,結果按預期運行,自動提示了哪個字段錯誤了,爲什麼錯:

2023-08-16-12-14-42.png

功能插件化

對於使用 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 即可

2023-08-16-12-44-10.png

一個基於 express 的框架。它可以快速生成 api 以及創造數據,開箱即用,便於部署。

github 地址:https://github.com/wll8/mockm,求 star 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章