当前位置: 首页 > 工具软件 > nest-cli > 使用案例 >

【nest】nest学习笔记(六)

龙永福
2023-12-01

前言

  • 继续学习nest,本篇为管道和异常过滤器。

管道

  • https://docs.nestjs.com/pipes
  • 管道的主要作用
    对客户端传递的数据进行转换,依赖包class-transformer(需要自己安装)
    对客户端传递的数据进行校验,依赖包class-validator(需要自己安装)
  • Nestjs官方提供的几个内置管道(官网说的开箱即用)
ValidationPipe
ParseIntPipe
ParseBoolPipe
ParseArrayPipe
ParseUUIDPipe
DefaultValuePipe
import { ValidationPipe } from '@nestjs/common';
...
app.useGlobalPipes(new ValidationPipe());
  • 模块注入:
import {  ValidationPipe } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  ...
  providers: [
    ...
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    }
  ]
})
export class UserModule { }
  • 控制器中使用:
@Get(':uuid')
async findOne(@Param('uuid', new ParseUUIDPipe()) uuid: string) {
  return this.catsService.findOne(uuid);
}
  • 使用自定义管道
nest g pi pipes/validation/validation
  • 会创建这样一个文件:
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}
  • 修改下:
import {
  ArgumentMetadata,
  Injectable,
  PipeTransform,
  HttpException,
  HttpStatus,
  Logger,
} from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';

@Injectable()
export class MYValidationPipe implements PipeTransform {
  async transform(value: any, metadata: ArgumentMetadata) {
    console.log(metadata);
    const { metatype } = metadata;
    // 如果没有传入验证规则,则不验证,直接返回数据
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    // 将对象转换为 Class 来验证
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    Logger.log(errors, 'validation.pipe处理');
    if (errors.length > 0) {
      //获取第一个错误并且直接返回
      const msg = Object.values(errors[0].constraints)[0];
      // 统一抛出异常
      throw new HttpException({ message: msg }, HttpStatus.OK);
    }
    return value;
  }

  private toValidate(metatype: any): boolean {
    const types = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}
  • 实际上这个metaType,就是后面要传入编写的验证规则。metaData里会像bodyparser一样规整传入格式,分配到对应对象。
  • 这里利用的就是classTransformer中会试着把对象变为其实例,metaType就是验证器的定义,而value就是普通对象,如果能成功变为其实例,则返回空数组,否则会有error产生,从而报错。
  • 然后再全局使用此管道:
 app.useGlobalPipes(new MYValidationPipe());
  • 编写dto验证规则:
import { IsNotEmpty, IsString } from 'class-validator';

export class CreateUserTdo {
  // 定义多个校验的装饰器,执行顺序是从下往上执行的,先执行IsNotEmpty然后执行IsString
  @IsString({ message: '用户名必须为字符串类型' })
  @IsNotEmpty({ message: '用户名不能为空' })
  username: string;

  @IsString({ message: '密码必须为字符串类型' })
  @IsNotEmpty({ message: '密码不能为空' })
  password: string;
}
  • 对createUser进行校验:
  @Post()
  async createUser(@Body() data: CreateUserTdo): Promise<UserEntity> {
    return await this.userService.createUser(data);
  }
  • 发送空用户名请求去验证:
{
	"username": "",
	"password": "sasaddsads"
}
  • 返回:
{
  "message": "用户名不能为空"
}
  • 即生效。

管道拦截器统一数据格式

  • 这个地方踩了下坑,怪不得会有这种class-transformer转换来转换去的。类似于exclude这种排除需要在返回其类的实例的情况下,但是像service的方法如果调用的是repo保存,虽然类型上写返回实例,实际却并不是其实例,需要自行转换。
  • 生成拦截器:
nest g in interceptors/transform --no-spec
  • 全局使用下
  • 编写拦截器:
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data: any) => {
        return {
          result: data,
          code: 0,
          message: '请求成功',
        };
      }),
    );
  }
}
  • 对密码做排除:
import { Exclude } from 'class-transformer';
...
@Exclude() // 排除返回字段,不返回给前端
@Column({
  type: 'varchar',
  nullable: false,
  length: 100,
  comment: '密码'
})
password: string;
  • 最后进行验证,看是否返回密码。

异常过滤器

nest g f filters/httpException --no-spec  
  • 会生成这玩意:
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';

@Catch()
export class HttpExceptionFilter<T> implements ExceptionFilter {
  catch(exception: T, host: ArgumentsHost) {}
}
  • 定义全局错误返回格式:
import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
  HttpStatus,
  Logger,
} from '@nestjs/common';

@Catch()
export class HttpExceptionFilter<T> implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const message = exception.message;
    Logger.log(exception, '错误提示');
    const errorResponse = {
      status,
      message,
      code: 1, // 自定义code
      path: request.url, // 错误的url地址
      method: request.method, // 请求方式
      timestamp: new Date().toISOString(), // 错误的时间
    };
    // 打印日志
    Logger.error(
      `${request.method} ${request.url}`,
      JSON.stringify(errorResponse),
      'HttpExceptionFilter',
    );
    // 设置返回的状态码、请求头、发送错误信息
    response.status(status);
    response.header('Content-Type', 'application/json; charset=utf-8');
    response.send(errorResponse);
  }
}
  • 全局使用下:
app.useGlobalFilters(new HttpExceptionFilter());
  • 然后可以试验个未捕获错误,是否触发了此过滤器。
 类似资料: