9

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

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

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

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

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

4

4 回答 4

15

Excel 导出:

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

  1. 使用exceljs 模块。因为它有一个针对这个确切问题的流 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 格式。无需在内存或文件中缓冲或存储整个数据。

于 2017-04-28T08:36:09.817 回答
0

请参阅使用 jedis(redis java 客户端)的此链接 关键是 LPOPRPUSH 命令

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

于 2017-05-02T19:48:31.137 回答
0

您可以将文件信息作为流发送,在通过创建每个单独的块时发送它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()消息,以防止服务器连接超时,然后在最终文件内容准备好发送时取消该间隔。

于 2017-04-27T19:49:24.423 回答
0

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

于 2017-04-27T13:57:46.857 回答