上一篇介绍了如何使用寥寥几行代码就实现 RBAC 0,解决了权限管理的痛点,这篇将解决另一个痛点:写文档。

上家公司在恒大的时候,项目的后端文档使用 Swagger UI 来展示,这是一个遵循 RESTful API 的、 可以互动的文档,所见即所得。

然后进入了目前的公司,接口文档是用 Markdown 写的,并保存在 SVN 上,每次接口修改,都要更新文档,并同步到 SVN,然后前端再拉下来更新。

这些都还好,之前还有直接丢个 .doc 文档过来的。。。。

以前我总吐槽后端太懒,文档都不愿更新,直到自己写后端时,嗯,真香。。。于是,为了不耽误摸鱼时间,寻找一个趁手的文档工具,就提上日程了。

GitHub 项目地址,欢迎各位大佬 Star。

怎样用通俗的语言解释 REST,以及 RESTful ? - 覃超的回答 - 知乎

安装完依赖包后,只需要在 main.ts 中引入,并设置一些基本信息即可:

接下来,我们访问 localhost:3000/api-doc/#/ (假设你的端口是 3000),不出意外,会看到下图:

这就是 Swagger UI,页面列出了我们之前写的 RouterDTO(即图中的 Schemas)

点开 RegisterInfoDTO,发现里面是空的,接下来,我们配置一下参数信息,在 user.dto.ts 中引入 ApiProperty,然后添加到之前的 class-validator 上:

  1. // src/logical/user/user.dto.ts
    import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
    import { ApiProperty } from '@nestjs/swagger';

    export class RegisterInfoDTO {
    @ApiProperty()
    @IsNotEmpty({ message: '用户名不能为空' })
    readonly accountName: string;
    @ApiProperty()
    @IsNotEmpty({ message: '真实姓名不能为空' })
    @IsString({ message: '真实姓名必须是 String 类型' })
    readonly realName: string;
    @ApiProperty()
    @IsNotEmpty({ message: '密码不能为空' })
    readonly password: string;
    @ApiProperty()
    @IsNotEmpty({ message: '重复密码不能为空' })
    readonly repassword: string;
    @ApiProperty()
    @IsNotEmpty({ message: '手机号不能为空' })
    @IsNumber()
    readonly mobile: number;
    @ApiProperty()
    readonly role?: string | number;
    }

保存,刷新页面(该页面没有热加载功能),再看看效果:

七、讨厌写文档,Swagger UI 了解一下? - 图1

看到已经有了字段信息了,但是我们的 字段是【可选】的,而文档中是【必填】的,接下来再完善一下描述:

其实,我们可以使用 ApiPropertyOptional 装饰器来表示【可选】参数,这样就不用频繁写 required: false 了:

    通过前面的截图可以看到,所有的接口都在 Default 栏目下,接口多了之后,就很不方便查找了。

    我们可以根据 Controller 来分类,添加装饰器 @ApiTags 即可:

    1. // src/logical/user/user.controller.ts
      import { Controller, Post, Body, UseGuards, UsePipes } from '@nestjs/common';
      import { AuthGuard } from '@nestjs/passport';
      import { AuthService } from '../auth/auth.service';
      import { UserService } from './user.service';
      import { ValidationPipe } from '../../pipe/validation.pipe';
      import { RegisterInfoDTO } from './user.dto';
      import { ApiTags } from '@nestjs/swagger';

      @ApiTags('user') // 添加 接口标签 装饰器
      @Controller('user')
      export class UserController {
      constructor(private readonly authService: AuthService, private readonly usersService: UserService) {}

      // JWT验证 - Step 1: 用户请求登录
      @Post('login')
      async login(@Body() loginParmas: any) {
      ...
      }

      @UseGuards(AuthGuard('jwt'))
      @UsePipes(new ValidationPipe())
      @Post('register')
      async register(@Body() body: RegisterInfoDTO) {
      return await this.usersService.register(body);
      }
      }

    七、讨厌写文档,Swagger UI 了解一下? - 图2

    接下来,我们测试一下注册接口的请求,先编辑参数,然后点击 Execute:

    然后看一下返回参数:

    七、讨厌写文档,Swagger UI 了解一下? - 图3

    看到返回的是 401 未登录。

    那么,如何在 Swagger 中登录呢?

    我们先完善登录接口的 DTO:

    然后在 main.ts 中加上 addBearerAuth() 方法,启用承载授权

    1. // src/main.ts
      import { NestFactory } from '@nestjs/core';
      import { AppModule } from './app.module';
      import * as express from 'express';
      import { logger } from './middleware/logger.middleware';
      import { TransformInterceptor } from './interceptor/transform.interceptor';
      import { HttpExceptionFilter } from './filter/http-exception.filter';
      import { AllExceptionsFilter } from './filter/any-exception.filter';
      import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

      async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      ...
      // 配置 Swagger
      const options = new DocumentBuilder()
      .addBearerAuth() // 开启 BearerAuth 授权认证
      .setTitle('Nest zero to one')
      .setDescription('The nest-zero-to-one API description')
      .setVersion('1.0')
      .addTag('test')
      .build();
      const document = SwaggerModule.createDocument(app, options);
      SwaggerModule.setup('api-doc', app, document);

      await app.listen(3000);
      }
      bootstrap();

    然后只需在 Controller 中添加 装饰器即可,顺便把登录的 DTO 也加上:

    1. // src/logical/user/user.controller.ts
      import { Controller, Post, Body, UseGuards, UsePipes } from '@nestjs/common';
      import { AuthGuard } from '@nestjs/passport';
      import { AuthService } from '../auth/auth.service';
      import { UserService } from './user.service';
      import { ValidationPipe } from '../../pipe/validation.pipe';
      import { LoginDTO, RegisterInfoDTO } from './user.dto';
      import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';

      @ApiBearerAuth() // Swagger 的 JWT 验证
      @ApiTags('user')
      @Controller('user')
      export class UserController {
      constructor(
      private readonly authService: AuthService,
      private readonly usersService: UserService,
      ) {}

      // JWT 验证 - Step 1: 用户请求登录
      @Post('login')
      async login(@Body() loginParmas: LoginDTO) {
      // console.log('JWT验证 - Step 1: 用户请求登录');
      const authResult = await this.authService.validateUser(
      loginParmas.username,
      loginParmas.password,
      );
      switch (authResult.code) {
      case 1:
      return this.authService.certificate(authResult.user);
      case 2:
      return {
      code: 600,
      msg: `账号或密码不正确`,
      };
      default:
      return {
      code: 600,
      msg: `查无此人`,
      };
      }
      }

      @UseGuards(AuthGuard('jwt'))
      @UsePipes(new ValidationPipe())
      @Post('register')
      async register(@Body() body: RegisterInfoDTO) {
      return await this.usersService.register(body);
      }
      }

    然后,我们去页面中登录:

    七、讨厌写文档,Swagger UI 了解一下? - 图4

    Responses body 中的 token 复制出来,然后将页面拖到顶部,点击右上角那个带锁的按钮:

    将 token 复制到弹窗的输入框,点击 Authorize,即可授权成功:

    七、讨厌写文档,Swagger UI 了解一下? - 图5

    七、讨厌写文档,Swagger UI 了解一下? - 图6

    成功!

    前面登录的时候,需要手动输入用户名、密码,那么有没有可能,事先写好,这样前端来看文档的时候,直接用默认账号登录就行了呢?

    我们先给 DTO 加点料:

    然后,去 Controller 中引入 ApiBody, 并用来装饰接口,type 直接指定 LoginDTO 即可:

    1. // src/logical/user/user.controller.ts
      import { Controller, Post, Body, UseGuards, UsePipes } from '@nestjs/common';
      import { AuthGuard } from '@nestjs/passport';
      import { AuthService } from '../auth/auth.service';
      import { UserService } from './user.service';
      import { ValidationPipe } from '../../pipe/validation.pipe';
      import { LoginDTO, RegisterInfoDTO } from './user.dto';
      import { ApiTags, ApiBearerAuth, ApiBody } from '@nestjs/swagger';
      @ApiBearerAuth()
      @ApiTags('user')
      @Controller('user')
      export class UserController {
      constructor(private readonly authService: AuthService, private readonly usersService: UserService) {}

      // JWT验证 - Step 1: 用户请求登录
      @Post('login')
      @ApiBody({
      description: '用户登录',
      type: LoginDTO,
      })
      async login(@Body() loginParmas: LoginDTO) {
      ...
      }

      @UseGuards(AuthGuard('jwt'))
      @UsePipes(new ValidationPipe())
      @Post('register')
      async register(@Body() body: RegisterInfoDTO) {
      return await this.usersService.register(body);
      }
      }

    保存代码,再刷新一下页面:

    并且点击 的时候,还能看到 DTO 详情:

    七、讨厌写文档,Swagger UI 了解一下? - 图7

    再点击 try it out 按钮的时候,就会自动使用默认参数了:

    本篇介绍了如何使用 Swagger 自动生成可互动的文档。

    可以看到,我们只需在写代码的时候,加一些装饰器,并配置一些属性,就可以在 Swagger UI 中生成文档,并且这个文档是根据代码,实时更新的。查看文档,只需访问链接即可,不用再传来传去了,你好我好大家好。

    本篇只是抛砖引玉, Swagger UI 还有很多可配置的玩法,比如数组应该怎么写,枚举应该怎么弄,如何设置请求头等等,因为篇幅原因,就不在这里展开了。有兴趣的同学,可以自行去官网了解~

    本篇收录于 NestJS 实战教程,更多文章敬请关注。

    参考资料:

    Nest 官网 - OpenAPI (Swagger)

    Swagger - OpenAPI Specification

    Swagger UI tutorial

    扫一扫关注上方公众号,拉学习群和答疑解惑

    • 本文作者: 图雀社区
    • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!