在NestJS 的 管道 学习文章中我们已经接触过 NestJs 中开箱即用的管道。而这篇文章主要是使用ValidationPipe
实现更高级的定制功能。
因为内置的ValidationPipe
管道内置使用了class-validator
和class-transformer
所以我们首先需要安装这两个库。
$ npm i --save class-validator class-transformer
ValidationPipe
依赖class-validator
和class-transformer
库,所以当我们创建此管道后可以设置如下配置参数,来做一些管道初始化的操作:
选项 | 类型 | 描述 |
---|---|---|
enableDebugMessages | boolean | 如果设置为 true,当出现问题时,验证器将向控制台打印额外的警告消息 |
skipUndefinedProperties | boolean | 如果设置为 true,则验证器将跳过验证对象中未定义的所有属性的验证 |
skipNullProperties | boolean | 如果设置为 true,则验证器将跳过验证对象中所有为 null 的属性的验证 |
skipMissingProperties | boolean | 如果设置为 true,则验证器将跳过验证对象中所有为 null 或未定义的属性的验证 |
whitelist | boolean | 如果设置为 true,验证器将删除已验证(返回)对象的任何不使用任何验证装饰器的属性 |
forbidNonWhitelisted | boolean | 如果设置为 true,验证器将抛出异常,而不是剥离非白名单属性 |
forbidUnknownValues | boolean | 如果设置为 true,尝试验证未知对象会立即失败 |
disableErrorMessages | boolean | 如果设置为 true,验证错误将不会返回给客户端 |
errorHttpStatusCode | number | 此设置允许您指定在发生错误时将使用哪种异常类型。默认情况下它会抛出 BadRequestException |
exceptionFactory | Function | 获取验证错误数组并返回要抛出的异常对象 |
groups | string[] | 验证对象期间要使用的组 |
always | boolean | 设置 always 装饰器选项的默认值。可以在装饰器选项中覆盖默认值 |
strictGroups | boolean | 如果 groups 未给出或为空,则忽略至少一组的装饰器 |
dismissDefaultMessages | boolean | 如果设置为 true,验证将不会使用默认消息。undefined 如果没有明确设置,错误消息总是会出现 |
validationError.target | boolean | 指示目标是否应暴露在 ValidationError |
validationError.value | boolean | 指示是否应在 中公开经过验证的值 ValidationError |
stopAtFirstError | boolean | 当设置为 true 时,给定属性的验证将在遇到第一个错误后停止。默认为 false |
我们将从 ValidationPipe 应用程序级别的绑定开始,从而确保所有端点都免受接收错误数据的影响。具体代码如下:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
我们添加了全局的校验器后,我们就可以在控制器中使用校验器,具体实例如下:
// 校验类
export class CreateDemoDto {
@IsString()
name: string;
@IsEmail()
email: string;
@IsNotEmpty()
password: string;
@IsNumber()
count: number;
}
// 控制器
@Post('/checkParams')
checkParams(@Body() createDemo: CreateDemoDto) {
return 'This action adds a new user';
}
当我们的请求参数为:
{
"name": "",
"email": "",
"password": "",
"count": 1
}
接口返回结果如下:
{
"statusCode": 400,
"message": ["email must be an email", "password should not be empty"],
"error": "Bad Request"
}
错误消息有助于解释请求中的错误内容。但是,某些生产环境更喜欢禁用详细错误。通过将选项对象传递给 ValidationPipe:
app.useGlobalPipes(
new ValidationPipe({
// 设置为true后,错误信息就不会在响应正文中显示
disableErrorMessages: true,
})
);
当我们发送上述的 JSON 请求参数后,接口返回的错误信息如下:
{
"statusCode": 400,
"message": "Bad Request"
}
ValidationPipe
还可以过滤掉不必要的属性,主要我们在ValidationPipe
中开启白名单就可以实现过滤属性功能。具体实例代码如下:
export class CreateDemoDto {
@IsString()
name: string;
@IsEmail()
email: string;
@IsNotEmpty()
password: string;
@IsNumber()
count: number;
}
请求参数为:
{
"name": "Tom",
"email": "[email protected]",
"password": "123456",
"count": 1,
"age": 12 // DTO中不存在该属性,参数接受不会显示该属性
}
上述我们只是开启了白名单,只是简单让不需要的属性过滤掉,我们还可以在存在非白名单属性时停止处理请求,并返回对应的错误信息,要开启此功能只要把forbidNonWhitelisted
属性设置为 true 即可。具体实例如下:
app.useGlobalPipes(
new ValidationPipe({
disableErrorMessages: true,
whitelist: true,
forbidNonWhitelisted: true,
})
);
通过网络传入的有效负载是纯 JavaScript 对象。可以 ValidationPipe 自动将请求参数转为对应的 DTO 对象。要启动自动转换,只要设置transform
属性为 true 即可。具体的代码如下:
// 方法级别开启转换
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
// 全局开启转换
app.useGlobalPipes(
new ValidationPipe({
transform: true,
}),
);
启用自动转换选项后,还将 ValidationPipe 执行基本类型的转换。在以下示例中,该 findOne()方法采用一个参数来表示提取的 id 路径参数,但是@Query()
是不支持自动转换对象的:
@Get(':id')
findOne(@Param('id') id: number) {
// 会自动把Id值转为数值
console.log(typeof id === 'number'); // true
return 'This action returns a user';
}
在上面的例子中,我们开启了自动转换功能来转换对应的参数类型,我们还可以使用管道来帮我们转换数据类型。具体实例如下:
@Get(':id')
findOne(
@Param('id', ParseIntPipe) id: number,
@Query('sort', ParseBoolPipe) sort: boolean,
) {
console.log(typeof id === 'number'); // true
console.log(typeof sort === 'boolean'); // true
return 'This action returns a user';
}
TypeScript 不存储有关泛型或接口的元数据,因此当您在 DTO 中使用它们时,ValidationPipe 可能无法正确验证传入数据。例如,在以下代码中,createUserDtos 将无法正确验证:
@Post()
createBulk(@Body() createUserDtos: CreateUserDto[]) {
return 'This action adds new users';
}
要验证数组,请创建一个专用类,其中包含包装数组的属性,或使用 ParseArrayPipe。
@Post()
createBulk(
@Body(new ParseArrayPipe({ items: CreateUserDto }))
createUserDtos: CreateUserDto[],
) {
return 'This action adds new users';
}
此外,ParseArrayPipe 在解析查询参数时可能会派上用场。让我们考虑一种 findByIds()根据作为查询参数传递的标识符返回用户的方法。
@Get()
findByIds(
// 请求参数为/?ids=1,2,3,通过ParseArrayPipe可以把ids解析成数组
@Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
ids: number[],
) {
return 'This action returns users by ids';
}
构建输入验证类型(也称为 DTO)时,在同一类型上构建创建和更新变体通常很有用。例如,创建变体可能需要所有字段,而更新变体可能使所有字段可选。
在 NestJS 中提供了PartialType()
的实用函数,使用此函数可以最大程度上减少样板代码和简化代码量。具体实例如下:
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
//
export class UpdateCatDto extends PartialType(CreateCatDto) {}
说明: PartialType 函数是需要安装@nestjs/mapped-types
插件包。
PartialType() 函数主要是在原先的类上选择需要继承的属性。具体实例如下:
export class CreateDemoDto {
@IsString()
name: string;
@IsEmail()
email: string;
@IsNotEmpty()
password: string;
@IsInt()
age: number;
}
// 选择继承age、name属性
export class UpdateDemoAgeDto extends PickType(CreateDemoDto, [
"age",
"name",
] as const) {}
OmitType() 函数主要是在原先的类上删除不需要的属性。具体实例如下:
// 在CreateDemoDto原先类的基础上剔除name属性
export class UpdateDemoExDto extends OmitType(CreateDemoDto, [
"name",
] as const) {}
IntersectionType() 函数两种类型组合成一种新类型(类)。具体实例如下:
export class AdditionalDemoInfo {
color: string;
}
// 将CreateDemoDto类和AdditionalDemoInfo类合并成UpdateDemoDto,这样UpdateDemoDto就具有CreateDemoDto和AdditionalDemoInfo的所有属性
export class UpdateDemoDto extends IntersectionType(
CreateDemoDto,
AdditionalDemoInfo
) {}