如何在Node.js中通过Multer仅在CRUD成功时上传Cloudinary图片
确保学生数据操作成功后再上传头像到Cloudinary
我是一名学生,开发了一个CRUD项目,使用Multer处理学生头像并存储到Cloudinary中。我希望仅在学生数据创建或更新成功时,才将头像上传至Cloudinary。以下是我当前用于创建和更新学生信息的控制器函数:
当前创建学生函数
async function addNewStudent(req, res) { try { const { student_roll, student_name, student_email } = req.body; const existingStudentID = await Students.findOne({ student_roll }); if (existingStudentID) { return res.status(400).json({ status: 400, message: `Student RollNo. '${student_roll}' already exists in the database.` }); } const existingStudentEmail = await Students.findOne({ student_email }); if (existingStudentEmail) { return res.status(400).json({ status: 400, message: `Student email '${student_email}' already exists in the database.` }); } const result = await cloudinary.uploader.upload(req.file.path, { resource_type: 'image' }); const newStudent = await Students.create({ student_roll, student_name, student_email, profile_image: { public_id: result.public_id, secure_url: result.secure_url } }); return actionCompleteResponse({ res, data: newStudent }); } catch (error) { console.error(error); return sendError({ res, error }); }}
当前更新学生函数
async function updateStudent(req, res) { try { const updates = req.body; if (!ObjectId.isValid(req.params.id)) { throw 'NOT_VALID_DOCUMENT_ID'; } const existingStudentID = await Students.findOne({ student_roll: updates.student_roll }); if (existingStudentID) { return res.status(400).json({ status: 400, message: `Student roll no. '${updates.student_roll}' already exists in the database.` }); } const existingStudentName = await Students.findOne({ student_name: updates.student_name }); if (existingStudentName) { return res.status(400).json({ status: 400, message: `Student name '${updates.student_name}' already exists in the database.` }); } const existingStudentEmail = await Students.findOne({ student_email: updates.student_email }); if (existingStudentEmail) { return res.status(400).json({ status: 400, message: `Student email '${updates.student_email}' already exists in the database.` }); } if (req.file) { const existingStudentImage = await Students.findOne({ _id: new ObjectId(req.params.id) }); const imageID = existingStudentImage.profile_image.public_id; if (existingStudentImage.profile_image && imageID) { const deletePreviousImage = await cloudinary.uploader.destroy(imageID); } const newImage = await cloudinary.uploader.upload(req.file.path); updates.profile_image = { public_id: newImage.public_id, secure_url: newImage.secure_url }; } const updatedStudent = await Students.updateOne({ _id: new ObjectId(req.params.id) }, { $set: updates }); return actionCompleteResponse({ res, data: updates }); } catch (error) { return sendError({ res, error }); }}
解决方案:调整操作顺序 + 错误回滚
一、修改创建学生函数
核心逻辑:先创建无头像的学生数据,确认数据写入成功后再上传头像,最后更新头像字段;若中间步骤失败,回滚已创建的资源。
async function addNewStudent(req, res) { try { const { student_roll, student_name, student_email } = req.body; // 唯一性校验 const existingStudentID = await Students.findOne({ student_roll }); if (existingStudentID) { return res.status(400).json({ status: 400, message: `Student RollNo. '${student_roll}' already exists in the database.` }); } const existingStudentEmail = await Students.findOne({ student_email }); if (existingStudentEmail) { return res.status(400).json({ status: 400, message: `Student email '${student_email}' already exists in the database.` }); } // 1. 先创建不带头像的基础学生数据 const newStudent = await Students.create({ student_roll, student_name, student_email, profile_image: null }); // 2. 数据创建成功后,上传头像到Cloudinary const result = await cloudinary.uploader.upload(req.file.path, { resource_type: 'image' }); // 3. 更新学生数据的头像字段 await Students.findByIdAndUpdate(newStudent._id, { profile_image: { public_id: result.public_id, secure_url: result.secure_url } }); // 返回更新后的完整数据 const updatedStudent = await Students.findById(newStudent._id); return actionCompleteResponse({ res, data: updatedStudent }); } catch (error) { console.error(error); // 回滚:如果已创建学生但头像上传失败,删除该学生数据 if (typeof newStudent !== 'undefined') { await Students.findByIdAndDelete(newStudent._id); } // 清理Multer本地临时文件 if (req.file) { require('fs').unlinkSync(req.file.path); } return sendError({ res, error }); } }
二、修改更新学生函数
核心逻辑:先更新非头像字段,确认数据更新成功后再处理头像上传和旧头像删除;若中间步骤失败,回滚已上传的新头像。
async function updateStudent(req, res) { try { const updates = { ...req.body }; const studentId = req.params.id; let newImage = null; if (!ObjectId.isValid(studentId)) { throw 'NOT_VALID_DOCUMENT_ID'; } // 获取原学生数据,用于后续校验和旧头像处理 const originalStudent = await Students.findById(studentId); if (!originalStudent) { return res.status(404).json({ status: 404, message: 'Student not found' }); } // 唯一性校验:排除当前学生自身的记录 const existingStudentID = await Students.findOne({ student_roll: updates.student_roll, _id: { $ne: studentId } }); if (existingStudentID) { return res.status(400).json({ status: 400, message: `Student roll no. '${updates.student_roll}' already exists in the database.` }); } const existingStudentEmail = await Students.findOne({ student_email: updates.student_email, _id: { $ne: studentId } }); if (existingStudentEmail) { return res.status(400).json({ status: 400, message: `Student email '${updates.student_email}' already exists in the database.` }); } // 1. 先更新非头像字段 const updateFields = { ...updates }; delete updateFields.profile_image; let updatedStudent = await Students.findByIdAndUpdate( studentId, { $set: updateFields }, { new: true } ); // 2. 数据更新成功后,处理头像(如果有新头像) if (req.file) { // 上传新头像 newImage = await cloudinary.uploader.upload(req.file.path); // 更新头像字段 updatedStudent = await Students.findByIdAndUpdate( studentId, { profile_image: { public_id: newImage.public_id, secure_url: newImage.secure_url } }, { new: true } ); // 删除旧头像 if (originalStudent.profile_image?.public_id) { await cloudinary.uploader.destroy(originalStudent.profile_image.public_id); } } return actionCompleteResponse({ res, data: updatedStudent }); } catch (error) { console.error(error); // 回滚:如果已上传新头像但数据更新失败,删除新头像 if (newImage?.public_id) { await cloudinary.uploader.destroy(newImage.public_id); } // 清理Multer本地临时文件 if (req.file) { require('fs').unlinkSync(req.file.path); } return sendError({ res, error }); } }
关键优化点
- 操作顺序反转:将数据写入/更新放在头像操作之前,确保只有数据操作成功才触发头像上传。
- 错误回滚机制:在catch块中清理已创建的学生数据、已上传的头像、Multer本地临时文件,避免产生垃圾资源。
- 唯一性校验修正:更新时排除当前学生自身记录,避免因修改自身信息触发重复校验报错。
- 移除不合理校验:删除了学生姓名的唯一性校验(同名学生是合理场景)。
内容的提问来源于stack exchange,提问作者Jaskaran Singh
相关产品推荐
相关产品推荐

