无filename的表单请求下Multer S3无法上传至S3的问题解决
解决方案:处理无filename的form-data文件上传
问题根源在于Multer默认仅会将带有filename参数的form-data字段解析为文件对象(存入req.file),而第三方请求的csv_file字段没有该参数,因此被Multer识别为普通表单字段,导致req.file不存在。
我们可以直接使用Multer底层依赖的busboy库手动解析请求,绕过Multer的自动字段判断逻辑,强制将csv_file字段视为文件处理,同时保留原有的S3上传、文件类型校验逻辑。
步骤1:安装busboy(如果未安装)
npm install busboy
步骤2:编写自定义上传中间件
替换原有的Multer-S3中间件,创建一个直接处理请求的中间件:
const Busboy = require('busboy'); const { S3 } = require('aws-sdk'); const logger = require('../../configs/winston-config'); const s3Config = require('../../configs/s3-config'); // 初始化S3客户端 const s3 = new S3({ region: s3Config.region }); const uploadCSVToS3Bucket = (req, res, next) => { // 仅处理multipart/form-data类型的请求 if (!req.is('multipart/form-data')) { return next(); } const busboy = new Busboy({ headers: req.headers }); const uploadPromises = []; req.body = {}; req.file = null; // 处理所有form-data部分(字段/文件) busboy.on('part', (part) => { const fieldname = part.fieldname; // 处理csv_file字段:强制作为文件上传 if (fieldname === 'csv_file') { // 校验文件类型(保留原逻辑) if (part.mimeType && part.mimeType !== 'text/csv') { part.resume(); // 丢弃不符合要求的文件内容 return next(new Error('仅允许上传text/csv类型的文件')); } // 确定文件名:优先用第三方提供的fileName字段,否则生成默认名 const targetFilename = req.body.fileName || `csv_upload_${Date.now()}.csv`; // 生成S3存储键(与原逻辑一致) const s3Key = `<folder_name>/${Date.now().toString()}`; // 构造S3上传参数 const uploadParams = { Bucket: s3Config.bucketName, Key: s3Key, Body: part, ContentType: part.mimeType || 'text/csv', Metadata: { fieldName: fieldname } }; // 执行S3上传 const uploadPromise = s3.upload(uploadParams).promise() .then(uploadResult => { // 构造与Multer格式一致的file对象,方便后续逻辑兼容 req.file = { fieldname: fieldname, originalname: targetFilename, encoding: part.transferEncoding, mimetype: part.mimeType || 'text/csv', key: s3Key, location: uploadResult.Location, bucket: s3Config.bucketName }; }) .catch(err => { logger.error('S3文件上传失败:', err); return next(err); }); uploadPromises.push(uploadPromise); } else { // 处理普通表单字段(batchId、fileName、reason等) let fieldValue = ''; part.on('data', chunk => { fieldValue += chunk.toString(); }); part.on('end', () => { // 尝试解析为JSON(适配可能的结构化字段),失败则保留原始字符串 try { req.body[fieldname] = JSON.parse(fieldValue); } catch (e) { req.body[fieldname] = fieldValue; } }); } }); // 所有部分处理完成后,进入下一个中间件 busboy.on('finish', () => { Promise.all(uploadPromises) .then(() => { if (!req.file) { return res.status(400).send('请求缺少csv_file字段'); } next(); }) .catch(err => next(err)); }); // 将请求流导入busboy进行解析 req.pipe(busboy); }; module.exports = { uploadCSVToS3Bucket };
步骤3:更新路由配置
将原有的Multer中间件替换为自定义中间件:
router.post('/', use(intercept), use(uploadCSVToS3Bucket));
关键说明
- 兼容性:自定义中间件构造的
req.file对象与Multer生成的格式一致,原有依赖req.file的业务逻辑无需修改。 - 文件类型校验:保留了原逻辑中仅允许
text/csv类型文件的校验。 - 文件名处理:优先使用第三方请求中
fileName字段的值作为文件名,避免无文件名的问题。 - 错误处理:包含了S3上传失败、缺少文件字段、文件类型不符等场景的错误处理。
内容的提问来源于stack exchange,提问作者Apurva Mayank
相关产品推荐
相关产品推荐

