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

Posted

技术标签:

【中文标题】Node.js 文件上传(Express 4、MongoDB、GridFS、GridFS-Stream)【英文标题】:Node.js File Upload (Express 4, MongoDB, GridFS, GridFS-Stream) 【发布时间】:2015-07-18 11:55:17 【问题描述】:

我正在尝试在我的 node.js 应用程序中设置文件 API。我的目标是能够将文件流直接写入gridfs,而无需最初将文件存储到磁盘。看来我的创建代码正在运行。我能够将文件上传保存到gridfs。问题是读取文件。当我尝试通过 Web 浏览器窗口下载保存的文件时,我看到文件内容包含以下内容:

------WebKitFormBoundarye38W9pfG1wiA100l
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/javascript

***File contents here***

------WebKitFormBoundarye38W9pfG1wiA100l--

所以我的问题是,在将文件流保存到 gridfs 之前,我需要做什么才能从文件流中去除边界信息?这是我正在使用的代码:

'use strict';

var mongoose = require('mongoose');
var _ = require('lodash');

var Grid = require('gridfs-stream');
Grid.mongo = mongoose.mongo;
var gfs = new Grid(mongoose.connection.db);

// I think this works. I see the file record in fs.files
exports.create = function(req, res) 
    var fileId = new mongoose.Types.ObjectId();

    var writeStream = gfs.createWriteStream(
        _id: fileId,
        filename: req.query.name,
        mode: 'w',
        content_type: req.query.type,
        metadata: 
            uploadedBy: req.user._id,
        
    );

    writeStream.on('finish', function() 
        return res.status(200).send(
            message: fileId.toString()
        );
    );

    req.pipe(writeStream);
;

// File data is returned, but it's wrapped with
// WebKitFormBoundary and has headers.
exports.read = function(req, res) 
    gfs.findOne( _id: req.params.id , function (err, file) 
        if (err) return res.status(400).send(err);

        // With this commented out, my browser will prompt
        // me to download the raw file where I can see the
        // webkit boundary and request headers
        //res.writeHead(200,  'Content-Type': file.contentType );

        var readstream = gfs.createReadStream(
            _id: req.params.id
            // I also tried this way:
            //_id: file._id
        );

        readstream.pipe(res);
    );
;

顺便说一句,我目前没有为这些路由使用任何中间件,但我愿意这样做。我只是不希望文件在被发送到 gridfs 之前撞到磁盘。

编辑:

根据@fardjad,我添加了node-multiparty 模块用于多部分/表单数据解析,它有点工作。但是当我下载上传的文件并与原始文件(作为文本)进行比较时,编码有很多差异,下载的文件无法打开。这是我最近的尝试。

'use strict';

var mongoose = require('mongoose');
var _ = require('lodash');
var multiparty = require('multiparty');
var Grid = require('gridfs-stream');
Grid.mongo = mongoose.mongo;
var gfs = new Grid(mongoose.connection.db);

exports.create = function(req, res) 
    var form = new multiparty.Form();
    var fileId = new mongoose.Types.ObjectId();

    form.on('error', function(err) 
      console.log('Error parsing form: ' + err.stack);
    );

    form.on('part', function(part) 
        if (part.filename) 
            var writeStream = gfs.createWriteStream(
                _id: fileId,
                filename: part.filename,
                mode: 'w',
                content_type: part.headers['content-type'],
                metadata: 
                    uploadedBy: req.user._id,
                
            )

            part.pipe(writeStream);
        
    );

    // Close emitted after form parsed
    form.on('close', function() 
        return res.status(200).send(
            message: fileId.toString()
        );
    );

    // Parse req
    form.parse(req);
;

exports.read = function(req, res) 
    gfs.findOne( _id: req.params.id , function (err, file) 
        if (err) return res.status(400).send(err);

        res.writeHead(200,  'Content-Type': file.contentType );

        var readstream = gfs.createReadStream(
            _id: req.params.id
        );

        readstream.pipe(res);
    );
;

最终编辑:

这是我从另一个开发人员那里复制并修改的简单实现。这对我有用:(我仍在试图弄清楚为什么它在我原来的快递应用程序中不起作用。似乎有一些干扰)

https://gist.github.com/pos1tron/094ac862c9d116096572

var Busboy = require('busboy'); // 0.2.9
var express = require('express'); // 4.12.3
var mongo = require('mongodb'); // 2.0.31
var Grid = require('gridfs-stream'); // 1.1.1"
var app = express();
var server = app.listen(9002);

var db = new mongo.Db('test', new mongo.Server('127.0.0.1', 27017));
var gfs;
db.open(function(err, db) 
  if (err) throw err;
  gfs = Grid(db, mongo);
);

app.post('/file', function(req, res) 
  var busboy = new Busboy( headers : req.headers );
  var fileId = new mongo.ObjectId();

  busboy.on('file', function(fieldname, file, filename, encoding, mimetype) 
    console.log('got file', filename, mimetype, encoding);
    var writeStream = gfs.createWriteStream(
      _id: fileId,
      filename: filename,
      mode: 'w',
      content_type: mimetype,
    );
    file.pipe(writeStream);
  ).on('finish', function() 
    // show a link to the uploaded file
    res.writeHead(200, 'content-type': 'text/html');
    res.end('<a href="/file/' + fileId.toString() + '">download file</a>');
  );

  req.pipe(busboy);
);

app.get('/', function(req, res) 
  // show a file upload form
  res.writeHead(200, 'content-type': 'text/html');
  res.end(
    '<form action="/file" enctype="multipart/form-data" method="post">'+
    '<input type="file" name="file"><br>'+
    '<input type="submit" value="Upload">'+
    '</form>'
  );
);

app.get('/file/:id', function(req, res) 
  gfs.findOne( _id: req.params.id , function (err, file) 
    if (err) return res.status(400).send(err);
    if (!file) return res.status(404).send('');

    res.set('Content-Type', file.contentType);
    res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"');

    var readstream = gfs.createReadStream(
      _id: file._id
    );

    readstream.on("error", function(err) 
      console.log("Got error while processing stream " + err.message);
      res.end();
    );

    readstream.pipe(res);
  );
);

【问题讨论】:

【参考方案1】:

看起来文件已通过 HTML 表单上传,在这种情况下,您需要解码 multipart/form-data 编码数据,如果需要重新组装部件并将文件保存到 GridFS。对于解析,您可以使用 node-multiparty 之类的内容。

【讨论】:

谢谢!这就是我需要的。我在我的问题中添加了一些更新的代码,显示了我如何集成节点多方。 我觉得我说得太早了。使用我在上面添加的最新代码,在上传文件的过程中,然后下载它,文件编码正在改变。我正在尝试使用 PDF 和 JPEG。如果我将原始副本作为文本与已上传然后下载的副本进行比较,则每个正文中的字符之间存在很多差异。有什么想法吗? 我用工作代码更新了我的问题。我最终使用了busboy,但多方可能也可以。【参考方案2】:

请参阅我对您在 github 上创建的问题的评论。我有同样的问题,但我设法调试了这个问题。我把它缩小到我确信问题是一个快速中间件修改了请求的地方。我一一禁用了我的中间件,直到我找到了不太可能的罪魁祸首:connect-livereload

我注释掉了 app.use(require('connect-livereload')());问题就消失了。 我相信它将 livereload 脚本注入响应(二进制图像文件)。

【讨论】:

因为这是最终导致我原来的问题的原因,所以我将其标记为正确答案。

以上是关于Node.js 文件上传(Express 4、MongoDB、GridFS、GridFS-Stream)的主要内容,如果未能解决你的问题,请参考以下文章

Node.js与Express联合中间件express-fileupload使用axios实现文件上传

node.js框架express文件上传操作

如何使用 node.js、Express 和 knox 将文件从浏览器上传到 Amazon S3? [关闭]

使用 Express 和 Node JS 在 MongoDB 中上传文本文件

上传图片文件到服务器node.js express axios

又拍云 Node.js 实现文件上传删除