前言
管道
- 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());
import { IsNotEmpty, IsString } from 'class-validator';
export class CreateUserTdo {
// 定义多个校验的装饰器,执行顺序是从下往上执行的,先执行IsNotEmpty然后执行IsString
@IsString({ message: '用户名必须为字符串类型' })
@IsNotEmpty({ message: '用户名不能为空' })
username: string;
@IsString({ message: '密码必须为字符串类型' })
@IsNotEmpty({ message: '密码不能为空' })
password: string;
}
@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());