Skip to content

DTO

DTO 的作用就是验证数据类型

DTO 离不开管道,通过管道抛出异常,被全局过滤器捕获

前期准备

安装依赖

bash
pnpm i class-validator class-transformer -S

新建全局管道

  • 抛出异常,需要使用全局过滤器捕获
ts
import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  HttpException,
} from "@nestjs/common";

import { plainToInstance } from "class-transformer";

import { validate } from "class-validator";
@Injectable()
export class ValidationPipe implements PipeTransform {
  async transform(value: any, metadata: ArgumentMetadata) {
    const { metatype } = metadata;
    console.log(metatype);
    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]}`, 302);
        }
      }
      // 第二种返回所有
      let result = [];
      errors.forEach((item) => {
        for (let arr in item.constraints) {
          result.push({
            message: item.constraints[arr],
            field: item.property,
          });
        }
      });
      throw new HttpException(result, 302);
    }
    return object;
  }

  private toValidate(metatype: Function): boolean {
    // 白名单
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}
  • 不抛出异常

DTO 里面就得改写下

ts
import { IsNotEmpty, IsInt } from "class-validator";

import { CustomNotEmpty } from "./customdto";
export class LoginDto {
  @CustomNotEmpty()
  @IsNotEmpty({ message: "用户名不能为空" })
  username: String;

  @IsInt({ message: "密码只能是数字" })
  password: number;

  // 加一个属性用来判断是否验证通过
  status?: String;
}

管道里面改写

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

import { plainToInstance } from "class-transformer";

import { validate } from "class-validator";
@Injectable()
export class ValidationPipe implements PipeTransform {
  async transform(value: any, metadata: ArgumentMetadata) {
    const { metatype } = metadata;
    console.log(metatype);
    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]}`, 302);
          return {
            code: 200,
            status: "error",
            message: `${errors[0].constraints[arr]}`,
          };
        }
      }
      // 第二种返回所有
      let result = [];
      errors.forEach((item) => {
        for (let arr in item.constraints) {
          result.push({
            message: item.constraints[arr],
            field: item.property,
          });
        }
      });
      return {
        code: 200,
        status: "error",
        message: result,
      };
    }
    return object;
  }

  private toValidate(metatype: Function): boolean {
    // 白名单
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

新建全局过滤器

ts
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from "@nestjs/common";
import { Request, Response } from "express";

@Catch(HttpException)
export class GlobalExceptionFilter implements ExceptionFilter {
  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    // 状态码
    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;
    // 异常消息 要是多个的话 message只会返回httpexception,所以最好单个
    const message =
      exception instanceof HttpException ? exception.message : exception.stack;
    response.status(status).json({
      code: status,
      timestamp: new Date().toLocaleString(),
      path: request.url,
      message: message,
      response: exception.response,
    });

    response.status(status).json();
  }
}

绑定到全局

ts
import { Module } from "@nestjs/common";

import { LoginModule } from "./Login/login.module";

// 全局管道
import { ValidationPipe } from "./common/Pipe/global.pipe";

// 全局异常
import { GlobalExceptionFilter } from "./common/exception/global.filter";

// 类型
import { APP_FILTER, APP_PIPE } from "@nestjs/core";

@Module({
  imports: [LoginModule],
  controllers: [],
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
    {
      provide: APP_FILTER,
      useClass: GlobalExceptionFilter,
    },
  ],
})
export class AppModule {}

使用 DTO

新建 DTO 文件

起名叫 xxx.dto

  • 要验证的参数
ts
import { IsInt, IsNotEmpty, IsString, Max, Min } from "class-validator";
export class LoginDto {
  @Min(6, { message: "用户名不能小于6位" })
  @IsNotEmpty({ message: "用户名不能为空" })
  username: string;

  @IsNotEmpty({ message: "密码不能为空" })
  @Max(20, { message: "密码长度不能超过20位" })
  @Min(6, { message: "密码长度不能小于6位" })
  password: number;
}

控制器内使用

ts
//1.引入
import { LoginDto } from '../../common/dto/Login';
//2 使用loginDto
 @Get('test')
  getLogins(@Query() Login: LoginDto): Promise<string> {
    console.log('进入控制器了');
    if (loginDto.status == 'error') {
      return loginDto;
    } else {
      return this.loginService.getone();
    }
  }

警告

如果不想验证可以不加 Dto 类型,直接返回 any 即可