Mongoose 在使用 .save() 方法更新文档时抛出 E11000 重复键错误

Posted

技术标签:

【中文标题】Mongoose 在使用 .save() 方法更新文档时抛出 E11000 重复键错误【英文标题】:Mongoose throws E11000 duplicate key error when updating a document with .save() method 【发布时间】:2022-01-22 03:07:41 【问题描述】:

我有一个如下所示的用户模型:

const userSchema = new mongoose.Schema(
    username: 
        type: String,
        required: true,
        minlength: 3,
        maxlength: 30,
        validate: 
            validator: function(v) 
                return /^[a-zA-Z0-9]+$/.test(v);
            ,
            message: "Your user name must be alphanumeric."
        ,
        unique: true
    ,
    email: 
        type: String,
        required: true,
        validate: 
            validator: function(v) 
                return /(?:[a-z0-9!#$%&'*+/=?^_`|~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`|~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.)3(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(v);
            ,
            message: "Invalid e-mail address."
        ,
        unique: true
    ,
    password: 
        type: String,
        required: true,
        minlength: 4,
        maxlength: 1024
    ,
    isAdmin: 
        type: Boolean,
        default: false
    ,
    devices: [
        type: mongoose.SchemaTypes.ObjectId,
        ref: 'Device'
    ],
    joinDate: 
        type: Date,
        default: Date.now
    
);

const User = mongoose.model('User', userSchema);

我有一个users.js Express.js 路由器来管理用户。这些路由之一使用指定的用户 ID 更新现有用户。路线如下:

// Modify a user's profile
router.put('/:userId', [auth, authAdmin], async function(req, res, next) 
    if(!isValidObjectID(req.params.userId)) return res.status(400).send( message: 'Given ID is not valid.', status: 400 );

    const  error  = validate(req.body);
    if(error) return res.status(400).send( message: error.details[0].message, status: 400 );

    let user = await User.findOne( email: req.body.email );
    if(user && user._id && user._id != req.params.userId) return res.status(400).send( message: 'E-mail address is already in use.', status: 400 );

    user = await User.findOne( username: req.body.username );
    if(user && user._id && user._id != req.params.userId) return res.status(400).send( message: 'Usename is already in use.', status: 400 );

    user = await User.findById(req.user._id);

    user.username = req.body.username;
    user.email = req.body.email;
    if(req.body.isAdmin) user.isAdmin = req.body.isAdmin;
    const salt = await bcrypt.genSalt(10);
    user.password = await bcrypt.hash(req.body.password, salt);

    try 
        user = await user.save();
        return res.send(_.omit(user.toObject(), 'password'));
     catch(exception) 
        console.log('Put 1:', exception);
    
);

当我使用此路由更新现有用户的唯一用户名时,我收到 MongoServerError: E11000 duplicate key error collection: iotapi.users index: email_1 dup key: email: "test@gmail.com" 错误。有什么说不通的。我还有另一条路线供用户更新他们的电子邮件地址。除了更新用户名之外,该路由具有几乎相同的功能。它工作得很好,但是当我更新用户名和电子邮件时,它会抛出错误。

我也尝试使用.findOneByIdAndUpdate() 方法来更新文档,但没有成功。我遇到了同样的错误。

【问题讨论】:

E11000 duplicate key error - 表示集合中可能有另一个文档与“test@gmail.com”的email字段值相同。 @prasad_ 是的,您是对的,但我检查了它,没有任何其他具有相同电子邮件字段的文档。我试图在不更改电子邮件但用户名的情况下更新文档。它们都是独一无二的,但我不知道有什么问题。 【参考方案1】:

有错别字

user = await User.findById(req.user._id);

应该是

user = await User.findById(req.params.userId);

更新

好吧,那不是拼写错误,而是真正的错误。

条件

let user = await User.findOne( email: req.body.email );
if(user && user._id && user._id != req.params.userId) 

仅当具有给定电子邮件的用户存在并且其 id 与查询字符串中发送的 Id 不同时,您才返回 400。换句话说,当 Id 相同时,代码将继续。

然后您到达从身份验证会话加载用户的行:

user = await User.findById(req.user._id);

此 ID 可能与请求中发送的 ID 不同,因此您尝试使用其他用户的电子邮件对其进行更新。它会导致重复错误。

【讨论】:

很抱歉,这不是一个错字:(req.user 是一个对象,它通过解码auth 中间件上的身份验证令牌来包含有关用户的信息。仍然感谢您的尝试。 天哪!等待!没错,我为什么要用指定用户的新用户信息来更改经过身份验证的管理员用户的信息。我一到我的电脑就会对此进行测试。我过得很艰难,因此我休息了一段时间。我相信这会奏效,并感谢我的救世主!

以上是关于Mongoose 在使用 .save() 方法更新文档时抛出 E11000 重复键错误的主要内容,如果未能解决你的问题,请参考以下文章

我可以从 post save 中间件执行 mongoose 更新吗?

Mongoose 模型的 save() 不会更新空数组

通过 Model.save() 更新时触发 Mongoose 预保存挂钩

Mongoose save() 方法不写入数据库

文档不会使用带有 mongoose 的 save() 方法保存

TypeError:在使用 Jests 和 Mongoose 测试 NestJS API 时,updatedService.save 不是一个函数