Meteor很棒,但它缺乏对传统文件上传的本地支持.有几个选项可以处理文件上载:

From the client,可以使用以下方式发送数据:

  • Meteor 调用('saveFile',data)或集合.插入({file:data})
  • "POST"表单或HTTP.电话(‘POST’)

In the server,文件可以保存到:

  • 一个接一个的mongodb文件集合.插入({file:data})
  • 文件系统位于/path/to/dir中
  • mongodb GridFS

这些方法的优缺点是什么?如何最好地实施它们?我知道还有其他 Select ,比如保存到第三方网站并获取url.

推荐答案

你可以通过Meteor实现文件上传,无需再使用任何软件包或第三方

选项1:DDP,将文件保存到mongo集合

/*** client.js ***/

// asign a change event into input tag
'change input' : function(event,template){ 
    var file = event.target.files[0]; //assuming 1 file only
    if (!file) return;

    var reader = new FileReader(); //create a reader according to HTML5 File API

    reader.onload = function(event){          
      var buffer = new Uint8Array(reader.result) // convert to binary
      Meteor.call('saveFile', buffer);
    }

    reader.readAsArrayBuffer(file); //read the file as arraybuffer
}

/*** server.js ***/ 

Files = new Mongo.Collection('files');

Meteor.methods({
    'saveFile': function(buffer){
        Files.insert({data:buffer})         
    }   
});

Explanation

首先,使用HTML5文件API从输入中获取文件.读卡器是使用新的FileReader创建的.该文件被读取为readAsArrayBuffer.这是arraybuffer,如果你愿意的话.log,返回{},DDP无法通过网络发送,因此必须将其转换为Uint8Array.

当你把这个放进Meteor 的时候.呼叫,Meteor自动运行EJSON.stringify(Uint8Array)并用DDP发送.你可以在chrome控制台websocket流量中查看数据,你会看到一个类似base64的字符串

在服务器端,Meteor呼叫EJSON.parse()并将其转换回缓冲区

Pros

  1. 简单,没有黑客手段,没有额外的软件包
  2. 坚持导线原理上的数据

Cons

  1. 更多带宽:生成的base64字符串比原始文件大约33%
  2. 文件大小限制:无法发送大文件(限制~16 MB?)
  3. 没有缓存
  4. 还没有gzip或压缩
  5. 如果发布文件,会占用大量内存

选项2:XHR,从客户端到文件系统的post

/*** client.js ***/

// asign a change event into input tag
'change input' : function(event,template){ 
    var file = event.target.files[0]; 
    if (!file) return;      

    var xhr = new XMLHttpRequest(); 
    xhr.open('POST', '/uploadSomeWhere', true);
    xhr.onload = function(event){...}

    xhr.send(file); 
}

/*** server.js ***/ 

var fs = Npm.require('fs');

//using interal webapp or iron:router
WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
    //var start = Date.now()        
    var file = fs.createWriteStream('/path/to/dir/filename'); 

    file.on('error',function(error){...});
    file.on('finish',function(){
        res.writeHead(...) 
        res.end(); //end the respone 
        //console.log('Finish uploading, time taken: ' + Date.now() - start);
    });

    req.pipe(file); //pipe the request to the file
});

Explanation

抓取客户端中的文件,创建一个XHR对象,并通过"POST"将文件发送到服务器.

在服务器上,数据通过管道传输到底层文件系统.您还可以在保存前确定文件名、执行清理或判断文件是否已存在等.

Pros

  1. 利用XHR 2,您可以发送arraybuffer,与选项1相比,不需要新的FileReader()
  2. 与base64字符串相比,Arraybuffer的体积更小
  3. 没有大小限制,我在localhost中发送了一个约200 MB的文件,没有问题
  4. 文件系统比mongodb更快(下文的基准测试将介绍更多这方面的内容)
  5. Cachable和gzip

Cons

  1. XHR 2在较旧的浏览器中不可用,例如IE10以下的浏览器,但您当然可以实现传统的post<;表格>;我只使用了xhr=new XMLHttpRequest(),而不是HTTP.调用('POST'),因为当前HTTP.呼叫Meteor尚未发送arraybuffer(如果我错了,请告诉我).
  2. /path/to/dir/必须在meteor之外,否则在/public中写入文件会触发重新加载

选项3:XHR,保存到GridFS

/*** client.js ***/

//same as option 2


/*** version A: server.js ***/  

var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var GridStore = MongoInternals.NpmModule.GridStore;

WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
    //var start = Date.now()        
    var file = new GridStore(db,'filename','w');

    file.open(function(error,gs){
        file.stream(true); //true will close the file automatically once piping finishes

        file.on('error',function(e){...});
        file.on('end',function(){
            res.end(); //send end respone
            //console.log('Finish uploading, time taken: ' + Date.now() - start);
        });

        req.pipe(file);
    });     
});

/*** version B: server.js ***/  

var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var GridStore = Npm.require('mongodb').GridStore; //also need to add Npm.depends({mongodb:'2.0.13'}) in package.js

WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
    //var start = Date.now()        
    var file = new GridStore(db,'filename','w').stream(true); //start the stream 

    file.on('error',function(e){...});
    file.on('end',function(){
        res.end(); //send end respone
        //console.log('Finish uploading, time taken: ' + Date.now() - start);
    });
    req.pipe(file);
});     

Explanation

客户端脚本与选项2中的相同.

根据Meteor 1.0.x mongo_driver.js最后一行,一个名为MongoInternals的全局对象被公开,您可以调用defaultRemoteCollectionDriver()来返回GridStore所需的当前数据库db对象.在版本A中,GridStore也由MongoInternals公开.当前Meteor 使用的mongo是v1.4.x

然后在路由内部,可以通过调用var file=new GridStore(…)来创建一个新的写对象(API). 然后打开文件并创建一个流.

我还包括了一个版本B.在这个版本中,GridStore通过Npm使用一个新的mongodb驱动器来调用.require('mongodb'),这个mongo是最新的v2.截至本文 compose 时为0.13.新的API不需要您打开文件,您可以直接调用stream(true)并启动管道

Pros

  1. 与选项2相同,使用arraybuffer发送,与选项1中的base64字符串相比,开销更小
  2. 无需担心文件名清理
  3. 与文件系统分离,无需写入临时目录,数据库可以备份、rep、shard等
  4. 无需实施任何其他方案
  5. 可计算且可压缩
  6. 与普通mongo系列相比,可储存更大的尺码
  7. 使用管道减少内存过载

Cons

  1. Unstable Mongo GridFS. I included version A (mongo 1.x) and B (mongo 2.x). In version A, when piping large files > 10 MB, I got lots of error, including corrupted file, unfinished pipe. This problem is solved in version B using mongo 2.x, hopefully meteor will upgrade to mongodb 2.x soon
  2. API confusion.在版本A中,您需要先打开文件,然后才能进行流式处理,但在版本B中,您可以在不调用open的情况下进行流式处理.API文档也不是很清楚,流不是API confusion%可与Npm进行语法交换的.require('fs').在fs中,调用file.在('finish')上,但在GridFS中,您调用file.当书写完成/结束时打开('end').
  3. GridFS不提供写原子性,因此如果对同一个文件有多个并发写操作,最终结果可能会非常不同
  4. Speed.Mongo GridFS比文件系统慢得多.

Benchmark

file size   GridFS  FS
100 KB      50      2
1 MB        400     30
10 MB       3500    100
200 MB      80000   1240

你可以看到FS比GridFS快得多.对于一个200 MB的文件,使用GridFS大约需要80秒,而在FS中只需要1秒.我还没试过SSD,结果可能不一样.然而,在现实生活中,带宽可能会决定文件从客户端传输到服务器的速度,达到200 MB/秒的传输速度并不常见.另一方面,传输速度~2 MB/秒(GridFS)更为常见.

Conclusion

这并不全面,但你可以决定哪种 Select 最适合你的需要.

  • DDP是最简单的,并坚持Meteor 核心原理,但数据更庞大,在传输过程中不可压缩,不可计算.但是如果你只需要小文件,这个选项可能会很好.
  • XHR coupled with file system是"传统"的方式.稳定的API、快速的、可流动的、可压缩的、可缓存的(ETag等),但需要放在单独的文件夹中
  • XHR coupled with GridFS,您可以获得rep set的好处,可扩展,不接触文件系统目录,大文件和许多文件,如果文件系统限制数量,也可压缩.然而,API是不稳定的,你会在多次写入中出错,这是..Lo、 ..W

希望很快,meteor DDP可以支持gzip、缓存等,GridFS可以达到faster...

Mongodb相关问答推荐

使用mongosh将大型json文件插入到mongo集合中

如何在MongoDB中查找和过滤嵌套数组

从MongoDB中的一个非空字段获取值

MongoDB DB参考返回null值

根据聚合管道MongoDB Atlas触发器中的条件更新多个字段

$group 和 sum + 添加所有大于

如何将以下聚合查询转换为 bson 并在 golang 中执行

mongoDB 过滤、排序和排名结果

嵌套数组 $unwind 和 $group 在 mongoDB 中重新组合在一起

Mongodb按值分组并计算出现次数

在mongoose中添加多个验证

如何将查询结果(单个文档)存储到变量中?

用 BsonRepresentation(BsonType.ObjectId) vs BsonId vs ObjectId 在 C# 中装饰属性之间的区别

MongoDB $或PHP中的查询

为多个日期范围聚合 $group

无法从 MongoDb C# 中的 BsonType ObjectId 反序列化字符串

有没有办法自动更新 MongoDB 中的两个集合?

MongoDB 和 Robomongo: Can't connect (authentication)

Mongodb 按字段名称查找任何值

MongoError:Can't extract geo keys from object