在 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 会正确生成。
请参阅下面的 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
}
]
}