如何使用Mongoose创建倒计时,计时结束触发函数实现测验页自动登出?
基于Node.js + Mongoose的测验倒计时自动登出实现方案
整体采用服务端逻辑兜底+前端倒计时展示的方案,避免前端篡改时间绕过登出限制,核心逻辑如下:
1. 定义测验会话Mongoose Schema
首先创建测验会话集合,存储用户的测验开始时间、时长、活跃状态等核心数据,借助MongoDB的时间计算能力判断是否到期:
const mongoose = require('mongoose'); const QuizSessionSchema = new mongoose.Schema({ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, quizId: { type: mongoose.Schema.Types.ObjectId, ref: 'Quiz', required: true }, // 服务端生成开始时间,禁止前端传入避免篡改 startTime: { type: Date, default: Date.now }, // 测验总时长,单位:分钟 durationMinutes: { type: Number, required: true }, isActive: { type: Boolean, default: true } }); // 虚拟字段:自动计算会话过期时间,无需持久化存储 QuizSessionSchema.virtual('expireAt').get(function () { return new Date(this.startTime.getTime() + this.durationMinutes * 60 * 1000); }); module.exports = mongoose.model('QuizSession', QuizSessionSchema);
2. 服务端定时触发到期登出逻辑
中小流量项目推荐用定时轮询方案,无需修改MongoDB配置,部署成本低:
- 先安装定时任务依赖:
npm install node-cron - 实现到期扫描和登出逻辑:
const cron = require('node-cron'); const QuizSession = require('./models/QuizSession'); // 初始化定时扫描任务,每分钟执行一次 function initQuizExpireMonitor() { cron.schedule('* * * * *', async () => { const currentTime = new Date(); // 筛选所有已到期但仍标记为活跃的会话 const expiredSessions = await QuizSession.find({ isActive: true, $expr: { $lte: [ { $add: ['$startTime', { $multiply: ['$durationMinutes', 60000] }] }, currentTime ] } }); // 批量处理到期会话 for (const session of expiredSessions) { // 执行自定义登出逻辑:比如销毁测验权限、自动提交未完成答案、清除会话token等 await customQuizLogoutLogic(session.userId, session.quizId); // 标记会话为已结束,避免重复处理 session.isActive = false; await session.save(); } }); } // 替换为你的业务登出逻辑 async function customQuizLogoutLogic(userId, quizId) { console.log(`用户${userId}的测验${quizId}已到期,执行自动登出`); // 你的业务逻辑写在这里 } // 项目启动时调用初始化方法 initQuizExpireMonitor();
如果需要秒级的触发精度,把定时任务的规则改为*/10 * * * * *即可每10秒扫描一次,可根据业务需求调整频率。
3. 前端配合实现自动跳转(可选)
服务端逻辑是兜底校验,前端可增加倒计时提升用户体验,到期自动触发页面跳转:
// 进入测验页时从服务端接口拉取剩余有效时长(单位:秒) const remainingTime = await fetch('/api/quiz/get-remaining-time').then(res => res.json()); const countdownTimer = setInterval(() => { remainingTime--; if (remainingTime <= 0) { clearInterval(countdownTimer); // 调用登出接口后跳转到到期提示页 fetch('/api/quiz/logout', { method: 'POST' }).then(() => { window.location.href = '/quiz/expired-notice'; }); } }, 1000);
注意事项
- 所有测验提交、页面访问请求,服务端都要校验会话的
isActive状态,不能完全依赖前端判断 - 测验开始时间必须由服务端生成,禁止接收前端传入的时间参数
内容的提问来源于stack exchange,提问作者JANATI MOHAMMED
相关产品推荐
相关产品推荐

