如何基于Node.js与MongoDB实现每日固定随机题的测验应用功能?
实现每日固定随机题的两种方案
嘿,这个需求挺实用的,我来给你分享两种靠谱的实现思路,你可以根据自己的场景来选~
方案一:基于日期种子生成固定随机题(轻量无额外存储)
核心逻辑是用当天的日期作为伪随机数生成器的种子,这样同一天内所有请求都会得到同一个随机数,从而拿到同一道题;次日日期更新,种子也跟着变,自动切换新题目。
具体修改步骤:
- 先实现一个伪随机数生成函数(原生
Math.random()不支持指定种子,我们需要一个种子固定则输出固定的算法,这里用轻量的Mulberry32) - 生成当天的唯一种子(比如当天0点的时间戳)
- 用种子生成固定的随机索引,再查询对应题目
修改后的代码如下:
// 伪随机数生成器:种子相同则输出的随机数序列固定 function mulberry32(a) { return function() { a |= 0; a = a + 0x6D2B79F5 | 0; var t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; } // 生成当天的唯一种子(重置到当天0点的时间戳) const getTodaySeed = () => { const today = new Date(); today.setHours(0, 0, 0, 0); return today.getTime(); }; exports.getRandomQuestion = catchAsync(async (req, res, next) => { const count = await Question.countDocuments(); if (count === 0) { return res.status(404).json({ status: 'fail', message: '暂无题目' }); } // 用当天种子初始化伪随机数生成器 const prng = mulberry32(getTodaySeed()); // 生成固定的随机索引 const randomIndex = Math.floor(prng() * count); const question = await Question.findOne().skip(randomIndex).exec(); res.status(200).json({ status: 'success', data: question }); });
方案优势:
- 不需要额外的数据库集合或缓存,代码改动小
- 不需要定时任务,逻辑简单直接
- 天然支持多实例部署(所有实例用同一个日期种子,生成的题目一致)
方案二:存储每日题目(适合需要历史记录/统计的场景)
如果你的应用后续需要查看历史每日题目、统计用户答题数据等,可以把每天的题目存到一个专门的集合里,用定时任务每天凌晨生成并更新当日题目。
具体步骤:
- 创建
DailyQuestion模型,用于存储每日对应的题目ID - 用定时任务(比如
node-schedule)每天凌晨生成并保存当日题目 - 接口直接查询
DailyQuestion集合,拿到当日题目
1. 定义DailyQuestion模型
const mongoose = require('mongoose'); const dailyQuestionSchema = new mongoose.Schema({ date: { type: Date, required: true, unique: true, // 确保日期存储为当天0点 set: val => new Date(new Date(val).setHours(0,0,0,0)) }, questionId: { type: mongoose.Schema.Types.ObjectId, ref: 'Question', required: true } }); const DailyQuestion = mongoose.model('DailyQuestion', dailyQuestionSchema); module.exports = DailyQuestion;
2. 定时任务生成每日题目
const schedule = require('node-schedule'); const DailyQuestion = require('./models/DailyQuestion'); const Question = require('./models/Question'); // 每天凌晨0点执行任务,生成当日题目 schedule.scheduleJob('0 0 * * *', async () => { try { const count = await Question.countDocuments(); if (count === 0) { console.log('题库为空,无法生成每日题目'); return; } const randomIndex = Math.floor(Math.random() * count); const question = await Question.findOne().skip(randomIndex).exec(); // 保存或更新当日记录(upsert: true 表示不存在则创建,存在则更新) await DailyQuestion.findOneAndUpdate( { date: new Date() }, { questionId: question._id }, { upsert: true, new: true } ); console.log('当日题目已更新'); } catch (err) { console.error('生成每日题目失败:', err); } });
3. 修改接口查询每日题目
const DailyQuestion = require('./models/DailyQuestion'); exports.getDailyQuestion = catchAsync(async (req, res, next) => { const today = new Date(); today.setHours(0, 0, 0, 0); // 查询当日题目,关联Question集合的完整数据 const dailyQuestion = await DailyQuestion.findOne({ date: today }).populate('questionId'); // 兜底逻辑:如果定时任务没执行(比如服务刚启动),即时生成一道题 if (!dailyQuestion) { const count = await Question.countDocuments(); if (count === 0) { return res.status(404).json({ status: 'fail', message: '暂无题目' }); } const randomIndex = Math.floor(Math.random() * count); const question = await Question.findOne().skip(randomIndex).exec(); return res.status(200).json({ status: 'success', data: question }); } res.status(200).json({ status: 'success', data: dailyQuestion.questionId }); });
方案优势:
- 可以保留历史每日题目,方便后续扩展功能(比如用户回顾往期题目)
- 可以统计每日题目的答题次数、正确率等数据
- 接口查询更快,不需要每次都计算随机索引和跳过查询
内容的提问来源于stack exchange,提问作者user8747578
相关产品推荐
相关产品推荐

