
简介这是一套基于SpringBoot与MySQL开发的上机考试系统完整项目包面向计算机、数学、电子信息等专业的学生与教师适用于课程设计、期末大作业及毕业设计参考。系统围绕管理员、教师、学生三类角色展开通过绑定IP地址控制学生登录防止代交涵盖教师账号管理、考试创建与清理、试卷上传、学生名单导入、提交文件打包下载及通知消息推送等核心业务。压缩包共218个文件约8.42MB包含52个Java源文件、23个HTML页面、22个JavaScript脚本、6个JSP页面及配套CSS样式另有数据库SQL脚本、依赖jar包与需求分析设计报告文档源码、数据库与文档齐全下载后可直接运行调试。目前已有91人学习关注。读者可借此掌握SpringBoot整合MySQL的实战开发流程理解UML用例图与顺序图驱动的需求分析与概要设计思路并参考IP绑定、文件上传下载、后台任务调度等模块的实现方式快速完成自己的课程或毕设项目。1. 上机考试系统为什么总在期末周翻车从一份 SpringBootMySQL 源码说起每到期末周机房里的上机考试系统就开始上演固定戏码学生端提交按钮转圈、教师端收不到卷、数据库连接池被打满。我见过太多团队用一份来路不明的「基于 SpringBootMySQL 开发的上机考试系统含源码数据库设计报告」压缩包救急结果部署当天就翻车。这份标题里的东西本质是一套完整的考试业务闭环题库管理、组卷、在线答题、自动判分、成绩统计技术栈锁定 SpringBoot 做后端、MySQL 存数据附带建表脚本和一份设计报告。它适合谁适合课程设计、毕业设计、以及中小型培训机构想快速搭一套内部测验平台的场景。但「能跑起来」和「能扛住 200 人同时交卷」是两码事这篇笔记就按我实际部署和压测的经验把这份源码从解压到上线的路径拆开讲重点说清楚参数怎么调、坑在哪。2. 拆开压缩包先看什么SpringBoot 项目结构与 MySQL 建表脚本的对应关系拿到一份陌生的 SpringBoot 考试系统源码别急着mvn spring-boot:run。先花十分钟把目录结构和数据库脚本对齐能省掉后面两小时的报错排查。常见做法是源码根目录下有src/main/java、src/main/resources、sql或db文件夹设计报告通常是doc目录下的 Word 或 PDF。你要做的是把 Java 包结构和 SQL 表名做一次映射确认业务实体和数据库表是一一对应的。2.1 从 application.yml 反推数据库连接与端口配置打开src/main/resources/application.yml有些老项目是application.properties这是整个系统的配置入口。重点看四块数据源、JPA/MyBatis 配置、服务端口、文件上传路径。下面是一份典型的配置片段我按考试系统的实际需要做了注释server: port: 8080 # 考试系统默认端口和学校其他系统冲突时改成 8081 servlet: context-path: /exam # 访问路径前缀前端请求要带这个前缀 spring: datasource: url: jdbc:mysql://localhost:3306/exam_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 # 考试并发核心参数200人考试建议调到 50 minimum-idle: 5 connection-timeout: 30000 # 获取连接超时默认30秒机房网络差可调大 idle-timeout: 600000 max-lifetime: 1800000 servlet: multipart: max-file-size: 10MB # 学生上传答案附件的大小限制 max-request-size: 20MB这里有几个参数直接决定考试当天会不会崩。maximum-pool-size默认往往只有 10200 人同时交卷时每个交卷请求要写答题记录、更新成绩、扣减剩余时间一个请求占用连接的时间可能到 200 毫秒10 个连接每秒只能处理 50 个请求排队就会超时。我一般按「并发人数 ÷ 10」来估算200 人考试就设 20 到 50 之间同时把connection-timeout从默认的 30 秒适当调大避免机房网络抖动直接抛异常。serverTimezone必须显式指定否则 MySQL 8.0 驱动会报时区错误这是新手最常见的翻车点。2.2 用建表脚本还原题库、试卷、答题记录三张核心表考试系统的数据库设计报告里通常会有 ER 图但落地时你只需要盯住三张表question题库、exam_paper试卷、answer_record答题记录。下面是从典型脚本里提炼的建表语句我加了字段说明-- 题库表所有题目存在这里组卷时按知识点和难度抽取 CREATE TABLE question ( id bigint NOT NULL AUTO_INCREMENT, content text NOT NULL COMMENT 题干支持富文本, type tinyint NOT NULL COMMENT 1单选 2多选 3判断 4填空 5编程, options json DEFAULT NULL COMMENT 选择题选项JSON格式存储, answer varchar(500) NOT NULL COMMENT 标准答案, score int DEFAULT 5 COMMENT 默认分值, difficulty tinyint DEFAULT 1 COMMENT 1易 2中 3难, knowledge_point varchar(100) DEFAULT NULL COMMENT 知识点标签组卷用, PRIMARY KEY (id), KEY idx_kp_diff (knowledge_point,difficulty) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 答题记录表每个学生每道题的作答都落一行交卷时批量写入 CREATE TABLE answer_record ( id bigint NOT NULL AUTO_INCREMENT, exam_id bigint NOT NULL, student_id bigint NOT NULL, question_id bigint NOT NULL, student_answer text, is_correct tinyint DEFAULT NULL COMMENT 判分后回填, score_got int DEFAULT 0, submit_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_exam_stu_q (exam_id,student_id,question_id), KEY idx_exam_stu (exam_id,student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;answer_record上的唯一索引uk_exam_stu_q是关键它防止同一个学生同一道题重复写入交卷接口用INSERT ... ON DUPLICATE KEY UPDATE就能实现幂等。idx_exam_stu这个联合索引则是为了教师端按考试和学号快速查分。很多源码包里的脚本没有这两个索引数据量一上来查询就慢建议部署前手动补上。题库表的knowledge_point和difficulty联合索引是组卷算法的性能保障随机抽题时如果没索引几千道题的全表扫描会让组卷接口卡住。2.3 设计报告里该看哪几页别被几十页文档淹没设计报告通常有四十到八十页但真正对部署有用的只有三部分数据库表结构说明、接口清单、部署环境要求。表结构说明用来核对 SQL 脚本有没有缺字段接口清单告诉你前端调哪些 URL方便你配 Nginx 或排查 404部署环境要求会写 JDK 版本和 MySQL 版本比如「JDK 1.8 MySQL 5.7」如果你本地装的是 JDK 17SpringBoot 2.x 的某些老依赖可能直接启动失败。我一般先把这三部分拍照或摘出来剩下的需求分析和 UML 图等系统跑通了再回头看。3. 本地跑通最小闭环从 MySQL 建库到 SpringBoot 启动的完整命令这一章是给需要当天就把系统跑起来的人准备的。我按「建库 → 导数据 → 改配置 → 启动 → 验证」的顺序写每条命令都能直接复制。假设你已经装好了 JDK 8 或 11、Maven 3.6、MySQL 5.7 或 8.0。3.1 建库、导脚本、验证字符集的三步命令先登录 MySQL 创建数据库字符集必须是utf8mb4否则学生姓名里的生僻字和编程题里的特殊符号会乱码# 登录 MySQL-u 用户名 -p 回车后输密码 mysql -uroot -p # 建库指定字符集和排序规则 CREATE DATABASE exam_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 退出后用命令行导入脚本注意路径换成你解压后的实际位置 mysql -uroot -p exam_system /path/to/sql/exam_system.sql # 验证导入结果看表数量和字符集 mysql -uroot -p -e USE exam_system; SHOW TABLES; SELECT TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMAexam_system;导入完成后SHOW TABLES应该列出题库、试卷、用户、答题记录等表。如果表数量明显少于设计报告里写的说明脚本不完整需要手动补建。TABLE_COLLATION那一列如果出现utf8mb4_general_ci以外的值比如latin1说明脚本里建表时没指定字符集得用ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4批量改一遍。这一步不做后面编程题里的代码符号存进去就是问号。3.2 改配置、编译、启动Maven 命令与常见启动失败对照配置改好后在项目根目录执行编译和启动。我习惯先clean再package避免旧的 class 文件干扰# 清理并打包跳过测试因为很多源码包的测试用例依赖外部环境会失败 mvn clean package -DskipTests # 启动方式一直接用 Maven 插件跑适合开发调试 mvn spring-boot:run # 启动方式二用打好的 jar 包跑适合部署 java -jar target/exam-system-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod启动失败时控制台报错信息就是黑匣子。我整理了几种高频错误和对应原因报Access denied for user是数据库密码错报Unknown database exam_system是库没建或名字拼错报Table exam_system.xxx doesnt exist是脚本没导全报Communications link failure是 MySQL 没启动或端口不对报java.lang.UnsupportedClassVersionError是 JDK 版本低于编译版本比如用 JDK 8 跑 JDK 11 编译的包。看到Started ExamApplication in X seconds才算真正启动成功。3.3 用 curl 验证登录、组卷、交卷三个接口是否通系统起来后别急着开浏览器先用 curl 打三个核心接口确认后端逻辑没问题# 1. 登录接口拿到 token curl -X POST http://localhost:8080/exam/auth/login \ -H Content-Type: application/json \ -d {username:student01,password:123456} # 2. 用返回的 token 请求组卷假设考试ID为 1 curl -X GET http://localhost:8080/exam/paper/generate?examId1 \ -H Authorization: Bearer 上一步的token # 3. 模拟交卷提交答题记录 curl -X POST http://localhost:8080/exam/answer/submit \ -H Content-Type: application/json \ -H Authorization: Bearer token \ -d {examId:1,answers:[{questionId:101,studentAnswer:A},{questionId:102,studentAnswer:B}]}登录接口返回的 JSON 里应该有token字段组卷接口应该返回题目列表和试卷 ID交卷接口应该返回判分结果或成功状态。如果登录返回 401检查密码加密方式很多源码用 BCrypt数据库里存的应该是密文而不是明文如果组卷返回空列表检查题库表里有没有对应知识点和难度的题目如果交卷报唯一键冲突说明重复提交了这正好验证了前面uk_exam_stu_q索引在起作用。4. 并发交卷时数据库先扛不住连接池、索引与判分逻辑的调优单机跑通只是及格线上机考试的真实考验是「最后五分钟集中交卷」。我压测过一套默认配置的考试系统50 个并发交卷就开始出现 504数据库 CPU 直接拉满。这一章讲清楚瓶颈在哪、参数怎么改、判分逻辑怎么优化。4.1 连接池打满的典型现象与 HikariCP 参数调整现象很直观学生点交卷前端转圈十几秒后提示「系统繁忙」后端日志刷HikariPool-1 - Connection is not available, request timed out after 30000ms。原因就是连接池里的连接被占满新请求排队等不到连接。除了前面提到的maximum-pool-size还有两个参数要一起调connection-timeout控制等连接的最长时间max-lifetime控制连接存活时间避免 MySQL 主动断开空闲连接后应用还在用死连接。我的调参习惯是先按并发人数估算200 人考试设maximum-pool-size: 50minimum-idle: 10connection-timeout: 1000010 秒等不到就快速失败让前端重试而不是干等。同时把交卷接口里的数据库操作尽量缩短比如判分逻辑不要在一个事务里逐题查询标准答案而是交卷时一次性把试卷所有题目和答案查出来放内存比对。这样单个请求占用连接的时间能从几百毫秒降到几十毫秒连接池的压力成倍下降。4.2 答题记录批量写入与判分 SQL 的写法对比交卷时最耗时的操作是写答题记录。逐条INSERT是最慢的写法一次交卷 50 道题就是 50 次数据库往返。改成批量插入能快一个数量级// 慢写法循环单条插入每次都要网络往返 for (Answer answer : answerList) { answerMapper.insert(answer); } // 快写法MyBatis 批量插入一条 SQL 搞定 // XML 里写 foreach 拼接 values或者用 MyBatis-Plus 的 saveBatch answerService.saveBatch(answerList, 100); // 每100条提交一次判分 SQL 也有讲究。如果每道题都去SELECT answer FROM question WHERE id ?50 道题就是 50 次查询。更好的做法是交卷时用SELECT id, answer, score FROM question WHERE id IN (101,102,...)一次查出所有标准答案在 Java 内存里比对。对于编程题这种需要运行代码判分的千万别在交卷请求里同步执行应该把判分任务丢进消息队列异步处理交卷接口只负责保存答案并返回「已提交判分中」。这样交卷接口的响应时间能稳定在 100 毫秒以内。4.3 考试场景下 MySQL 隔离级别与锁等待的取舍MySQL 默认隔离级别是REPEATABLE READ考试系统里有两个地方会因此产生锁等待。一是组卷时如果用了SELECT ... FOR UPDATE锁题库行多个教师同时组卷会互相阻塞二是交卷时更新成绩统计表如果统计逻辑用了行锁高并发下会排队。我的做法是组卷查询不加锁用普通SELECT配合应用层的随机算法成绩统计改成异步更新或者用INSERT ... ON DUPLICATE KEY UPDATE这种原子操作代替「先查后改」。如果确实需要事务把隔离级别降到READ COMMITTED能减少间隙锁但要在测试环境验证判分结果不受影响。这个取舍没有标准答案取决于你的业务对一致性的要求有多高。5. 部署上线前必查的避坑清单从 JDK 版本到前端打包路径这一章是我踩过的坑合集每条都按「现象 → 原因 → 解决」写。部署前对着过一遍能省掉上线当天的后悔药。坑一启动报UnsupportedClassVersionError。现象是 jar 包双击或java -jar直接抛异常提示 class file version 不匹配。原因是源码用 JDK 11 编译服务器上装的是 JDK 8。解决方法是统一版本要么服务器升级 JDK要么在pom.xml里把maven.compiler.source和target改成 1.8 重新打包。我一般先在服务器上java -version确认版本再决定用哪个包。坑二前端页面 404但接口能通。现象是浏览器打开http://ip:8080显示 Whitelabel Error Page但直接访问/exam/auth/login有返回。原因是前后端分离项目前端静态文件没放进 SpringBoot 的static目录或者 Vue 打包后的publicPath配错了。解决方法是把dist目录下的文件复制到src/main/resources/static重新打包Vue 项目里vue.config.js的publicPath要设成./或/exam/和context-path保持一致。坑三交卷时提示「考试已结束」但时间没到。现象是学生明明在截止时间前提交系统却拒绝。原因是服务器时间和数据库时间不一致或者考试结束时间存的是 UTC 而服务器是东八区。解决方法是统一用Asia/Shanghai时区数据库连接串加serverTimezoneAsia/Shanghai服务器用ntpdate同步时间。这个坑很隐蔽因为本地测试时区一致不会复现。坑四编程题判分结果全是 0 分。现象是学生代码明明能跑系统判 0 分。原因是判分沙箱环境没装对应语言的编译器或者编译命令路径不对。解决方法是检查判分服务里javac、gcc、python的路径配置确保服务器上装了对应运行时。如果源码里判分是调外部命令还要注意权限问题SpringBoot 进程的用户要有执行权限。坑五数据库连接数暴涨导致 MySQL 拒绝服务。现象是考试中途所有学生掉线MySQL 报Too many connections。原因是连接池maximum-pool-size设得太大多个应用实例叠加超过了 MySQL 的max_connections。解决方法是先查 MySQL 的max_connections默认 151再算应用实例数乘以单实例连接池上限留出 20% 余量。比如两个实例各 50 连接MySQL 至少要设 120。改完重启 MySQL 生效。6. 让判分更靠谱编程题沙箱判分的参数控制与结果校验最后一章聊一个具体技巧编程题判分怎么做得更稳。很多考试系统的编程题判分就是简单跑一遍测试用例学生写个死循环就把判分服务卡死了。我一般会在判分环节加三层控制超时、内存限制、输出比对。超时控制用Process.waitFor(timeout, TimeUnit.SECONDS)比如设 5 秒超时直接destroyForcibly()杀掉进程。内存限制在 Linux 下可以用ulimit -v限制虚拟内存或者用 cgroup 更精细。输出比对别用字符串全等学生多打一个空格就判错太冤应该先trim()再比对或者用忽略行尾空白的比较器。下面是一段判分核心逻辑的伪代码展示参数怎么传// 判分任务编译并运行学生代码比对输出 public JudgeResult judge(String code, String input, String expectedOutput) { // 1. 写临时文件 Path sourceFile Files.writeString(tempDir.resolve(Main.java), code); // 2. 编译超时 10 秒 Process compile new ProcessBuilder(javac, sourceFile.toString()) .redirectErrorStream(true).start(); if (!compile.waitFor(10, TimeUnit.SECONDS)) { compile.destroyForcibly(); return JudgeResult.compileTimeout(); } // 3. 运行超时 5 秒限制内存 256MB Process run new ProcessBuilder(java, -Xmx256m, Main) .redirectInput(ProcessBuilder.Redirect.from(inputFile)) .start(); if (!run.waitFor(5, TimeUnit.SECONDS)) { run.destroyForcibly(); return JudgeResult.runtimeTimeout(); } // 4. 读取输出并比对忽略行尾空白 String actual new String(run.getInputStream().readAllBytes()).trim(); boolean pass actual.equals(expectedOutput.trim()); return pass ? JudgeResult.pass() : JudgeResult.wrongAnswer(actual); }这段代码里-Xmx256m限制堆内存waitFor的超时参数是判分稳定性的关键。我一般把编译超时设 10 秒、运行超时设 5 秒因为上机考试题目不会太复杂超过这个时间基本是死循环或效率极低的算法。输出比对用trim()能过滤掉大部分格式差异但要注意如果题目本身要求保留空格就不能这么干得在题目配置里加一个「是否忽略空白」的开关。验证判分逻辑是否靠谱我会准备一组边界用例空输入、超大输入、死循环代码、输出多一个换行、输出少一个换行。跑一遍看判分结果是否符合预期。这个习惯是从一次血泪经验来的有次考试判分把「输出多一个空格」判成错误学生集体申诉最后只能人工重判。从那以后我判分前必跑边界用例宁可判分逻辑复杂一点也别在考后返工。希望帮到你。本文还有配套的精品资源点击获取