背景
最近在做基于express+mongoose的个人博客项目开发,但在新增用户的时候却出现了这个错误:
MongoServerError: E11000 duplicate key error collection: test.users index: username_1 dup key: { username: "123abc" }
错误分析
duplicate key error collection
是指在数据库集合中出现了重复键错误。在数据库中,集合是一组相关的文档存储的容器,而键则是用于唯一标识每个文档的字段。
当尝试向数据库集合插入一个新文档时,如果该文档中包含了一个与已有文档中某个键的值相同的键,就会触发重复键错误。
那么就很容易知道,我的当前报错信息是:在 test.users 集合中,username 字段的值为 “123abc” 的文档已经存在,并且该字段被设置为了唯一索引。事实也是如此。
因为我的项目需求是:当用户注册的时候,用户名是不允许重复的,是唯一的,所以我在Schema中为username设置unique: true
属性。
当我初次注册用户的时候是可以正常打印出用户的信息的,
当我再次运行代码(也就是再以相同的用户信息注册一次),这时候就会出现重复键的问题。也就是出现了上面所说的报错。
解决方案
现在的业务场景是用户注册,并且用户名是不允许重复的(在我的项目是这样规定的)。下面有几种解决方法:
- 修改字段的值:即文档以存在 username 值为 “123abc”,那么就需要修改username的值,不能为"123abc";
- 更新以存在的文档:使用
updateOne()
或updateMany()
方法,通过查询条件更新文档; - 删除已存在的文档:可以使用
deleteOne()
或deleteMany()
方法删除已存在的文档,然后再插入新的文档。
上面的方法虽然可以解决问题,但是用户注册的时候难免会出现用户名重复的情况,一旦这个情况出现就会出现报错,程序就会终止,用户就无法再次进行注册。我们要做的就是处理这个报错,并且告诉用户是用户名重复的原因导致无法注册,提示用户更换一个username。
最后的解决方案:
因为是用mongoose开发的嘛,所以我们可以去mongoose文档看看有没有有关这种错误的处理。错误信息的关键信息在于duplicate key error
,而当时我是在用Validation给SchemaType做校验,而刚好文档里又说到unique
不是一个校验器,那么它必然会给出处理使用unique
出现的错误处理。
文档给出了当出现了duplicate key error
该如何处理,而也是受到这个启发,我就对duplicate key error
做了处理。
U2.init().
then(() => U2.create(dup)).
catch(error => {
// `U2.create()` will error, but will *not* be a mongoose validation error, it will be
// a duplicate key error.
// See: https://masteringjs.io/tutorials/mongoose/e11000-duplicate-key
assert.ok(error);
assert.ok(!error.errors);
assert.ok(error.message.indexOf('duplicate key error') !== -1);
});
用try catch
捕捉异常错误,捕捉到的错误有两种情况:一种是重复键的问题,另一种是mongoose内置的校验器设置的错误。两种错误有不同的处理方法。如果查询错误信息有duplicate key error
,就return false
;
如果没有的话,就是mongoose内置的校验器设置的错误,对它做进一步的处理。
mongoose内置的校验器设置的错误可以自定义的,比如像我这里这样:
username: {
type: String,
required: [true, 'username required'], // 必填项
unique: true, // 唯一项
validate: {
validator(value) {
return /^(?!\d+$)(?![a-zA-Z]+$)[a-zA-Z0-9]{6,8}$/.test(value)
},
message: props => `${props.value} is not a valid username!`
}
}
完整的demo代码:
let schema = new mongoose.Schema({
username: {
type: String,
required: [true, 'username required'], // 必填项
unique: true, // 唯一项
validate: {
validator(value) {
return /^(?!\d+$)(?![a-zA-Z]+$)[a-zA-Z0-9]{6,8}$/.test(value)
},
message: props => `${props.value} is not a valid username!`
}
},
// 密码不需要加validate验证条件,因为前端发送过来的密码是经过加密的
passowrd: {
type: String,
select: false,
required: [true, 'passowrd required'],
set(value) { // 加密密码
return encrypt(value)
}
},
email: {
type: String,
unique: true,
validate: {
validator(value) {
return /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/.test(value)
},
message: props => `${props.value} is not a valid email!`
}
},
avatar: {
type: String,
default: "http://127.0.0.1:3000/public/images/avatar.jpg"
},
nikname: {
type: String,
validate: {
validator(value) {
return /^([\w\W]){1,8}$/.test(value)
},
message: props => `${props.value} is not a valid nikname!`
},
default: "用户"
}
})
let User = mongoose.model('User', schema)
User.create({
username: "123abc",
passowrd: "1234cdf",
email: "example@example.com"
}).then(data => {
console.log(data);
}).catch(err => {
// console.log(err.message); // E11000 duplicate key error collection: test.users index: username_1 dup key: { username: "123abc" }
if (err.message.indexOf('duplicate key error') !== -1) {
console.log('存在重复键', err.keyPattern);
return false
}
Object.entries(err).map(([key, value]) => {
console.log(`error: ${key}, ${value.message} `)
})
})
这里是模拟用户注册的功能,里面的错误处理得不是很细致,但在真正运用到项目中的话是会把错误信息处理得更细致一些的。