You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
      });
  });
};

关键优化说明

  1. 异步逻辑修复:csvParse和generateFile都返回Promise,确保await能正确等待异步操作完成。
  2. 数据传递正确:直接传递CSV解析后的二维数组,避免字符串转换破坏结构,并且在generateFile中真正将数据填入模板工作表。
  3. 避免临时文件:使用createOutputStream直接生成文件流,无需写入本地临时文件再读取,提升性能并减少文件操作错误。
  4. 响应流正确关联:将Excel文件流pipe到响应对象,替代原来的json("Success"),确保客户端接收到正确的Excel文件内容。

内容的提问来源于stack exchange,提问作者devZ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 16:27:25