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产品:一纸云深

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!

点击「阅读原文」查看我的独立开发笔记


👉 点击查看我的个人介绍

👉 点击查看我的小红书主页

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐