如何在TypeScript DTO(结合class-validator)中使用MongoDB查询运算符?
解决MongoDB查询运算符在DTO中的使用限制问题
问题背景
现有QueryUserDto代码如下:
import { ApiHideProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsEmpty, IsEnum, IsMongoId, IsOptional, IsString, } from 'class-validator'; import { QueryModifierDto } from 'src/common/dtos/query-modifier.dto'; export class QueryUserDto extends QueryModifierDto { @IsOptional() @IsMongoId() _id?: string; @IsOptional() @IsString() firstName?: string; @IsOptional() @IsString() lastName?: string; @IsOptional() @Transform(({ value }) => value.toLowerCase()) @IsString() email?: string; @IsOptional() @IsEnum(['admin', 'user'], { message: 'Role must be either admin or user' }) role?: string; @ApiHideProperty() @IsOptional() @IsEmpty() tokens?: string[]; }
该DTO无法支持包含MongoDB查询运算符的查询,例如:
const user = await this.usersService.findOne({ tokens: { $in: [payload.token] }, });
需求是允许使用任意合法的MongoDB查询运算符,同时不影响客户端发起的常规查询。参考的QueryModifierDto代码:
import { IsNumber, IsOptional, IsPositive, IsString } from 'class-validator'; export class QueryModifierDto { @IsOptional() @IsPositive() @IsNumber() page?: number; @IsOptional() @IsPositive() @IsNumber() limit?: number; @IsOptional() @IsString() fields?: string; @IsOptional() @IsString() sort?: string; }
解决方案
核心思路是给需要支持运算符的字段定义混合类型,通过条件验证区分普通值和运算符对象,既保留原有客户端查询的验证逻辑,又允许服务层使用MongoDB查询语法。
1. 修改字段验证规则
以tokens字段为例,调整验证逻辑,同时支持数组和MongoDB运算符对象:
import { ApiHideProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsEnum, IsMongoId, IsOptional, IsString, IsArray, ValidateIf, IsObject, } from 'class-validator'; import { QueryModifierDto } from 'src/common/dtos/query-modifier.dto'; export class QueryUserDto extends QueryModifierDto { @IsOptional() @IsMongoId() _id?: string; @IsOptional() @IsString() firstName?: string; @IsOptional() @IsString() lastName?: string; @IsOptional() @Transform(({ value }) => { // 仅对普通字符串转小写,运算符对象不处理 return typeof value === 'string' ? value.toLowerCase() : value; }) @ValidateIf((obj, value) => typeof value === 'string') @IsString() @ValidateIf((obj, value) => typeof value === 'object' && !Array.isArray(value)) @IsObject() email?: string | Record<string, any>; @IsOptional() @IsEnum(['admin', 'user'], { message: 'Role must be either admin or user' }) role?: string; @ApiHideProperty() @IsOptional() // 条件验证:非对象时验证为数组,对象时验证为合法查询结构 @ValidateIf((obj, value) => typeof value !== 'object' || Array.isArray(value)) @IsArray() @ValidateIf((obj, value) => typeof value === 'object' && !Array.isArray(value)) @IsObject() tokens?: string[] | Record<string, any>; }
2. 服务层使用示例
修改后即可自由使用MongoDB查询运算符,同时客户端常规查询不受影响:
// 使用$in运算符查询 const user = await this.usersService.findOne({ tokens: { $in: [payload.token] }, }); // 客户端传入普通值的查询依然有效 const activeUsers = await this.usersService.find({ role: 'user', });
3. 类型安全优化(可选)
为了获得TypeScript类型提示,可以定义MongoDB查询运算符的类型:
// 定义常用查询运算符类型 type MongoQueryOperator<T> = { $eq?: T; $ne?: T; $in?: T[]; $nin?: T[]; $regex?: RegExp | string; // 按需添加其他运算符 }; // 字段类型替换为普通值或运算符对象 tokens?: string[] | MongoQueryOperator<string[]>; email?: string | MongoQueryOperator<string>;
内容的提问来源于stack exchange,提问作者Faizan Ashiq
相关产品推荐
相关产品推荐

