如何结合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
相关产品推荐
相关产品推荐

