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
相关产品推荐
相关产品推荐

