上一篇介绍了如何使用寥寥几行代码就实现 RBAC 0,解决了权限管理的痛点,这篇将解决另外一个痛点:写文档。html
上家公司在恒大的时候,项目的后端文档使用 Swagger UI 来展现,这是一个遵循 RESTful API 的、 能够互动的文档,所见即所得。前端
而后进入了目前的公司,接口文档是用 Markdown 写的,并保存在 SVN 上,每次接口修改,都要更新文档,并同步到 SVN,而后前端再拉下来更新。git
这些都还好,以前还有直接丢个 .doc 文档过来的。。。。github
之前我总吐槽后端太懒,文档都不肯更新,直到本身写后端时,嗯,真香。。。因而,为了避免耽误摸鱼时间,寻找一个趁手的文档工具,就提上日程了。typescript
GitHub 项目地址,欢迎各位大佬 Star。express
怎样用通俗的语言解释 REST,以及 RESTful ? - 覃超的回答 - 知乎json
$ yarn add @nestjs/swagger swagger-ui-express -S
复制代码
安装完依赖包后,只须要在 main.ts 中引入,并设置一些基本信息便可:bootstrap
// 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);
app.use(express.json()); // For parsing application/json
app.use(express.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded
// 监听全部的请求路由,并打印日志
app.use(logger);
// 使用拦截器打印出参
app.useGlobalInterceptors(new TransformInterceptor());
app.setGlobalPrefix('nest-zero-to-one');
app.useGlobalFilters(new AllExceptionsFilter());
app.useGlobalFilters(new HttpExceptionFilter());
// 配置 Swagger
const options = new DocumentBuilder()
.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();
复制代码
接下来,咱们访问 localhost:3000/api-doc/#/
(假设你的端口是 3000),不出意外,会看到下图:后端
这就是 Swagger UI,页面列出了咱们以前写的 Router
和 DTO
(即图中的 Schemas)api
点开 RegisterInfoDTO
,发现里面是空的,接下来,咱们配置一下参数信息,在 user.dto.ts
中引入 ApiProperty
,而后添加到以前的 class-validator
上:
// 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;
}
复制代码
保存,刷新页面(该页面没有热加载功能),再看看效果:
看到已经有了字段信息了,可是咱们的 role
字段是【可选】的,而文档中是【必填】的,接下来再完善一下描述:
// src/logical/user/user.dto.ts
@ApiProperty({
required: false,
description: '[用户角色]: 0-超级管理员 | 1-管理员 | 2-开发&测试&运营 | 3-普通用户(只能查看)',
})
readonly role?: number | string;
复制代码
其实,咱们可使用 ApiPropertyOptional
装饰器来表示【可选】参数,这样就不用频繁写 required: false
了:
// src/logical/user/user.dto.ts
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class RegisterInfoDTO {
...
@ApiPropertyOptional({
description: '[用户角色]: 0-超级管理员 | 1-管理员 | 2-开发&测试&运营 | 3-普通用户(只能查看)',
})
readonly role?: number | string;
}
复制代码
经过前面的截图能够看到,全部的接口都在 Default 栏目下,接口多了以后,就很不方便查找了。
咱们能够根据 Controller 来分类,添加装饰器 @ApiTags
便可:
// 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);
}
}
复制代码
保存再刷新一下页面,看到用户相关的都在一个栏目下了:
接下来,咱们测试一下注册接口的请求,先编辑参数,而后点击 Execute:
而后看一下返回参数:
看到返回的是 401 未登陆。
那么,如何在 Swagger 中登陆呢?
咱们先完善登陆接口的 DTO:
// src/logical/user/user.dto.ts
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class LoginDTO {
@ApiProperty()
@IsNotEmpty({ message: '用户名不能为空' })
readonly username: string;
@ApiProperty()
@IsNotEmpty({ message: '密码不能为空' })
readonly password: string;
}
export class RegisterInfoDTO {
...
}
复制代码
而后在 main.ts
中加上 addBearerAuth()
方法,启用承载受权
// 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 中添加 @ApiBearerAuth()
装饰器便可,顺便把登陆的 DTO 也加上:
// 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);
}
}
复制代码
而后,咱们去页面中登陆:
将 Responses body
中的 token
复制出来,而后将页面拖到顶部,点击右上角那个带锁的按钮:
将 token 复制到弹窗的输入框,点击 Authorize
,便可受权成功:
注意:这里显示的受权
Value
是密文,也就是,若是你复制错了,或者 token 过时了,也不会有任何提示。
如今,咱们再从新请求一下注册接口:
成功!
前面登陆的时候,须要手动输入用户名、密码,那么有没有可能,事先写好,这样前端来看文档的时候,直接用默认帐号登陆就好了呢?
咱们先给 DTO 加点料:
// src/logical/user/user.dto.ts
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
// @ApiExtraModels(LoginDTO)
export class LoginDTO {
@ApiProperty({ description: '用户名', example: 'koa2', })
@IsNotEmpty({ message: '用户名不能为空' })
readonly username: string;
@ApiProperty({ description: '密码', example: 'a123456' })
@IsNotEmpty({ message: '密码不能为空' })
readonly password: string;
}
export class RegisterInfoDTO {
...
}
复制代码
而后,去 Controller 中引入 ApiBody
, 并用来装饰接口,type 直接指定 LoginDTO
便可:
// 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);
}
}
复制代码
保存代码,再刷新一下页面:
而且点击 Schema
的时候,还能看到 DTO 详情:
再点击 try it out
按钮的时候,就会自动使用默认参数了:
本篇介绍了如何使用 Swagger 自动生成可互动的文档。
能够看到,咱们只需在写代码的时候,加一些装饰器,并配置一些属性,就能够在 Swagger UI 中生成文档,而且这个文档是根据代码,实时更新的。查看文档,只需访问连接便可,不用再传来传去了,你好我好你们好。
本篇只是抛砖引玉, Swagger UI 还有不少可配置的玩法,好比数组应该怎么写,枚举应该怎么弄,如何设置请求头等等,由于篇幅缘由,就不在这里展开了。有兴趣的同窗,能够自行去官网了解~
本篇收录于NestJS 实战教程,更多文章敬请关注。
参考资料: