NestJS中Zod结合Swagger实现多文件上传的验证与文档问题
NestJS + nestjs-zod 处理多文件上传:验证与Swagger文档解决方案
问题核心
Zod无内置File类型,nestjs-zod默认无法为文件字段生成@ApiProperty({ format: 'binary' }),导致Swagger无法识别文件上传控件;同时Multer拦截器会将文件从multipart/form-data中单独提取,无法直接通过@Body()的DTO验证文件。
解决方案步骤
1. 自定义Zod文件验证Schema
通过z.custom()实现文件类型验证,同时扩展Zod支持OpenAPI元数据:
// dto.ts import { createZodDto } from 'nestjs-zod/dto'; import { z } from 'nestjs-zod/z'; import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; import type { Express } from 'express'; extendZodWithOpenApi(z); // 自定义文件验证逻辑,适配Multer处理后的文件对象 const fileSchema = z.custom<Express.Multer.File>( (val) => { return typeof val === 'object' && val !== null && 'buffer' in val && val.buffer instanceof Buffer; }, { message: '无效的上传文件' } ); export const createStoreSchema = z.object({ field: z.string().optional().openapi({ description: '可选文本字段' }), requiredFile: fileSchema.openapi({ type: 'string', format: 'binary', description: '必填文件' }), optionalFile: fileSchema.optional().openapi({ type: 'string', format: 'binary', description: '可选文件' }) }); export class CreateStoreDto extends createZodDto(createStoreSchema) {}
2. 调整Controller逻辑,统一验证与Swagger识别
Multer拦截器会将文件存入@UploadedFiles(),需合并文本字段与文件后统一验证,同时手动补充Swagger配置确保文件控件显示:
// controller.ts import { Controller, Post, UseInterceptors, Body, UploadedFiles } from '@nestjs/common'; import { FileFieldsInterceptor } from '@nestjs/platform-express'; import { ApiConsumes, ApiBody } from '@nestjs/swagger'; import { CreateStoreDto, createStoreSchema } from './dto'; import type { Express } from 'express'; @Controller('stores') export class StoresController { @Post() @UseInterceptors( FileFieldsInterceptor([{ name: 'requiredFile' }, { name: 'optionalFile' }]) ) @ApiConsumes('multipart/form-data') // 手动定义Swagger请求体,强制识别文件字段为二进制类型 @ApiBody({ schema: { type: 'object', properties: { field: { type: 'string', nullable: true }, requiredFile: { type: 'string', format: 'binary' }, optionalFile: { type: 'string', format: 'binary', nullable: true } }, required: ['requiredFile'] } }) async create( @Body() body: CreateStoreDto, @UploadedFiles() files: { requiredFile?: Express.Multer.File[]; optionalFile?: Express.Multer.File[]; } ) { // 合并文本字段与文件,适配Zod Schema结构 const requestData = { ...body, requiredFile: files.requiredFile?.[0], optionalFile: files.optionalFile?.[0] }; // 统一验证所有数据 const validatedData = createStoreSchema.parse(requestData); // 业务逻辑处理 return { status: 'success', data: validatedData }; } }
3. 进阶:自动生成Swagger文件字段(适合PR扩展)
若想完全通过Zod Schema自动生成Swagger配置,可扩展createZodDto逻辑,检测字段的OpenAPI元数据并自动添加@ApiProperty:
// 自定义createZodDto扩展 import { createZodDto as baseCreateZodDto } from 'nestjs-zod/dto'; import { z } from 'nestjs-zod/z'; import { ApiProperty } from '@nestjs/swagger'; export function createZodDto<T extends z.ZodObject<any>>(schema: T) { class Dto extends baseCreateZodDto(schema) {} // 遍历Schema字段,为binary格式字段添加ApiProperty Object.entries(schema.shape).forEach(([key, field]) => { const openapiMeta = (field as any)._def.openapi; if (openapiMeta?.format === 'binary') { ApiProperty({ format: 'binary', required: !field.isOptional(), description: openapiMeta.description })(Dto.prototype, key); } }); return Dto as typeof Dto & z.infer<T>; }
使用该扩展后,无需手动添加@ApiBody,Swagger会自动识别文件字段。
内容的提问来源于stack exchange,提问作者Saybers
相关产品推荐
相关产品推荐

