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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 02:25:59