【问题标题】:How to make the client download a very large file that is genereted on the fly如何使客户端下载动态生成的非常大的文件
【发布时间】:2017-09-22 06:36:44
【问题描述】:

我有一个导出函数,可以读取整个数据库并创建一个包含所有记录的 .xls 文件。然后将文件发送给客户端。

当然,导出全库的时间需要很多时间,请求很快就会以超时错误结束。

处理这种情况的最佳解决方案是什么?

我听说过一些关于使用 Redis 创建队列的事情,但这需要两个请求:一个用于启动将生成文件的作业,第二个用于下载生成的文件。

这可以通过来自客户端的单个请求来实现吗?

【问题讨论】:

    标签: node.js redis queue jobs kue


    【解决方案1】:

    Excel 导出:

    使用Streams。以下是可能会做的粗略想法:

    1. 使用exceljs module。因为它有一个针对这个确切问题的streaming API。

      var Excel = require('exceljs')
      
    2. 因为我们正在尝试启动下载。为响应写入适当的标头。

      res.status(200);
      res.setHeader('Content-disposition', 'attachment; filename=db_dump.xls');
      res.setHeader('Content-type', 'application/vnd.ms-excel');
      
    3. 创建一个由Streaming Excel writer 支持的工作簿。给 writer 的流是服务器响应。

      var options = {
          stream: res, // write to server response
          useStyles: false,
          useSharedStrings: false
      };
      
      var workbook = new Excel.stream.xlsx.WorkbookWriter(options);
      
    4. 现在,输出流式处理流程已全部设置完毕。对于输入流,更喜欢将查询结果/光标作为流提供的 DB 驱动程序。

    5. 定义一个将 1 个表转储到 1 个工作表的异步函数。

      var tableToSheet = function (name, done) {
          var str = dbDriver.query('SELECT * FROM ' + name).stream();
          var sheet = workbook.addWorksheet(name);
      
          str.on('data', function (d) {
              sheet.addRow(d).commit(); // format object if required
          });
      
          str.on('end', function () {
              sheet.commit();
              done();
          });
      
          str.on('error', function (err) {
              done(err);
          });
      }
      
    6. 现在,让我们使用async 模块的mapSeries 导出一些数据库表:

      async.mapSeries(['cars','planes','trucks'],tableToSheet,function(err){
         if(err){
           // log error
         }
         res.end();
      })
      

    CSV 导出:

    对于单个表/集合模块的 CSV 导出,可以使用 fast-csv:

    // response headers as usual
    res.status(200);
    res.setHeader('Content-disposition', 'attachment; filename=mytable_dump.csv');
    res.setHeader('Content-type', 'text/csv');
    
    // create csv stream
    var csv = require('fast-csv');
    var csvStr = csv.createWriteStream({headers: true});
    
    // open database stream
    var dbStr = dbDriver.query('SELECT * from mytable').stream();
    
    // connect the streams
    dbStr.pipe(csvStr).pipe(res);
    

    您现在正在将数据从 DB 流式传输到 HTTP 响应,并即时将其转换为 xls/csv 格式。无需在内存或文件中缓冲或存储整个数据。

    【讨论】:

    • @S.D.谢谢!如果文件可以逐行写入呢?例如 .CSV 文件?
    • @S.D 我正在使用类似的代码,我只需要 xlsx 中的一张工作表,但似乎在完全添加(提交)工作表时发送块,而不是在完全添加(提交)行时发送块。如果我在任何行开始添加到工作表时就开始获取数据会更好。
    • @RaghuChahar 这必须得到 exceljs 模块实现的支持。看起来你已经already asked there。
    • @S.D.在这里没有得到任何回复后,我就打开了那个问题,请你帮帮我!
    • @S.D.你有什么办法解决这个问题吗?由于 sequelize 不适用于流
    【解决方案2】:

    您不必一次发送整个文件,您可以将该文件分块发送(例如逐行发送),只需在完成时使用res.write(chunk) 和res.end() 将其标记为已完成。

    【讨论】:

    • 这样可以发送给客户端吗,比如.csv文件?将是一个文件还是只是一个包含文本的浏览器页面?
    • 对,xls 有点问题。这个想法是 - 每 25-30 秒向客户端发送一些小东西,然后当文件准备好时我们实际发送它,在这种情况下 TIMEOUT 不会发生。但是您不能通过块生成 XLS :(。您应该将下载分为 2 个部分。1) 客户端请求文件,您将一些任务号返回给客户端并开始工作。 2) 客户询问任务编号是否完成。如果是,则返回静态文件的 http url,如果不是,客户端应在 X 秒内再次询问。
    【解决方案3】:

    您可以将文件信息作为流发送,发送通过res.write(chunk) 创建的每个单独的块,或者,如果不能逐块发送文件,您必须等待整个文件在发送任何信息之前,您始终可以通过将超时持续时间设置为 Infinity 或您认为足以允许创建文件的任何值来保持连接打开。然后设置一个创建 .xls 文件的函数,然后:

    1) 接受一个回调,该回调在准备好后将数据输出作为参数接收,发送该数据,然后关闭连接,或者;

    2) 返回一个承诺,一旦数据输出准备就绪,它就会解析,允许您发送解析的值并关闭连接,就像使用回调版本一样。

    看起来像这样:

    function xlsRouteHandler(req, res){
      res.setTimeout(Infinity) || res.socket.setTimeout(Infinity)
    
      //callback version
      createXLSFile(...fileCreationArguments, function(finishedFile){
        res.end(finishedFile)
      })
    
      //promise version
      createXLSFile(...fileCreationArguments)
        .then(finishedFile => res.end(finishedFile))
    }
    

    如果您仍然担心超时,您始终可以设置一个间隔计时器来发送偶尔的res.write() 消息,以防止服务器连接超时,然后在最终文件内容准备就绪后取消该间隔已发送。

    【讨论】:

      【解决方案4】:

      参考这个使用jedis(redis java客户端)的链接 关键是 LPOPRPUSH 命令

      https://blog.logentries.com/2016/05/queuing-tasks-with-redis/

      【讨论】:

        猜你喜欢
        • 2021-09-08
        • 1970-01-01
        • 2016-10-23
        • 1970-01-01
        • 1970-01-01
        • 2013-11-23
        • 2011-11-10
        • 2014-09-04
        • 2010-12-31
        相关资源
        最近更新 更多