在使用 Multer 的自定义存储引擎之前访问表单字段

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

我目前正在使用带有自定义存储引擎的 Multer,该引擎将文件上传到请求中指定的位置。我目前正在从查询中获取目的地和其他几个参数,但我更愿意将所有输入数据合并到表单数据中。此目标输入需要一些额外的验证,需要访问

res
next
。目前的实现如下:

upload(req, res, next) {
    // Ideally this would be `req.body.destination`;
    const destination = req.query.destination;
    
    /* Validation ... */

    return multer({
        storage: new CustomStorage(destination),
    }).single('file')(req, res, next);
}

但是,Multer 需要在此验证之前运行,以便首先将表单数据解析为

req.body
。我试图通过在存储引擎之前运行第二个 Multer 实例来解决这个问题——这个实例会忽略文件并将任何文本字段解析为
req.body
。实施如下:

multer({
    fileFilter: (req, file, cb) => { return cb(null, false); },
}).any();

运行它确实允许我在上传中间件中访问

req.body
中的表单数据,但随后我收到以下错误:

Error: Unexpected end of form
    at Multipart._final (/Users/robert/bucket/node_modules/busboy/lib/types/multipart.js:588:17)
    at callFinal (node:internal/streams/writable:694:27)
    at prefinish (node:internal/streams/writable:723:7)
    at finishMaybe (node:internal/streams/writable:733:5)
    at Multipart.Writable.end (node:internal/streams/writable:631:5)
    at onend (node:internal/streams/readable:693:10)
    at processTicksAndRejections (node:internal/process/task_queues:78:11) {
  storageErrors: []
}

我在阅读 issue 1144 后尝试降级到 Multer 1.4.3,这样做确实阻止了错误,但导致 API 路由出错并显示

400
状态并且没有错误消息。

如何访问此处的表单数据字段?

node.js express file-upload multipartform-data multer
1个回答
0
投票

正如您正确指出的那样,要使

req.body
填充任何内容,Multer 需要先运行。只有在调用 Multer 中间件后,
req
流才会通过 Busboy(Multer 使用的多部分/表单数据请求解析器)传输,并且字段和文件会被解析和填充,按照它们出现的顺序,以
req.body
.

这意味着,如果您在任何文件之前发送所有必要的字段,您的

CustomStorage._handleFile()
实现将可以访问
req.body
,填充字段。

因此,要直接回答您的问题,您可以在

CustomStorage._handleFile()
中实施字段验证 –
req.body
将填充字段,但仅限于在任何文件之前收到的字段! 字段/文件的顺序在这里很重要。

这有几个缺点:

  1. 将您的验证实现与 Multer 的 API 紧密联系在一起;
  2. 在某些情况下,您可能没有发送/接收任何文件,但仍想验证字段。
  3. 每次遇到一个文件时都会重复运行验证——如果你有 3 个文件,对 req.body 的验证将运行 3 次。这应该不会对性能产生巨大影响,但它仍然不是很优雅。

Multer 的设计不够“灵活”以允许此类自定义逻辑,并且它不提供单独处理字段事件的功能,仅提供文件事件。

我已经写了

pechkin
来解决这个确切的问题。

  • pechkin.parseFormData()
    返回一个
    Promise
    ,它在所有字段都被解析时解析,and 如果遇到第一个文件(或请求结束)。
  • 承诺包含一个填充的
    fields
    对象,和一个
    files
    AsyncIterator/AsyncIterable
    .
  • 无需提供存储引擎、文件处理器、事件监听器等

这意味着您可以对

fields
对象执行验证,然后才需要处理文件——文件不会被解析、处理、写入内存、保存在任何地方等,直到您决定这样做。无需将您的功能包装到自定义存储引擎类中 - 文件可作为
Promise
s of
stream.Readable
instances.

// ESM
import { parseFormData } from 'pechkin';

// CommonJS
const { parseFormData } = require('pechkin');

// ...

app.post(
  '/upload',
  fileUploadWithFieldValidation(/* ...optional configs */),
  (req, res, next) => {
    // `req.body` will now contain validated fields
    // and `req.files` will be an AsyncIterator that you can iterate over
    // to process files, save them, upload to S3, etc.
    // I.e. do whatever your `CustomEngine` implementation did.
  }
);

// Express middleware
function fileUploadWithFieldValidation (/* ...optional configs */) {
  return async (req, res, next) => {
    try {
      const { fields, files } = await parseFormData(req, /* ...optional configs */);

      // You can VALIDATE fields here, without having to do anything
      // related to file handling / processing.

      req.body = fields;
      req.files = files;

      return next();
    } catch (err) {
      return next(err);
    }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.