Node.js使用exceljs生成下载Excel文件时文件损坏且内容错误
问题描述
我在Node.js API中使用exceljs npm包实现Excel文件下载功能,需求为读取并解析CSV文件数据,读取Excel模板文件并填入CSV数据,最终下载新生成的Excel文件。
当前代码执行后虽能触发文件下载,但下载的文件损坏;将文件改为CSV格式后,内容仅为"Success"字符串,不清楚问题出在哪里。
问题代码
export const generateExcel = async (req: Request, res: Response) => { try { res.setHeader("Content-disposition", "attachment; filename=" + "Report.xlsx"); res.contentType( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ); await csvParse(); return res.status(200).json("Success"); } catch (err) { return res.status(500).json("False"); } }; const csvParse = async () => { fs.createReadStream("./content/TestCsv.csv") .pipe(csv.parse()) .on("error", (error: any) => console.log("Error")) .on("data", (row: any) => { let line: any = String(row); line = line.split(";"); //let parsedData = line[0]; let parsedData = line; allParsedData.push(parsedData); }) .on("end", (rowCount: any) => { let test = allParsedData.toString(); generateFile(test); }); }; const generateFile = (data: any) => { return new Promise<fs.ReadStream>((resolve, reject) => { const workbook = new Excel.Workbook(); workbook.xlsx.readFile("./utilities/template.xlsx").then(() => { workbook.xlsx.writeFile("./content/Test.xlsx").then( () => { let stream = fs.createReadStream("./content/Test.xlsx"); stream.on("close", () => { fs.unlink("./content/Test.xlsx", (error) => { if (error) { throw error; } }); }); resolve(stream); }, (err) => { throw err; } ); }); }); };
问题分析与修复方案
核心问题点
- 响应内容错误:接口最终返回
json("Success"),而非生成好的Excel文件流,直接导致下载文件内容为字符串"Success",自然损坏。 - 异步逻辑失效:
csvParse标记为async但内部未返回Promise,await csvParse()不会等待CSV解析和文件生成完成就直接返回响应。 - 数据未填入模板:
generateFile接收了CSV数据但未使用,且csvParse中将数组转成字符串传递,破坏了数据结构。 - 文件流未关联响应:生成的Excel文件流没有pipe到响应对象,无法正确输出给客户端。
修复后的完整代码
import fs from "fs"; import csv from "csv-parse"; import Excel from "exceljs"; import { Request, Response } from "express"; export const generateExcel = async (req: Request, res: Response) => { try { // 解析CSV数据 const csvData = await csvParse(); // 生成填充好数据的Excel文件流 const excelStream = await generateFile(csvData); // 设置响应头 res.setHeader("Content-disposition", "attachment; filename=Report.xlsx"); res.contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // 将文件流pipe到响应对象 excelStream.pipe(res); // 监听流结束事件,确保响应正确完成 excelStream.on("end", () => { res.end(); }); } catch (err) { console.error(err); res.status(500).send("生成Excel文件失败"); } }; // 修改csvParse为返回Promise,正确收集CSV数据 const csvParse = () => { return new Promise<string[][]>((resolve, reject) => { const allParsedData: string[][] = []; fs.createReadStream("./content/TestCsv.csv") .pipe(csv.parse({ delimiter: ";" })) // 指定分隔符为;,无需手动split .on("error", (error) => { console.error("CSV解析错误:", error); reject(error); }) .on("data", (row) => { allParsedData.push(row as string[]); }) .on("end", () => { resolve(allParsedData); }); }); }; // 修改generateFile,接收CSV数据并填入模板 const generateFile = (csvData: string[][]) => { return new Promise<fs.ReadStream>((resolve, reject) => { const workbook = new Excel.Workbook(); workbook.xlsx.readFile("./utilities/template.xlsx") .then(() => { // 获取模板中的第一个工作表(根据实际情况调整) const worksheet = workbook.getWorksheet(1); if (!worksheet) { throw new Error("模板中未找到工作表"); } // 将CSV数据写入工作表(从第2行开始,假设第1行是表头) csvData.forEach((row) => { worksheet.addRow(row); }); // 直接将工作簿写入响应流,无需生成临时文件 const stream = workbook.xlsx.createOutputStream(); resolve(stream); }) .catch((err) => { console.error("Excel生成错误:", err); reject(err); }); }); };
关键优化说明
- 异步逻辑修复:
csvParse和generateFile都返回Promise,确保await能正确等待异步操作完成。 - 数据传递正确:直接传递CSV解析后的二维数组,避免字符串转换破坏结构,并且在
generateFile中真正将数据填入模板工作表。 - 避免临时文件:使用
createOutputStream直接生成文件流,无需写入本地临时文件再读取,提升性能并减少文件操作错误。 - 响应流正确关联:将Excel文件流pipe到响应对象,替代原来的
json("Success"),确保客户端接收到正确的Excel文件内容。
内容的提问来源于stack exchange,提问作者devZ
相关产品推荐
相关产品推荐

