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

React Next.js测验应用中含答案题目数据的安全存储方案咨询

解决测验应用的答案泄露问题

核心原则:绝对不要将答案传递到前端,所有判分逻辑必须在后端完成,前端只负责展示题目、收集用户输入、展示结果。

推荐以下两种可行方案:

方案一:逐题请求+后端实时判分

每次只从后端获取当前要展示的题目(仅包含题干、选项,不含答案),用户提交选项后,将「题目ID+用户选择的选项」发送给后端接口,由后端比对数据库中的答案,返回是否正确、当前累计得分等结果,前端仅更新页面状态和分数。

示例代码

后端API路由(Next.js app/api/quiz/question/[id]/route.ts)

import { NextResponse } from 'next/server';
import prisma from '@/lib/prisma'; // 假设用Prisma操作数据库

export async function GET(request: Request, { params }: { params: { id: string } }) {
  const question = await prisma.question.findUnique({
    where: { id: params.id },
    select: { id: true, title: true, options: true }, // 只返回需要的字段,排除answer
  });
  return NextResponse.json(question);
}

export async function POST(request: Request, { params }: { params: { id: string } }) {
  const { userAnswer } = await request.json();
  const question = await prisma.question.findUnique({
    where: { id: params.id },
    select: { answer: true },
  });

  const isCorrect = question?.answer === userAnswer;
  // 这里可结合用户会话更新累计得分,比如从session取当前分数计算后更新
  return NextResponse.json({ isCorrect });
}

前端调用逻辑(React组件)

'use client';
import { useState, useEffect } from 'react';
import { useStore } from '@/store/quiz'; // Zustand仅存当前得分、当前题目ID等

export default function QuestionPage({ questionId }: { questionId: string }) {
  const [question, setQuestion] = useState<{id:string; title:string; options:string[]} | null>(null);
  const [userAnswer, setUserAnswer] = useState('');
  const { score, setScore } = useStore();

  // 获取当前题目
  useEffect(() => {
    fetch(`/api/quiz/question/${questionId}`)
      .then(res => res.json())
      .then(data => setQuestion(data));
  }, [questionId]);

  // 提交答案
  const handleSubmit = async () => {
    if (!userAnswer) return;
    const res = await fetch(`/api/quiz/question/${questionId}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userAnswer }),
    });
    const { isCorrect } = await res.json();
    if (isCorrect) {
      setScore(prev => prev + 10); // 假设每题10分
    }
    // 跳转到下一题逻辑...
  };

  if (!question) return <div>加载中...</div>;

  return (
    <div>
      <h3>{question.title}</h3>
      {question.options.map((opt) => (
        <label key={opt} style={{display: 'block', margin: '8px 0'}}>
          <input
            type="radio"
            value={opt}
            checked={userAnswer === opt}
            onChange={(e) => setUserAnswer(e.target.value)}
          />
          {opt}
        </label>
      ))}
      <button onClick={handleSubmit} disabled={!userAnswer}>提交答案</button>
      <p>当前得分:{score}</p>
    </div>
  );
}

方案二:批量获取题干,后端统一判分

如果不想频繁请求接口,可以一次性从后端获取所有题目的题干和选项(不含答案),存在前端状态中逐个展示。用户完成所有题目后,将「所有题目ID+对应用户答案」的数组发送给后端,由后端批量比对答案,计算最终得分后返回给前端。

这种方式减少了请求次数,但要注意:如果题目数量很多,一次性加载可能影响性能,需要做分页或懒加载。

关键注意事项

  1. 前端状态(包括Zustand)仅存储题干、用户输入、得分,绝对不能包含答案。
  2. 后端要做好用户会话验证,防止用户重复提交或篡改请求参数(比如修改题目ID、答案)。
  3. 如果需要保存答题进度,可以将「已答题目ID+当前得分」存在前端localStorage,但同样不能涉及答案;后端也可以在数据库中记录用户的答题进度,防止刷新后丢失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 08:12:58