You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 20:59:53