Tengo una función de exportación que lee toda la base de datos y crea un archivo .xls con todos los registros. A continuación, el archivo se envía al cliente.
Por supuesto, el momento de exportar la base de datos completa requiere mucho tiempo y la solicitud terminará pronto en un error de tiempo de espera.
¿Cuál es la mejor solución para manejar este caso?
Escuché algo sobre hacer una cola con Redis, por ejemplo, pero esto requerirá dos solicitudes: una para iniciar el trabajo que generará el archivo y la segunda para descargar el archivo generado.
¿Es esto posible con una sola solicitud del cliente?
Exportación de Excel:
Utilice flujos . Lo que sigue es una idea aproximada de lo que podría hacerse:
Utilice el módulo exceljs . Porque tiene una API de transmisión dirigida a este problema exacto.
var Excel = require('exceljs')Ya que estamos tratando de iniciar una descarga. Escriba encabezados apropiados para la respuesta.
res.status(200); res.setHeader('Content-disposition', 'attachment; filename=db_dump.xls'); res.setHeader('Content-type', 'application/vnd.ms-excel');Cree un libro de trabajo respaldado por Streaming Excel Writer . El flujo dado al escritor es la respuesta del servidor.
var options = { stream: res, // write to server response useStyles: false, useSharedStrings: false }; var workbook = new Excel.stream.xlsx.WorkbookWriter(options);Ahora, el flujo de transmisión de salida está configurado. para la transmisión de entrada, prefiera un controlador de base de datos que brinde resultados de consulta/cursor como una transmisión.
Defina una función asíncrona que vuelque 1 tabla en 1 hoja de trabajo.
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); }); }Ahora, exportemos algunas tablas de db, usando mapSeries del módulo asíncrono:
async.mapSeries(['cars','planes','trucks'],tableToSheet,function(err){ if(err){ // log error } res.end(); })Exportación CSV:
Para la exportación a CSV de un solo módulo de tabla/colección, se puede usar 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);Ahora está transmitiendo datos de la base de datos a la respuesta HTTP, convirtiéndolos al formato xls/csv sobre la marcha. No es necesario almacenar en búfer o almacenar todos los datos en la memoria o en un archivo.
No es necesario que envíe el archivo completo una vez, puede enviar este archivo por fragmentos (línea por línea, por ejemplo), simplemente use res.write(chunk) y res.end() al finalizar para marcarlo como completado.
Puede enviar la información del archivo como una secuencia, enviando cada fragmento individual a medida que se crea a través res.write(chunk) o, si enviar el archivo fragmento por fragmento no es una opción y tiene que esperar el archivo completo antes de enviar cualquier información, siempre puede mantener abierta la conexión configurando la duración del tiempo de espera en Infinito o cualquier valor que crea que será lo suficientemente alto para permitir que se cree el archivo. Luego configure una función que cree el archivo .xls y:
1) Acepta una devolución de llamada que recibe la salida de datos como un argumento una vez que está lista, envía esos datos y luego cierra la conexión, o;
2) Devuelve una promesa que se resuelve con la salida de datos una vez que está lista, lo que le permite enviar el valor resuelto y cerrar la conexión al igual que con la versión de devolución de llamada.
Se vería algo como esto:
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)) } Si aún le preocupa el tiempo de espera, siempre puede configurar un temporizador de intervalo para enviar un mensaje res.write() ocasional para evitar un tiempo de espera en la conexión del servidor y luego cancelar ese intervalo una vez que el contenido final del archivo esté listo para ser enviado .