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
相关产品推荐
相关产品推荐

