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

NestJS v10+中ParseFilePipeBuilder校验错误文件仍被上传问题

NestJS v10+ 实现仅允许PNG文件上传(校验不通过则不上传)

你的问题核心在于:当前代码里Multer会先把文件保存到磁盘,之后才执行ParseFilePipeBuilder的校验,导致错误文件已经被上传到服务器。要解决这个问题,必须让文件校验逻辑在磁盘存储之前执行,下面是两种可行方案:

方案一:用Multer的fileFilter前置过滤

直接在Multer处理文件的阶段就过滤掉非PNG格式的文件,不符合条件的文件根本不会写入磁盘。

修改后的完整代码:

import {
  Controller, Post, UploadedFile, UseInterceptors, Request, HttpStatus,
  ParseFilePipeBuilder, BadRequestException,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';

@Controller()
export class UploadController {
  @Post('/upload')
  @UseInterceptors(
    FileInterceptor('file', {
      storage: diskStorage({
        destination: './path',
        filename: (req, file, cb) => {
          const uniqueIdentifier: string = new Date().getTime().toString();
          return cb(
            null,
            `${uniqueIdentifier}${file.originalname.replace(/\s/g, '')}`,
          );
        },
      }),
      // 添加文件过滤逻辑,存储前校验类型
      fileFilter: (req, file, cb) => {
        // 校验MIME类型
        if (file.mimetype !== 'image/png') {
          return cb(new BadRequestException('仅支持PNG格式文件'), false);
        }
        // 额外校验文件扩展名(防止篡改MIME类型的情况)
        const fileExt = file.originalname.split('.').pop()?.toLowerCase();
        if (fileExt !== 'png') {
          return cb(new BadRequestException('仅支持PNG格式文件'), false);
        }
        cb(null, true);
      },
    }),
  )
  // 可保留ParseFilePipe做二次校验,双重保障
  uploadCv(
    @UploadedFile(
      new ParseFilePipeBuilder()
        .addFileTypeValidator({ fileType: 'png' })
        .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY }),
    )
    file: Express.Multer.File,
    @Request() req,
  ) {
    // 处理上传成功后的逻辑
    return { message: '文件上传成功', fileInfo: { filename: file.filename, size: file.size } };
  }
}

方案二:先存内存,校验通过后再写入磁盘

先把文件临时存在内存中,等ParseFilePipeBuilder校验通过后,再手动将文件写入磁盘,校验失败的文件不会被保存。

修改后的完整代码:

import {
  Controller, Post, UploadedFile, UseInterceptors, Request, HttpStatus,
  ParseFilePipeBuilder,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import * as fs from 'fs';
import * as path from 'path';

@Controller()
export class UploadController {
  @Post('/upload')
  @UseInterceptors(
    FileInterceptor('file', {
      // 将文件临时存储到内存
      storage: memoryStorage(),
    }),
  )
  uploadCv(
    @UploadedFile(
      new ParseFilePipeBuilder()
        .addFileTypeValidator({ fileType: 'png' })
        .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY }),
    )
    file: Express.Multer.File,
    @Request() req,
  ) {
    const uploadDir = './path';
    // 确保上传目录存在,不存在则创建
    if (!fs.existsSync(uploadDir)) {
      fs.mkdirSync(uploadDir, { recursive: true });
    }
    // 生成唯一文件名
    const uniqueIdentifier = new Date().getTime().toString();
    const filename = `${uniqueIdentifier}${file.originalname.replace(/\s/g, '')}`;
    const filePath = path.join(uploadDir, filename);

    // 校验通过后,手动写入磁盘
    fs.writeFileSync(filePath, file.buffer);

    return { message: '文件上传成功', fileInfo: { filename, filePath } };
  }
}

两种方案对比

  • 方案一:性能更高,直接在Multer阶段拦截,适合简单的类型校验场景。
  • 方案二:灵活性更强,适合需要在校验后对文件做自定义处理(比如压缩、加水印)的场景。

内容的提问来源于stack exchange,提问作者M Nouman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 07:48:21