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

React前端导出XLSX至Express后端并邮件发送的问题求助

问题:大Excel文件邮件发送报413错误,改用FormData传输后附件空白

我用xlsx库把对象数组转成Excel表格,原本用nodemailer发邮件没问题,但数组超过75个对象时,base64编码后的请求Payload太大,返回413错误。改成用FormData+axios把.xlsx文件传到后端,用multer接收,但邮件附件变成空白表格,求排查问题。


前端代码

const exportDataToSheet = async (email: string, dataArray: any) => {
  try {
    const listProducts = [];
    for (const object of dataArray.objects) {
            const dataObject = await getData(data.nr_pedido, data.cd_filial);
            listProducts.push(dataObject)
    }

    const workBook = XLSX.utils.book_new();
    const excelHeaders: string[] = [
      "Product",
      "Network",
      "Branch",
      "Branch name",
    ];

    const productData = [];
    for (const product of listProducts) {
      const productDetails = [
        `${product?.data[0]?.nr_product ?? "-"}`,
        `${product?.data[0]?.id_branch ?? "-"}`,

        //...a lot of more info...

      ]
      productData.push(productDetails)
    }
    
    productData.unshift(excelHeaders);

    const workSheet = XLSX.utils.aoa_to_sheet(productData);
  
    XLSX.utils.book_append_sheet(workBook, workSheet, "Infos on the products");

    const file = XLSX.writeFile(workBook, {bookType:'xlsx'});

    const formData = new FormData();

    formData.append('file', file);

    axios.post('http://localhost:3001/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    }).then(response => {
      console.log('File successfully sent.');
})
.catch(error => {
  console.error(error);
    })

    await requestMailer({
      email: email,
      model: "exportProductData",
      filename: `data_products.xlsx`,
    });
  } catch (error) {
    showAlert(error.response ? error.response.data.message : error.message, "error");
  }
}

后端代码

export default class EmailNodeMailerService {
  static async sendEmail(req: Request) {
    const express = require('express');
    const multer = require('multer');
    const upload = multer({dest: './uploads'});
    const app = express();
    let file;

    app.post('/upload', upload.single('arquivo'), (req, res) => {
      file = req.file;
      console.log('File received: ', file.originalName);
      res.send('File successfully sent.')
    })

//...various lines regarding node mailer...

            mailOptions.attachments = [
              {
                filename: filename ? filename : 'product_data.xlsx',
                content: file,
                contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
              }
            ];
          }
          
//...more code regarding node mailer...

    } catch (error) {
      throw new Error(error);
    }
  }
}

问题排查与修复方案

前端核心问题

  1. XLSX.writeFile使用错误:XLSX.writeFile是直接写入本地文件的方法,返回值为undefined,你把它赋值给file等于传了空值给FormData。应该用XLSX.write生成Buffer,再转成Blob对象。
  2. FormData文件格式错误:需要把Excel Buffer包装成Blob并指定正确的MIME类型,再添加到FormData。
  3. 请求时序错误:你在发送FormData后立刻调用requestMailer,此时后端还没接收到文件,邮件发送时file变量为空。必须等待文件上传完成再触发邮件。

修复后的前端关键代码:

// 替换原XLSX.writeFile部分
const excelBuffer = XLSX.write(workBook, { bookType: 'xlsx', type: 'buffer' });
const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });

const formData = new FormData();
formData.append('file', blob, 'data_products.xlsx'); // 第三个参数指定文件名

// 等待文件上传完成再发邮件
await axios.post('http://localhost:3001/upload', formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
});

await requestMailer({
  email: email,
  model: "exportProductData",
  filename: `data_products.xlsx`,
});

后端核心问题

  1. Multer字段名不匹配:前端FormData的字段是file,但后端用upload.single('arquivo'),字段名不一致导致req.file为undefined。
  2. 邮件附件内容错误:multer返回的req.file是文件信息对象,不是文件内容。需要读取文件二进制数据作为附件content。
  3. Express实例重复创建:在sendEmail方法内每次创建新的Express实例和路由是错误的,应该把路由定义在服务启动时,而非邮件方法内部。

修复后的后端关键代码:

// 路由配置放在服务启动文件中(不是sendEmail方法内)
const express = require('express');
const multer = require('multer');
const fs = require('fs').promises;
const app = express();
const upload = multer({ dest: './uploads' });

// 临时存储上传的文件信息(生产环境建议用数据库或唯一标识)
let uploadedFileInfo;

app.post('/upload', upload.single('file'), async (req, res) => {
  uploadedFileInfo = req.file;
  console.log('File received: ', uploadedFileInfo.originalname);
  res.send('File successfully sent.');
});

// 邮件服务类
export default class EmailNodeMailerService {
  static async sendEmail(req) {
    // ...其他nodemailer配置...

    if (uploadedFileInfo) {
      // 读取上传的文件内容
      const fileContent = await fs.readFile(uploadedFileInfo.path);
      mailOptions.attachments = [
        {
          filename: req.body.filename || 'product_data.xlsx',
          content: fileContent,
          contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        }
      ];
    }

    // ...发送邮件的代码...
  }
}

额外优化建议

  • 避免用全局变量存储文件信息,可在上传时返回文件ID,邮件请求携带ID读取文件。
  • 定时清理上传的临时文件,避免磁盘占用过高。
  • 考虑把Excel生成逻辑移到后端,前端只传原始数据,减少前端计算压力和传输量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 09:10:55