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

3
我正在使用multer, gridfs-streammongoose与mongodb运行express 4,尝试上传文件并将其流式传输到gridfs。
执行此操作的express路由定义如下:
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服务器的输出为:

$ 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)

有人有什么想法是什么导致了错误,以及如何修复?

2个回答

5
问题在于您没有将文件请求直接传输到gridfs-stream。使用Multer作为中间件捕获任何multipart/form-data的post请求。当流进来时,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);

我尚未找到一种方法,可以直接将multipart/form-data post请求流式传输到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的方法。我正在尝试是否可以对skipper-gridfs进行一些更新,以便将html表单上的文本输入设置为元数据等。对skipper-gridfs的调整似乎相当小。当这个问题得到解决时,我会更新的。如果您查看skipper,请确保您认识到您的html表单输入的顺序(如果有)确实很重要。

如果有人感兴趣,我使用了skipper-gridfs的分支快速解决了问题。对仓库进行的调整很小,需要进行一些清理,但它对我来说运行得非常好,我可能很快就会在生产中使用它。这里有一个拉取请求--https://github.com/willhuang85/skipper-gridfs/pull/9现在我直接流式传输到gridfs--带有表单元数据--无需先保存文件。运行良好。 - AddieD

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"
    }
 });

我正在为传递到gfs.createWriteStream的对象的_id属性分配ObjectId而不是其字符串表示形式。结果发现这导致了我的代码中的MongoError。
解决方案在此处发现:这里,需要更改引起问题的代码行如下:
_id: fileId.str,

在完成这些操作后,我发现文件没有被放入fs.chunks mongodb集合中,尽管元数据最终会出现在fs.files mongodb集合中。AddieD的答案开始解决了这个问题 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接