likes
comments
collection
share

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

作者站长头像
站长
· 阅读数 15

最近在学习神光大神的《Nest通关秘籍》,该小册主要包含下面这些内容:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式 想购买的可以点击《传送门》。

接下来的日子里,我将更新一系列的学习笔记。感兴趣的可以关注我的专栏《Nest 通关秘籍》学习总结

特别申明:本系列文章已经经过作者本人的允许。 大家也不要想着白嫖,我的笔记只是个人边学习边记录的,不是很完整,大家想要深入学习还是要自己去购买原版小册。

本章我们来学习如何通过nest中的Pipe来对参数进行校验。

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

Pipe 是在参数传给 handler 之前对参数做一些验证和转换的 class。

内置的 Pipe 有这些:

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • DefaultValuePipe
  • ParseEnumPipe
  • ParseFloatPipe
  • ParseFilePipe

它们都实现了 PipeTransform 接口:

1. ParseIntPipe

import { Controller, Get, ParseIntPipe, Query } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(@Query('a', ParseIntPipe) aa): string {
    return aa + 1;
  }
}

当我们传入的参数是int类型的时候,会正常返回,如果不是,会报错:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

这里我们还可以指定错误状态码:

import {
  Controller,
  Get,
  HttpStatus,
  ParseIntPipe,
  Query,
} from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(
    @Query(
      'a',
      new ParseIntPipe({
        errorHttpStatusCode: HttpStatus.NOT_FOUND,
      }),
    )
    a,
  ): string {
    return a + 1;
  }
}

注意:需要new ParseIntPipe()

返回的错误码就会变成404了。

还可以抛一个异常出来,然后让 exception filter 处理:

import {
  Controller,
  Get,
  HttpException,
  HttpStatus,
  ParseIntPipe,
  Query,
} from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(
    @Query(
      'a',
      new ParseIntPipe({
        exceptionFactory: (msg) => {
          console.log(msg);
          throw new HttpException('xxx' + msg, HttpStatus.NOT_FOUND);
        },
      }),
    )
    a,
  ): string {
    return a + 1;
  }
}

可以看到,状态码和 message 都改了:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

也可以加个 @UseFilters 来使用自己的 exception filter 处理.

2. ParseFloatPipe

@Get('b')
getB(
  @Query('b', new ParseFloatPipe())
  a,
): string {
  return a + 1;
}

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

3. ParseBoolPipe

@Get('c')
getC(
  @Query('a', new ParseBoolPipe())
  a,
): string {
  return typeof a;
}

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

4. ParseArrayPipe

安装两个包:

pnpm install -D class-validator class-transformer
  • class-validator:用装饰器和非装饰器两种方式对 class 属性做验证的库
  • class-transformer:把普通对象转换为对应的 class 实例的包
@Get('d')
getD(
  @Query('a', new ParseArrayPipe({}))
  a: Array<number>,
) {
  return a.reduce((total, item) => total + item, 0);
}

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

发现它并没有转化成Number类型,所以返回了0123

这时候就需要用 new XxxPipe 的方式传入参数了:

@Get('d')
getD(
  @Query(
    'a',
    new ParseArrayPipe({
      items: Number,
    }),
  )
  a: Array<number>,
) {
  return a.reduce((total, item) => total + item, 0);
}

这时候就能得到正确的结果了:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式此外,你还可以指定分隔符:

@Get('f')
getF(
  @Query(
    'a',
    new ParseArrayPipe({
      separator: '..',
    }),
  )
  a: Array<string>,
) {
  return a;
}

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

如果不传参的时候会报错,可以设置为optional

@Get('f')
getF(
  @Query(
    'a',
    new ParseArrayPipe({
      separator: '..',
      optional: true,
    }),
  )
  a: Array<string>,
) {
  return a;
}

5. ParseEnumPipe

ParseEnumPipe的使用场景是这样的:

限制参数的取值范围

先定义一个路径的枚举类型:

enum PATHENUM {
  AAA = '111',
  BBB = '222',
  CCC = '333',
}
@Get('g/:enum')
getG(
  @Param('enum', new ParseEnumPipe(PATHENUM))
  e: PATHENUM,
) {
  return e;
}

当我们访问PATHENUM中的值的时候正常返回,如果我们访问一个http://127.0.0.1:3000/g/444,就会报下面的错误:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

6. ParseUUIDPipe

UUID 是一种随机生成的几乎不可能重复的字符串,可以用来做 id。

我们先来生成一个UUID

// eslint-disable-next-line @typescript-eslint/no-var-requires
const uuid = require('uuid');

console.log(uuid.v4());

这样会生成一个v4的uuid

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

然后我们使用ParseUUIDPipe来校验一下:

@Get('h/:uuid')
getH(
  @Param('uuid', new ParseUUIDPipe())
  uuid: string,
) {
  return uuid;
}

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

如果是错误的UUID,会报错:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

7. DefaultValuePipe

这个是设置参数默认值的,当你没传参数的时候,会使用默认值:

@Get('i')
getI(
  @Query('iii', new DefaultValuePipe('123'))
  iii: string,
) {
  return iii;
}

无参数:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

有参数:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

以上都是Get参数。如果用POST参数,阁下应该如何应对?

8. ValidationPipe

post 请求的数据是通过 @Body 装饰器来取,并且要有一个 dto class 来接收,

export class CreateAaaDto {
  name: string;
}
import {
  Controller,
  Post,
} from '@nestjs/common';
import { AaaService } from './aaa.service';
import { CreateAaaDto } from './dto/create-aaa.dto';

@Controller('aaa')
export class AaaController {
  constructor(private readonly aaaService: AaaService) {}

  @Post()
  create(@Body() createAaaDto: CreateAaaDto) {
    console.log(createAaaDto);
  }
}

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

可以看到正常接收到了参数。当我们在Body中接受的参数类型各种各样的时候,就需要对这些参数进行验证,也就是Validationpipe

它需要依赖下面两个包:

pnpm install -D class-validator class-transformer

然后在 @Body 里添加这个Validationpipe

import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  ValidationPipe,
} from '@nestjs/common';
import { AaaService } from './aaa.service';
import { CreateAaaDto } from './dto/create-aaa.dto';

@Controller('aaa')
export class AaaController {
  constructor(private readonly aaaService: AaaService) {}

  @Post()
  create(@Body(new ValidationPipe()) createAaaDto: CreateAaaDto) {
    console.log(createAaaDto);
  }
}

在 dto 这里,用 class-validator 包的 @IsInt 装饰器标记一下:

import { IsInt } from 'class-validator';
export class CreateAaaDto {
  name: string;
  @IsInt()
  age: number;
}

现在当我们把age的参数传成非number类型的,就会报错了:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

如果传成正确的类型,就会正常返回了:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

class-validator 都支持的验证方式:

import { Contains, IsDate, IsEmail, IsFQDN, IsInt, Length, Max, Min } from 'class-validator';

export class Ppp {
    @Length(10, 20)
    title: string;
  
    @Contains('hello')
    text: string;
  
    @IsInt()
    @Min(0)
    @Max(10)
    rating: number;
  
    @IsEmail()
    email: string;
  
    @IsFQDN()
    site: string;
}

大家可以自己去一一验证。

9. ParseFilePipe

在讲ParseFilePipe之前,我们先来做个文件上传的功能。

我们需要实现单文件和多文件上传的功能。

首先需要安装一个 multer 的 ts 类型的包:

pnpm install -D @types/multer

添加一个文件上传的handler方法:

import {
  Controller,
  Post,
  Body,
  UseInterceptors,
  UploadedFile,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { AaaService } from './aaa.service';
import { CreateAaaDto } from './dto/create-aaa.dto';

@Controller('aaa')
export class AaaController {
  constructor(private readonly aaaService: AaaService) {}

  @Post('file')
  @UseInterceptors(
    FileInterceptor('aaa', {
      dest: 'uploads',
    }),
  )
  uploadFile(@UploadedFile() file: Express.Multer.File, @Body() body) {
    console.log('body', body);
    console.log('file', file);
  }
}

使用 FileInterceptor 来提取 aaa 字段,然后通过 UploadedFile 装饰器把它作为参数传入。

因为我们用 nest start --watch 跑的,一保存,就可以看到uploads目录被创建了。

为了支持跨域请求,我们在main.ts中添加配置:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    cors: true,
  });
  await app.listen(3000);
}
bootstrap();

在创建一个public目录,用来放index.html,内容如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
  </head>
  <body>
    <input id="fileInput" type="file" multiple />
    <script>
      const fileInput = document.querySelector('#fileInput');

      async function formData() {
        const data = new FormData();
        data.set('name', '朽木白');
        data.set('age', 18);
        data.set('aaa', fileInput.files[0]);

        const res = await axios.post('http://localhost:3000/aaa/file', data);
        console.log(res);
      }

      fileInput.onchange = formData;
    </script>
  </body>
</html>

然后在这个目录下运行命令:

npx http-server

在浏览器打开http://127.0.0.1:8080/

然后上传一个文件:

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

可以看到文件已经成功上传到uploads目录下了。

接下来我们需要对文件的大小和文件的类型进行校验,这里就需要使用pipe了,文件校验常用的有两个:MaxFileSizeValidator FileTypeValidator****

@Post('file')
@UseInterceptors(
  FileInterceptor('aaa', {
    dest: 'uploads',
  }),
)
uploadFile(
  @UploadedFile(
    new ParseFilePipe({
      validators: [
        new MaxFileSizeValidator({ maxSize: 1000 }),
        new FileTypeValidator({ fileType: 'image/jpeg' }),
      ],
    }),
  )
  file: Express.Multer.File,
  @Body() body,
) {
  console.log('body', body);
  console.log('file', file);
}

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

神光《Nest 通关秘籍》学习总结-9种常见的Pipe参数校验方式

可以看到抛出错误了已经。