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

Mongoose Schema定时更新MongoDB模型实现登录失败次数重置

原有代码不生效的核心原因
  • 你把setTimeout判断逻辑写在模型文件顶层,这部分代码只会在模块首次被加载时执行1次,不会在每次登录失败、用户数据更新时触发,根本跑不到对应逻辑。
  • Node.js的setTimeout是内存级任务,服务重启就会全部丢失,多实例部署时不同实例的内存不共享,用户量上来后内存里堆积大量定时器还会引发内存泄漏,完全不适合做这类业务状态控制。
  • Mongoose本身没有提供定时扫描全表、自动更新字段的内置能力,自带的TTL索引仅支持自动删除过期文档,无法直接用来重置字段值。
正确实现方案

不要用内存定时器做状态控制,用时间戳对比的方式实现锁定逻辑,这是登录限频场景的通用实现方案,无额外性能开销,不受服务重启、多实例部署影响。

第一步:调整Schema结构

新增锁定到期时间字段,替代定时重置逻辑:

const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
  // 其他原有字段(用户名、密码哈希等)
  loginAttempts: { type: Number, default: 0, required: true },
  lockUntil: { type: Date, default: null, required: true }
});

// 新增虚拟字段,快速判断账号是否处于锁定状态
UserSchema.virtual('isLocked').get(function() {
  return !!(this.lockUntil && this.lockUntil.getTime() > Date.now());
});

// 其他模型方法...

const User = mongoose.model("User", UserSchema);
module.exports = User;

第二步:在登录逻辑中嵌入判断

不需要提前重置字段,每次登录请求时实时判断锁定状态即可:

// 登录接口核心逻辑示例
async function handleLogin(username, inputPassword) {
  const user = await User.findOne({ username });
  if (!user) return '用户不存在';

  // 账号处于锁定状态,直接返回提示
  if (user.isLocked) {
    const remainSec = Math.ceil((user.lockUntil.getTime() - Date.now()) / 1000);
    return `登录尝试次数过多,请${Math.ceil(remainSec/60)}分钟后再尝试`;
  }

  // 校验密码
  const passValid = await bcrypt.compare(inputPassword, user.passwordHash);
  if (passValid) {
    // 登录成功,清空失败计数和锁定标记
    user.loginAttempts = 0;
    user.lockUntil = null;
    await user.save();
    return '登录成功';
  }

  // 密码错误,累加失败次数
  user.loginAttempts += 1;
  // 失败次数超过3次,设置5分钟锁定期
  if (user.loginAttempts > 3) {
    user.lockUntil = new Date(Date.now() + 5*60*1000);
  }
  await user.save();
  const leftChance = Math.max(0, 3 - user.loginAttempts);
  return leftChance > 0 ? `密码错误,剩余尝试次数${leftChance}次` : '密码错误次数过多,账号已锁定5分钟';
}

可选:定期清理过期数据

如果需要清理数据库里的过期锁定记录(非必须,不影响业务逻辑),可以单独部署定时任务,不要写在模型定义层:

// 单独的定时任务脚本,比如每小时执行一次
const cron = require('node-cron');
cron.schedule('0 * * * *', async () => {
  await User.updateMany(
    { lockUntil: { $lt: new Date() } },
    { $set: { loginAttempts: 0, lockUntil: null } }
  );
});

注意:这类全表更新的定时任务不要执行太频繁,避免给数据库造成不必要的压力。

内容的提问来源于stack exchange,提问作者Raghav Patel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 06:06:27