为什么我不能在同一条路线上使用 multer 两次? (Nodejs/Express)

Why can't I use multer twice in the same route? (Nodejs/Express)

问题

我正在使用 express 在 Nodejs API 中开发文件上传功能。我试图阻止超过特定大小限制的文件上传到我的 AWS S3 存储桶。

我知道您可以在其配置中使用 multer 的限制 属性 object:

limits: { fileSize: mbToBytes(sizeLimit) }

问题是在抛出 File Too Large 错误之前,文件上传到 S3 的数量达到了限制,例如,如果我的限制是 6mb 和一个 40mb 的文件已上传,只有前 6mb 会上传到我的存储桶,这就是我要防止的。

我试过的

使用 Multer 将文件存储在内存中,并在上传过程中检查文件是否超过限制,如果超过限制,则抛出错误并停止进程。否则,继续并再次调用 multer 将文件上传到 S3。

  // ROUTE

    router.post(
      '/upload-cover',
      userController.checkCoverSize,
      userController.uploadCover,
      userController.updateUserCover
    );


    // CONTROLLER

const {
  s3CoverMulterConfig,
  memoryProfileImgMulterConfig
} = require('../utils/awsMulterConfig');

        // Upload cover picture to S3
        const coverUploader = new Uploader(s3CoverMulterConfig, 'cover');
        exports.uploadCover = coverUploader.startUpload.bind(coverUploader);
        
        // Upload cover picture to memory
        const coverMemoryUploader = new Uploader(memoryProfileImgMulterConfig, 'cover');
        exports.checkCoverSize = coverMemoryUploader.startUpload.bind(
          coverMemoryUploader
        );

上传器是 class 因为我要上传不同类型的图片:

// uploader.js

const util = require('util');
const multer = require('multer');
const AppError = require('./appError');
    
class Uploader {
  constructor(options, fileName) {
    this.options = options;
    this.fileName = fileName;
    this.upload = multer(options);
  }

  async startUpload(req, res, next) {
    try {
      const upload = util.promisify(this.upload.single(this.fileName));
      await upload(req, res);
    } catch (err) {
      return next(new AppError(err));
    }

    return next();
  }
}

我还有一个文件可以导出我的 multer 配置 objects:

     // awsMulterConfig.js
        
        const aws = require('aws-sdk');
        const multerS3 = require('multer-s3');
        const { nanoid } = require('nanoid');
        const multer = require('multer');
            
        const s3 = new aws.S3();
         const mbToBytes = (numberInMb) => numberInMb * 1024 * 1024;
        
        aws.config.update({
          secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
          accessKeyId: process.env.AWS_KEY_ID,
          region: process.env.REGION
        });
        
        // Validate the file type
        const fileFilter = (validMimeTypes, errMsg) => (req, file, cb) => {
          if (!validMimeTypes.includes(file.mimetype)) {
            cb(new Error(errMsg), false);
          }
          cb(null, true);
        };
            
                const generateMulterConfigS3 = (sizeLimit, folderName) => {
                  return {
                    limits: { fileSize: mbToBytes(sizeLimit) },
                    storage: multerS3({
                      acl: 'public-read',
                      s3,
                      bucket: process.env.BUCKET_NAME,
                      metadata: (req, file, cb) => {
                        return cb(null, { fieldName: 'TEST_STATIC' });
                      },
                      key: (req, file, cb) => {
                        const fileName = `${folderName}/${nanoid(15)}.${
                          file.mimetype.split('/')[1]
                        }`;
                
                        return cb(null, fileName);
                      },
                      contentType: multerS3.AUTO_CONTENT_TYPE 
                    })
                  };
                };
                
                const generateMulterConfigMemory = (validMimeTypes, errMsg, sizeLimit) => {
                  return {
                    fileFilter: fileFilter(validMimeTypes, errMsg),
                    limits: { fileSize: mbToBytes(sizeLimit) },
                    storage: multer.memoryStorage()
                  };
                };

exports.s3CoverMulterConfig = generateMulterConfigS3(2, 'cover');
exports.memoryProfileImgMulterConfig = generateMulterConfigMemory(
  ['image/jpeg', 'image/png'],
  'Your image must be .jpeg/jpg or .png!',
  2
);

问题的表现

第一个函数正确运行并将文件放入内存,第二个函数运行但没有将文件上传到 S3,因此它没有更新 req.file。如果我删除第一个,第二个将文件正确上传到 S3,就好像你无法在同一路径中使用 Multer 两次解析多部分表单数据。

   // ROUTE

    router.post(
      '/upload-cover',
      userController.checkCoverSize,
      userController.uploadCover,
      userController.updateUserCover
    );

我期待的行为

第二个函数应该将文件上传到S3并重写req.file的内容。 我已经验证 checkImgSize 能够在文件大于限制时抛出错误。

我用来发出请求的客户端

邮递员 v7.34.0。 Content-Type header设置为multipart/form-data; boundary= < 发送请求时计算 >

任何帮助将不胜感激:)

您不能在同一个请求中使用 multer 两次,因为一旦从传入读取流中读取数据,它就会消失,除非您将其放在其他地方(例如文件系统中)。相反,您有多种选择:

  1. 您可以让它上传到 S3,直到它超过大小限制,然后停止上传,然后从 S3 中删除该项目。

  2. 您可以将文件下载到您的文件系统,当它成功完成并且不超过文件大小限制时,您就可以开始将它上传到 S3。如果超过限制,则必须从文件系统中删除部分下载。

  3. 您可以将文件下载到内存,当它成功完成并且不超过文件大小限制时,您就可以开始将它上传到S3。如果超过限制,则必须从内存中删除部分下载。

选项 #2 和 #3 在超过文件大小的情况下将带宽节省到 S3(如果这是一项重要的费用),但选项 #1 可能会更快地正常上传到 S3。