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+对应用户答案」的数组发送给后端,由后端批量比对答案,计算最终得分后返回给前端。
这种方式减少了请求次数,但要注意:如果题目数量很多,一次性加载可能影响性能,需要做分页或懒加载。
关键注意事项
- 前端状态(包括Zustand)仅存储题干、用户输入、得分,绝对不能包含答案。
- 后端要做好用户会话验证,防止用户重复提交或篡改请求参数(比如修改题目ID、答案)。
- 如果需要保存答题进度,可以将「已答题目ID+当前得分」存在前端localStorage,但同样不能涉及答案;后端也可以在数据库中记录用户的答题进度,防止刷新后丢失。
内容的提问来源于stack exchange,提问作者lool
相关产品推荐
相关产品推荐

