如何为NestJS v10构建兼容文件上传的自定义Zod验证管道
NestJS v10 自定义Zod验证器兼容文件上传解决方案
问题诊断
当前实现中attachment返回undefined的核心原因:
- 全局绑定的
ZodValidationPipe会处理所有请求参数,当参数类型不是ZodDtoClass时,代码执行if (!schemaClass.schema) return;直接返回undefined,覆盖了@UploadedFile获取到的文件对象。 - 验证器返回
{ data: result.data }的嵌套结构,导致DTO实例不符合常规使用习惯。
解决方案
1. 修复ZodValidationPipe逻辑
修改验证器,仅处理ZodDtoClass类型的参数,其他参数直接返回原始值:
// zod-validation.pipe.ts import { Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; import { ZodDtoClass } from '../utils/zod-dto.interface'; @Injectable() export class ZodValidationPipe { transform(value: unknown, metadata: ArgumentMetadata) { // 仅处理带有schema静态属性的DTO类 if (!metadata.metatype || !('schema' in metadata.metatype)) { return value; } const schemaClass = metadata.metatype as typeof ZodDtoClass; if (!schemaClass.schema) { return value; } const result = schemaClass.schema.safeParse(value); if (!result.success) { throw new BadRequestException(result.error.flatten()); } // 直接返回验证后的数据,取消嵌套的data结构 return result.data; } }
2. 完善ZodDtoClass抽象类
定义标准的抽象类约束DTO结构,支持类型推断:
// zod-dto.interface.ts import { z, ZodSchema } from 'zod'; export abstract class ZodDtoClass<T extends ZodSchema> { static schema: ZodSchema; constructor(data: z.infer<T>) { Object.assign(this, data); } }
3. 调整DTO实现
补充属性声明以获得完整的类型提示:
// create-feed.dto.ts import { z } from 'zod'; import { ZodDtoClass } from '../../utils/zod-dto.interface'; const schema = z .object({ title: z.string().min(5).max(35).nullish(), body: z.string().max(140).nullish(), }) .refine( (d) => { return !!(d.title || d.body); }, { path: ['title'], message: "标题、正文和附件不能同时为空", }, ); export class CreateFeedDto extends ZodDtoClass<typeof schema> { static override schema = schema; // 显式声明属性,提升IDE类型提示 title?: string; body?: string; }
4. 验证控制器效果
调整后控制器可以正常获取文件和验证后的DTO:
@UseInterceptors(FileInterceptor('attachment')) @Post('/create') create( @Body() createFeedDto: CreateFeedDto, @UploadedFile( new ParseFilePipe({ validators: [ new MaxFileSizeValidator({ maxSize: 5000000 }), new FileTypeValidator({ fileType: '(jpg|png)$' }), ], }), ) attachment: Express.Multer.File, ) { console.log({ createFeedDto, // 输出:{ title: 'hello', body: 'This is body text' } attachment, // 正常输出文件对象 }); return this.feedsService.create(createFeedDto); }
额外优化:Zod直接验证文件字段
如果需要在Zod Schema中统一验证文件,可以将文件字段加入Schema,结合AnyFilesInterceptor将文件合并到请求体(或自定义逻辑):
// create-feed.dto.ts const schema = z .object({ title: z.string().min(5).max(35).nullish(), body: z.string().max(140).nullish(), attachment: z.object({ originalname: z.string(), size: z.number().max(5000000), mimetype: z.string().regex(/(jpg|png)$/), }).nullish(), }) .refine( (d) => { return !!(d.title || d.body || d.attachment); }, { path: ['title'], message: "标题、正文和附件不能同时为空", }, );
内容的提问来源于stack exchange,提问作者Md. A. A. Apu
相关产品推荐
相关产品推荐

