Meteor:从客户端上传文件到 Mongo 集合 vs 文件系统 vs GridFS

2024-01-24

Meteor 很棒,但它缺乏对传统文件上传的原生支持。有多种选项可以处理文件上传:

来自客户,可以使用以下方式发送数据:

  • Meteor.call('saveFile',data) 或 collection.insert({file:data})
  • 'POST' 形式或 HTTP.call('POST')

在服务器中,文件可以保存到:

  • 通过 collection.insert({file:data}) 得到的 mongodb 文件集合
  • /path/to/dir 中的文件系统
  • MongoDB GridFS

这些方法的优点和缺点是什么以及如何最好地实施它们?我知道还有其他选项,例如保存到第三方网站并获取网址。


您可以使用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})         
    }   
});

解释

首先,使用 HTML5 文件 API 从输入中获取文件。使用新的 FileReader 创建读取器。该文件被读取为 readAsArrayBuffer。如果您console.log,此数组缓冲区将返回{},并且DDP无法通过网络发送此数据,因此必须将其转换为Uint8Array。

当您将其放入 Meteor.call 中时,Meteor 会自动运行 EJSON.stringify(Uint8Array) 并使用 DDP 发送它。你可以在chrome控制台websocket流量中查看数据,你会看到类似base64的字符串

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

Pros

  1. 简单,没有hacky方式,没有额外的包
  2. 坚持线上数据原则

Cons

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

选项2:XHR,从客户端发布到文件系统

/*** 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
});

解释

抓取客户端中的文件,创建 XHR 对象,然后通过“POST”将文件发送到服务器。

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

Pros

  1. 利用 XHR 2,您可以发送 arraybuffer,与选项 1 相比,不需要新的 FileReader()
  2. 与 Base64 字符串相比,Arraybuffer 体积更小
  3. 没有大小限制,我在本地主机中发送了约 200 MB 的文件,没有任何问题
  4. 文件系统比 mongodb 更快(稍后将在下面的基准测试中详细介绍)
  5. 可缓存和 gzip

Cons

  1. XHR 2 在较旧的浏览器中不可用,例如IE10以下,但当然可以实现传统的 post
    我只使用了 xhr = new XMLHttpRequest(),而不是 HTTP.call('POST') 因为目前 Meteor 中的 HTTP.call 还不能发送 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);
});     

解释

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

根据 Meteor 1.0.xmongo_driver.js https://github.com/meteor/meteor/blob/devel/packages/mongo/mongo_driver.js最后一行,暴露了一个名为 MongoInternals 的全局对象,您可以调用 defaultRemoteCollectionDriver() 返回 GridStore 所需的当前数据库 db 对象。在版本 A 中,GridStore 也由 MongoInternals 公开。目前meteor使用的mongo是v1.4.x

然后在路由中,您可以通过调用 var file = new GridStore(...) (API http://mongodb.github.io/node-mongodb-native/1.4/markdown-docs/gridfs.html)。然后,您打开该文件并创建一个流。

我还包含了版本 B。在此版本中,通过 Npm.require('mongodb') 使用新的 mongodb 驱动器调用 GridStore,此 mongo 是截至撰写本文时最新的 v2.0.13。新的API http://mongodb.github.io/node-mongodb-native/2.0/tutorials/gridfs/不需要你打开文件,你可以直接调用stream(true)并开始管道

Pros

  1. 与选项 2 相同,使用 arraybuffer 发送,与选项 1 中的 base64 字符串相比开销更少
  2. 无需担心文件名清理问题
  3. 与文件系统分离,无需写入临时目录,数据库可以备份、重复、分片等
  4. 无需实现任何其他包
  5. 可缓存且可压缩
  6. 与普通 mongo 集合相比,存储更大的尺寸
  7. 使用管道减少内存过载

Cons

  1. 不稳定的 Mongo GridFS。我包含版本 A (mongo 1.x) 和 B (mongo 2.x)。在版本 A 中,当管道大于 10 MB 的大文件时,我遇到了很多错误,包括文件损坏、管道未完成。这个问题在版本B中使用mongo 2.x解决了,希望meteor很快升级到mongodb 2.x
  2. API混乱。在版本 A 中,您需要先打开文件才能进行流式传输,但在版本 B 中,您无需调用 open 即可进行流式传输。 API 文档也不是很清楚,并且流不能 100% 与 Npm.require('fs') 进行语法交换。在 fs 中,您调用 file.on('finish') 但在 GridFS 中,您在写入完成/结束时调用 file.on('end') 。
  3. GridFS不提供写入原子性,因此如果对同一个文件有多个并发写入,最终结果可能会有很大不同
  4. Speed。 Mongo GridFS 比文件系统慢得多。

基准您可以在选项 2 和选项 3 中看到,我包含了 var start = Date.now() ,并且在写入 end 时,我 console.log 输出了时间ms,下面是结果。双核、4 GB 内存、硬盘、基于 ubuntu 14.04。

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) 更为常见。

结论

这绝不是全面的,但您可以决定哪个选项最适合您的需求。

  • DDP是最简单的,遵循 Meteor 的核心原理,但数据体积更大,传输过程中不可压缩,不可缓存。但如果您只需要小文件,此选项可能会很好。
  • XHR 与文件系统结合是“传统”方式。稳定的 API、快速、“可流式传输”、可压缩、可缓存(ETag 等),但需要位于单独的文件夹中
  • XHR 与 GridFS 结合,您将获得重复集、可扩展、不接触文件系统目录、大文件和许多文件(如果文件系统限制数量)的好处,还可以缓存可压缩。然而,API 不稳定,多次写入都会出错,s..l..o..w..

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Meteor:从客户端上传文件到 Mongo 集合 vs 文件系统 vs GridFS 的相关文章

  • 无法上传大于 8MB 的文件

    我正在尝试制作一个文件上传脚本 并且我已经为这个问题苦苦挣扎了一段时间 我已阅读并尝试了与此相关的所有答案 但无济于事 这是我在 php 中尝试过的 文件名 file uploads On upload max filesize 100M
  • mongodb 通配符匹配特定键的所有值[重复]

    这个问题在这里已经有答案了 我试图弄清楚如何匹配一个键并返回该键的所有值 是否可以将值作为通配符给出 我想在值上使用通配符返回该特定键的所有内容 db collection find key 我还希望这也能返回整个集合 其中的键也与通配符值
  • 如何在猫鼬中使用聚合

    如何在 mongoose 中定义以下 MongoDB 聚合查询 db contacts aggregate group id code Code name Name 查询的目的是获取不同代码和名称的列表 我当前的模型代码是 use stri
  • 从 JSON 字符串创建 BSON 对象

    我有一个 Java 应用程序 它从外部应用程序获取数据 传入的 JSON 位于字符串中 我想解析该字符串并创建 BSON 对象 不幸的是 我在 Java 的 BSON 实现中找不到该 API 我是否需要使用外部解析器 例如 GSON 而且
  • 为什么我在 Nodejs 中收到“在将标头发送到客户端后无法设置标头”错误?

    我正进入 状态 Cannot set headers after they are sent to the clientNodejs 中出现错误 我无法弄清楚原因 代码如下 我正在使用 mongoose 将数据保存在 mongodb 中 我
  • Mongoose:转换为 ObjectId 失败

    我正在尝试在 MongoDB 中创建一个类别层次结构 以便通过 Mongoose 与 Node js 一起使用 我正在使用祖先数组方法 http docs mongodb org manual tutorial model tree str
  • 在 mongodb 中更新异步重复数据的最佳实践

    我正在权衡 Web 应用程序从关系 SQL 数据库迁移到 mongodb 的利弊 这是为了解决性能问题 将所有对象依赖项存储在对象本身中允许快速 读 用于向用户显示数据 另一方面 某些数据存在于不同的集合中 例如用户名位于用户集合中 但也在
  • 使用 PHP 将文件上传到 MySql DB

    我希望用户通过我在后端使用 MySql 用 PHP 开发的 web 应用程序上传文件 我想将文件存储在数据库中 我在这样做时遇到了问题 此外 一旦文件存储在数据库中 我们如何下载它 并在 web 应用程序中正确显示它 文件类型和文件的其他属
  • Jquery 文件上传 - 如何限制上传的文件数量

    我正在尝试从 blueimp 进行 Jquery 文件上传 该文档说我应该能够使用 maxNumberOfFiles 设置限制要上传的文件数量 然而 这似乎不适用于我的情况 其他诸如acceptFileTypes loadImageMaxF
  • MongoDB 将数字转换为科学计数法的字符串

    我想获得完整的号码String 但反而 1490650000000 它返回科学计数法 1 49065e 12 这是我尝试转换它的方法 substr myNumber 0 1 有什么想法如何预防吗 Note 我使用的是v3 6 无法升级使用
  • 如何使用socket.io发送图像文件(二进制数据)?

    我无法从以下位置发送数据Android Client to NodeJS Server I use Socket IO 客户端 https github com socketio socket io client java我的客户端中的ja
  • Mongoose 4.4.12 中 Schema 方法范围内的“this”为空 {}

    当在 Schema 方法内记录到控制台时 对象 this 为 这发生在一天前 我一直在阅读教程和其他堆栈溢出问题 但不幸的是我没有找到原因的解决方案 这是我的模型 var mongoose require mongoose var Sche
  • 环回关系不填充对象 ID 数组

    到目前为止我有 2 个模型 工作流程核心 工作流程步骤 工作流核心有一个steps属性 该属性是数组类型并且包含1 多个步骤 当呼叫接通时工作流程核心响应正文不会使用实际步骤对象填充步骤数组 工作流程核心 json name workflo
  • 如何修复 MongoClient is not a constructor 错误

    我刚刚学习 JavaScript 和 Nodejs 根据我在网上找到的一些代码 我编写了以下应用程序 当我尝试运行它时 我在第 9 行收到错误 其中显示 new MongoClient 错误提示 MongoClient 不是构造函数 您能解
  • 如何在两个或多个 Heroku dyno 上运行 Meteor 应用程序?

    我有 Meteor 应用程序 它使用 1 dyno 在 Heroku 平台上运行 当我增加测功机数量时 它会停止工作 并在客户端报告一些 XHR 问题 错误 404 有人在两个或更多 Heroku dyno 上成功运行 Meteor 应用程
  • 在 shell/shell 脚本中设置 MongoDB 写关注

    我正在尝试填充一个集合MongoDB的壳 据我了解 使用轻松的Write Concern可以大大加快这个过程 我说的是文档 http docs mongodb org manual core write concern write oper
  • 将 Django +1.10 与 MongoDB 连接

    在过去的几个月里 有人为 MongoDB 更换了 Django 1 10 中的默认数据库引擎吗 我在谷歌上得到的所有信息都是六四年前的 最常见的结果包括mongodb 引擎这需要Django nonrel 来自 Django 1 5 的一个
  • mongo BadValue 未知运算符:$or

    该集合有一份文档 id ObjectId 54b513933aca242d9915a787 carriers carrier ObjectId 54b54d223aca242d9915a788 carryingInterval from I
  • 为关联数组选择哪种映射类型?学说ODM

    我有一个关于 顺便说一句 真的很棒 Doctrine ODM 的简单问题 假设您有一个类似以下的文档 Document class Test Id public id WHICHTYPE public field array 现在我想存储一
  • 将视频上传/保存到数据库或文件系统

    我以前从未尝试过保存视频 所以我对此了解不多 我知道如果视频很小 我可以转换为字节数组并保存到数据库 但是为了提高效率 我想了解如何将任何上传的视频保存到我的服务器文件中 然后只保存该文件的文件路径我的数据库表中的视频 我完全不知道如何开始

随机推荐