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

无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));

关键说明

  1. 兼容性:自定义中间件构造的req.file对象与Multer生成的格式一致,原有依赖req.file的业务逻辑无需修改。
  2. 文件类型校验:保留了原逻辑中仅允许text/csv类型文件的校验。
  3. 文件名处理:优先使用第三方请求中fileName字段的值作为文件名,避免无文件名的问题。
  4. 错误处理:包含了S3上传失败、缺少文件字段、文件类型不符等场景的错误处理。

内容的提问来源于stack exchange,提问作者Apurva Mayank

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 22:32:02