我是小ao,大三计算机在读。最近,我在做一个AI驱动的面试准备平台------面试克星。用户系统做完后,摆在面前的问题是:题从哪来,题怎么展示,题怎么管理?
这篇文章记录我从"接入DeepSeek API"到"题库完整闭环"的实践过程。没有高深理论,全是踩坑和思考。
一、题从哪来?用Prompt批量生成种子数据
我一开始想过爬虫。但冷静下来算了一笔账:爬虫要学反爬、学解析、还要处理版权问题。而我的后端已经接好了DeepSeek API,为什么不直接让它出题?
我设计了一个出题Prompt,要求AI按分类每个生成10道,难度按比例分配。核心是输出格式锁死为JSON数组,方便后续直接入库。
结果出乎意料的好。AI生成了60道题,我抽查了几道,质量完全可以作为基础题库。这次实践还为我后续的AI出题功能奠定基础。
二、怎么入库?写一个seed脚本
拿到了JSON,接下来要把它导入MongoDB。我第一反应是手动一条条插入,但60条数据手动插太蠢了。正确姿势是写一个seed脚本。
脚本逻辑就四步:
ts
// 连接数据库
await connectDB()
// 清空旧数据(避免重复)
await Question.deleteMany({})
// 批量插入
await Question.insertMany(data)
// 断开连接
mongoose.disconnect()
看起来简单,但我踩了两个真实的坑。
坑1:connectDB() 没加 await
数据库连接是异步的,不等待就直接插入,等于"快递员还没到,你就开始收货"。后果就是脚本卡住不动,或者报uri参数为undefined。
坑2:忘记加载 .env
脚本文件里不主动加载环境变量,process.env.MONGO_URI 就是 undefined。必须:
ts
import dotenv from 'dotenv';
dotenv.config();
这两个坑都不难,但第一次写脚本的人一定会遇到。每个坑都让我更理解"异步"和"环境变量"这两个基础概念。
三、Question模型设计:enum和timestamps
Question模型本身不复杂,但有两点值得说。
第一,enum 约束让数据更干净。 题目的类型、分类、难度都是有限的选项,用 enum 锁死,防止脏数据混进来。这也逼着我去对齐"AI生成的JSON字段"和"模型字段"------两边对不上,insertMany 直接报验证错误。
第二,timestamps: true 是隐形的未来。 自动记录的 createdAt 和 updatedAt,现在看起来没用,但之后做练习记录、复盘回顾、学习进度统计时,全都要靠它。数据库设计时的"多想一步",就是为未来省事。
四、题目接口:动态筛选条件的构造
题库列表页按分类筛选,接口要能接收不同的筛选参数。我用的是query参数:
txt
GET /api/questions?category=JavaScript
GET /api/questions?difficulty=easy
后端逻辑很简单,但有个细节值得注意:
ts
const filter: any = {}
if (category) filter.category = category
if (difficulty) filter.difficulty = difficulty
const questions = await Question.find(filter)
动态构造查询条件------前端传什么,就筛选什么;不传,就返回全部。这个模式在后续做练习抽题、收藏筛选时都能复用。
五、前端列表页:从左到右的布局与数据流
列表页的交互设计是:左侧分类栏,右侧题目列表。 点击分类,右侧题目实时切换。
技术上就是 flex 布局加上"选中状态"管理。但真正让我重新理解的,是useEffect 依赖数组的威力:
ts
const [selectedCategory, setSelectedCategory] = useState(categories[0])
useEffect(() => {
// 请求题目
}, [selectedCategory])
selectedCategory 变化时,useEffect 自动重新执行,请求新分类的题目。这在Vue里是用watch实现的,在React里就是依赖数组。 换的是语法,不变的是"状态驱动视图"的底层逻辑。
六、详情页:useParams从URL里取id
点击题目跳转到详情页,路径是 /questions/:id。React Router 里,从URL里取动态参数靠 useParams:
ts
const { id } = useParams()
拿到id后,请求 /api/questions/${id},展示题目、选项、正确答案、解析。后端接口用 Question.findById(req.params.id) 查单条,查不到返回404。
写在最后
这个题库模块做完,我最大的感受是:前端和后端之间,数据流的对齐。
- AI生成的JSON字段,必须和Question模型对齐
- 后端返回的
{ questions: [...] },前端必须取res.data.questions而不是整个res.data - URL里的斜杠要
encodeURIComponent编码 - 每个受保护接口都要带上
Authorization头
这些都不是高深的知识,但每错一次,就是一次真实的成长。项目驱动的学习,就是让你在真实场景里,把这些细节刻进肌肉记忆。
现在题库跑通了。下一步,就是让用户真正"练习"起来------这是从"浏览"到"互动"的关键一步。
我是小ao,欢迎在评论区交流你的全栈学习经验。