Spring Boot + React 从0搭建在线考试系统实战
Spring Boot + React 从0搭建在线考试系统实战
一、背景
前段时间有个初二信息老师找到我,说需要一个在线模拟考试系统,学生浏览器打开就能考。需求其实不复杂:
- 老师 Excel 导入试题
- 学生选试卷、答题、交卷、看成绩
- 单选题、多选题、判断题都要支持
- 倒计时、答题卡、自动交卷
- 老师后台看成绩统计
但作为一个独立开发者,我得考虑成本和技术选型。这篇文章就记录一下我是怎么从0搭起来的。
二、技术选型
后端:Spring Boot 3.2 + MyBatis-Plus + MySQL 8.0
前端:React 18 + TypeScript + Vite + shadcn/ui + TailwindCSS
认证:JWT(jjwt 0.12.x)
部署:同一台 99 元 ECS + Nginx
为什么这么选?
Spring Boot 生态成熟,MyBatis-Plus 做 CRUD 确实快。前端选 React 是因为考试页面交互复杂(倒计时、答题卡弹窗、题目切换),组件化比纯 HTML 好做太多。shadcn/ui 颜值在线,Tailwind 写样式快。
全部跑在 99 元的 ECS 上,学生端接口不需要登录,直接访问即可考试。
三、数据库设计
核心表就几张:t_exam_paper(试卷)、t_question(试题)、t_exam_result(考试结果)、t_exam_session(正在作答会话)。
每张表都遵循规范加上了审计字段:
CREATE TABLE t_exam_paper (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100) NOT NULL COMMENT '试卷名称',
duration INT NOT NULL DEFAULT 45 COMMENT '考试时长(分钟)',
status TINYINT DEFAULT 0 COMMENT '0=草稿 1=已发布',
score_total INT DEFAULT 0 COMMENT '试卷总分',
pass_score INT DEFAULT NULL COMMENT '及格线',
created_by VARCHAR(50) COMMENT '创建人用户名',
updated_by VARCHAR(50) COMMENT '更新人用户名',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
);
试题表支持三种题型:单选(选项 JSON 存 A/B/C/D)、多选(答案用逗号分隔 A,B,C)、判断(对/错)。
考试结果表存的是完整答题详情 JSON,方便后面做错题分析。
四、JWT 认证的几个坑
做管理后台认证时踩了几个坑,写出来给新手参考:
4.1 OPTIONS 预检请求被拦截
浏览器发跨域请求前会先发一个 OPTIONS 预检。但自定义的 JWT 过滤器会拦截它——因为没有 Authorization 头。
// 在 JWT 过滤器开头加
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
chain.doFilter(request, response);
return;
}
4.2 中文乱码
返回值直接 response.setContentType("application/json") 默认是 ISO-8859-1 编码,中文变 ???。要加 charset:
response.setContentType("application/json;charset=UTF-8");
4.3 Spring CGLIB 代理的坑
在 @Configuration 类里用匿名类定义 JWT 过滤器时,this.jwtUtil 可能拿不到值。需要提前捕获到局部变量:
@Bean
public OncePerRequestFilter jwtAuthFilter() {
JwtUtil jwt = this.jwtUtil; // 先捕获
return new OncePerRequestFilter() {
void doFilterInternal(...) {
var claims = jwt.parseToken(token); // 用局部变量
}
};
}
五、试卷保护机制
发布后如果有人作答了,试卷就不能随便删了。我做了个保护逻辑:
| 试卷状态 | 删除 | 编辑题目 | 下架 | 复制 |
|---|---|---|---|---|
| 草稿 | ✅ | ✅ | N/A | ✅ |
| 已发布 & 无人作答 | ✅ | ✅ | ✅ | ✅ |
| 已发布 & 已有人作答 | ❌ | ❌ | ❌ | ✅ |
已有人作答的试卷只能复制,复制后老师可以在副本上修改再发布,不影响原始数据。
六、考试防挂机机制
学生开始考试后,如果关了浏览器或者网络断了,后台会一直显示「正在作答」。我用心跳机制来解决:
- 前端每 30 秒发一次心跳到后端
- 后端更新
last_heartbeat_at时间戳 - 1 分钟没有收到心跳,自动清理会话
- 关闭标签页时用
fetch keepalive发最后一条心跳
这样就算学生突然断网,最多 1 分钟后台就自动释放了。
七、成绩统计
交卷后老师能看到完整的统计分析面板:
- 参考人数、平均分、最高/最低分
- 及格率(≥60%)、优秀率(≥80%)
- 分数段分布柱状图(0-59 / 60-79 / 80-100)
还有一个错题汇总,能清楚看到每个学生的答题情况。
八、总结
这个项目前后端加起来大概写了 3000 多行代码(不含配置),从需求沟通到上线用了不到一周。Spring Boot + React 的组合对于个人开发者做全栈项目来说效率确实高。
如果你也在做类似的项目,欢迎关注我的公众号「软件工坊」,后续会分享更多的独立开发实战经验。
💡 完整项目代码在 Gitee 上(私库),如果需要参考设计思路欢迎留言交流。
关于作者:无羡,独立开发者,专注AI应用开发。
📌 分类:全栈开发
👉 关注我获取更多技术分享
👉 个人博客:云深不知处
👉 独立开发省钱攻略:查看详情
👉 体验我的AI产品:一纸云深
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
点击「阅读原文」查看我的独立开发笔记
更多推荐



所有评论(0)