上一篇介紹瞭如何使用寥寥幾行代碼就實現 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 實戰教程,更多文章敬請關注。
參考資料: