6、Nest.js 中的管道与验证器
什么是管道(pipe)?
管道就是一个实现了 PipeTransform 接口并用 @Injectable() 装饰器修饰的类。
管道的作用简单来说就是,可以将输入的数据处理过后输出。
我们在前面的例子中将参数验证的逻辑写在了控制器中,这就打破了单一责任原则,控制器只应该处理请求的分发,验证的逻辑应该让验证器来做。
在 Nest 中正确的做法是,使用管道验证器,改写我们的 findOne 如下:
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id): Promise<User> {
return await this.usersService.findOne(id);
}
我们使用了 @nestjs/common 包中内置的一个管道 ParseIntPipe,它将一个字符串类型的数据转换成一个 int 类型,如果失败则抛出异常,现在访问 http://127.0.0.1:3000/users/exception :
{
"statusCode":400,
"date":"2018-7-31",
"path":"/users/exception"
}
我们自定义的错误码又没了!这可不是我们想要的结果,看来还是需要自己定义管道验证器。
现在新建一个管道:
$ nest g pi users/pipes/user-id
CREATE /src/users/pipes/user-id/user-id.pipe.ts (216 bytes)
去掉 user-id/ 这一层目录,默认生成的管道使用的还是老版本的 @Pipe 装饰器, Nest 5.0 已经修改为 @Injectable 装饰器了,所以我们修改生成的代码再给它加上两行打印语句,如下:
import { ArgumentMetadata, PipeTransform, Injectable } from '@nestjs/common';
@Injectable()
export class UserIdPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
console.log(value);
console.log(metadata);
return value;
}
}
PipeTransfrom 接口就只有一个 transfrom 方法, 其中的 value 参数就是我们需要处理的数据源,而 ArgumentMetadata 就是这个对象的一些 元数据(metadate),它的接口定义是下面这样的:
export interface ArgumentMetadata {
type: 'body' | 'query' | 'param' | 'custom';
metatype?: new (...args) => any;
data?: string;
}
这里直接引用官方文档的说明:
image.png
TypeScript接口在编译期间消失,所以如果你使用接口而不是类,那么元类型的值将是一个 Object。
现在让我们的管道工作起来,修改 findOne 如下:
@Get(':id')
async findOne(@Param('id', new UserIdPipe()) id): Promise<User> {
return await this.usersService.findOne(id);
}
访问 http://127.0.0.1:3000/users/exception, 看到控制台输出:
exception
{ metatype: [Function: Object], type: 'param', data: 'id' }
我们已经在管道中拿到了参数的值和元数据,我们可以写自己的验证逻辑然后在验证失败的时候让他抛出 ApiException:
import { ArgumentMetadata, PipeTransform, Injectable, HttpStatus } from '@nestjs/common';
import { ApiException } from 'common/exceptions/api.exception';
import { ApiErrorCode } from 'common/enums/api-error-code.enum';
@Injectable()
export class UserIdPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
value = parseInt(value)
if(isNaN(value) || typeof value !== 'number' || value <= 0) {
throw new ApiException('用户ID无效', ApiErrorCode.USER_ID_INVALID, HttpStatus.BAD_REQUEST);
}
return value;
}
}
访问 http://127.0.0.1:3000/users/exception,发现我们的管道工作的很好,我们自定义的业务状态码也回来了啦:
{
"errorCode":10001,
"errorMessage":"用户ID无效",
"date":"2018-8-1",
"path":"/users/exception"
}
本篇只介绍管道最基本的用法,在后面我们会循序渐进的学习管道的高级用法。