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

Express表单提交req.body为空问题求助(基于Nodemailer)

解决Nodemailer表单提交时req.body为空的问题

我来帮你排查这个问题,看起来有几个关键的点导致了req.body为空,咱们一步步来解决:

核心问题分析

  1. 表单包含文件上传,却没设置正确的编码类型:你的表单里有CV文件上传字段,但默认的表单编码application/x-www-form-urlencoded无法处理文件,必须用multipart/form-data,而body-parser不支持解析这种类型的请求体。
  2. 表单字段命名与路由不匹配:你路由里用req.body.name、req.body.email,但表单里的字段名是job_form[role]、job_form[email],就算拿到请求体也会出现变量未定义的问题。
  3. body-parser无法处理multipart/form-data:这个中间件只负责解析application/x-www-form-urlencoded和application/json类型的请求,对文件上传无能为力。

解决方案步骤

1. 修改表单的编码类型

首先给你的表单加上enctype="multipart/form-data"属性,确保能正确传输文件和表单数据:

<form method="POST" action="/jobs/join-us" enctype="multipart/form-data">
  <div class='input-wrapper'>
    <input class="flying-label-input" type="text" name="job_form[role]" id="job_form_role" />
    <label class="flying-label required" for="job_form_role">Role</label>
  </div>
  <div class='input-wrapper'>
    <input class="flying-label-input" type="text" name="job_form[email]" id="job_form_email" />
    <label class="flying-label required" for="job_form_email">E-mail</label>
  </div>
  <div class='input-wrapper'>
    <input class="flying-label-input" type="text" name="job_form[phone_number]" id="job_form_phone_number" />
    <label class="flying-label" for="job_form_phone_number">Phone number</label>
  </div>
  <div class='input-wrapper'>
    <label class="label required" for="job_form_cv">CV (PDF)</label>
    <input type="file" name="job_form[cv]" id="job_form_cv" />
  </div>
  <div class='input-wrapper-space-top'>
    <input type="hidden" name="job_form[referer]" id="job_form_referer" />
    <input type="submit" name="commit" value="Submit Job Application" class="btn-round btn-primary" />
  </div>
</form>

2. 替换body-parser为multer

multer是专门处理multipart/form-data的Express中间件,既能解析普通表单字段,也能处理文件上传。

首先安装multer:

npm install multer

然后修改你的app.js中间件配置:

const express = require('express')
var app = express();
var path = require('path')
const nodemailer = require('nodemailer');
// 引入multer
const multer = require('multer');

// 配置multer为内存存储(如果需要保存文件到服务器磁盘,可以修改为diskStorage)
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

// 静态文件服务保留
app.use(express.static('public'))
// 保留JSON解析(如果有其他JSON请求的话)
app.use(express.json())

// 在路由中使用upload中间件,upload.any()会处理所有字段(包括文件)
app.post('/jobs/join-us', upload.any(), (req, res) => {
  // 现在req.body包含普通表单字段,req.files包含上传的文件
  console.log('表单字段:', req.body);
  console.log('上传的文件:', req.files);

  // 解析job_form[xxx]格式的字段为嵌套对象
  const jobForm = {};
  Object.keys(req.body).forEach(key => {
    if (key.startsWith('job_form[')) {
      const fieldName = key.slice(9, -1); // 提取role、email等字段名
      jobForm[fieldName] = req.body[key];
    }
  });
  console.log('解析后的表单数据:', jobForm);

  // 构建邮件内容,注意字段名对应(路由里原来的name对应表单里的role)
  const output = `
    <p>You have a new job application from the contact form.</p>
    <h3>Application Details</h3>
    <ul>
      <li>Applied Role: ${jobForm.role}</li>
      <li>Email: ${jobForm.email}</li>
      <li>Phone Number: ${jobForm.phone_number || 'Not provided'}</li>
    </ul>
  `;

  // Nodemailer配置不变
  let transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 587,
    secure: false, // true for 465, false for other ports
    auth: {
      user: 'xx', // 你的邮箱
      pass: 'xx', // 你的邮箱密码/应用密码
    },
    tls: { rejectUnauthorized: false }
  });

  // 如果需要把CV作为附件发送,添加attachments选项
  let mailOptions = {
    from: 'xx', // 发件人
    to: 'xx', // 收件人
    subject: 'New Job Application Received',
    text: 'A new job application has been submitted.',
    html: output,
    attachments: req.files.map(file => ({
      filename: file.originalname,
      content: file.buffer
    }))
  };

  // 发送邮件
  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      console.error('邮件发送失败:', error);
      return res.status(500).send("Failed to send application");
    }
    console.log('Message sent: %s', info.messageId);
    res.sendStatus(200);
  });
});

3. 额外注意事项

  • 字段名对应:一定要确保路由里使用的字段名和表单里的一致,我在示例里把原来的name改成了role,因为你的表单里是job_form[role]。
  • Gmail应用密码:如果你用Gmail作为SMTP服务器,现在需要用应用密码代替普通密码,尤其是开启了两步验证的账号,否则会认证失败。
  • 文件存储选项:如果不想把文件存在内存里,可以用multer.diskStorage()配置磁盘存储路径,方便后续管理。

内容的提问来源于stack exchange,提问作者Mikk Küttim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:07:33