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])来使用路由中间件
大家知道写一个接口文档很麻烦的,是一个重复的工作,有人说,大家接到需求的时候就要写呀,要不怎么前后端分离开发,可是随着接口体系的越来越庞大,所有的接口就需要一个总的文档,并且后续如果需要改接口的话,前端也可以很快进行修改,方便维护。
希望大家能从文章中学到东西,有什么使用问题都可以随时在评论区留言