使用 mongoose、gridfs-stream 和 multer 上传文件时出现 MongoError

Posted

技术标签:

【中文标题】使用 mongoose、gridfs-stream 和 multer 上传文件时出现 MongoError【英文标题】:MongoError when uploading a file using mongoose, gridfs-stream and multer 【发布时间】:2015-01-25 06:39:49 【问题描述】:

我正在使用 multer、gridfs-stream 和 mongoose 和 mongodb 运行 express 4,我正在尝试上传文件并将其流式传输到 gridfs。

执行此操作的快速路线定义为:

app.post('/uploadfile', function (req, res) 
    console.dir(req.files);

    // The mongodb instance created when the mongoose.connection is opened
    var db = mongoose.connection.db;

    // The native mongo driver which is used by mongoose
    var mongoDriver = mongoose.mongo;

    // Create a gridfs-stream
    var gfs = new Gridfs(db, mongoDriver);

    var file = req.files.myFile;

    var fileId = new ObjectId();

    console.log("Creating WriteStream");
    var writeStream = gfs.createWriteStream(
        _id: fileId,
        filename: file.originalname,
        mode: 'w',
        content_type: file.mimetype,
        metadata: 
            id: '123',
            number: '2',
            name: "Kenny Erasmuson"
        
     );
     console.log("Created WriteStream");
     req.pipe(writeStream);
     console.log("Finished!");
);

一旦 express 应用程序运行,就会选择并上传一个文件(通过 html multipart/form-data 表单),节点服务器的输出是:

$ node server.js 
Listening on port 8001
 myFile: 
     fieldname: 'myFile',
    originalname: 'kenny-credit-rating.pdf',
    name: '1082e5071ede1002c4ae5be6123226d8.pdf',
    encoding: '7bit',
    mimetype: 'application/pdf',
    path: 'uploads/1082e5071ede1002c4ae5be6123226d8.pdf',
    extension: 'pdf',
    size: 110782,
    truncated: false,
    buffer: null  

Creating WriteStream
Created WriteStream
Finished!
/Users/kenny/Dropbox/Dev/fileUploader/node_modules/mongoose/node_modules/mongodb/lib/mongodb/co nnection/base.js:246
    throw message;      
          ^
MongoError: The dollar ($) prefixed field '$conditionalHandlers' in '_id.$conditionalHandlers' is not valid for storage.
at Object.toError (/Users/kenny/Dropbox/Dev/fileUploader/node_modules/mongoose/node_modules/mongodb/lib/mongodb/utils.js:114:11)
at /Users/kenny/Dropbox/Dev/fileUploader/node_modules/mongoose/node_modules/mongodb/lib/mongodb/collection/core.js:569:27
at /Users/kenny/Dropbox/Dev/fileUploader/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1157:7
at /Users/kenny/Dropbox/Dev/fileUploader/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1890:9
at Server.Base._callHandler (/Users/kenny/Dropbox/Dev/fileUploader/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/base.js:448:41)
at /Users/kenny/Dropbox/Dev/fileUploader/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:481:18
at MongoReply.parseBody (/Users/kenny/Dropbox/Dev/fileUploader/node_modules/mongoose/node_modules/mongodb/lib/mongodb/responses/mongo_reply.js:68:5)
at null.<anonymous> (/Users/kenny/Dropbox/Dev/fileUploader/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:439:20)
at emit (events.js:95:17)
at null.<anonymous> (/Users/kenny/Dropbox/Dev/fileUploader/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/connection_pool.js:201:13)

有没有人知道是什么导致了错误以及如何解决?

【问题讨论】:

【参考方案1】:

问题在于您不只是将文件请求传送到 gridfs-stream。使用 Multer 作为中间件,捕获任何 multipart/form-data 发布请求。当流进入时,Multer(基于Busboy)监视on('field')on('file') 事件并进行相应的解析。您向 Multer 发送的内容不仅仅是一个文件。

这段代码运行良好,因为此时 Multer 确实为您解析了 req.filesreq.body

   // Create a gridfs-stream
   var gfs = new Gridfs(db, mongoDriver);

   var file = req.files.myFile;

   var fileId = new ObjectId();

   console.log("Creating WriteStream");
   var writeStream = gfs.createWriteStream(
        _id: fileId,
        filename: file.originalname,
        mode: 'w',
        content_type: file.mimetype,
        metadata: 
            id: '123',
            number: '2',
            name: "Kenny Erasmuson"
        
     );
     console.log("Created WriteStream");

但是由于上述原因,您的代码遇到问题的地方在这里:

 req.pipe(writeStream);

我还没有找到一种方法来流式传输多部分/表单数据发布请求,而不仅仅是将文件直接上传到 GridFS。如果您的 post 请求不包含文件以外的任何内容(意味着表单中没有其他 html 输入字段),那么您可能需要考虑从中间件中删除 Multer 至少对于这条路线。

对于我的用例,我需要能够接收带有文本输入的 html 表单帖子以及文件上传(我在上传时存储有关文件的元数据)。以下是我使用 Multer 完成所需的方法:

var uploadImg = function(req,res) 
      var writestream = gfs.createWriteStream(
        filename: req.files.file.name,
        mode:'w',
        content_type:req.files.file.mimetype,
        metadata:req.body,
      );
    fs.createReadStream(req.files.file.path).pipe(writestream);

    writestream.on('close', function (file) 
        res.send("Success!");
        fs.unlink(req.files.file.path, function (err) 
          if (err) console.error("Error: " + err);
          console.log('successfully deleted : '+ req.files.file.path );
        );
    );

;

默认情况下,Multer 会将您的文件存储在磁盘上。一种快速的解决方案是创建一个读取流并将其直接流式传输回 GridFS。写入完成后,删除 tmp 文件。

作为旁注,似乎有人认为最好只在需要它的路由上使用 Multer 作为中间件。你可以在this 的最后部分阅读更多关于这个想法的信息。

更新

我想我已经接近找到一种通过使用Skipper 直接流式传输到 GridFS with 元数据的方法。我正在查看是否无法将一些更新发送到skipper-gridfs,这些更新将获取 html 表单上的文本输入并将其设置为元数据等。对 skipper-gridfs 的调整似乎很小。当它被冲洗掉时,我会更新。如果您查看 skipper,请确保您认识到 html 表单输入的顺序(如果有的话)确实很重要。

【讨论】:

如果有人有兴趣,我用一个skipper-gridfs 的分支打造了一个快速的解决方案。对 repo 的调整很小,可以进行一些清理,但它对我来说效果很好,我可能很快就会在生产中使用它。这里有一个拉取请求——https://github.com/willhuang85/skipper-gridfs/pull/9我现在直接流式传输到 gridfs——使用表单中的元数据——无需先保存文件。工作得很好。【参考方案2】:

在我的代码中:

var fileId = new ObjectId();

console.log("Creating WriteStream");
var writeStream = gfs.createWriteStream(
    _id: fileId,
    filename: file.originalname,
    mode: 'w',
    content_type: file.mimetype,
    metadata: 
        id: '123',
        number: '2',
        name: "Kenny Erasmuson"
    
 );

我将 ObjectId 而不是 ObjectId 的字符串表示形式分配给传递给 gfs.createWriteStream 的对象的 _id 属性。事实证明,这导致了我的代码中的 MongoError。

发现 here 的修复方法是更改​​导致问题的代码行:

_id: fileId.str,

完成此操作后,我遇到了文件未放入 fs.chunks mongodb 集合的问题,尽管元数据确实最终位于 fs.files mongodb 集合中。 AddieD 的回答 here 开始解决这个问题:)

【讨论】:

以上是关于使用 mongoose、gridfs-stream 和 multer 上传文件时出现 MongoError的主要内容,如果未能解决你的问题,请参考以下文章

使用 gridfs-stream 将字符串写入 gridfs

Node.js 文件上传(Express 4、MongoDB、GridFS、GridFS-Stream)

gridfs-stream 总是用“contentType: binary/octet-stream”存储文件

node.js上传下载pdf文件

错误 ReferenceError: ObjectID 未定义

使用 react 功能组件添加和删除两个集合