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

Node.js中Nodemailer发送多数据Excel附件0字节问题排查

排查Nodemailer发送带图片多行Excel附件为0字节的问题

你遇到的问题核心原因很明确:Excel文件生成是异步操作,但你没有等待所有文件完全写入磁盘,就立刻用fs.readFileSync读取文件并发送邮件。单行数据的Excel生成速度快,刚好能赶上读取时机;但带图片的多行数据生成耗时更长,读取时文件还处于未写完的状态,所以读出来的内容是空的,导致邮件附件为0字节。

结合你的代码,我给你梳理下修复步骤:

1. 确认并封装createExcelFile为异步Promise

首先要确保createExcelFile函数返回一个Promise,这样我们才能等待它完成。假设你的createExcelFile内部是用类似xlsx库的writeFile方法生成文件,那可以把它改成返回Promise的形式:

// 示例:封装createExcelFile为Promise函数
function createExcelFile(order, type, filePath) {
  // 你的原有逻辑:创建workbook、填充数据、插入图片等
  // ...
  // 最后返回writeFile的Promise(xlsx库的writeFile本身支持Promise)
  return workbook.xlsx.writeFile(filePath);
}

2. 等待所有Excel文件生成完成后再发送邮件

在你的代码中,现在是直接调用createExcelFile后立刻发送邮件,这时候文件还在写入中。我们需要用Promise.all等待四个Excel文件全部生成完毕,再执行邮件发送逻辑:

exports.getExcelFile = (req, res, next) => { 
  const excelFile = {}; 
  Order.findById(req.params.id)
  .then(order => { 
    // 初始化文件名和路径(改成绝对路径,避免读取位置错误)
    const basePath = path.join(__dirname, "../data/excel"); // 根据你的项目结构调整层级
    excelFile.excelOffFile = `Off_${order.factory}_${order._id}.xls`;
    excelFile.excelOffFilePath = path.join(basePath, excelFile.excelOffFile);
    excelFile.excelFactFile = `Fact_${order.factory}_${order._id}.xls`;
    excelFile.excelFactFilePath = path.join(basePath, excelFile.excelFactFile);
    excelFile.excelBrFile = `Br_${order.factory}_${order._id}.xls`;
    excelFile.excelBrFilePath = path.join(basePath, excelFile.excelBrFile);
    excelFile.excelSuFile = `Su_${order.factory}_${order._id}.xls`;
    excelFile.excelSuFilePath = path.join(basePath, excelFile.excelSuFile);

    // 先保存要返回给前端的workbook实例,避免重复生成
    let workbookFinal;
    // 等待所有Excel文件生成完成
    return Promise.all([
      (async () => {
        workbookFinal = await createExcelFile(order, "C", excelFile.excelOffFilePath);
        return workbookFinal;
      })(),
      createExcelFile(order, "F", excelFile.excelFactFilePath),
      createExcelFile(order, "B", excelFile.excelBrFilePath),
      createExcelFile(order, "S", excelFile.excelSuFilePath)
    ]).then(() => {
      // 处理前端下载的响应
      res.setHeader("Content-Type", "application/vnd.ms-excel");
      res.setHeader("Content-Disposition", `attachment;filename='${excelFile.excelOffFile}'`);
      return workbookFinal.xlsx.write(res);
    }).then(() => {
      res.end();
      // 现在所有文件都已写入磁盘,发送邮件
      return transporter.sendMail({
        from: defaultMailId,
        to: config.get("mail.defaultAdd"),
        subject: "Order with excel attachment send through software ",
        html: `<h1> The mail has been sent as trial through program for testing excel file attachement. </h1> 
        <h3> file 1- ${url.format({ 
          protocol: req.protocol, 
          host: req.get("host"), 
          pathname: "orders/genExcelFile/" + excelFile.excelOffFile 
        })}</br> 
        file 2- ${url.format({ 
          protocol: req.protocol, 
          host: req.get("host"), 
          pathname: "orders/genExcelFile/" + excelFile.excelFactFile 
        })}</br> 
        file 3- ${url.format({ 
          protocol: req.protocol, 
          host: req.get("host"), 
          pathname: "orders/genExcelFile/" + excelFile.excelBrFile 
        })}</br> 
        file 4- ${url.format({ 
          protocol: req.protocol, 
          host: req.get("host"), 
          pathname: "orders/genExcelFile/" + excelFile.excelSuFile 
        })}</br> </h3> `,
        attachments: [
          { 
            content: fs.readFileSync(excelFile.excelOffFilePath, { encoding: "base64" }),
            filename: excelFile.excelOffFile,
            type: "application/vnd.ms-excel"
          },
          { 
            content: fs.readFileSync(excelFile.excelFactFilePath, { encoding: "base64" }),
            filename: excelFile.excelFactFile,
            type: "application/vnd.ms-excel"
          },
          { 
            content: fs.readFileSync(excelFile.excelBrFilePath, { encoding: "base64" }),
            filename: excelFile.excelBrFile,
            type: "application/vnd.ms-excel"
          },
          { 
            path: excelFile.excelSuFilePath,
          }
        ]
      });
    });
  })
  .then(info => {
    console.log("message Send");
  })
  .catch(err => console.log(err)); 
};

3. 额外优化建议

  • 使用绝对路径:把相对路径改成基于__dirname的绝对路径,避免不同运行环境下文件读取路径错误。
  • 错误处理增强:在读取文件前可以添加fs.existsSync(filePath)检查,确保文件存在后再读取,避免抛出文件不存在的错误。
  • 复用workbook实例:在生成第一个Excel时保存workbook实例,避免给前端返回时重复生成,提高效率。

为什么换SendGrid问题依旧?因为问题不在邮件服务商,而是你读取文件的时机不对——文件还没写完就被读取,不管用哪个邮件服务发送空内容,附件都会是0字节。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:47:45