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

如何基于Node.js与MongoDB实现每日固定随机题的测验应用功能?

实现每日固定随机题的两种方案

嘿,这个需求挺实用的,我来给你分享两种靠谱的实现思路,你可以根据自己的场景来选~

方案一:基于日期种子生成固定随机题(轻量无额外存储)

核心逻辑是用当天的日期作为伪随机数生成器的种子,这样同一天内所有请求都会得到同一个随机数,从而拿到同一道题;次日日期更新,种子也跟着变,自动切换新题目。

具体修改步骤:

  1. 先实现一个伪随机数生成函数(原生Math.random()不支持指定种子,我们需要一个种子固定则输出固定的算法,这里用轻量的Mulberry32)
  2. 生成当天的唯一种子(比如当天0点的时间戳)
  3. 用种子生成固定的随机索引,再查询对应题目

修改后的代码如下:

// 伪随机数生成器:种子相同则输出的随机数序列固定
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 });
});

方案优势:

  • 不需要额外的数据库集合或缓存,代码改动小
  • 不需要定时任务,逻辑简单直接
  • 天然支持多实例部署(所有实例用同一个日期种子,生成的题目一致)

方案二:存储每日题目(适合需要历史记录/统计的场景)

如果你的应用后续需要查看历史每日题目、统计用户答题数据等,可以把每天的题目存到一个专门的集合里,用定时任务每天凌晨生成并更新当日题目。

具体步骤:

  1. 创建DailyQuestion模型,用于存储每日对应的题目ID
  2. 用定时任务(比如node-schedule)每天凌晨生成并保存当日题目
  3. 接口直接查询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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 09:37:35