Node.js Excel读写全攻略:从SheetJS/xlsx入门到实战应用
1. 项目概述:为什么Node.js需要处理Excel?
在日常的后端开发、数据迁移、报表生成或者自动化办公场景里,我们经常需要和Excel文件打交道。无论是从业务部门导出的原始数据,还是需要生成给财务的统计报表,Excel都是绕不开的一环。作为一个Node.js开发者,你可能会想:我可以用fs模块读写文本,用数据库驱动操作数据,但面对一个.xlsx文件,难道要自己去解析那一堆压缩的XML吗?这显然不现实。
这时候,一个成熟、强大的库就显得至关重要。在Node.js生态中,xlsx(或称为SheetJS)是处理Excel文件的“瑞士军刀”。它不仅能读取Excel文件,将其转化为JSON等易于操作的格式,还能将JSON数据或HTML表格反向生成为标准的.xlsx文件,功能非常全面。这个项目,就是带你从零开始,掌握使用xlsx插件在Node.js环境中进行Excel文件读写操作的核心技能。无论你是需要做数据清洗、批量导入导出,还是构建一个报表服务,这套方法都能成为你工具箱里的利器。
2. 核心工具解析:深入理解SheetJS/xlsx库
在开始动手之前,我们有必要先了解一下我们将要使用的核心工具。xlsx库,其全称是SheetJS,它是一个用纯JavaScript编写的、支持多种表格格式(如XLSX, XLS, CSV, ODS等)的解析器和编写器。它的强大之处在于完全在浏览器和Node.js环境中运行,不依赖任何本地软件(如Microsoft Office)。
2.1 库的核心能力与架构
xlsx库的设计哲学是提供一个统一的API来处理不同格式的电子表格。当你调用XLSX.readFile读取一个文件时,库内部会完成一系列复杂操作:解压ZIP包、解析内部的XML文件(如workbook.xml,sheet1.xml,sharedStrings.xml等)、将单元格数据、样式、公式等信息构建成一个内存中的工作簿对象。这个对象的结构非常清晰,主要包含以下几个关键部分:
Sheets: 这是核心对象,一个键值对集合。键(Key)是工作表的名字,例如Sheet1;值(Value)是一个代表该工作表所有数据的对象。这个工作表对象内部,使用类似Excel的“A1”单元格地址格式作为键,来定位每一个单元格。SheetNames: 一个数组,按顺序列出了工作簿中所有工作表的名称。Workbook: 包含工作簿级别元信息的对象,比如作者、创建日期等。
这种结构化的数据,使得我们后续的读取和生成操作变得有迹可循。生成文件则是逆过程:你构建或修改这个工作簿对象,然后调用XLSX.writeFile,库会帮你生成所有必要的XML文件,打包成ZIP,最后写入磁盘。
2.2 安装与项目初始化
使用xlsx的第一步是将其引入你的项目。通过npm可以轻松安装:
npm install xlsx或者,如果你使用yarn:
yarn add xlsx安装完成后,在你的Node.js脚本中,通过CommonJS的require或ES Module的import语法引入即可:
// CommonJS 方式 const XLSX = require('xlsx'); // ES Module 方式 (需在package.json中设置"type": "module") // import XLSX from 'xlsx';这里有一个实操心得:虽然xlsx库功能强大,但它的包体积在Node.js生态中不算小,因为它包含了处理多种格式的完整逻辑。如果你的应用场景非常固定,比如只处理XLSX格式,且对安装包大小有极致要求,可以考虑寻找更轻量的替代品。但对于绝大多数通用场景,xlsx的丰富功能和稳定性是首选。
3. 从文件到数据:Excel读取全流程详解
读取Excel文件是我们最常遇到的需求。xlsx库提供了同步和异步两种读取方式,并支持从文件路径、Buffer甚至URL读取数据。
3.1 基础文件读取与工作簿解析
最常用的方法是XLSX.readFile,它同步读取指定路径的文件并返回一个工作簿对象。
const XLSX = require('xlsx'); // 同步读取Excel文件 try { const workbook = XLSX.readFile('./data/示例数据.xlsx'); console.log('工作表名称列表:', workbook.SheetNames); } catch (error) { console.error('读取文件失败:', error.message); }读取成功后,workbook对象就包含了这个Excel文件的所有信息。workbook.SheetNames数组让你知道这个文件里有几个工作表,分别叫什么名字。
3.2 工作表数据提取与格式转换
拿到工作簿对象后,下一步是从特定工作表中提取数据。xlsx库提供了XLSX.utils.sheet_to_json方法,这是将工作表数据转换为JSON数组的“神器”。这个方法有几个关键参数,决定了你得到的数据形态:
sheet: 必需,要转换的工作表对象,通过workbook.Sheets[sheetName]获取。range: 可选,指定要转换的单元格范围,例如A1:D10。不指定则默认转换整个工作表的有数据区域。header: 这个参数至关重要,它决定了JSON的键(Key)如何生成。header: 1: 将工作表的第一行作为JSON对象的键。这是最常用的方式,假设第一行是表头。header: “A”: 使用列字母(A, B, C...)作为JSON对象的键。header: null或不设置: 则数据将作为一个二维数组返回,第一行就是数据的一部分。
// 假设我们有一个“员工信息”表,第一行是:姓名, 部门, 工号, 入职日期 const sheetName = workbook.SheetNames[0]; // 获取第一个工作表名 const worksheet = workbook.Sheets[sheetName]; // 方式1:将第一行作为表头,生成对象数组 const dataWithHeader = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); // 输出类似:[{“姓名”: “张三”, “部门”: “技术部”, …}, {…}] // 方式2:生成二维数组 const dataAsArray = XLSX.utils.sheet_to_json(worksheet, { header: null }); // 输出类似:[[“姓名”, “部门”, …], [“张三”, “技术部”, …], …] console.log(`共读取到 ${dataWithHeader.length} 条数据`);注意事项:使用header: 1时,务必确保Excel表的第一行确实是规范的表头,且没有合并单元格、空单元格等情况,否则会导致后续数据错位。一个常见的技巧是,在转换前可以先通过XLSX.utils.decode_range(worksheet[‘!ref’])获取工作表的数据范围,手动检查第一行的内容。
3.3 处理复杂单元格类型(日期、数字、公式)
Excel单元格不仅仅是文本。xlsx库在解析时,会为每个单元格提供一个原始值(v)和一个格式化后的文本值(w)。对于日期和数字,需要特别注意。
- 日期:Excel内部将日期存储为一个数字(从1899-12-30开始的天数)。
xlsx库解析出的v值就是这个数字。你需要手动将其转换为JavaScript的Date对象。const cell = worksheet[‘A2’]; // 假设A2是一个日期单元格 if (cell.t === ‘n’ && XLSX.SSF.is_date(cell.w)) { // 使用库提供的工具函数转换Excel日期数字 const excelDate = cell.v; const jsDate = XLSX.SSF.parse_date_code(excelDate); console.log(‘转换后的日期:’, new Date(jsDate.y, jsDate.m-1, jsDate.d)); } - 公式:如果单元格包含公式,
cell.t会是’n’(数字)或’s’(字符串)等,但cell.f属性会保存公式字符串(如”SUM(A1:A10)”)。默认情况下,v值是公式计算后的结果。如果原文件没有保存计算结果,v可能是undefined。 - 数字与文本:有时,像工号“001”这样的数据,在Excel中可能被保存为数字1。在转换时,可以通过设置
cellStyles: true等选项,或事后根据cell.t类型(’n’为数字,’s’为字符串)进行数据处理。
实操心得:对于数据导入场景,我强烈建议在转换JSON后,增加一个数据清洗和验证的步骤。例如,检查必填字段是否为空、日期格式是否正确、数字是否在合理范围内。这能提前拦截脏数据,避免它们进入下游业务系统。
4. 从数据到文件:Excel生成与定制化输出
将数据写回Excel文件是另一个核心需求。与读取相反,我们需要从JSON或数组数据构建出工作表对象,再组合成工作簿,最后写入文件。
4.1 从JSON数据生成基础工作表
XLSX.utils.json_to_sheet方法可以将一个对象数组转换为工作表对象。对象的键将成为表头(第一行),对象的值将填充到对应的单元格中。
const data = [ { 姓名: ‘李四’, 部门: ‘市场部’, 工号: 1002, 绩效: ‘A’ }, { 姓名: ‘王五’, 部门: ‘销售部’, 工号: 1003, 绩效: ‘B+’ }, ]; // 将JSON数据转换为工作表 const newWorksheet = XLSX.utils.json_to_sheet(data); // 创建一个新的工作簿,并添加这个工作表 const newWorkbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(newWorkbook, newWorksheet, ‘员工绩效’); // 将工作簿写入文件 XLSX.writeFile(newWorkbook, ‘./output/员工绩效表.xlsx’); console.log(‘Excel文件已生成!’);4.2 高级工作表构建与样式控制
基础生成只能处理数据和简单的表头。在实际项目中,我们往往有更复杂的需求:自定义表头(可能和JSON键名不同)、设置列宽、添加单元格样式(字体、颜色、边框)、写入公式等。
xlsx库提供了底层操作单元格的能力。你可以直接操作工作表对象,它是一个以单元格地址为键的对象。
// 创建一个空工作表 const ws = {}; // 1. 手动设置表头 ws[‘A1’] = { v: ‘员工姓名’, t: ‘s’ }; ws[‘B1’] = { v: ‘本月销售额’, t: ‘n’ }; ws[‘C1’] = { v: ‘目标达成率’, t: ‘n’ }; // 2. 写入数据 ws[‘A2’] = { v: ‘赵六’, t: ‘s’ }; ws[‘B2’] = { v: 85000, t: ‘n’ }; ws[‘C2’] = { v: 1.13, t: ‘n’, z: ‘0.00%’ }; // z 指定数字格式 // 3. 写入公式 (例如,C3单元格计算B3/B2) ws[‘A3’] = { v: ‘钱七’, t: ‘s’ }; ws[‘B3’] = { v: 92000, t: ‘n’ }; ws[‘C3’] = { f: ‘B3/B2’, t: ‘n’, z: ‘0.00%’ }; // 4. 设置数据范围(非常重要!) ws[‘!ref’] = ‘A1:C3’; // 5. 设置列宽(可选,通过`!cols`属性) ws[‘!cols’] = [ { wpx: 100 }, { wpx: 120 }, { wpx: 100 } ]; // 宽度像素 const customWorkbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(customWorkbook, ws, ‘销售报表’); XLSX.writeFile(customWorkbook, ‘./output/自定义销售报表.xlsx’);注意事项:手动构建工作表时,务必正确设置!ref属性。它定义了工作表中有数据的矩形区域(例如A1:D10)。如果设置不正确,生成的Excel文件可能看起来是空的,或者数据显示不全。xlsx.utils.aoa_to_sheet(将二维数组转为工作表)和json_to_sheet方法会自动计算这个范围,但手动操作时需要自己维护。
4.3 文件写入选项与格式支持
XLSX.writeFile是同步写入。xlsx库也提供了XLSX.write方法,它返回文件的二进制数据(Buffer),你可以配合Node.js的fs.writeFileSync或fs.promises.writeFile进行异步写入,这在处理大文件时更友好。
const XLSX = require(‘xlsx’); const fs = require(‘fs’).promises; async function writeExcelAsync(filePath, workbook) { const buffer = XLSX.write(workbook, { type: ‘buffer’, bookType: ‘xlsx’ }); await fs.writeFile(filePath, buffer); console.log(‘文件异步写入完成’); }XLSX.write的第二个参数是一个选项对象,其中bookType可以指定输出格式,支持’xlsx’,’xlsm’,’xlsb’,’ods’,’csv’,’txt’等。例如,要生成一个CSV文件:
XLSX.writeFile(workbook, ‘./output/data.csv’, { bookType: ‘csv’ });5. 实战应用:构建一个简单的数据导入导出服务
现在,我们将前面学到的知识组合起来,构建一个模拟的、完整的“员工信息导入导出”服务。这个服务包含两个核心功能:1. 解析上传的Excel文件,将数据存入内存(模拟数据库);2. 根据查询条件,将数据导出为新的Excel报表。
5.1 场景设计与模块划分
假设我们有一个简单的Express.js Web服务。我们需要两个端点:
POST /api/upload:接收上传的Excel文件,解析并“存储”。GET /api/export:根据查询参数(如部门),生成并返回一个Excel文件供下载。
为了简化,我们用一个内存数组let employeeData = [];来模拟数据库。
5.2 实现文件上传与解析接口
首先,我们需要处理文件上传。这里使用multer中间件来处理multipart/form-data格式的上传。
// server.js const express = require(‘express’); const multer = require(‘multer’); const XLSX = require(‘xlsx’); const path = require(‘path’); const app = express(); const upload = multer({ dest: ‘uploads/’ }); // 临时存储上传文件 let employeeData = []; // 模拟数据库 // 上传并解析Excel的接口 app.post(‘/api/upload’, upload.single(‘excelFile’), (req, res) => { if (!req.file) { return res.status(400).json({ error: ‘请上传文件’ }); } const filePath = path.join(__dirname, req.file.path); try { // 1. 读取Excel文件 const workbook = XLSX.readFile(filePath); const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; // 2. 转换为JSON,假设第一行是表头 const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); // 3. 数据清洗与验证(示例:简单检查) const cleanedData = jsonData.map((row, index) => { // 跳过表头行(index === 0) if (index === 0) return null; // 假设列顺序:姓名,部门,工号,邮箱 const [name, department, id, email] = row; if (!name || !department) { console.warn(`第${index + 1}行数据不完整,已跳过`); return null; } return { name, department, id: Number(id) || 0, email }; }).filter(item => item !== null); // 过滤掉空行和无效数据 // 4. “存入数据库” employeeData.push(...cleanedData); // 5. 清理临时文件(生产环境建议使用异步删除) const fs = require(‘fs’); fs.unlinkSync(filePath); res.json({ message: ‘文件解析成功’, importedCount: cleanedData.length, totalCount: employeeData.length }); } catch (error) { console.error(‘解析文件失败:’, error); res.status(500).json({ error: ‘解析Excel文件失败’, details: error.message }); } });5.3 实现数据筛选与报表导出接口
接下来,实现导出接口。这个接口根据查询参数department筛选员工,并生成一个包含“姓名”、“部门”、“工号”和“导出时间”的新Excel文件。
// 导出Excel报表的接口 app.get(‘/api/export’, (req, res) => { const { department } = req.query; // 1. 数据筛选 let dataToExport = employeeData; if (department) { dataToExport = employeeData.filter(emp => emp.department === department); } if (dataToExport.length === 0) { return res.status(404).json({ error: ‘未找到符合条件的数据’ }); } // 2. 准备导出数据,可以添加额外字段 const exportData = dataToExport.map(emp => ({ ‘姓名’: emp.name, ‘部门’: emp.department, ‘工号’: emp.id, ‘导出时间’: new Date().toLocaleString(‘zh-CN’) // 添加时间列 })); // 3. 创建工作簿和工作表 const worksheet = XLSX.utils.json_to_sheet(exportData); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, ‘员工列表’); // 4. 设置响应头,告诉浏览器这是一个要下载的Excel文件 res.setHeader( ‘Content-Type’, ‘application/vnd.openxmlformats-officedocument.spreadsheetml.sheet’ ); res.setHeader( ‘Content-Disposition’, `attachment; filename=”员工导出_${department || ‘全部’}_${Date.now()}.xlsx”` ); // 5. 将工作簿写入响应流 const buffer = XLSX.write(workbook, { type: ‘buffer’, bookType: ‘xlsx’ }); res.end(buffer); }); const PORT = 3000; app.listen(PORT, () => { console.log(`服务已启动,访问 http://localhost:${PORT}`); });这个简单的服务演示了读写Excel在Web后端中的典型应用。你可以使用Postman或curl测试上传和导出功能。
实操心得:在生产环境中,直接使用multer的磁盘存储可能会成为性能瓶颈和安全隐患。对于大文件或高并发场景,可以考虑:
- 使用
multer.memoryStorage()将文件暂存内存,但要注意内存限制。 - 解析完成后,立即将文件流式处理或删除临时文件。
- 将解析逻辑放入消息队列或后台任务,避免阻塞主请求线程。
6. 性能优化与大规模数据处理
当处理的Excel文件包含数万甚至数十万行数据时,内存占用和解析时间会成为问题。xlsx库虽然强大,但一次性读取超大文件到内存中可能会导致Node.js进程内存溢出。
6.1 流式读取与分块处理
xlsx库本身不直接支持流式API,但我们可以结合Node.js的流(Stream)和文件分块读取来优化。核心思路是:不一次性读取整个文件,而是读取一部分,处理一部分。
一种可行的方案是,对于.xlsx文件(本质是ZIP),使用adm-zip等库进行流式解压,然后使用xml2js或sax等流式XML解析器来处理内部的sheet1.xml等文件。但这实现起来相当复杂。
更实用的折中方案是分页处理。如果你的文件有多个工作表,可以逐个处理。或者,如果你知道数据的大致结构,可以在读取时指定range参数,只读取当前需要处理的数据区域。
// 示例:分批读取一个超大工作表的特定区域 const XLSX = require(‘xlsx’); const workbook = XLSX.readFile(‘./huge_file.xlsx’); const worksheet = workbook.Sheets[‘大数据’]; const totalRows = 100000; const batchSize = 10000; for (let startRow = 1; startRow <= totalRows; startRow += batchSize) { const endRow = Math.min(startRow + batchSize - 1, totalRows); const range = `A${startRow}:Z${endRow}`; // 假设有26列 // 注意:sheet_to_json的range参数在xlsx库中可能不是对所有格式都完美支持。 // 另一种方法是直接操作worksheet对象,按单元格地址分批提取。 const cellRange = XLSX.utils.decode_range(worksheet[‘!ref’]); cellRange.s.r = startRow - 1; // 修改读取范围的起始行(0-indexed) cellRange.e.r = endRow - 1; // 修改读取范围的结束行 const newRange = XLSX.utils.encode_range(cellRange); // 创建一个只包含目标范围的新工作表对象(简化示例,实际需遍历单元格) // 更稳健的做法是遍历worksheet的每个单元格键,判断其行号是否在范围内。 const batchData = []; for (let cellAddress in worksheet) { if (cellAddress[0] === ‘!’) continue; // 跳过 !ref, !cols 等特殊键 const cell = XLSX.utils.decode_cell(cellAddress); if (cell.r >= startRow - 1 && cell.r <= endRow - 1) { // 处理这个单元格的数据... } } console.log(`处理了第 ${startRow} 到 ${endRow} 行数据`); // 在这里将batchData存入数据库或进行下一步处理 }6.2 内存管理与写入优化
对于生成超大Excel文件,一次性在内存中构建整个workbook对象同样危险。xlsx库提供了streamAPI(在xlsx.stream中),支持流式写入,这对于生成大型文件非常有用。
const XLSX = require(‘xlsx’); const fs = require(‘fs’); // 创建一个可写流 const outputStream = fs.createWriteStream(‘./output/large_file.xlsx’); // 初始化流式写入器 const stream = XLSX.stream.to_csv(workbook.Sheets[‘Sheet1’]); // 注意:这里以CSV为例,XLSX的流式写入支持有限 // 或者使用第三方库如 ‘excel4node’ 或 ‘exceljs’ 来获得更好的流式支持。 stream.pipe(outputStream); outputStream.on(‘finish’, () => { console.log(‘流式写入完成’); });重要提示:xlsx库的核心优势在于格式支持的广泛性和API的简洁性,但在处理超大规模数据的流式读写方面并非其最强项。如果你的项目主要涉及生成或读取海量数据的Excel文件,我建议评估一下专门为此优化的库,例如exceljs。它提供了更友好的流式读写API,并且在处理大文件时的内存控制更好。
6.3 库的选型考量:xlsx vs exceljs
这是一个常见的抉择。简单对比一下:
| 特性 | SheetJS/xlsx | exceljs |
|---|---|---|
| 格式支持 | 极其广泛(XLS, XLSX, CSV, ODS, etc.) | 主要支持 XLSX, CSV |
| API 简洁性 | 非常简洁,核心API就几个函数 | 相对更面向对象,API更丰富 |
| 样式控制 | 基础支持,稍显繁琐 | 强大且直观,易于设置样式、边框、颜色 |
| 流式处理 | 有限支持 | 原生支持流式读写,适合大文件 |
| 性能 (大文件) | 一般,全量内存操作 | 更优,尤其流式模式下 |
| 社区与维护 | 非常活跃,历史悠久 | 活跃,在Node.js社区很受欢迎 |
选择建议:
- 如果你的需求是读取/生成各种格式的表格文件,且数据量不大,追求快速上手和API简洁,选
xlsx。 - 如果你的需求集中在XLSX格式,需要生成带复杂样式的报表,或者必须处理GB级别的大文件,选
exceljs会更得心应手。
7. 常见问题排查与调试技巧
在实际使用xlsx库的过程中,你肯定会遇到一些“坑”。下面我总结了一些常见问题及其解决方法,希望能帮你快速排雷。
7.1 读取文件失败或返回空数据
- 问题现象:调用
XLSX.readFile后,workbook.SheetNames为空,或者sheet_to_json返回空数组。 - 可能原因与解决:
- 文件路径错误:这是最常见的原因。请使用绝对路径或仔细检查相对路径。可以用
path.join(__dirname, ‘相对路径’)来确保路径正确。 - 文件格式不支持或已损坏:确保文件是
.xlsx,.xls,.csv等支持的格式。尝试用Excel或WPS软件打开,看文件本身是否正常。 - 文件被其他进程占用:确保文件没有被Excel编辑器或其他程序打开。
- 工作表没有数据:有些Excel文件可能有隐藏的工作表或格式数据。检查
workbook.SheetNames,并尝试用XLSX.utils.sheet_to_json(worksheet, {header: null})看看是否能读出二维数组。 - 编码问题(仅CSV/TXT):读取CSV时,可以指定编码选项:
XLSX.readFile(filePath, {type: ‘file’, codepage: 65001}),其中65001代表UTF-8。
- 文件路径错误:这是最常见的原因。请使用绝对路径或仔细检查相对路径。可以用
7.2 生成的文件用Excel打开报错或样式错乱
- 问题现象:生成的文件无法用Excel打开,提示“文件已损坏”,或者打开后样式(如日期、数字格式)显示不正常。
- 可能原因与解决:
- 未设置
!ref范围:手动构建worksheet对象时,忘记设置ws[‘!ref’]属性,导致Excel不知道数据区域在哪。务必根据你实际写入的单元格设置正确的范围,如A1:D10。 - 单元格类型
t设置错误:数字应设为t: ‘n’,字符串应设为t: ‘s’,布尔值设为t: ‘b’,错误设为t: ‘e’。如果类型不匹配,Excel可能无法正确解析。 - 日期数字格式问题:Excel日期是特殊的数字。如果你写入了一个JavaScript的Date对象,需要先转换为Excel的序列号。可以使用
XLSX.SSF.parse_date或手动计算:const excelDate = (jsDate.getTime() / (1000 * 60 * 60 * 24)) + 25569;(注意时区)。同时,最好设置单元格的z(数字格式)属性,例如z: ‘yyyy-mm-dd’。 - 文件写入不完整:在异步写入文件时,如果程序在写入完成前就退出了,会导致文件损坏。确保在写入完成的回调或Promise resolve后再进行后续操作。
- 未设置
7.3 中文乱码问题
- 问题现象:读取或生成的文件中,中文字符显示为乱码。
- 可能原因与解决:
- CSV文件编码:这是乱码重灾区。Excel默认可能用GBK或ANSI编码打开CSV,而Node.js默认写入UTF-8。解决方案是在生成CSV时,在文件开头添加BOM头(Byte Order Mark)。
const data = [[‘姓名’, ‘年龄’], [‘张三’, 25]]; const worksheet = XLSX.utils.aoa_to_sheet(data); const csvOutput = XLSX.utils.sheet_to_csv(worksheet); // 添加UTF-8 BOM头 const csvWithBOM = ‘\uFEFF’ + csvOutput; fs.writeFileSync(‘./output/带BOM的.csv’, csvWithBOM); - 库内部处理:
xlsx库对UTF-8支持良好。乱码通常发生在与其他系统(如旧版Windows Excel)交互时。确保整个数据流水线(读取、处理、写入)都使用统一的字符编码(推荐UTF-8)。
- CSV文件编码:这是乱码重灾区。Excel默认可能用GBK或ANSI编码打开CSV,而Node.js默认写入UTF-8。解决方案是在生成CSV时,在文件开头添加BOM头(Byte Order Mark)。
7.4 调试技巧:窥探工作表对象的结构
当你对转换结果有疑问时,最好的调试方法是直接打印出工作表对象的结构。
const worksheet = workbook.Sheets[‘Sheet1’]; console.log(‘工作表范围:’, worksheet[‘!ref’]); console.log(‘A1单元格内容:’, worksheet[‘A1’]); // 查看前几个单元格的详细信息 Object.keys(worksheet).slice(0, 10).forEach(key => { if (!key.startsWith(‘!’)) { console.log(`单元格 ${key}:`, worksheet[key]); } });通过查看每个单元格对象的v(原始值)、w(格式化文本)、t(类型)、f(公式)等属性,你可以精确地知道xlsx库从文件中读出了什么,从而判断问题出在源文件还是你的处理逻辑上。
处理Excel文件是Node.js后端开发中一项非常实用的技能。从简单的数据导出,到复杂的报表生成,再到海量数据的批处理,xlsx库都能提供有力的支持。掌握它,意味着你能在数据与办公世界之间自由架设桥梁。记住,关键不在于死记硬背API,而在于理解Excel文件的结构(工作簿、工作表、单元格)与xlsx库对象模型之间的映射关系。遇到问题时,多打印中间数据,多查阅官方文档,大部分难题都能迎刃而解。