资讯动态

SpringBoot在线考试系统:高并发交卷与断网自动保存实战设计

发布时间:2026/10/17 18:16:59 来源:尧图企业网站定制
简介本资源是一套完整的基于SpringBoot开发的在线考试系统毕业设计项目面向计算机专业本科生及Java Web初学者解决课程考核数字化、考试流程线上化与角色权限精细化管理等实际教学需求。压缩包共3个文件1个ZIP源码包、1个TXT说明文档、1个SQL数据库脚本总大小24.12MB结构清晰ZIP含完整前后端代码与项目配置TXT提供部署说明与运行指引SQL支持一键初始化考试所需基础数据。系统采用典型B/S架构严格区分管理员与用户双角色前台涵盖试卷选择、公告浏览、课程查看与个人中心后台支持公告/课程/考试全生命周期管理及基础数据维护模块划分明确、代码注释充分适合作为课程设计参考或毕设二次开发基础。目前已有58人学习下载配套报告与可运行源码显著降低学习门槛助力快速掌握SpringBootMyBatisVue技术栈在教育类系统中的落地实践。1. 这不是又一个“SpringBoot CRUD 毕业设计模板”它跑通了真实监考逻辑、支持并发交卷防丢分、数据库字段级可审计——适合真要答辩、真要部署、真怕导师问“你怎么保证考试中途断网不丢答案”的人你见过太多标着“在线考试系统”的毕业设计源码首页一个登录框后台三个表user/exam/question点开 controller 全是GetMapping(/list)和return exam/list。但真正卡住答辩的从来不是“能不能显示题目”而是“学生切屏三次后还能不能续答”“交卷瞬间服务器崩了答案存没存住”“管理员删错一道题历史考生成绩怎么回滚”。这份基于 SpringBoot 的在线考试系统从数据库设计开始就埋了审计钩子每张业务表带create_time/update_time/operator_id后端用Transactional(timeout 60)包裹整场考试生命周期前端答题页内置beforeunload监听 本地 localStorage 自动快照30 秒存一次未提交答案连 MySQL 的innodb_flush_log_at_trx_commit1都在部署文档里加粗提醒。它不炫技但每个模块都对应答辩时可能被追问的“为什么这么设计”。如果你的毕设需要过答辩、能本地演示、甚至能扔到学校测试服务器上跑一周不翻车——这份资源不是“能用”而是“经得起问”。2. 从零启动三步拉起可交互的考试环境含数据库初始化、关键配置项说明与本地验证路径2.1 下载解压后必须做的三件事校验文件完整性、确认 JDK 版本边界、识别主启动模块拿到压缩包后先别急着mvn clean install。打开根目录你会看到四个核心文件夹exam-serverSpringBoot 后端、exam-webVue 前端、sql建库脚本、doc设计报告。重点检查exam-server/pom.xml中的java.version—— 本项目锁定为17不是 8不是 11是 17。很多同学本地装的是 JDK 8mvn compile会直接报record关键字不识别因为实体类用了 Java 14 的 record 语法。解决方案只有两个要么sdk install java 17.0.1-tem推荐 Temurin要么改pom.xml并手动重写ExamPaperRecord.java等 7 个 record 类为传统 POJO不推荐后续 JWT 工具类会因sealed关键字再报错。另外sql/init.sql文件末尾有-- MD5: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855这是空文件的校验值实际脚本开头有CREATE DATABASE IF NOT EXISTS exam_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;说明它要求 MySQL 5.7.8 或 MariaDB 10.2.2否则utf8mb4_unicode_ci会报错。提示不要用 Navicat 双击运行init.sql它的默认字符集可能是 latin1会导致中文字段注释乱码。务必用命令行mysql -u root -p exam_system sql/init.sql且执行前确认SET NAMES utf8mb4;已生效。2.2 数据库初始化不只是建表更要理解字段设计背后的业务约束sql/init.sql不是简单 CREATE TABLE。以核心表exam_paper试卷表为例CREATE TABLE exam_paper ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, title varchar(100) NOT NULL COMMENT 试卷标题, duration_minutes tinyint NOT NULL DEFAULT 60 COMMENT 考试时长分钟, start_time datetime NOT NULL COMMENT 考试开始时间, end_time datetime NOT NULL COMMENT 考试结束时间, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0-未发布1-进行中2-已结束3-已归档, created_by bigint NOT NULL COMMENT 创建人ID, created_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_by bigint DEFAULT NULL COMMENT 最后更新人ID, updated_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 最后更新时间, PRIMARY KEY (id), KEY idx_status_start (status,start_time) COMMENT 复合索引按状态和开始时间快速筛选可考试卷 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT试卷主表;注意三点duration_minutes用tinyint而非int—— 考试时长不可能超过 255 分钟省下 3 字节空间百万级数据时索引更紧凑idx_status_start是业务高频查询索引管理员查“当前进行中的考试”或“今天即将开始的考试”WHERE 条件必带status IN (1,0) AND start_time NOW()这个索引能让查询从全表扫描降到毫秒级created_time和updated_time默认值不同created_time仅插入时生效updated_time插入和更新都触发ON UPDATE CURRENT_TIMESTAMP这是审计溯源的基础——谁在什么时候改了试卷状态日志里一查便知。2.3 启动服务绕过前端构建用 mock 数据直连后端验证接口可用性很多同学卡在“前端 npm run serve 启动不了”其实答辩演示根本不需要完整前后端联调。exam-server自带 H2 内存数据库 Profileapplication-h2.yml只需两步在exam-server/src/main/resources/application.yml中将spring.profiles.active改为h2运行ExamServerApplication.java主类。启动成功后访问http://localhost:8080/h2-console填入 JDBC URLjdbc:h2:mem:examdb、User Namesa就能看到 H2 自动建好的表包括预置的管理员账号admin/123456。此时用 Postman 测试关键接口POST /api/v1/loginBody{username:admin,password:123456}→ 返回token说明认证模块通GET /api/v1/exam/paper/active→ 返回 JSON 数组说明试卷查询接口通POST /api/v1/exam/submitHeader 带Authorization: Bearer tokenBody 模拟交卷{paperId:1,answers:[{questionId:1,answer:A},{questionId:2,answer:B}]}→ 返回{code:200,msg:提交成功}即核心链路闭环。这比等 Webpack 编译 3 分钟更高效——答辩时你只需要证明“系统能跑、逻辑能走通”而不是“界面多好看”。3. 核心功能拆解为什么“自动保存草稿”不是前端定时器而是前后端协同的事务补偿机制3.1 答题页自动保存localStorage 快照 后端增量同步解决断网丢答案痛点前端exam-web/src/views/exam/AnswerPage.vue的保存逻辑不是简单的setInterval(() { localStorage.setItem(draft, JSON.stringify(data)) }, 30000)。它做了三层保障本地快照每次用户点击选项、输入填空题立即触发saveToLocalStorage()存的是{paperId, questionId, answer, timestamp}结构化数据而非整个试卷 JSON避免大对象序列化阻塞 UI后台静默同步每 45 秒发起一次POST /api/v1/exam/draft/saveBody 为当前 localStorage 中所有题目答案后端收到后不落业务表而是写入exam_draft表字段paper_id,user_id,question_id,answer,save_time恢复策略当用户重新进入答题页前端先GET /api/v1/exam/draft/recover?paperId123后端返回该试卷下所有已保存的答案前端再merge到当前答题状态。关键点在于exam_draft表的save_time是datetime(3)毫秒精度且INSERT语句加了ON DUPLICATE KEY UPDATE answerVALUES(answer), save_timeVALUES(save_time)。这意味着同一道题多次保存只保留最后一次避免网络抖动导致旧答案覆盖新答案。3.2 并发交卷防丢分Transactional Redis 分布式锁 数据库唯一约束三重保险交卷接口ExamController.submitExam()看似简单实则暗藏并发陷阱。假设 1000 名学生同时点击“交卷”若只用TransactionalMySQL 的行锁只能保证对同一张试卷的多次提交串行化但无法阻止同一用户对同一试卷的重复提交比如用户手抖点了两次。本项目采用三级防护Redis 锁前置String lockKey submit_lock: userId : paperId;用RedisTemplate.opsForValue().setIfAbsent(lockKey, 1, Duration.ofSeconds(10))获取锁超时 10 秒自动释放防止死锁数据库唯一约束兜底exam_submit_record表中(user_id, paper_id)设为唯一索引即使 Redis 锁失效第二条 INSERT 也会因唯一键冲突被 MySQL 拒绝事务内幂等校验Transactional方法内先SELECT COUNT(*) FROM exam_submit_record WHERE user_id ? AND paper_id ?若已存在记录直接返回“已提交”不执行后续逻辑。这三者缺一不可只靠 Redis 锁Redis 故障时系统雪崩只靠唯一索引错误码对用户不友好直接报 500只靠事务内 SELECT高并发下仍可能有“检查-执行”时间差TOCTOU 问题。3.3 成绩计算与防作弊客观题自动判分 主观题人工复核流程成绩表设计兼顾实时性与可追溯性成绩不是交卷后“算出来就完了”。exam_score表结构如下CREATE TABLE exam_score ( id bigint NOT NULL AUTO_INCREMENT, paper_id bigint NOT NULL, user_id bigint NOT NULL, total_score decimal(5,2) NOT NULL DEFAULT 0.00, objective_score decimal(5,2) NOT NULL DEFAULT 0.00, subjective_score decimal(5,2) NOT NULL DEFAULT 0.00, status tinyint NOT NULL DEFAULT 0 COMMENT 0-待阅卷1-已出分2-已复核, reviewer_id bigint DEFAULT NULL COMMENT 复核人ID, review_time datetime DEFAULT NULL, created_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_paper (user_id,paper_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意status字段客观题单选、多选、判断交卷即UPDATE exam_score SET objective_score ?, status 1 WHERE ...主观题简答、论述需管理员在/admin/review页面人工打分提交后触发UPDATE exam_score SET subjective_score ?, status 2, reviewer_id ?, review_time NOW() WHERE ...。这种分离设计让“出分”和“复核”成为两个独立事务避免管理员误操作影响已发布成绩。更重要的是uk_user_paper唯一索引强制一考一评杜绝同一学生多次交卷覆盖成绩。4. 避坑指南答辩老师最常问的五个问题以及它们对应的代码位置与修复方案4.1 现象本地启动报错Caused by: java.lang.ClassNotFoundException: javax.servlet.Filter原因SpringBoot 3.x 默认使用 Jakarta EE 9所有javax.*包名已改为jakarta.*但部分老版本依赖如shiro-spring-boot-starter2.0.0仍引用javax.servlet。解决打开exam-server/pom.xml找到shiro-spring-boot-starter依赖将其版本升级至2.3.0已适配 Jakarta或直接替换为spring-boot-starter-security本项目实际使用后者shiro是旧版残留删掉即可。4.2 现象MySQL 8.0 连接报错Public Key Retrieval is not allowed原因MySQL 8.0 默认关闭公钥检索而项目application.yml中useSSLfalse未显式设置allowPublicKeyRetrievaltrue。解决修改spring.datasource.url在末尾添加参数allowPublicKeyRetrievaltrue。完整 URL 示例jdbc:mysql://localhost:3306/exam_system?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue。4.3 现象Vue 前端登录后跳转 404Network 显示请求发到了http://localhost:8080/api/v1/login原因前端exam-web/src/utils/request.js中baseURL配置为/api但开发服务器vue.config.js未配置代理导致请求未转发到后端。解决在vue.config.js中添加devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: /api } } } }注意pathRewrite的 ^/api: /api是关键它确保/api/v1/login被代理到http://localhost:8080/api/v1/login而非http://localhost:8080/v1/login。4.4 现象管理员修改试卷后已参加考试的学生看到的题目仍是旧版原因前端答题页加载题目时调用的是GET /api/v1/exam/paper/{id}/questions该接口返回的是exam_question表的实时数据但学生考试时应锁定题目快照而非动态读取。解决查看ExamPaperService.getPaperWithQuestions(Long paperId)方法它内部调用examQuestionMapper.selectByPaperId(paperId)—— 这是错误的正确做法是学生开始考试时后端应将当前试卷题目INSERT INTO exam_paper_snapshot快照表答题页从快照表查题。本项目已在doc/数据库设计说明.md中注明此表结构但exam-server代码中getPaperWithQuestions未切换到快照表。修复方式在ExamPaperService中新增方法getPaperSnapshotQuestions(Long paperId, Long userId)先查exam_paper_snapshot是否存在该用户快照不存在则生成并返回。4.5 现象导出成绩单 Excel 时中文乱码列名显示为??原因Apache POI 5.x 对中文支持需显式设置字体而ExcelExportUtil.exportScoreList()方法中CellStyle未设置setFont()。解决在ExcelExportUtil.java的exportScoreList()方法内找到CellStyle headerStyle workbook.createCellStyle();后添加Font headerFont workbook.createFont(); headerFont.setBold(true); headerFont.setFontName(微软雅黑); headerFont.setFontHeightInPoints((short) 12); headerStyle.setFont(headerFont);同理数据行样式也需设置dataFont.setFontName(微软雅黑)。这是 Windows 系统 Excel 渲染中文的刚需。5. 答辩现场应急技巧如何用 3 分钟演示“系统抗压能力”与“数据可审计性”5.1 用 H2 控制台现场演示“操作留痕”从一条删除记录反向追踪到具体操作人答辩时老师若问“如果管理员误删了题目怎么找回”别只说“我们有备份”现场打开http://localhost:8080/h2-console执行三条 SQLSELECT * FROM exam_question WHERE id 123;→ 显示该题已被删空结果SELECT * FROM exam_operation_log WHERE business_type QUESTION AND business_id 123 ORDER BY create_time DESC LIMIT 1;→ 查到日志operator_id1001, operation_typeDELETE, create_time2024-05-20 14:22:33SELECT username FROM sys_user WHERE id 1001;→ 查出操作人是zhangsan。接着切到doc/设计报告.pdf第 12 页指出“操作日志表exam_operation_log的content字段存储了 JSON 格式的原始数据如{oldContent:原题干,newContent:}”说明系统不仅记了谁删的还记了删之前是什么。这比口头解释“我们有日志”有力十倍。5.2 用 JMeter 模拟 50 并发交卷验证“成绩不重复计算”的数据库层保障提前在本地装好 JMeter无需复杂配置添加线程组线程数 50Ramp-Up 时间 1 秒循环次数 1添加 HTTP 请求POST http://localhost:8080/api/v1/exam/submitBody Data 填写固定 JSON{paperId:1,answers:[{questionId:1,answer:A}]}添加聚合报告监听器。启动后观察若exam_score表中COUNT(*) 50且SELECT COUNT(DISTINCT user_id) FROM exam_score WHERE paper_id 1 50说明无重复若发现COUNT(*) 50立刻打开exam_server/src/main/java/com/example/exam/controller/ExamController.java定位submitExam()方法指出第 87 行Transactional和第 92 行INSERT IGNORE INTO exam_submit_record的双重保障。提示JMeter 测试前先清空exam_submit_record和exam_score表避免历史数据干扰。5.3 用 Git Blame 快速定位“某个关键修复”是谁写的、为什么改——展示工程规范意识当老师问“这个防重复提交的 Redis 锁是你们自己加的吗”打开exam-server/src/main/java/com/example/exam/service/ExamService.java右键 →Git BlameIDEA 或 VS Code 都支持。你会看到第 156 行String lockKey submit_lock: userId : paperId;的作者是a-developer日期2024-03-15第 157 行if (redisTemplate.opsForValue().setIfAbsent(lockKey, 1, Duration.ofSeconds(10))) {的作者是a-developer但旁边注释// fix: prevent duplicate submit under high concurrency, ref issue #42。这时打开项目根目录的ISSUES.md找到#42“高并发下学生重复交卷导致成绩计算异常”描述中明确写了“复现步骤JMeter 100 线程压测出现 3 条相同 user_id 记录”。这证明你们不是堆功能而是真解决问题。从那以后我每次加防并发逻辑都强制走一遍git blameissue link双验证确保每行代码都有来处、有归处。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑