Multer 中间件问题:Express 应用程序中只有单个文件未正确处理”

问题描述 投票:0回答:1

在 Express 应用程序中使用 Multer 时,如果文件上传的 maxCount 设置为 1:

预期行为:上传单个文件时,应使用该文件填充 req.files,并应生成相应的 URL。 实际行为:上传单个文件时,req.files 未正确填充或丢失,导致不生成 URL。但是,当上传两个文件时,第一个文件的 URL 会正确生成。

//Controller Logic
// Allows a user to submit basic information during their first login
async function submitBasicInfo(req, res) {
  const userId = req.user.userId; // Extracted from JWT by authentication middleware
  const role = req.user.role; // Extracted from JWT by authentication middleware

  try {
    const user = await User.findById(userId);
    if (!user) {
      return res.status(404).json({ message: "User not found" });
    }

    // Log to verify incoming files
    console.log("Files received:", req.files);

    // Existing URLs
    const existingProfilePhotoUrls = user.profilePhotos || [];
    const existingProfileVideoUrls = user.profileVideos || [];
    const existingProfileDocumentUrls = user.profileDocuments || [];

    // New URLs
    const newProfilePhotoUrls = req.files.profilePhotos
      ? req.files.profilePhotos.map((file) =>
          path.join("profileUploads", role, userId.toString(), file.filename)
        )
      : [];

    const newProfileVideoUrls = req.files.profileVideos
      ? req.files.profileVideos.map((file) =>
          path.join("profileUploads", role, userId.toString(), file.filename)
        )
      : [];

    const newProfileDocumentUrls = req.files.profileDocuments
      ? req.files.profileDocuments.map((file) =>
          path.join("profileUploads", role, userId.toString(), file.filename)
        )
      : [];

    // Combine existing and new URLs
    const updatedProfilePhotoUrls =
      existingProfilePhotoUrls.concat(newProfilePhotoUrls);
    const updatedProfileVideoUrls =
      existingProfileVideoUrls.concat(newProfileVideoUrls);
    const updatedProfileDocumentUrls = existingProfileDocumentUrls.concat(
      newProfileDocumentUrls
    );

    // Log the constructed URLs for debugging
    console.log(
      `Backend - Constructed profilePhotoUrls: ${updatedProfilePhotoUrls}`
    );
    console.log(
      `Backend - Constructed profileVideoUrls: ${updatedProfileVideoUrls}`
    );
    console.log(
      `Backend - Constructed profileDocumentUrls: ${updatedProfileDocumentUrls}`
    );

    // Update user with the basic information from request body
    const updatedUser = await User.findByIdAndUpdate(
      userId,
      {
        $set: {
          profileInfo: req.body,
          profilePhotos: updatedProfilePhotoUrls,
          profileVideos: updatedProfileVideoUrls,
          profileDocuments: updatedProfileDocumentUrls,
          isFirstLogin: false,
          onboardingState: "basicInfoComplete", // Update the state to basicInfoComplete
        },
      },
      { new: true }
    );

    // Log the stored URLs and role for debugging
    console.log(`Stored profilePhotos: ${updatedUser.profilePhotos}`);
    console.log(`Stored profileVideos: ${updatedUser.profileVideos}`);
    console.log(`Stored profileDocuments: ${updatedUser.profileDocuments}`);
    console.log(`User role: ${role}`);

    // Log onboardingState for debugging
    console.log(
      `SBI- User ${updatedUser.email} updated onboardingState to: ${updatedUser.onboardingState}`
    );

    res.status(200).json({
      user: updatedUser,
      role: updatedUser.role, // Include the role in the response
      message: "Basic information updated successfully",
    });
  } catch (error) {
    console.error("Error updating user's basic information:", error);
    res.status(500).json({ message: "Internal server error" });
  }
}

//Multer 中间件

const multer = require("multer");
const path = require("path");
const fs = require("fs");

// Define upload limits for each file type
const uploadLimits = {
  profilePhotos: 1, // Max count for profilePhotos
  profileVideos: 1, // Max count for profileVideos
  profileDocuments: 1, // Max count for profileDocuments
};

// Define file size limits in bytes (1 MB = 1,000,000 bytes)
const fileSizeLimits = {
  profilePhotos: 5 * 1e6, // 5 MB for profile photos
  profileVideos: 100 * 1e6, // 100 MB for profile videos
  profileDocuments: 10 * 1e6, // 10 MB for documents
};

// Configure storage for multer
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    const role = req.user.role;
    const userId = req.user.userId.toString();
    const uploadPath = path.join(
      __dirname,
      "..",
      "..",
      "mediaUploads",
      role,
      userId
    );

    // Ensure the upload directory exists
    fs.mkdirSync(uploadPath, { recursive: true });

    console.log(`Uploading file to: ${uploadPath}`);
    cb(null, uploadPath);
  },
  filename: (req, file, cb) => {
    const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e9)}`;
    const fileName = `${file.fieldname}-${uniqueSuffix}${path.extname(
      file.originalname
    )}`;
    console.log(`Saving file as: ${fileName}`);
    cb(null, fileName);
  },
});

// Multer middleware to handle file upload
const profileUploads = multer({
  storage,
  limits: {
    fileSize: Math.max(...Object.values(fileSizeLimits)), // Max size for the largest allowed file type
    files: Object.values(uploadLimits).reduce((a, b) => a + b, 0), // Total files limit
  },
}).fields([
  { name: "profilePhotos", maxCount: uploadLimits.profilePhotos },
  { name: "profileVideos", maxCount: uploadLimits.profileVideos },
  { name: "profileDocuments", maxCount: uploadLimits.profileDocuments },
]);

// Middleware to construct and store file URLs
const constructFileUrls = (req, res, next) => {
  try {
    const userId = req.user.userId.toString();
    const role = req.user.role;
    const basePath = "profileUploads";

    // Initialize arrays to store the file URLs
    req.body.profilePhotoUrls = [];
    req.body.profileVideoUrls = [];
    req.body.profileDocumentUrls = [];

    if (req.files) {
      // Check for and log received files
      console.log("Files received:", req.files);

      if (req.files.profilePhotos) {
        req.body.profilePhotoUrls = req.files.profilePhotos.map((file) => {
          const profilePhotoPath = path.join(
            basePath,
            role,
            userId,
            file.filename
          );
          return profilePhotoPath.replace(/\\/g, "/");
        });
        console.log(`Profile photos URLs: ${req.body.profilePhotoUrls}`);
      }

      if (req.files.profileVideos) {
        req.body.profileVideoUrls = req.files.profileVideos.map((file) => {
          const profileVideoPath = path.join(
            basePath,
            role,
            userId,
            file.filename
          );
          return profileVideoPath.replace(/\\/g, "/");
        });
        console.log(`Profile videos URLs: ${req.body.profileVideoUrls}`);
      }

      if (req.files.profileDocuments) {
        req.body.profileDocumentUrls = req.files.profileDocuments.map(
          (file) => {
            const profileDocumentPath = path.join(
              basePath,
              role,
              userId,
              file.filename
            );
            return profileDocumentPath.replace(/\\/g, "/");
          }
        );
        console.log(`Profile documents URLs: ${req.body.profileDocumentUrls}`);
      }
    }

    next();
  } catch (error) {
    console.error("Error in constructFileUrls middleware:", error);
    res
      .status(500)
      .json({ error: "Internal server error during URL construction" });
  }
};

// Middleware to validate file upload limits
const validateFileUploads = (req, res, next) => {
  const errors = [];
  const totalFiles = Object.keys(uploadLimits).reduce((count, field) => {
    return count + (req.files[field] ? req.files[field].length : 0);
  }, 0);

  if (totalFiles > Object.values(uploadLimits).reduce((a, b) => a + b, 0)) {
    errors.push(
      `Total file upload limit exceeded. Allowed: ${Object.values(
        uploadLimits
      ).reduce((a, b) => a + b, 0)}, Provided: ${totalFiles}`
    );
  }

  for (const [field, limit] of Object.entries(uploadLimits)) {
    if (req.files[field] && req.files[field].length > limit) {
      errors.push(`Field "${field}" exceeds its limit of ${limit} files.`);
    }
  }

  if (errors.length > 0) {
    return res.status(400).json({ errors });
  }

  next();
};

// Error-handling middleware for Multer errors
const multerErrorHandler = (err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    console.error("Multer Error:", err);
    let message;
    if (err.code === "LIMIT_UNEXPECTED_FILE") {
      message = `Unexpected file field: ${err.field}`;
    } else if (err.code === "LIMIT_FILE_SIZE") {
      message = `File size limit exceeded for field: ${err.field}`;
    } else {
      message = "File upload error";
    }
    return res.status(400).json({ message, error: err.message });
  }
  console.error("Unexpected Error:", err);
  res.status(500).json({ message: "Something broke!", error: err.message });
};

module.exports = {
  profileUploads,
  constructFileUrls,
  validateFileUploads,
  multerErrorHandler,
};


router.post(
  "/submit-basic-info",
  authenticate,
  profileUploads,
  constructFileUrls,
  validateFileUploads,
  multerErrorHandler,
  submitBasicInfo
);


PostMan 键和值

在此输入图片描述

在 Express 应用程序中使用 Multer 时,如果文件上传的 maxCount 设置为 1:

预期行为:上传单个文件时,应使用该文件填充 req.files,并应生成相应的 URL。 实际行为:上传单个文件时,req.files 未正确填充或丢失,导致不生成 URL。但是,当上传两个文件时,第一个文件的 URL 会正确生成。

express middleware mean-stack multer
1个回答
0
投票

请参阅下面的 MRE - 最小可重现示例。 它采用了与通过设置 maxCount : 1 来上传单个文件相同的情况。结果表明它有效 - 它上传了单个文件。请查看下面的测试运行结果。

提案

此 MRE 可以排除将 maxCount 设置为 1 时 Multer 中出现错误的可能性。因此,报告的错误可能是由代码中的其他内容引起的。请您使用此 MRE 再次检查您的代码,如果问题仍然存在,请恢复。

服务器.js

const express = require('express'); const multer = require('multer'); const app = express(); const upload = multer({ dest: './uploads' }); app.post( '/upload', upload.fields([{ name: 'fileset1', maxCount: 1 }]), (req, res) => { console.log(req.files); res.send('done'); } ); const formdata = new FormData(); formdata.append('fileset1', new Blob(['some content'], { type: 'text/plain' })); fetch('http://localhost:3000/upload', { method: 'POST', body: formdata }); app.listen(3000, () => console.log('l@3000'));

试运行

node server.js

结果:

[Object: null prototype] { fileset1: [ { fieldname: 'fileset1', originalname: 'blob', encoding: '7bit', mimetype: 'text/plain', destination: './uploads', filename: '0141edfd8f0c889f36d4c99e2f1e2ecb', path: 'uploads/0141edfd8f0c889f36d4c99e2f1e2ecb', size: 12 } ] }

	
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.