Mongoose模型无法同时强制用户名与邮箱唯一性问题求助
用户名与邮箱唯一性校验问题排查与解决
我正在开发个人项目的用户注册功能,核心需求是确保用户名(name字段)和邮箱的唯一性。目前邮箱的唯一性校验已生效,但用户名的唯一性始终无法正常生效,同时设置两者唯一性时似乎会互相覆盖,导致允许重复数据。以下是我的模型代码:
const mongoose = require('mongoose'); const validator = require('validator'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const userSchema = new mongoose.Schema({ name: { type: String, unique: true, required: true, trim: true, lowercase: true // Ensure lowercase for consistency }, email: { type: String, unique: true, required: true, trim: true, lowercase: true }, password: { type: String, required: true, minlength: 7, validate(value) { if (value.toLowerCase().includes('password')) { throw new Error('Password cannot contain "password"'); } } }, characters: [{ character: { type: String } }], tokens: [{ token: { type: String, required: true } }], groups: [{ group: { type: String } }] }, { timestamps: true }); userSchema.methods.generateAuthToken = async function () { const user = this; const token = jwt.sign({ _id: user._id.toString() }, process.env.JWT_SECRET); user.tokens = user.tokens.concat({ token }); await user.save(); return token; } userSchema.methods.toJSON = function () { const user = this; const userObject = user.toObject(); delete userObject.password; delete userObject.tokens; return userObject; } userSchema.statics.findByCredentials = async (email, password) => { const user = await User.findOne({ email }); if (!user) { throw new Error('Unable to login...'); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { throw new Error('Unable to login...'); } return user; } // Hash the plain text password before saving userSchema.pre('save', async function (next) { const user = this; if (user.isModified('password')) { user.password = await bcrypt.hash(user.password, 8); } next(); }); const User = mongoose.model('User', userSchema); module.exports = User;
问题原因与修复步骤
1. 检查并重建MongoDB唯一索引
Mongoose的unique: true仅在集合首次创建时自动生成索引,若集合已存在(之前未配置unique时创建),该属性不会自动更新索引。需手动操作:
- 进入MongoDB Shell,切换目标数据库:
use your-db-name - 查看用户集合当前索引:
db.users.getIndexes() - 若
name字段无唯一索引,手动创建:db.users.createIndex({ name: 1 }, { unique: true }) - 确保
email索引正常:db.users.createIndex({ email: 1 }, { unique: true })
2. 等待索引初始化完成
Mongoose创建索引是异步操作,可能在插入数据时索引未生效。在数据库连接代码中添加索引初始化等待:
const connectDB = async () => { await mongoose.connect(process.env.MONGODB_URI); // 等待User模型索引创建完成 await User.init(); console.log('数据库连接成功,索引初始化完成'); }; connectDB();
3. 捕获并区分唯一性冲突错误
MongoDB抛出唯一键冲突时(错误码11000),在注册逻辑中区分重复字段并返回明确提示:
// 注册路由示例 app.post('/register', async (req, res) => { try { const user = new User(req.body); await user.save(); const token = await user.generateAuthToken(); res.status(201).send({ user, token }); } catch (error) { if (error.code === 11000) { const duplicateField = Object.keys(error.keyPattern)[0]; res.status(400).send({ error: `${duplicateField.charAt(0).toUpperCase() + duplicateField.slice(1)} 已存在` }); } else { res.status(400).send(error); } } });
4. 确认大小写转换逻辑生效
在pre('save')钩子中手动确保用户名和邮箱转小写,避免因Schema选项未触发导致的大小写重复:
userSchema.pre('save', async function (next) { const user = this; // 强制转小写并去空格 user.name = user.name.toLowerCase().trim(); user.email = user.email.toLowerCase().trim(); if (user.isModified('password')) { user.password = await bcrypt.hash(user.password, 8); } next(); });
5. 重置异常索引
若多次修改Schema导致索引异常,可先删除旧索引再重建:
// 临时执行一次,完成后可移除 db.users.dropIndexes(); db.users.createIndexes([ { name: 1, unique: true }, { email: 1, unique: true } ]);
内容的提问来源于stack exchange,提问作者Recypher
相关产品推荐
相关产品推荐

