前端更新用户头像失效求助:姓名邮箱可正常更新
问题分析:前端提交头像无法更新,仅姓名邮箱正常
实现用户头像、姓名及邮箱更新功能时,后端测试完全正常,但前端提交请求时,仅姓名和邮箱能成功更新,头像无法同步更新,核心原因及解决方案如下:
前端原有代码
const userDataForm = document.querySelector('.profile-update'); if (userDataForm) userDataForm.addEventListener('submit', async (e) => { e.preventDefault(); const name = document.getElementById('nameInput').value; const email = document.getElementById('emailInput').value; const image = document.getElementById('profile-img-file-input').files[0]; try { const res = await fetch('/api/v1/users/updateMe', { method: 'PATCH', body: JSON.stringify({ name, email, image }), headers: { 'Content-Type': 'application/json' }, }); console.log(res); const data = await res.json(); if (data.status == 'success') { alert('success'); } } catch (error) { alert(error); } });
profile.ejs页面代码
<div class="profile-user position-relative d-inline-block mx-auto mb-4"> <img src="/assets/img/<%= user.photo %>" id="user-profile-image" class="rounded-circle avatar-xl img-thumbnail user-profile-image" alt="user-profile-image" /> <div class="avatar-xs p-0 rounded-circle profile-photo-edit"> <input id="profile-img-file-input" name="photo" accept="image/*" type="file" class="profile-img-file-input" /> <label for="profile-img-file-input" class="profile-photo-edit avatar-xs"> <span class="avatar-title rounded-circle bg-light text-body"> <i class="ri-camera-fill"></i> </span> </label> </div> </div>
后端控制器代码
const multerStorage = multer.memoryStorage(); const multerFilter = (req, file, cb) => { if (file.mimetype.startsWith('image')) { cb(null, true); } else { cb(new AppError('not an image! please upload only images', 400), false); } }; const upload = multer({ storage: multerStorage, fileFilter: multerFilter, }); exports.uploadUserPhoto = upload.single('photo'); exports.resizeUserPhoto = catchAsync(async (req, res, next) => { if (!req.file) return next(); req.file.filename = `user-${req.user.id}-${Date.now()}.jpeg`; await sharp(req.file.buffer) .resize(500, 500) .toFormat('jpeg') .jpeg({ quality: 90 }) .toFile(`public/assets/img/${req.file.filename}`); next(); }); const filterObj = (obj, ...allowedFields) => { const newObj = {}; Object.keys(obj).forEach((el) => { if (allowedFields.includes(el)) newObj[el] = obj[el]; }); return newObj; }; exports.updateMe = async (req, res, next) => { if (req.body.password || req.body.passwordConfirm) { return next(new AppError('This route is not for password update Please use /updateMyPassword', 400)); } const filteredBody = filterObj(req.body, 'name', 'email'); if (req.file) filteredBody.photo = req.file.filename; const updatedUser = await User.findByIdAndUpdate(req.user._id, filteredBody, { new: true, runValidators: true, }); res.status(200).json({ status: 'success', data: { user: updatedUser, }, }); };
路由配置代码
router.patch('/updateMe', userController.uploadUserPhoto, userController.resizeUserPhoto, userController.updateMe);
核心问题与解决方案
问题根源:前端尝试用
JSON.stringify传输文件对象,这是无效的——JSON无法序列化二进制文件,导致后端multer无法解析到req.file,自然不会更新头像字段。后端配置的upload.single('photo')是用来解析multipart/form-data格式的文件上传,而非JSON格式。修改后的前端代码:使用
FormData来构造请求体,浏览器会自动设置正确的Content-Type(包含multipart边界):
const userDataForm = document.querySelector('.profile-update'); if (userDataForm) userDataForm.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(); formData.append('name', document.getElementById('nameInput').value); formData.append('email', document.getElementById('emailInput').value); const image = document.getElementById('profile-img-file-input').files[0]; if (image) formData.append('photo', image); // 键名与后端upload.single('photo')保持一致 try { const res = await fetch('/api/v1/users/updateMe', { method: 'PATCH', body: formData, // 无需手动设置Content-Type,浏览器会自动添加正确的multipart/form-data及边界 }); console.log(res); const data = await res.json(); if (data.status == 'success') { alert('success'); // 可选:实时更新页面头像显示 document.getElementById('user-profile-image').src = `/assets/img/${data.user.photo}`; } } catch (error) { alert(error); } });
- 额外验证点:
- 前端input的name属性已设置为
photo,与后端upload.single('photo')参数一致,这部分无需修改。 - 后端保存文件路径为
public/assets/img/,前端头像src路径为/assets/img/<%= user.photo %>,路径匹配正常。
内容的提问来源于stack exchange,提问作者Tosin
相关产品推荐
相关产品推荐

