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

如何结合Multer与Nodemailer实现简历附件发送?

我来帮你解决这个问题,你遇到的req.files为空是因为Multer配置不当,同时混用了Formidable和Multer导致请求解析冲突,下面一步步帮你调整实现需求:

使用Multer + Nodemailer实现简历附件发送解决方案

更新:你当前尝试的方案

app.post('/jobs/join-us', upload.array(), function (req, res, next) { 
  console.log(req.body); 
  res.json(req.body); 
  var form = new formidable.IncomingForm(); 
  form.parse(req, function(err, fields, files) { 
    // `file` is the name of the <input> field of type `file` 
    var old_path = files.file.path, 
        file_size = files.file.size, 
        file_ext = files.file.name.split('.').pop(), 
        index = old_path.lastIndexOf('/') + 1, 
        file_name = old_path.substr(index), 
        new_path = path.join(process.env.PWD, '/uploads/', file_name + '.' + file_ext); 
    fs.readFile(old_path, function(err, data) { 
      fs.writeFile(new_path, data, function(err) { 
        fs.unlink(old_path, function(err) { 
          if (err) { 
            res.status(500); 
            res.json({'success': false}); 
          } else { 
            res.status(200); 
            res.json({'success': true}); 
          } 
        }); 
      }); 
    }); 
  }); 
});

你的需求与当前问题

我有一个职位申请表单,用户需上传简历至网站,想了解如何使用Nodemailer和Multer实现附件发送?以下是我用于发送邮件的app.js代码:

app.post('/jobs/join-us', upload.array(), function (req, res, next) { 
  console.log(req.body); 
  res.json(req.body); 
  const output = ` 
  <p>You have a new message from contact form.</p> 
  <h3>Contact Details</h3> 
  <ul> 
    <li>Role: ${req.body.role}</li> 
    <li>First name: ${req.body.first_name}</li> 
    <li>Last name: ${req.body.last_name}</li> 
    <li>Email: ${req.body.email}</li> 
    <li>Phone: ${req.body.phone}</li> 
    <li>Agreement: ${req.body.agreement}</li> 
    <li>Referer: ${req.body.referer}</li> 
    <li>CV: ${req.files}</li> 
  </ul> 
  <h3>Message</h3> 
  <p>${req.body.description}</p> 
  `; 
  // create reusable transporter object using the default SMTP transport 
  let transporter = nodemailer.createTransport({ 
    host: 'smtp.gmail.com', 
    port: 587, 
    secure: false, // true for 465, false for other ports 
    auth: { 
      user: 'xx', // generated ethereal user 
      pass: 'xx', 
    }, 
    tls: { 
      rejectUnauthorized: false 
    } 
  }); 
});

我还尝试在app.post下添加console.log(req.files),但返回空值,恳请各位提供指导。


具体解决步骤

1. 正确配置Multer(解决req.files为空的核心)

你当前使用的upload.array()没有指定文件字段名,Multer无法识别要解析的文件输入,且同时混用Formidable会导致请求体被重复解析,文件无法正常获取。先重新配置Multer:

const multer = require('multer');
const path = require('path');
const fs = require('fs');

// 确保uploads文件夹存在,不存在则创建
const uploadDir = './uploads';
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir);
}

// 配置Multer的存储规则
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, uploadDir);
  },
  filename: (req, file, cb) => {
    // 自定义文件名,避免重名冲突
    const uniqueName = `${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`;
    cb(null, uniqueName);
  }
});

// 初始化upload中间件,指定接受的文件字段名(假设前端表单的文件input name为'cv')
const upload = multer({ storage: storage }).single('cv'); // single表示接受单个文件,若需多个用array('cv', 2)

2. 修改路由处理逻辑,整合Multer与Nodemailer

移除Formidable的代码,统一用Multer处理文件上传,再通过Nodemailer发送带附件的邮件:

app.post('/jobs/join-us', upload, function (req, res, next) { 
  console.log('表单数据:', req.body);
  console.log('上传的简历:', req.file);

  // 校验是否有文件上传
  if (!req.file) {
    return res.status(400).json({ success: false, message: '请上传简历文件' });
  }

  // 构建邮件HTML内容
  const output = ` 
  <p>You have a new message from contact form.</p> 
  <h3>Contact Details</h3> 
  <ul> 
    <li>Role: ${req.body.role}</li> 
    <li>First name: ${req.body.first_name}</li> 
    <li>Last name: ${req.body.last_name}</li> 
    <li>Email: ${req.body.email}</li> 
    <li>Phone: ${req.body.phone}</li> 
    <li>Agreement: ${req.body.agreement}</li> 
    <li>Referer: ${req.body.referer}</li> 
    <li>CV: ${req.file.originalname}</li> 
  </ul> 
  <h3>Message</h3> 
  <p>${req.body.description}</p> 
  `; 

  // 配置Nodemailer传输器
  let transporter = nodemailer.createTransport({ 
    host: 'smtp.gmail.com', 
    port: 587, 
    secure: false, 
    auth: { 
      user: 'your-email@gmail.com', 
      pass: 'your-app-password', // 注意:Gmail需用应用专用密码(开启2FA后),而非原密码
    }, 
    tls: { 
      rejectUnauthorized: false 
    } 
  }); 

  // 邮件配置,添加附件
  const mailOptions = {
    from: '"职位申请系统" <your-email@gmail.com>',
    to: 'recipient-email@yourcompany.com', // 接收申请的邮箱
    subject: `新职位申请:${req.body.role}`,
    html: output,
    attachments: [
      {
        filename: req.file.originalname, // 邮件中显示的附件名
        path: req.file.path // 本地文件的路径(Multer存储的路径)
      }
    ]
  };

  // 发送邮件
  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      console.error('邮件发送失败:', error);
      // 发送失败后删除本地临时文件
      fs.unlink(req.file.path, (err) => err && console.error('删除临时文件失败:', err));
      return res.status(500).json({ success: false, message: '申请提交失败,请稍后重试' });
    }
    console.log('邮件发送成功:', info.messageId);
    // 发送成功后删除本地临时文件(若不需要保留简历)
    fs.unlink(req.file.path, (err) => err && console.error('删除临时文件失败:', err));
    res.status(200).json({ success: true, message: '申请提交成功,我们会尽快联系你' });
  });
});

3. 前端表单注意事项

确保HTML表单正确设置enctype="multipart/form-data",且文件输入的name属性与Multer配置的一致:

<form action="/jobs/join-us" method="POST" enctype="multipart/form-data">
  <!-- 其他表单字段 -->
  <input type="text" name="role" placeholder="申请职位" required>
  <input type="file" name="cv" accept=".pdf,.doc,.docx" required> <!-- name必须为'cv',和Multer配置一致 -->
  <button type="submit">提交申请</button>
</form>

关键提醒

  • 不要同时使用Multer和Formidable:两者都是表单数据解析中间件,混用会导致请求体被重复读取,文件无法正常获取。
  • Gmail SMTP认证:如果使用Gmail,需开启两步验证后创建应用专用密码,或开启"不太安全的应用访问"(不推荐)。
  • 文件清理:邮件发送完成后记得删除本地临时文件,避免服务器存储空间被占用。

内容的提问来源于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:09:14