如何从Mongoose重复键错误中获取模型名并生成对应错误消息
问题描述
Node.js应用中通过Mongoose定义了Movie和User两个模型:Movie的name字段、User的email字段均设置了唯一约束。当前的duplicateKeyErrorHandler函数返回固定错误消息,无法区分触发重复键错误的模型,导致重复电影名与重复邮箱的错误提示完全一致。需要实现根据不同模型生成对应错误消息:
- 重复电影名时返回:
已存在名为'The Matrix'的电影,请更换名称 - 重复邮箱时返回:
已存在邮箱abc@gmail.com,请更换邮箱
原错误控制器代码
const CustomError = require('../utils/CustomError'); const castErrorHandler = (err) => { const msg = `Invalid value for ${err.path}: ${err.value}`; return new CustomError(msg, 400); } const duplicateKeyErrorHandler = (err) => { const msg = `There is already a movie with name ${err.keyValue.name}. Please use another name.`; return new CustomError(msg, 400); } const devErrors = (res, error) => { res.status(error.statusCode).json({ status: error.status, message: error.message, stackTrace: error.stack, error: error }); } const prodErrors = (res, error) => { if (error.isOperational) { res.status(error.statusCode).json({ status: error.status, message: error.message }); } else { res.status(500).json({ status: 'error', message: 'Something went wrong! Please try again later.' }); } } module.exports = (error, req, res, next) => { error.statusCode = error.statusCode || 500; error.status = error.status || 'error'; if (process.env.NODE_ENV === "development") { if(error.name === "CastError") error = castErrorHandler(error); if(error.code === 11000) error = duplicateKeyErrorHandler(error); devErrors(res, error); } else if (process.env.NODE_ENV === "production") { if(error.name === "CastError") error = castErrorHandler(error); if(error.code === 11000) error = duplicateKeyErrorHandler(error); prodErrors(res, error); } };
解决方案
提供两种可靠的实现方式,可根据场景选择:
方式一:通过keyValue字段直接判断模型
因为User的唯一字段是email,Movie的唯一字段是name,可以直接通过err.keyValue中的字段名区分模型,代码简洁直接:
修改duplicateKeyErrorHandler函数:
const duplicateKeyErrorHandler = (err) => { let msg; if (err.keyValue.email) { msg = `已存在邮箱${err.keyValue.email},请更换邮箱`; } else if (err.keyValue.name) { msg = `已存在名为'${err.keyValue.name}'的电影,请更换名称`; } else { // 兼容其他可能的重复键场景 const [field, value] = Object.entries(err.keyValue)[0]; msg = `已存在重复的${field}: ${value},请更换后重试`; } return new CustomError(msg, 400); }
方式二:从错误消息提取集合名转换为模型名
MongoDB返回的错误消息中包含集合名(如users、movies),通过正则匹配提取后转换为对应模型名,扩展性更强,适合后续新增模型的场景:
修改duplicateKeyErrorHandler函数:
const duplicateKeyErrorHandler = (err) => { // 从错误消息中匹配集合名 const collectionMatch = err.errmsg.match(/collection: .+\.(\w+)/); if (!collectionMatch) { return new CustomError('已存在重复数据,请更换后重试', 400); } const collectionName = collectionMatch[1]; // 转换为模型名(users -> User,movies -> Movie) const modelName = collectionName.charAt(0).toUpperCase() + collectionName.slice(1).slice(0, -1); // 获取重复的字段和对应值 const [field, value] = Object.entries(err.keyValue)[0]; // 根据模型生成对应提示 let msg; if (modelName === 'User') { msg = `已存在邮箱${value},请更换邮箱`; } else if (modelName === 'Movie') { msg = `已存在名为'${value}'的电影,请更换名称`; } else { msg = `已存在重复的${field}: ${value},请更换后重试`; } return new CustomError(msg, 400); }
完整修改后的错误控制器代码
以方式一为例,替换原函数后的完整代码:
const CustomError = require('../utils/CustomError'); const castErrorHandler = (err) => { const msg = `Invalid value for ${err.path}: ${err.value}`; return new CustomError(msg, 400); } const duplicateKeyErrorHandler = (err) => { let msg; if (err.keyValue.email) { msg = `已存在邮箱${err.keyValue.email},请更换邮箱`; } else if (err.keyValue.name) { msg = `已存在名为'${err.keyValue.name}'的电影,请更换名称`; } else { const [field, value] = Object.entries(err.keyValue)[0]; msg = `已存在重复的${field}: ${value},请更换后重试`; } return new CustomError(msg, 400); } const devErrors = (res, error) => { res.status(error.statusCode).json({ status: error.status, message: error.message, stackTrace: error.stack, error: error }); } const prodErrors = (res, error) => { if (error.isOperational) { res.status(error.statusCode).json({ status: error.status, message: error.message }); } else { res.status(500).json({ status: 'error', message: 'Something went wrong! Please try again later.' }); } } module.exports = (error, req, res, next) => { error.statusCode = error.statusCode || 500; error.status = error.status || 'error'; if (process.env.NODE_ENV === "development") { if(error.name === "CastError") error = castErrorHandler(error); if(error.code === 11000) error = duplicateKeyErrorHandler(error); devErrors(res, error); } else if (process.env.NODE_ENV === "production") { if(error.name === "CastError") error = castErrorHandler(error); if(error.code === 11000) error = duplicateKeyErrorHandler(error); prodErrors(res, error); } };
内容的提问来源于stack exchange,提问作者pradeep ecommerce
相关产品推荐
相关产品推荐

