使用nodejs/exceljs读取、操作、写入excel文件

'# 使用nodejs/exceljs读取、操作、写入excel文件

一、背景与问题

在企业级应用开发中,Excel文件处理是一个常见需求。传统开发中,处理Excel文件通常需要依赖第三方库,而Node.js生态中,exceljs库提供了强大的功能支持。但开发者在使用过程中常常面临以下问题:

  1. 如何高效处理大文件(如数百万行数据)
  2. 如何处理复杂格式(如样式、公式、图表)
  3. 如何保证数据一致性与安全性
  4. 如何在不同版本之间保持兼容性
  5. 如何处理各种异常情况(如文件损坏、格式错误)

本文将深入探讨exceljs的工作原理、实现细节、最佳实践和常见陷阱,帮助开发者在实际项目中合理使用这一工具。

二、基本原理

exceljs的核心原理基于XLSX.js库,其工作流程分为三个阶段:

  1. 文件解析:将Excel文件(.xlsx/.xls)的二进制数据解析为内存中的数据结构
  2. 数据操作:通过DOM-like API对工作表、行、单元格进行增删改查
  3. 文件生成:将内存中的数据结构转换为Excel文件格式

其底层使用了zip.js处理zip压缩包,使用XML解析器处理工作表数据,通过CSS选择器语法进行单元格定位(如worksheet.getRow(1).getCell('A'))。

三、环境准备

npm install exceljs

推荐版本:exceljs@4.3.0(最新稳定版)

需要同时安装的依赖:

npm install xlsx

注意:不同版本的exceljs对xlsx库的依赖版本有差异,需注意兼容性。

四、核心实现

1. 基础读取操作

const ExcelJS = require('exceljs');
const fs = require('fs');

async function readExcel(filePath) {
  const workbook = new ExcelJS.Workbook();
  try {
    await workbook.xlsx.readFile(filePath);
    const worksheet = workbook.getWorksheet('Sheet1');
    
    // 读取所有行
    const rows = worksheet.getRow(1, worksheet.rowCount);
    console.log('读取到行数:', rows.length);
    
    // 读取特定单元格
    const cell = worksheet.getCell('A1');
    console.log('单元格内容:', cell.value);
    
    // 读取格式信息
    console.log('单元格样式:', cell.style);
  } catch (error) {
    console.error('读取错误:', error.message);
  }
}

关键点解释:

  • 使用xlsx.readFile方法异步读取文件
  • getWorksheet方法获取工作表,支持按名称或索引获取
  • getRow方法支持分页读取,避免一次性加载全部数据
  • getCell方法可获取单元格的值和样式信息

2. 高级写入操作

const ExcelJS = require('exceljs');
const fs = require('fs');

async function writeExcel(filePath) {
  const workbook = new ExcelJS.Workbook();
  const worksheet = workbook.addWorksheet('Sheet1');
  
  // 设置单元格样式
  worksheet.getCell('A1').value = '测试数据';
  worksheet.getCell('A1').style.fill = {
    type: 'pattern',
    pattern: 'solid',
    fgColor: {argb: 'FF00FF00'}
  };
  
  // 添加行数据
  worksheet.addRow(['ID', '名称', '数量']);
  worksheet.addRow([1, '商品A', 100]);
  worksheet.addRow([2, '商品B', 200]);
  
  // 写入文件
  await workbook.xlsx.writeFile(filePath);
}

关键点解释:

  • 使用addWorksheet创建新工作表
  • 支持设置单元格的多种样式(字体、边框、填充等)
  • addRow方法支持批量添加行数据
  • writeFile方法支持异步写入文件

3. 数据操作示例

const ExcelJS = require('exceljs');

async function manipulateExcel() {
  const workbook = new ExcelJS.Workbook();
  const worksheet = workbook.addWorksheet('Sheet1');
  
  // 添加数据
  worksheet.addRow(['Name', 'Age', 'Country']);
  worksheet.addRow(['Alice', 25, 'USA']);
  worksheet.addRow(['Bob', 30, 'China']);
  
  // 操作数据
  const row = worksheet.getRow(2);
  row.getCell('B').value = 35; // 修改年龄
  row.getCell('C').value = 'Japan'; // 修改国家
  
  // 添加公式
  worksheet.getCell('D1').value = '=SUM(B2:B3)';
  
  // 保存文件
  await workbook.xlsx.writeFile('output.xlsx');
}

关键点解释:

  • 支持直接修改单元格的值
  • 可以添加公式(支持常见运算符)
  • 支持复杂的数据操作(如合并单元格、设置边框等)

五、完整案例:用户数据导出系统

1. 前端上传接口(Express)

const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer({ dest: 'uploads/' });

app.post('/upload', upload.single('file'), async (req, res) => {
  try {
    const file = req.file;
    if (!file) {
      return res.status(400).send('No file uploaded');
    }
    
    const excelFile = await processExcel(file.path);
    res.download(excelFile, 'processed_data.xlsx');
  } catch (error) {
    res.status(500).send(error.message);
  }
});

2. 后端处理逻辑(核心部分)

const ExcelJS = require('exceljs');
const fs = require('fs');
const path = require('path');

async function processExcel(filePath) {
  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.readFile(filePath);
  
  const worksheet = workbook.getWorksheet('Sheet1');
  
  // 清洗数据
  const rows = worksheet.getRow(1, worksheet.rowCount);
  const cleanedData = rows.map(row => {
    const rowData = {};
    for (let i = 1; i <= row.values.length; i++) {
      rowData[`${String.fromCharCode(64 + i)}`] = row.getCell(i).value;
    }
    return rowData;
  });
  
  // 创建新文件
  const newWorkbook = new ExcelJS.Workbook();
  const newWorksheet = newWorkbook.addWorksheet('Processed Data');
  
  // 写入数据
  newWorksheet.addRow(['ID', 'Name', 'Country']);
  cleanedData.forEach(data => {
    newWorksheet.addRow([data.ID, data.Name, data.Country]);
  });
  
  // 保存文件
  const newFilePath = path.join(__dirname, 'processed_data.xlsx');
  await newWorkbook.xlsx.writeFile(newFilePath);
  return newFilePath;
}

3. 安全处理

function validateFile(file) {
  const allowedExtensions = ['.xls', '.xlsx'];
  const ext = path.extname(file.originalname).toLowerCase();
  
  if (!allowedExtensions.includes(ext)) {
    throw new Error(`Unsupported file type: ${ext}`);
  }
  
  // 检查文件大小
  if (file.size > 10 * 1024 * 1024) { // 10MB
    throw new Error('File size exceeds limit');
  }
}

六、源码解析

exceljs的源码结构主要包括:

  1. Workbook类:处理整个工作簿的创建、读写、工作表管理
  2. Worksheet类:处理单个工作表的行、列、单元格操作
  3. Cell类:处理单元格的值、样式、公式等
  4. Reader/Writer类:处理文件的读写逻辑

关键代码片段(简化版):

class Workbook {
  constructor() {
    this.worksheets = [];
  }
  
  addWorksheet(name) {
    const worksheet = new Worksheet(name);
    this.worksheets.push(worksheet);
    return worksheet;
  }
  
  async readFile(filePath) {
    const data = await fs.readFileSync(filePath);
    // 解析zip文件
    const zip = new JSZip(data);
    const workbook = await zip.loadAsync();
    // 解析工作表数据
    this.parseWorkSheets(workbook);
  }
  
  parseWorkSheets(workbook) {
    // 解析XML数据,创建worksheet对象
  }
}

七、进阶使用

1. 处理大文件

async function processLargeFile(filePath) {
  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.readFile(filePath);
  
  // 分页读取
  const pageSize = 1000;
  const rowCount = workbook.worksheets[0].rowCount;
  
  for (let i = 1; i <= rowCount; i += pageSize) {
    const endRow = Math.min(i + pageSize - 1, rowCount);
    const rows = workbook.worksheets[0].getRows(i, endRow);
    
    // 处理数据...
  }
}

2. 处理复杂格式

const worksheet = workbook.addWorksheet('Sheet1');
worksheet.getColumn(1).width = 30; // 设置列宽
worksheet.getColumn(2).numFmt = '0.00'; // 设置数字格式
worksheet.getRow(1).height = 20; // 设置行高

3. 处理公式和图表

worksheet.getCell('D1').value = '=SUM(B2:B3)';
worksheet.getCell('D1').style.font.bold = true;

// 添加图表
const chart = workbook.addChart({
  type: 'bar',
  title: { text: 'Sales Data' },
  legend: { show: true },
  series: [
    { name: 'Sales', data: [10, 20, 30] }
  ]
});

八、性能与工程实践

1. 性能优化策略

优化策略说明
分页处理避免一次性加载全部数据
流式处理使用readFile的流式接口
压缩数据使用zip压缩减少传输体积
缓存数据对常用数据进行缓存

2. 内存管理

处理大文件时需注意内存占用,可使用以下策略:

// 使用流式读取
workbook.xlsx.readFile(filePath, {
  type: 'buffer',
  callback: (err, buffer) => {
    // 处理buffer数据
  }
});

3. 安全风险控制

  1. 文件类型验证:严格校验文件扩展名和MIME类型
  2. 内容过滤:防止恶意代码注入(如公式攻击)
  3. 权限控制:限制用户对文件的访问权限
  4. 沙箱环境:对用户上传文件进行隔离处理

九、常见问题与踩坑

1. 常见错误及解决方法

错误类型错误示例解决方案
文件读取失败Error: ENOENT: no such file or directory检查文件路径和权限
单元格值丢失cell.value === null使用`cell.value '默认值'`处理
格式转换错误TypeError: Cannot read property 'value' of undefined添加空值判断
内存溢出Error: Out of memory使用分页处理或流式处理

2. 常见陷阱

  1. 格式兼容性问题:不同版本的Excel文件格式差异
  2. 样式丢失:未正确设置单元格样式
  3. 公式计算错误:未正确设置公式依赖关系
  4. 图表不显示:未正确配置图表数据源

十、最佳实践

1. 推荐使用场景

  1. 需要处理大量数据(10万+行)时
  2. 需要保持格式完整性的场景
  3. 需要复杂样式和公式处理的场景
  4. 需要跨平台兼容性的场景

2. 不推荐使用场景

  1. 需要处理CSV文件时(推荐使用csv-parser)
  2. 需要处理JSON格式数据时(推荐使用jsonfile)
  3. 需要处理小文件时(推荐使用fs模块)

3. 推荐方案

  1. 使用流式处理处理大文件
  2. 使用分页读取避免内存溢出
  3. 使用严格校验机制防止安全风险
  4. 使用缓存机制提升性能

十一、总结

exceljs作为Node.js处理Excel文件的强大工具,具有以下特点:

  • 支持多种文件格式(.xlsx/.xls)
  • 提供丰富的API进行数据操作
  • 支持复杂格式(样式、公式、图表)
  • 具备良好的扩展性

在实际开发中,需要注意:

  1. 合理选择处理方式(分页/流式/缓存)
  2. 严格校验文件类型和内容
  3. 注意内存管理
  4. 处理异常情况

通过合理使用exceljs,可以有效提升数据处理效率,但需注意其适用场景和限制。在处理复杂数据时,建议结合其他工具(如csv-parser、jsonfile)形成完整的解决方案。

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日