前言
大二下学期,有一门课要求做一个"完整的 Web 应用"作为期末项目。选题的时候我想了很久------做一个普通的 CRUD 管理系统太无聊,做一个电商又太常见。后来看到身边同学都在用各种刷题 App 备考,但要么题型单一、要么手动录入题目累死人、要么没有学习数据分析......
于是我做了一个叫 Cognix 的东西:一个支持多用户的智能题库练习平台,可以用 AI 自动从图片、PDF、文本里提取题目,支持选择题、判断题、填空题、简答题等多种题型,还有错题本和学习数据仪表盘。
这篇文章会从技术选型、核心功能实现、部署上线到踩坑经验,完整复盘整个项目的诞生过程。希望能给正在做类似项目的朋友一些参考。
为什么选这套技术栈?
| 技术 | 选择理由 |
|---|---|
| React 19 | 当时刚发布不久,想尝鲜新特性(比如 use() Hook、Server Components 虽然我没用到),而且生态成熟 |
| TypeScript | 期末项目也要认真写类型,后期维护和重构时真香 |
| Vite | 开发体验极佳,热更新快得像本地文件 |
| Zustand | 比 Redux 轻量太多,API 简洁,配合 TypeScript 类型推导完美 |
| React Router v6 | 嵌套路由 + loader 很适合这种多页面应用 |
| Tailwind CSS | 写样式最快的方式,不用切文件,原子化类名 |
| Supabase | 后端即服务(BaaS),提供 PostgreSQL 数据库 + 认证 + 存储 + Row Level Security,省去自己写后端 API 的精力 |
| Recharts | 轻量图表库,用来画学习趋势折线图和题型分布饼图 |
说实话,选 Supabase 是我做过最正确的决定之一。作为一个前端为主的学生,我不需要再单独写一个 Node.js 后端,只需要定义好表结构和 RLS 策略,前端直接调用 Supabase SDK 就能完成所有数据操作。
核心功能实现
1. 多题型支持与答题引擎
题库系统最基础的就是题型支持。我设计了统一的 questions表结构:
go
interface Question {
id: string;
type: 'single_choice' | 'multiple_choice' | 'true_false' | 'fill_blank' | 'short_answer';
content: string; // 题干(支持 Markdown)
options?: string[]; // 选项(选择题用)
answer: string | string[]; // 正确答案
explanation?: string; // 解析
difficulty: 1 | 2 | 3; // 难度等级
tags: string[]; // 知识点标签
}
答题时,每种题型对应不同的渲染组件和判题逻辑。例如多选题允许多选,判题时需要比较数组是否完全一致;填空题则用正则忽略大小写和空格。
小技巧:判题函数全部写成纯函数,方便单元测试。我用 Vitest 写了 50+ 个测试用例覆盖所有题型边界情况。
2. AI 智能导入(这个最值得聊)
传统的题库录入方式是手工一条条打字,效率极低。我的目标是:用户上传一张试卷照片或一段文本,AI 自动解析出结构化题目。
实现思路很简单:
- 用户上传图片 / PDF / 输入文本 → 前端预览
- 调用 OCR(Tesseract.js 或第三方 API)提取文字(如果是图片)
- 将提取的文本发送到大模型 API(这里我接入了 OpenAI-compatible 的接口,也可以用国内模型)
- Prompt 设计是关键:
javascript
你是一个题目解析助手。请从以下文本中提取所有题目,按照 JSON 格式返回:
[
{
"type": "single_choice",
"content": "...",
"options": ["A. ...", "B. ...", "C. ...", "D. ..."],
"answer": "A",
"explanation": "..."
}
]
注意:判断题答案用 true/false,填空题答案用字符串数组。
- 收到返回的 JSON 后,前端进行校验和格式化,然后批量插入数据库
踩坑:大模型有时会返回格式错误的 JSON,或者漏掉题目。我加了两次校验:一次是 JSON.parse 前的正则修复,一次是字段完整性检查,不通过的题目进入"待人工修正"列表,用户可以手动编辑后再导入。
最终效果:导入一份 20 道题的试卷,从上传到入库不到 30 秒,准确率大概 85%------剩下的手动改一下就行,比起逐条输入快了 10 倍不止。
3. 错题分析与学习追踪
每次用户提交答案,系统都会记录一条 attempts记录:
ini
interface Attempt {
id: string;
user_id: string;
question_id: string;
answer_given: string | string[];
is_correct: boolean;
created_at: timestamp;
}
基于这些数据,我实现了:
- 错题本:自动收集所有答错的题目,按知识点分组,支持重新练习
- 学习仪表盘:用 Recharts 绘制近 7 天正确率趋势、各题型正确率对比、薄弱知识点雷达图
- 间隔重复提示:根据遗忘曲线,在合适的时间提醒用户复习旧错题
4. 多用户与权限管理
利用 Supabase Auth 实现邮箱注册/登录。每个用户只能看到自己的数据(通过 RLS 策略保证):
sql
-- 只允许查看自己的答题记录
CREATE POLICY "Users can view their own attempts"
ON attempts FOR SELECT
USING (auth.uid() = user_id);
部署上线:零成本搞定
前端部署在 EdgeOne (免费,绑定 Gitee/GitHub 仓库自动 CI/CD)。Supabase 本身是 SaaS,不需要额外部署。域名我用的是 cognix.liveling.top。
注意事项:
- EdgeOne 环境变量里配置 Supabase URL 和 anon key
- EdgeOne会自动里配置
rewrites规则,确保所有路由都指向index.html - Supabase 的免费套餐足够个人和小团队使用(500MB 数据库、5GB 带宽)
踩坑总结
- RLS 策略调试 :Supabase 的 RLS 一开始总是不生效,后来发现需要在 SQL Editor 里手动启用
FORCE ROW LEVEL SECURITY,并且在客户端使用select('*')时要确保用户已认证。 - AI 导入的稳定性:大模型偶尔会"幻觉"出题目,或者把题干和选项混淆。我在前端加了人工审核弹窗,让用户确认后再入库。
- 状态管理:Zustand 虽然好用,但当多个页面共享同一份题库数据时,缓存失效是个坑。最后我用 React Query(TanStack Query)替代了部分 Zustand store,利用它的 cache invalidation 自动刷新。
- 移动端适配:Tailwind 的响应式类很方便,但答题时的交互(尤其是多选题拖拽排序)在手机上体验不好,后面改用简单的勾选框。
项目地址与在线体验
- 开源仓库(Gitee):gitee.com/whpa24soft/...
- 开源仓库(GitHub):github.com/live-ling/c...
- 在线 Demo:cognix.liveling.top
如果你也在做类似的项目,或者想找个现成的题库系统来用,欢迎 Star、Fork、提 Issue。也欢迎在评论区交流技术细节!
最后
做这个项目最大的收获不是学会了某个框架,而是如何把一个想法变成可用的产品。从需求分析、技术选型、编码实现到部署上线,每一步都踩了不少坑,但也真正理解了"全栈"的含义。
如果你是相关专业的学生,正在纠结期末项目做什么,我的建议是:找一个真实的需求,哪怕很小,把它做到能用的程度。比起做一个完美的 demo,一个能跑起来、有人愿意用的项目,对你的成长帮助更大。
感谢阅读,希望对你有帮助
写在后面:跪求gitee star 这对我的期末成绩很重要,求求你了orz