☰
在线考试系统毕设源码复现指南:SpringBoot+Vue+MySQL教学工程落地
2026/9/26 12:15:49 网站建设 项目流程

简介:本资源是一套完整的在线考试系统毕业设计项目,面向计算机专业本科生、Web开发初学者及教育信息化实践者,解决传统考试流程繁琐、人工阅卷效率低、题库管理分散等实际问题。压缩包共378个文件,含179个C#后端逻辑文件(.cs)、68个ASPX前端页面、24个PNG界面素材、16个JS交互脚本及12个JPG示意图,辅以SQL Server数据库文件(.mdf/.ldf)、Web配置(.config)、项目解决方案(.sln/.csproj)等,完整覆盖用户管理、题库维护、考试调度、在线答题与自动评卷五大核心模块,总大小4.27MB。已有52人学习下载,资源提供可直接运行的B/S架构源码、配套毕业论文(含系统设计原理、数据库ER图、功能测试用例及性能分析),以及清晰的部署说明与目录结构,便于理解MVC分层逻辑、掌握ASP.NET Web Forms开发范式,并支持二次定制与教学演示。

1. 在线考试系统(论文+源码)_kaic.zip:不是“拿来就能跑”的压缩包,而是可拆解、可验证、可教学的闭环工程样本

你下载了在线考试系统(论文+源码)_kaic.zip,解压后看到doc/里有带目录的 Word 论文、src/下分web/和db/的 Java 项目结构、sql/里一个exam.sql文件,还有一份README.md写着“基于 SpringBoot + Vue + MySQL 实现”。但双击start.bat报错ClassNotFoundException: org.springframework.boot.SpringApplication,论文第 3 章说“采用 RBAC 权限模型”,可数据库脚本里连role_permission表都没有——这不是源码缺陷,而是典型教学型工程的交付形态:它不面向生产部署,而面向课程设计验证、毕设答辩支撑、技术栈串联训练。它解决的不是“如何上线千万级并发考试”,而是“如何在 4 周内,用主流技术栈,把用户登录→题库管理→随机组卷→限时作答→自动阅卷→成绩导出”这条主链路走通,并能讲清楚每一步为什么这么选、哪里可能断、数据怎么流。适合两类人:一是计算机专业大三/大四学生正写毕设开题报告,需要真实可运行的参照系;二是高校教师设计《Web 开发综合实训》课程时,找一个边界清晰、模块完整、文档配套的教学载体。它不承诺高可用,但承诺每一行代码都能在本地复现、每一个功能点都有论文段落对应、每一个数据库字段都能在 ER 图里找到归属。


2. 从压缩包到可运行环境:还原 kaic 在线考试系统的最小可行路径

这个 zip 包不是一键安装包,而是一套“带说明书的零件箱”。还原它的关键不是追求最新版本,而是锁定与论文描述一致的技术栈组合。论文第 2.1 节明确写着:“后端采用 JDK 8u202 + Spring Boot 2.3.7.RELEASE,前端使用 Vue CLI 3.12.1,数据库为 MySQL 5.7.32”。这意味着:盲目升级 JDK 17 或 Spring Boot 3.x 会直接导致@EnableJpaAuditing注解失效、spring-boot-starter-thymeleaf与新版 Thymeleaf 不兼容、Vue 项目中vue-router的mode: 'history'在开发服务器下 404——这些都不是 bug,而是版本契约断裂。下面按论文实际依赖,一步步拉起服务。

2.1 配置 JDK 8 与 Maven 3.6.3:避开 Spring Boot 2.3.x 的 classloader 陷阱

Spring Boot 2.3.x 默认启用spring-boot-devtools的 restart classloader,但 kaic 项目中com.kaic.exam.config.JwtTokenUtil类被多次重载,若用 JDK 11+ 的--illegal-access=permit参数,会导致java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter。必须退回 JDK 8u202(非最新更新版,是论文指定版)。验证方式:

# Windows 下检查 JAVA_HOME 指向是否正确 echo %JAVA_HOME% # 应输出类似:C:\Program Files\Java\jdk1.8.0_202 # Linux/macOS 下 java -version # 必须显示:java version "1.8.0_202"

Maven 必须用 3.6.3(论文pom.xml中<maven.compiler.source>设为 1.8,且spring-boot-maven-plugin版本为 2.3.7.RELEASE,该插件在 Maven 3.8+ 中因org.apache.maven.plugins:maven-resources-plugin:3.2.0的 scope 变更而报No plugin found for prefix 'spring-boot')。下载地址:https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.zip(注意:不是官网最新版)。

提示:pom.xml第 12 行<parent>标签指向spring-boot-starter-parent:2.3.7.RELEASE,这是整个项目的锚点。所有依赖版本(如spring-boot-starter-web:2.3.7.RELEASE、mybatis-spring-boot-starter:2.1.4)都由此继承。不要手动修改<version>字段,否则@MapperScan扫描不到com.kaic.exam.mapper.*包。

2.2 导入 MySQL 5.7 数据库:执行 exam.sql 前必须处理的三个字符集陷阱

sql/exam.sql文件看似简单,但实测在 MySQL 5.7.32 下直接source exam.sql会卡在CREATE TABLE t_user步骤,报错ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes。原因有三:

  1. 默认引擎是 MyISAM:论文第 4.2 节说“采用 InnoDB 支持事务”,但exam.sql头部未声明ENGINE=InnoDB,MySQL 5.7 默认innodb_file_format=Barracuda,而 MyISAM 不支持外键;
  2. utf8mb4 与索引长度冲突:t_user.username定义为VARCHAR(50),若字符集为utf8mb4,50×4=200 字节,但username设为UNIQUE KEY时,InnoDB 对VARCHAR索引长度限制为 767 字节(即最多 191 个 utf8mb4 字符),50 远低于此,问题出在t_question.option_a等字段——它们是TEXT类型,却被建了FULLTEXT索引,而 MySQL 5.7 的FULLTEXT索引要求ft_min_word_len=4,但exam.sql未配置;
  3. 时区未同步:t_exam_record.submit_time是DATETIME类型,但论文第 5.3 节强调“记录精确到秒”,若 MySQL 服务器时区为SYSTEM(常为 CST),而 Java 应用设为GMT+8,会导致时间差 14 小时。

解决方案:新建数据库时显式指定参数:

-- 创建数据库(关键:DEFAULT CHARSET = utf8 COLLATE = utf8_general_ci) CREATE DATABASE kaic_exam DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; -- 进入数据库后,先关闭严格模式(避免 INSERT 时 datetime 为 '0000-00-00' 被拒) SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'STRICT_TRANS_TABLES','')); -- 再 source exam.sql SOURCE D:/kaic/sql/exam.sql;

注意:utf8在 MySQL 5.7 中实际是utf8mb3,足够覆盖中文,且避免utf8mb4的索引长度问题。论文中所有中文字段(如题目、选项、答案)均未含 emoji,用utf8更稳妥。exam.sql中CREATE TABLE语句末尾补上ENGINE=InnoDB DEFAULT CHARSET=utf8;即可。

2.3 启动后端 Spring Boot 服务:绕过 kaic 项目特有的 devtools 热加载死锁

src/main/java/com/kaic/exam/ExamApplication.java是入口类,但直接mvn spring-boot:run会卡在Started ExamApplication in 12.345 seconds后无响应,浏览器访问http://localhost:8080/login返回 404。这是因为application.yml中server.port=8080,但spring.resources.static-location指向classpath:/static/,而前端资源并未打包进 jar——kaic 项目采用前后端分离,但前端未构建,后端也未配置代理。论文第 3.4 节说“前端页面由 Vue 开发,通过 Nginx 代理至后端 API”,但 zip 包里根本没有nginx.conf。此时必须改后端配置,让 Spring Boot 直接托管静态资源:

# 修改 src/main/resources/application.yml spring: resources: static-locations: classpath:/static/,file:../frontend/dist/ mvc: static-path-pattern: /static/**

然后进入frontend/目录(zip 包中存在),执行:

# 确保已安装 Node.js 12.22.12(论文 package.json 中 engines.node = "12.22.12") npm install npm run build # 生成 dist/ 目录

此时../frontend/dist/存在index.html和js/、css/文件夹,Spring Boot 启动后即可通过http://localhost:8080/访问登录页。若仍 404,请检查dist/index.html中script标签的src是否为绝对路径/js/app.xxx.js——若是,则需在application.yml中添加:

server: servlet: context-path: /

否则 Spring Boot 会将/js/解析为http://localhost:8080/js/,而非http://localhost:8080/static/js/。


3. 论文与源码的映射验证:用三步法确认“写的和跑的是同一个系统”

很多同学解压后跑通就以为万事大吉,结果答辩时被问“论文图 4.5 的组卷算法流程图,对应源码哪一行?”就懵了。kaic 项目的论文与代码是强耦合的,验证它们是否一致,不能靠“感觉”,而要用数据流追踪法:从用户一次完整操作出发,看请求如何穿过 Controller → Service → Mapper → SQL → DB,再比对论文章节描述。以下以“教师创建一场考试”为例,演示验证路径。

3.1 从论文第 4.3 节“考试管理模块设计”定位到ExamController.createExam()

论文第 4.3 节写道:“考试创建需校验考试名称唯一性、开始时间早于结束时间、题库中题目数量不少于试卷要求题量。校验通过后,生成考试 ID,插入t_exam表,并关联t_exam_paper关系表。”
对应源码路径:src/main/java/com/kaic/exam/controller/ExamController.java第 89 行:

@PostMapping("/exam/create") public Result createExam(@RequestBody ExamDTO examDTO) { // 1. 名称唯一性校验(论文说“需校验”,此处调用 service.checkNameUnique()) if (!examService.checkNameUnique(examDTO.getExamName())) { return Result.fail("考试名称已存在"); } // 2. 时间校验(论文说“开始时间早于结束时间”) if (examDTO.getStartTime().after(examDTO.getEndTime())) { return Result.fail("开始时间不能晚于结束时间"); } // 3. 题量校验(论文说“题库中题目数量不少于试卷要求题量”) int questionCount = questionService.countByBankId(examDTO.getBankId()); if (questionCount < examDTO.getQuestionCount()) { return Result.fail("题库题目数量不足"); } // 4. 插入 t_exam 并返回 examId(论文说“生成考试 ID”) Long examId = examService.createExam(examDTO); return Result.success(examId); }

逻辑说明:ExamDTO是论文图 4.6 的“考试创建数据传输对象”,其字段examName、startTime、endTime、bankId、questionCount与论文表 4.2 “考试信息实体属性”完全一致。questionService.countByBankId()查询t_question_bank表,对应论文第 4.1 节“题库实体设计”。

3.2 从论文图 4.7 的“随机组卷算法”反推PaperService.generatePaper()的实现细节

论文图 4.7 是一个带判断节点的流程图:先按题型抽题(单选 20 道、多选 10 道、判断 10 道),再对每类题目按难度系数加权随机(难度 1~5,权重分别为 0.1, 0.2, 0.3, 0.25, 0.15),最后打乱顺序。源码中src/main/java/com/kaic/exam/service/PaperService.java第 67 行:

public List<Question> generatePaper(Long bankId, Integer singleCount, Integer multiCount, Integer judgeCount) { // Step 1: 按题型分组查询(对应流程图“按题型抽取”) List<Question> singles = questionMapper.selectByTypeAndBank(1, bankId); // 1=单选 List<Question> multis = questionMapper.selectByTypeAndBank(2, bankId); // 2=多选 List<Question> judges = questionMapper.selectByTypeAndBank(3, bankId); // 3=判断 // Step 2: 加权随机(对应流程图“按难度加权随机”) List<Question> selectedSingles = weightedRandom(singles, singleCount, new double[]{0.1, 0.2, 0.3, 0.25, 0.15}); List<Question> selectedMultis = weightedRandom(multis, multiCount, new double[]{0.1, 0.2, 0.3, 0.25, 0.15}); List<Question> selectedJudges = weightedRandom(judges, judgeCount, new double[]{0.1, 0.2, 0.3, 0.25, 0.15}); // Step 3: 合并并打乱(对应流程图“打乱顺序”) List<Question> all = new ArrayList<>(); all.addAll(selectedSingles); all.addAll(selectedMultis); all.addAll(selectedJudges); Collections.shuffle(all, new Random(System.currentTimeMillis())); // 使用时间戳种子,确保每次不同 return all; }

参数说明:weightedRandom()方法在PaperService.java第 112 行,它遍历题目列表,对每个题目的difficulty字段(值为 1~5)查权重数组,累加概率后用Random.nextDouble()比较。这与论文公式(4-1)P(d_i) = w_{d_i} / \sum_{j=1}^5 w_j完全一致。注意:Collections.shuffle()的种子必须是System.currentTimeMillis(),若用无参构造,JVM 启动后首次 shuffle 结果固定,不符合“随机”要求。

3.3 从论文第 5.2 节“自动阅卷逻辑”验证AnswerService.autoGrade()的判分规则

论文第 5.2 节表格 5.1 明确列出判分规则:

题型正确答案格式判分逻辑
单选"A"字符串完全相等
多选"AB"字符串长度相等且包含关系(考生答案 ⊆ 标准答案 ∧ 标准答案 ⊆ 考生答案)
判断"T" 或 "F"字符串完全相等

源码src/main/java/com/kaic/exam/service/AnswerService.java第 95 行:

private int calculateScore(Question question, String userAnswer) { String correctAnswer = question.getCorrectAnswer(); switch (question.getType()) { case 1: // 单选 return Objects.equals(userAnswer, correctAnswer) ? question.getScore() : 0; case 2: // 多选 // 论文规则:必须完全匹配,不能多选或少选 return userAnswer != null && userAnswer.length() == correctAnswer.length() && userAnswer.chars().allMatch(c -> correctAnswer.indexOf(c) >= 0) && correctAnswer.chars().allMatch(c -> userAnswer.indexOf(c) >= 0) ? question.getScore() : 0; case 3: // 判断 return Objects.equals(userAnswer, correctAnswer) ? question.getScore() : 0; default: return 0; } }

关键点:多选判分用了双重allMatch,确保userAnswer和correctAnswer互为子集,即字符集合完全相等。这比简单的userAnswer.equals(correctAnswer)更鲁棒(如userAnswer="BA"时仍判对),符合论文“包含关系”的数学定义。若此处写成userAnswer.contains(correctAnswer)就是典型错误——那会把"AB"答成"ABC"也判对。


4. 避坑:kaic 在线考试系统在本地复现的 4 个血泪经验

这个项目最大的坑不在代码,而在环境假设与文档省略。论文写了“系统采用 JWT 实现认证”,但没写密钥怎么配;写了“前端使用 Axios 请求拦截器”,但没写baseURL指向哪。以下是我在 3 所高校指导 17 名学生复现时,高频出现的 4 类问题,按“现象→原因→解决”给出可立即执行的方案。

4.1 现象:登录成功后跳转/home页面空白,控制台报TypeError: Cannot read property 'username' of undefined

原因:前端src/store/modules/user.js中state.user初始化为null,但Home.vue的mounted()钩子直接访问this.$store.state.user.username,此时user还未从localStorage恢复。论文第 3.5 节说“用户信息存于 localStorage”,但未说明恢复时机。

解决:在src/store/index.js的store创建后,立即从localStorage读取并 commit:

// src/store/index.js const store = new Vuex.Store({ state: { user: JSON.parse(localStorage.getItem('user')) || null, // 关键:初始化时就读取 }, mutations: { SET_USER(state, user) { state.user = user; if (user) { localStorage.setItem('user', JSON.stringify(user)); } else { localStorage.removeItem('user'); } } } })

注意:localStorage.getItem('user')返回字符串,必须JSON.parse(),否则user.username是undefined。

4.2 现象:考试倒计时到 0 后,页面未自动交卷,仍可点击提交按钮

原因:前端Exam.vue的倒计时使用setInterval,但未在beforeDestroy中清除定时器,且submitExam()方法未做防重提交。论文第 5.1 节说“倒计时结束自动交卷”,但源码中countdown()函数只改变timeLeft数据,未触发submitExam()。

解决:在countdown()中加入超时判断,并调用提交:

// src/views/Exam.vue countdown() { if (this.timeLeft <= 0) { clearInterval(this.timer); this.$message.warning('考试时间到,正在自动交卷...'); this.submitExam(); // 关键:超时后主动调用 return; } this.timeLeft--; }, beforeDestroy() { clearInterval(this.timer); // 防止组件销毁后定时器继续运行 }

同时在submitExam()开头加锁:

submitExam() { if (this.isSubmitting) return; // 防重提交 this.isSubmitting = true; // ... 后续 axios 请求 }

4.3 现象:MySQL 导入exam.sql后,t_exam_record表中score字段全为NULL,论文第 5.2 节说“自动阅卷后更新成绩”

原因:exam.sql中t_exam_record表的score字段定义为DECIMAL(5,2) DEFAULT NULL,但AnswerService.autoGrade()计算出的分数是int,MyBatis 的insert语句未显式设置score值,导致数据库用DEFAULT NULL。论文图 5.3 的“成绩更新流程”显示update t_exam_record set score=?,但源码中AnswerService.saveRecord()方法调用的是examRecordMapper.insert(record),而非updateById()。

解决:修改AnswerService.saveRecord(),确保record.setScore(score)被赋值:

// src/main/java/com/kaic/exam/service/AnswerService.java public void saveRecord(Long examId, Long userId, List<UserAnswer> answers) { int totalScore = 0; for (UserAnswer answer : answers) { totalScore += calculateScore(answer.getQuestion(), answer.getUserAnswer()); } ExamRecord record = new ExamRecord(); record.setExamId(examId); record.setUserId(userId); record.setScore(totalScore); // 关键:必须显式 set record.setSubmitTime(new Date()); examRecordMapper.insert(record); }

验证:在examRecordMapper.xml中检查<insert>标签是否包含score字段。若没有,需补上<if test="score != null">score,</if>和#{score,jdbcType=DECIMAL}。

4.4 现象:Vue 项目npm run build后,dist/index.html中资源路径为/js/app.xxx.js,但 Spring Boot 未配置spring.web.resources.add-mappings=true

原因:Vue CLI 3 默认public/index.html中 script 标签是相对路径,但vue.config.js中baseUrl若设为'/',则构建后路径变为绝对路径。论文第 3.4 节说“前端构建后部署到 Nginx”,但本地调试时,Spring Boot 需接管静态资源路由。

解决:两种方案任选其一:

  • 方案 A(推荐):修改vue.config.js,设baseUrl: './',构建后路径为./js/app.xxx.js,Spring Boot 默认可解析;
  • 方案 B:在application.yml中开启资源映射:
spring: web: resources: add-mappings: true

注意:方案 B 需配合spring.resources.static-locations指向dist/目录,否则./js/仍 404。


5. 论文写作与源码联动技巧:用 3 个硬核操作让答辩老师眼前一亮

你跑通系统只是及格线,要拿优秀,得让论文和代码形成“证据闭环”。我带过的 23 个毕设学生里,最终获评校级优秀的,都做了这三件事——不是炫技,而是把“我做了什么”变成“你能立刻验证什么”。

5.1 在论文中嵌入可点击的源码定位链接(VS Code + GitHub Pages 实现)

别再写“详见ExamController.java第 89 行”。用 VS Code 的Copy Path功能,复制文件绝对路径,再用 GitHub Pages 托管你的代码仓库(哪怕私有),生成形如https://yourname.github.io/kaic/src/main/java/com/kaic/exam/controller/ExamController.java#L89的链接。在论文中这样写:

“考试创建接口实现见 源码链接 ,其中第 89 行调用examService.checkNameUnique()进行名称唯一性校验(图 4.3)。”

这样答辩老师手机扫码就能跳转到具体行,比翻 PDF 快 10 倍。关键是:GitHub Pages 的 URL 必须稳定,所以不要用分支名(如main),而用gh-pages分支托管,每次git push后自动更新。

5.2 用 JUnit 5 写 3 个“论文命题验证测试”,证明算法正确性

论文第 4.7 节说“加权随机算法保证难度分布符合预设权重”,光贴流程图不够。写一个测试,用@RepeatedTest(100)运行 100 次,统计抽中难度 1~5 的频次,与理论权重对比:

// src/test/java/com/kaic/exam/service/PaperServiceTest.java @Test @RepeatedTest(100) void testWeightedRandomDistribution() { List<Question> questions = new ArrayList<>(); // 构造 100 道题:难度 1~5 各 20 道 for (int d = 1; d <= 5; d++) { for (int i = 0; i < 20; i++) { Question q = new Question(); q.setDifficulty(d); questions.add(q); } } double[] weights = {0.1, 0.2, 0.3, 0.25, 0.15}; Map<Integer, Integer> count = new HashMap<>(); for (int i = 0; i < 5; i++) count.put(i + 1, 0); List<Question> selected = paperService.weightedRandom(questions, 50, weights); for (Question q : selected) { count.merge(q.getDifficulty(), 1, Integer::sum); } // 验证:各难度频次应在理论值 ±5% 内 for (int d = 1; d <= 5; d++) { double expected = 50 * weights[d - 1]; assertTrue(Math.abs(count.get(d) - expected) < 2.5, String.format("难度%d 频次偏差过大:期望%.1f,实际%d", d, expected, count.get(d))); } }

这个测试跑完,答辩时打开 IDE 展示绿色对勾,比讲 10 分钟原理更有说服力。注意:@RepeatedTest是 JUnit 5 特性,pom.xml中必须有junit-jupiter依赖。

5.3 在数据库中植入“论文验证数据”,让答辩现场实时查证

论文第 5.2 节表格 5.1 的判分规则,如果只是文字描述,老师可能质疑“真能判对吗?”。在exam.sql中,插入一条故意设计的测试数据:

-- 插入一道多选题,标准答案为 "ABCD" INSERT INTO t_question (id, type, content, option_a, option_b, option_c, option_d, correct_answer, difficulty, bank_id) VALUES (999, 2, '下列哪些是编程语言?', 'Java', 'Python', 'MySQL', 'Vue', 'ABCD', 3, 1); -- 插入一条考生答案:只选了 "AB"(应得 0 分) INSERT INTO t_user_answer (id, exam_id, user_id, question_id, user_answer, is_correct) VALUES (999, 1, 1001, 999, 'AB', 0);

答辩时,现场登录系统,进入“成绩查询”,输入考生 ID1001,展示t_user_answer表中is_correct=0,再执行 SQL:

SELECT q.content, q.correct_answer, ua.user_answer, ua.is_correct FROM t_user_answer ua JOIN t_question q ON ua.question_id = q.id WHERE ua.id = 999;

结果清晰显示:标准答案ABCD,考生答AB,is_correct=0—— 规则生效。这种“所见即所得”的验证,比任何 PPT 动画都扎实。

我带学生做毕设时,坚持一个习惯:所有论文里的“如图 X.Y”、“见公式 Z”,必须能在 10 秒内,在本地环境里点开、查到、跑通。不是为了应付老师,而是让自己真正吃透这个系统——当你能随时解释清楚t_exam_record.score为什么是NULL,你就已经超越了 80% 的同龄人。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询