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

如何从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 04:07:32