node实战 == koa-swagger-decorator 编写接口文档

53 阅读7分钟

node实战 == koa-swagger-decorator 编写接口文档

目录

[TOC]

前言

因为看官网的例子,报错后:

开始尝试百度、谷歌搜索、掘金、甚至连腾讯课堂、慕课网、群里都搜索咨询过,仍然没有任何回应,正当我快要放弃的时候,我从百度搜索看到很后的文章,看到Git有一篇叫 Koa2-Swagger 服务器脚手架 ,看介绍有 koa-swagger-decorator 的字眼,一言不合就下载下来看看,终于皇天不负有心人,找到正确的写法了。

注意的几点 下文会展开说

  • 必须使用 SwaggerRouter 替换 koa-router  ,否则路由404
  • 必须注册router以及注册装饰器所在控制器文件 ,否则文档无内容
  • 控制器文件必须 导出一个类 ,否则文档无内容
  • 必须利用 @babel进行编译,否则语法不支持

install

npm i koa-swagger-decorator --save-dev

配置 Babel 兼容装饰器

Babel是一个流行的JavaScript编译器,它允许开发者使用最新的JavaScript特性,同时确保代码可以在旧版环境中运行。如何使用Babel的配置文件( .babelrc )来优化代码的兼容性,

Babel配置文件定义了如何转换代码,包括预设(presets)和插件(plugins)。预设是一组插件的集合,它们可以自动应用一系列的JavaScript语言特性转换。插件则是单独的转换规则,用于处理特定的语法或提案。

.babelrc配置文件解析
{
    "presets": [
        [
            "@babel/preset-env",
            {
                "targets": {
                    "node": "current"
                }
            }
        ]
    ],
    "plugins": [
        [
            "@babel/plugin-proposal-decorators",
            {
                "legacy": true
            }
        ]
    ]
}

Presets配置

  • "@babel/preset-env" :这是一个智能预设,它会根据你指定的目标环境自动选择需要的插件。在这个例子中,我们指定了 "node": "current" ,意味着Babel将转换代码以兼容当前版本的Node.js。

Plugins配置

  • "@babel/plugin-proposal-decorators" :这个插件用于将装饰器(decorators)提案转换为当前JavaScript版本可识别的形式。装饰器是一种特殊类型的声明,它能够被附加到类声明、方法或属性上。

配置选项

  • "legacy": true :这个选项指定使用旧版的装饰器语法。这是因为装饰器提案尚未被完全标准化,而这个选项允许我们在装饰器被完全标准化之前使用它们。
Babel依赖项详解

为了使用Babel,你需要在你的项目中安装一些核心依赖项。以下是这些依赖项的详细说明:

  • "@babel/cli :Babel的命令行工具,允许你从命令行运行Babel来转换代码。

  • "@babel/preset-env":  智能预设,用于自动选择需要的插件以支持不同的环境。

  • "@babel/core": Babel的核心库,负责处理代码的转换。

  • "@babel/plugin-proposal-decorators": 插件,用于转换装饰器语法。

  • "@babel/register": 这个库允许你通过Babel实时编译Node.js环境中的代码,而不是编译到文件系统中。这对于开发环境非常有用,因为它可以自动转换代码,而不需要手动编译。

安装和使用

下载依赖

npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/plugin-proposal-decorators @babel/register

项目启动之前先引入  babel

const { APP_PORT } = require("./config/config.default");
// 引入babel
require("@babel/register");
const app = require("./app");
//  测试 数据库连接
// const seq = require("./db/seq");
 
app.listen(APP_PORT, () => {
  console.log(`server is running on http://localhost:${APP_PORT}`);
});

引入SwaggerRouter

这里看下目录结构方法理解

注册路由与控制器文件内的装饰器类

因为网上好多文章说明不用单独注册路由,这里一定要注意

mapDir 扫描之后的文件确实会被注册为路由 所以理论上是不用注册的,但是如果使用mvc分层架构模式就需要注册路由, 扫描controller控制器文件 注册router文件下的路由

const { SwaggerRouter } = require("koa-swagger-decorator");
const fs = require("fs");
const path = require("path");
const router = new SwaggerRouter();
// 读取router 目录下的所有文件 注册路由
fs.readdirSync(path.resolve(__dirname, "../router/")).forEach((file) => {
  // 排除 index.js 文件和非路由文件
  if (file !== "index.js") {
    // 导入路由模块
    const r = require(`../router/${file}`);
    // 注册路由 (r.routes() 返回一个中间件)
    router.use(r.routes());
  }
});
// swagger docs avaliable at http://localhost:9999/swagger-html
router.swagger({
  title: "电商系统",
  description: "API DOC",
  version: "1.0.0",
});
// mapDir 注册注解 ( 注册controller 目录下的所有文件)
router.mapDir(path.resolve(__dirname, "../controller/"));
 
module.exports = router;
重新编写路由文件替换为 SwaggerRouter

这里要使用 SwaggerRouter 因为 Swagger集成了koa-router 所以使用同理

// 引入路由对象
// const Router = require("koa-router");
const { SwaggerRouter } = require("koa-swagger-decorator");
const userController = require("../controller/user.controller");
const { getCaptcha } = new userController();
 
const router = new SwaggerRouter({ prefix: "/users" });
 
// 获取验证码接口
router.get("/captcha", getCaptcha);
 
module.exports = router;

为啥要注册路由?

因为一个路由可能有好多路由中间件需要处理流转


koa-swagger-decorator  可能以装饰器的方式兼容了路由中间件,所以网上大部分说不用注册的原因吧,但是这样对于 已有项目改造成本太大,

但是作者这样让 mapDir 注册一次 router 注册一次观察到路由表会有重复 但是路由中间件以及路由处理可以正常被访问,暂时没发现问题 ,如果大家发现问题可以留言讨论

// 发送邮箱验证码
router.post("/emailcode", userValidator("emailCode"), sendEmailCode);
app入口文件路由注册到app

注意这里依旧是SwaggerRouter 不要使用koa-router

const path = require("path");
const Koa = require("koa");
const { koaBody } = require("koa-body");
const koaStatic = require("koa-static");
const KoaParameter = require("koa-parameter");
const validate = require("koa-validate");
const cors = require("@koa/cors");
// const router = require("../router");
const router = require("../utils/swaggerDec");
const errHandler = require("./errHandler");
const loggerMiddleware = require("../middleware/logger.middleware");
// 引入rabbitmq 消费者
const { mailSubscribe } = require("../utils/rabbit-mq");
// 启动rabbitmq 消费者
(async () => {
  await mailSubscribe();
  console.log("rabbitmq 消费者启动成功");
})();
 
// 创建koa 实例
const app = new Koa();
 
// 注册日志中间件
app.use(loggerMiddleware);
 
// 注册 cors 中间件
app.use(
  cors({
    origin: "*",
    credentials: true,
  })
);
 
// 解析body
app.use(
  koaBody({
    multipart: true, // 支持文件上传(会挂载ctx.request.files)
    // 文件上传配置
    formidable: {
      uploadDir: path.join(__dirname, "../upload"), // 上传目录(不能使用相对路径,不会相对于当前路径,而是process.cwd()的执行路径)
      keepExtensions: true, // 保留文件扩展名
      maxFieldsSize: 5 * 1024 * 1024, // 限制文件大小为`5`MB
    },
    parsedMethods: ["POST", "PUT", "PATCH", "DELETE"], // 只解析这些方法的body
  })
);
// 静态资源
app.use(koaStatic(path.join(__dirname, "../upload")));
// 参数校验
app.use(KoaParameter(app));
validate(app);
 
// 注册路由 routes方法返回一个中间件注册所有的 (allowedMethods 处理404 500)
app.use(router.routes()).use(router.allowedMethods());
 
// 监听错误
app.on("error", errHandler);
 
module.exports = app;

生成文档

访问http://localhost:9999/swagger-html 这个预览文档

代码例子

注意一定要导出 的是class 否则文档无内容

1.request

这里request是必须的,它其实就是为你提供了router.get('/register', ()=>{ 需要执行的逻辑 });

上文讲过作者也注册了路由  但是这个依然是必须的否则文档无内容

2.summary 提供了一个头部的注释

3.description 提供较详细的接口描述

4.responses response返回结果的描述

5.body 提供请求参数到body

6.query 提供请求参数到query中

更多decorator请参考文档: github.com/Cody2333/ko…

const tag = ["user:用户模块"];
@prefix("/users") // 设置路由前缀
class UserController {
  @request("get", "/captcha")
  @summary("获取验证码")
  @tags(tag)
  @responses(
    createResponseSchema(200, "生成验证码成功", UserSwagger.captchaRes)
  )
  async getCaptcha(ctx, next) {
    // 1. 生成验证码
    let captcha = svgCaptcha.create({
      size: 4, // 验证码长度
      ignoreChars: "0o1i", // 验证码字符中排除 0o1i
      noise: 0, // 干扰线条数
      color: true, // 验证码字体颜色
      background: "#f0f0f0", // 验证码图片背景颜色
      width: 150, // 验证码图片宽度
      height: 50, // 验证码图片高度
    });
    // 2. 将验证码存入redis
    const captchaKey = `captcha:${uuidv4()}`;
    // 3. 保存到 redis 设置过期时间10分钟
    await Redis.set(captchaKey, captcha.text.toLowerCase(), 60 * 10);
    // 4. 返回结果
    ctx.body = {
      code: 0,
      message: "生成验证码成功",
      result: {
        captcha: captcha.data,
        captchaKey,
      },
    };
  }
  @request("post", "/sendEmailCode")
  @summary("发送邮箱验证码")
  @tags(tag)
  @body(UserSwagger.sendEmailCodeBody)
  @responses({
    ...createResponseSchema(200, "发送邮箱验证码成功"),
    ...createErrorResponseSchema(500, sendEmailCodeError),
  })
 
}
module.exports = UserController;
简单的二次封装

这里作者简单的做了二次封装 用起来更方便

// responseFactory.js
 
// 创建标准响应结构
const createResponseSchema = (statusCode, description, dataSchema = {}) => {
  const resultSchema =
    Object.keys(dataSchema).length > 0
      ? {
          type: "object",
          properties: dataSchema,
        }
      : {
          type: "string",
          example: "",
        };
 
  return {
    [statusCode]: {
      description,
      schema: {
        type: "object",
        properties: {
          code: { type: "string", example: "0" },
          message: { type: "string", example: "Success" },
          result: resultSchema,
        },
      },
    },
  };
};
 
// 创建错误响应结构
const createErrorResponseSchema = (statusCode, errorType) => {
  const { code, message } = errorType;
 
  return {
    [statusCode]: {
      description: message,
      schema: {
        type: "object",
        properties: {
          code: { type: "string", example: code },
          message: { type: "string", example: message },
          result: { type: "string", example: "" },
        },
      },
    },
  };
};
 
module.exports = {
  createResponseSchema,
  createErrorResponseSchema,
};

总结

koa-swagger-decorator 开箱即用的特点省去了我们 不少写decorator的工程,但这也造成了它的一个缺点:入侵性很强, 二次开发 变得不那么灵活。当然对于我们想要速成的项目来说还是挺方便的。

如果是新项目可以尝试不注册路由 直接使用

eg. @middlewares([func1,func2])

来使用路由中间件

大家知道写一个接口文档很麻烦的,是一个重复的工作,有人说,大家接到需求的时候就要写呀,要不怎么前后端分离开发,可是随着接口体系的越来越庞大,所有的接口就需要一个总的文档,并且后续如果需要改接口的话,前端也可以很快进行修改,方便维护。

希望大家能从文章中学到东西,有什么使用问题都可以随时在评论区留言