使用Meteor.js从服务器下载文件

4

以下是我的工作流程:

在按钮点击事件中,我将搜索结果导出为 .csv 文件,并保存到服务器。一旦文件保存完毕,我希望将其发送到浏览器进行下载。使用这个问题《如何处理 Meteor.js 中的条件文件下载》,我创建了一个方法,在保存文件的方法返回后调用该方法。以下是该方法:

exportFiles: function(file_to_export) {
    console.log("to export = "+file_to_export);
    Meteor.Router.add('/export', 'GET', function() {
        console.log('send '+file_to_export+' to browser');
        return [200,
        {
           'Content-type': 'text/plain',
           'Content-Disposition': "attachment; filename=" + this.request.query.file
        }, fs.readFileSync( save_path + this.request.query.file )];
    });
}

然而,我的问题是如何调用该路由?使用.Router.to('/export?file=filename.ext')不起作用,并导致用户离开当前页面。我希望这对用户来说是无缝的,并且我不希望他们知道自己被重定向了。在任何人问之前,save_path在方法外声明,因此它确实存在。

2个回答

6
我懂了!但是需要使用一些额外的软件包。首先,让我更清楚地描述一下工作流程:

我们网站上的用户执行搜索。在随后的搜索结果页面上,存在一个按钮,允许用户将其搜索结果导出到.csv文件中。然后将文件导出到浏览器以进行下载。

我们关心的一个问题是,如果文件被写入服务器,确保只有导出文件的用户能够查看文件。为了控制谁能看到文件,我使用了Meteorite包,CollectionFS(mrt add collectionFS或从github克隆)。该包将文件缓冲区写入Mongo集合。在保存时提供“所有者”字段可以控制访问权限。

无论文件是如何创建的,无论是通过上传表单保存到服务器还是像我使用json2csv程序包生成的那样动态生成,文件都必须作为缓冲区流式传输到CollectionFS。

var userId = Meteor.userId()
var buffer = Buffer(csv.length);  //csv is a var holding the data for write
var filename = "name_of_file.csv";
for ( var i=0; i<csv.length; i++ ) {
  buffer[i] = csv.charCodeAt(i);
}
CollectionFS.storeBuffer(filename, buffer, {
    contentType: 'text/plain',
    owner: userId
});

现在,我已经将数据文件作为缓冲区流式传输到mongo集合中。由于我的数据存在于变量csv的内存中,所以通过循环每个字符来将其作为缓冲区流式传输。如果这是保存在物理磁盘上的文件,我将使用fs.readFileSync(file)并发送返回的缓冲区到CollectionFS.storeBuffer()。
现在,文件已经以缓冲区的形式保存在带有所有者的mongo中,我可以通过限制发布CollectionFS集合的方式来下载/更新/删除文件,甚至不让知道文件的存在。
为了从mongo中读取文件并将文件发送到浏览器进行下载,需要另一个Javascript库:FileSaver(github)。
使用CollectionFS的retrieveBlob方法,通过提供在mongo集合中引用文件的_id,将您的文件作为blob从mongo中提取出来。 FileSaver有一个saveAs方法,接受blob,并将其导出到浏览器以指定的文件名进行下载。
var file = // file object stored in meteor
CollectionFS.retrieveBlob(file._id, function(fileItem) {
    if ( fileItem.blob ) saveAs(fileItem.blob, file.filename);
    else if ( fileItem.file ) saveAs(fileItem.file, file.filename);
});

我希望有人会发现这个有用!


它是否适用于外部服务上文件的直接链接,还是只适用于文本文件? - mhlavacka
假设您能够读取文件并将其作为缓冲区流传递给CollectionFS,那么外部服务上的文件链接应该可以正常工作。 - SpacePope

0
如果您的路由正常工作,当方法返回时,您可以打开一个包含文本文件链接的新窗口。
您已经添加了内容分发标头,因此该文件应始终要求保存。
即使您只是重定向到该文件,因为它具有这些内容分发标头,它也会要求保存并不会中断您的会话。

谢谢回复!是的,看起来应该是这样的。我在第2行上有一个console.log(),所以我知道该方法已经被调用了。然而,在方法返回后,我尝试做一个Meteor.Router.to(/export?file=filename.ext'),它会重定向到一个空页面,并且没有下载对话框出现。你有什么线索吗?我做错了什么吗? - SpacePope
也许你不需要在方法块内使用 Meteor.Router.add?毕竟你只需要运行一次,因为你传递了 file=xx。它可以在 Meteor 启动时运行,然后直接重定向到文件,而无需 method/call。如果需要,你还可以通过查询字符串检查是否经过身份验证。 - Tarang

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