→ Существует множество хороших пакетов javascript, с помощью которых возможна загрузка и выгрузка файлов Excel, таких как exceljs, filedownloader, и это работало как шарм.

→ Лично я бы предпочел exceljs, когда дело доходит до загрузки excel. Доступно множество вариантов форматирования цвета.

→ Сначала мы реализовали загрузку данных с помощью exceljs для доменных приложений здравоохранения, в основном с большим объемом данных — названия лекарств, имена клиентов и т. д.

→ Мы загружали от 20 000 до 30 000 строк данных, и сервер занимал много времени. Иногда это занимало более 20 минут 😥

→ Нашей главной целью было сократить время загрузки. Этого удалось добиться с помощью csv-парсера.

Что отличает csv-parser?

→ Парсер Csv использует потоки Node JS для преобразования 90000 строк в секунду

→ Мы внедрили и были в шоке от таких положительных результатов ⚡

→ Давайте посмотрим, как это реализовать в Node.js

ШАГ 1. Установка пакета csv-parser

Использование npm:

$ npm install csv-parser // For Parsing Excel file
$ npm install multer     // For uploading file

Использование пряжи:

$ yarn add csv-parser
$ yarn add multer

ШАГ 2. Создайте маршруты:

Мы можем загрузить как один файл, так и несколько файлов.

var express = require("express");
var router = express.Router();
const upload = require("../common");
router.post("/users", common.uploadFile(importPath).upload, User.validateFile);
module.exports = router;

ШАГ 3. Контроллер для загрузки файла:

→ CSV Parser имеет в основном три конечных точки, использующие потоки узлов:

а. событие заголовка —здесь можно проверить заголовок файла Excel.

const readable =   fs.createReadStream(importedFilepath).pipe(csv(csvOption));
readable
.on('headers', async (headers) => {
// FIND CODE BELOW
})

б. событие данных —здесь данные парсинга excel

readable
.on('data', async (headers) => {
// FIND CODE BELOW
})

в. конечное событие — после парсинга и загрузки базы данных это событие запустится.

readable
.on('end', async (headers) => {
// FIND CODE BELOW
})

д. событие ошибки. Если возникнут какие-либо ошибки, будет запущен следующий код:

.on('error', err => {
// This executes when some error happens in csv-parser execution
console.log("NODE STREAM ERROR ===>", err);
  req.file.path ? fs.unlinkSync(req.file.path) : "";
return res.status(500).send({
    status: "error",
    message: err,
  });
});

Файл контроллера: app/src/server/controllers/User.js

const csv = require('csv-parser');
const fs = require("fs");
const { execArgv } = require('process');
// Import Controller of Node JS route
const validateFile = async (req, res) => {
  try {
    const importFilename = req.file.filename;
    const importedFilepath = req.file.path;
    const loggedinUser = parseInt(req.params.loggedinUser);
    if(importFilename && !importedFilepath) {
      if(!req.file.originalname.endsWith("csv")) {
        invalidData.push({
          row: "NA",
          column: "General",
          notes: "File should be in CSV Format
      })
      return res.send({ error: invalidData });
     }
// NEW CODE - CSV PARSER
const results = [];
let csvOption = {
  mapHeaders: ({ header, index }) => header.trim()
};
let uniqueData = [];
let err = [];
let start_date_highest = null;
let end_date_highest = null;
let fromYYYY = null;
let toYYYY = null;
let fromMM = null;
let toMM = null;
let dataRows = await new Promise((resolve, reject) => {
try {
  let index = 1;
  const readable =   fs.createReadStream(importedFilepath).pipe(csv(csvOption));
readable
.on('headers', async (headers) => {
 // We can validate headers of excel here - Validating excel columns
 try {
  const isValidExcel = await validImportColumn("MyFile", headers);
  if(!isValidExcel) {
    
  invalidData.push({
    row: "NA",
    column: "General",
    notes: "Invalid excel header name."
  });
  readable.destroy();
  return res.send({ err: invalidData })
  }
} catch (err) {
  console.log(err);
  readable.destroy();
  return res.send({ err: invalidData });
 }
})
.on('data', (eachData) => {
// Here we get data from the excel file
  let iterate = {
    country: eachData.country || "",
    id: eachData.id || "",
    customername1: eachData.customername1 || "",
    customername2: eachData.customername2 || "",
    phone: eachData.phone || "",
    address1: eachData.address1 || "",
    address2: eachData.address2 || "",
    status: eachData.status || "",
};
// Here we do all possible excel validation checks based on requirements
// Push in validData arr when row is correct
// Push in invalidData arr when some error found
// Push in updateData array when some data exists and need to be updated
// eg:
// invalidData.push({
//     row: "2",
//     column: "Empty Excel",
//     notes: "Excel has no data!"
// });
if(invalidData.length === 0) {
  // Insert data
  if(validData.length > 0) {
    // WRITE ADD TO DATABASE LOGIC
  }
  // Update data
  if(updateData.length > 0) {
    // WRITE UPDATE TO DATABASE LOGIC
  }
// Removing File once data is captured in database
req.file.path ? fs.unlinkSync(req.file.path) :"";
  return res.send({
    success: updateData.length
    ? validData.length + updateData.length
    : validData.length
  });
}
results.push(iterate);
})
.on('end', () => {
// This executes when data handling is done i.e. uploaded
  console.log("Parsing done ", results.length); 
  
  fromYYYY = moment(start_date_highest).format("YYYY");
  fromMM = moment(start_date_highest).format("MM");
  toYYYY = moment(start_date_highest).format("YYYY");
  toMM = moment(start_date_highest).format("MM");
  return resolve(results);
})
.on('error', err => {
  // This executes when some error happens in csv-parser execution
  console.log("NODE STREAM ERROR ===>", err);
  req.file.path ? fs.unlinkSync(req.file.path) : "";
  return res.status(500).send({
    status: "error",
    message: err,
  });
 });
}  catch (err) {
  req.file.path ? fs.unlinkSync(req.file.path) : "";
  return res.status(500).send({
    status: "error",
    message: err,
  });
 }
 });
}
} catch (err) {
  req.file.path ? fs.unlinkSync(req.file.path) : "";
  return res.status(500).send({
    status: "error",
    message: err,
  });
 }
}
const validateImportColumn = async(importType, columns) => {
  if (importType === "MyFile") {
    if (
      columns[0] = "Country";
      columns[1] = "ID";
      columns[2] = "Customer Name 1";
      columns[3] = "Customer Name 2";
      columns[4] = "Phone Number";
      columns[5] = "Address 1";
      columns[6] = "Address 2";
      columns[7] = "Status"
    ) {
     return true;
   }
  }
}
module.exports = { validateFile };

→ Подробнее о мультере для загрузки файлов читайте здесь

common.js

const multer = require("multer");
var fs = require("fs");var dir = "./public/images/nov";   // PATH TO UPLOAD FILE
if (!fs.existsSync(dir)) {  // CREATE DIRECTORY IF NOT FOUND
  fs.mkdirSync(dir, { recursive: true });
}
const fileStorageEngine = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, dir);
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + "-" + file.originalname);
  },
});
const upload = multer({ storage: fileStorageEngine }).single("file");
const uploadMultiple = multer({ storage: fileStorageEngine }).array("file");
module.exports = { upload, uploadMultiple };

→ Мы будем использовать Postman для импорта файлов. Пожалуйста, проверьте здесь почтальона и мультера.

Маршрут: https://localhost:5000/users

Тип: ПОСТ

Тело: данные формы, выберите ключ как изображение типа Файл

Заключительные мысли:

Производительность приложения значительно улучшилась благодаря концепции Node Stream. Это становится очень полезным, когда мы импортируем очень большие строки данных (скажем, от 20 000 до 30 000 данных). 20 минут сокращены до 2-3 минут максимум 😊.

Спасибо, что дочитали до конца 🙌 . Если вам понравилась эта статья или вы узнали что-то новое, поддержите меня, нажав кнопку Поделиться ниже, чтобы охватить больше людей, и/или подпишитесь на Twitter и подпишитесь на Happy Learnings !! чтобы увидеть некоторые другие советы, статьи и вещи, о которых я узнаю и делюсь там.