Skip to content

管道

管道是一个使用@Injectable()装饰器注解的类,它实现了PipeTransform接口

管道它要么不变地返回值,要么抛出一个异常

管道有两种典型的用例

  • 转换:将输入数据转换为所需的形式(例如:从字符串到整数)

  • 验证:评估输入数据,如果有效则简单的将其原样传递,否则抛出异常

在这两种情况下,管道都作用于控制器路由处理器正在处理的参数上.

Nest 在方法被调用之前插入管道

管道接收传递给方法的参数并对其进行操作.

任何转换或者验证操作都在此时发生,然后路由处理器使用任何(可能)已转换的参数被调用

Nest 自带了许多内置的管道,你可以直接使用

你也可以构建自己的定制管道

内建管道

  • ValidationPipe:验证输入数据,确保输入数据符合预期格式

  • ParseIntPipe:将输入数据转换为整数

  • ParseBoolPipe:将输入数据转换为布尔值

  • ParseFloatPipe:将输入数据转换为浮点数

  • ParseUUIDPipe:将输入数据转换为 UUID

  • ParseArrayPipe:将输入数据转换为数组

  • ParseEnumPipe:将输入数据转换为枚举

  • DefaultValuePipe:为输入数据设置默认值

  • ParseFilePipe:将输入数据转换为文件

  • 新建一个文件夹起名 pipe ,新建一个文件起名 validation.pipe.ts

ts
import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  HttpException,
} from "@nestjs/common";

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    console.log("value", value);
    console.log(metadata);
    if (value.length != 11) {
      throw new HttpException("手机号格式不正确", 401);
    }
    return value;
  }
}
  • 使用
ts
import { ValidationPipe } from '../pipe/validate.pipe';

  @Post(':id')
  async getGuandao2(
    @Param('id') id: number,
    @Body('phone', ValidationPipe) phone: number,
  ): Promise<string> {
    console.log('见来了啊哈哈哈');
    return 'guandao';
  }

通用验证器

安装

bash

pnpm i class-validator class-transformer -S

写 dto(验证规则)

新建一个文件夹起名 dto,新建一个文件起名 user-create.dto.ts

bash

import { IsInt, IsString } from 'class-validator';

export class UserCreateDto {
  @IsString({ message: '用户名必须是字符串' })
  username: string;
  @IsString({ message: '密码必须是字符串' })
  password: string;
  @IsString({ message: '邮箱必须是字符串' })
  email: string;
  @IsInt({ message: '年龄必须是整数' })
  age: number;
  message: string;
}

创建一个管道

  • (1)抛出异常版本不推荐 (接口会飘红)

新建一个文件夹起名 pipe,新建一个文件起名 user-create.pipe.ts

ts
import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  HttpException,
} from "@nestjs/common";
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
@Injectable()
export class GlobalPipe implements PipeTransform {
  // 校验类型
  private toValidate(metatype: Function): boolean {
    // 其他类型不验证
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
  async transform(value: any, metadata: ArgumentMetadata) {
    const { metatype } = metadata;

    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    // 变成对象,把规则和值 组合成验证的对象
    const object = plainToInstance(metatype, value);
    // 验证 errors 是个数组
    const errors = await validate(object);

    if (errors.length > 0) {
      // 第一种只返回第一个
      if (errors.length == 1) {
        for (let arr in errors[0].constraints) {
          throw new HttpException(`${errors[0].constraints[arr]}`, 403);
        }
      }

      // 第二种返回所有
      let result: { message: string; field: string }[] = [];
      errors.forEach((item) => {
        for (let arr in item.constraints) {
          result.push({
            message: item.constraints[arr],
            field: item.property,
          });
        }
      });
      throw new HttpException(JSON.stringify(result), 403);
    }
  }
}

不抛出异常

流程

管道是验证了,它抛出来一个自定义验证异常 但是异常设定状态码是 200.这样前端接口调用不会飘红

全局异常过滤器 捕获了这个异常 返回前端给的 code 是 403 这样前端知道了是验证失败

1.先创建一个自定义异常类

  • globalcheckparams.exception.ts
ts
export class GlobalCheckParamsException {
  message: string;
  constructor(message?) {
    this.message = message;
  }
}

2.创建一个过滤器

这个过滤器就用来捕捉我这个异常

  • globalcheck.filter.ts
ts
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpStatus,
} from "@nestjs/common";

import { GlobalCheckParamsException } from "./globalcheckparams.exception";

@Catch(GlobalCheckParamsException)
export class GlobalCheckExceptionsFilter implements ExceptionFilter {
  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    // 我这里设置死了 200 方便前端不用飘红
    const status = 200;
    // 错误内容
    const message =
      exception instanceof GlobalCheckParamsException
        ? exception.message
        : exception.stack;
    // 返回
    response.status(status).json({
      code: 403,
      timestamp: new Date().toLocaleString(),
      path: request.url,
      data: message,
      message: message,
      name: "验证拦截器",
    });
  }
}

3.把这个过滤器挂载到全局

  • app.module.ts
ts
import { GlobalCheckExceptionsFilter } from "./globalcheck.filter";
import { GlobalPipe } from "./global.pipe";
@Module({
  imports: [UserModule],
  controllers: [],
  providers: [
    {
      provide: APP_FILTER,
      useClass: GlobalCheckExceptionsFilter,
    },
    {
      provide: APP_PIPE,
      useClass: GlobalPipe,
    },
  ],
})
export class AppModule {}

4.使用

  • 全局管道 global.pipe 验证参数类型
ts
import { PipeTransform, Injectable, ArgumentMetadata } from "@nestjs/common";
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
// 引入我自己验证的异常类
import { GlobalCheckParamsException } from "./globalcheckparams.exception";
@Injectable()
export class GlobalPipe implements PipeTransform {
  // 校验类型
  private toValidate(metatype: Function): boolean {
    // 其他类型不验证
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
  async transform(value: any, metadata: ArgumentMetadata) {
    const { metatype } = metadata;

    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    // 变成对象,把规则和值 组合成验证的对象
    const object = plainToInstance(metatype, value);
    // 验证 errors 是个数组
    const errors = await validate(object);

    if (errors.length > 0) {
      // 第一种只返回第一个
      if (errors.length == 1) {
        for (let arr in errors[0].constraints) {
          throw new GlobalCheckParamsException(`${errors[0].constraints[arr]}`);
        }
      }

      // 第二种返回所有
      let result: { message: string; field: string }[] = [];
      errors.forEach((item) => {
        for (let arr in item.constraints) {
          result.push({
            message: item.constraints[arr],
            field: item.property,
          });
        }
      });
      throw new GlobalCheckParamsException(JSON.stringify(result));
    }
  }
}

全局管道(全局使用)

  • app.module.ts
ts
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import { CoreModule } from "./core/core.module";
import { TaskModule } from "./task/task.module";
import { APP_FILTER, APP_PIPE } from "@nestjs/core";
import { HttpExceptionFilter } from "./task/filter/HttpException.filter";
import { ValidationPipe2 } from "./task/pipe/validate2.pipe";
@Module({
  imports: [CoreModule, TaskModule],
  controllers: [],
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
    {
      provide: APP_PIPE,
      useClass: ValidationPipe2,
    },
  ],
})
// 模块实现 NestModule 接口
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    // 限定路由
    consumer.apply().forRoutes("*");
  }
}