SpringBoot+Vue在线考试系统搭建与核心实现 简介这是一套面向计算机专业本科生的Java毕业设计级在线考试系统源码适用于课程设计、毕设开发与教学实践场景解决传统纸质考试组织难、阅卷慢、数据统计不便等痛点。资源包含SpringBootVue前后端分离架构的完整工程含244个文件47个Java后端逻辑类、19个Vue组件、33个HTML页面、32个JS交互脚本及29个SCSS样式文件覆盖学生前台登录、考试、记录与管理员后台用户、班级、试题、试卷、考试、记录六大管理模块全功能压缩包8.67MB附带MySQL 5.7建库SQL、Navicat配置说明、详细项目文档LW及可直接运行的源码结构。已有55人学习下载读者可直接导入IDEA或Eclipse运行调试快速掌握SpringBoot整合MyBatis、Vue路由与Axios通信、RBAC权限控制及在线判题核心逻辑具备良好扩展性与二次开发基础。1. 为什么用 SpringBoot Vue 做在线考试系统不是“堆技术”而是绕不开的工程现实你手头这份【java毕业设计】技术的在线考试系统源码springbootvuemysql说明文档LW.zip表面看是套“毕业设计模板”实则是高校教学场景下被反复验证过的最小可行技术闭环后端用 SpringBoot 快速搭出 RESTful API、用户状态管理、试卷生成与阅卷逻辑前端用 Vue 实现答题页动态渲染、倒计时控制、防切屏检测、主观题富文本提交MySQL 存试卷结构、考生作答记录、成绩统计结果LW论文和说明文档则把这套闭环的业务边界、数据流向、权限约束写清楚——不是炫技是让答辩老师一眼看懂“你真干了活”。很多同学一开始想用纯 JSP/Servlet 或 Thymeleaf 写结果卡在前端交互上倒计时不准、切换浏览器标签就丢答题状态、上传图片失败、主观题编辑器粘贴格式乱飞。Vue 的响应式 组件化直接把这类问题封装掉而 SpringBoot 的自动配置 Starter 机制让连接 MySQL、集成 Redis 缓存考试锁、加 JWT 登录校验这些事3 行配置 1 个注解就能跑通。这不是“为了用而用”是当你需要支持 200 人同时在线考试、每场考 90 分钟、交卷后 5 秒内出客观题分数时SpringBoot Vue 成为唯一不翻车的组合。它不解决算法难题但能让你把精力聚焦在“怎么防止学生复制题目搜答案”“怎么保证同一场考试不同考生试卷题序随机”这些真实业务点上。2. 搭建环境从零开始配齐 SpringBoot Vue MySQL 三件套避开新手最常卡死的 4 个环节2.1 后端环境SpringBoot 2.7.x 是当前毕业设计最稳版本别碰 3.x提示SpringBoot 3.x 要求 JDK 17 且默认移除 javax.* 包而多数高校机房、老师电脑还停留在 JDK 8/11强行升级会导致 MyBatis-Plus 报NoClassDefFoundError: javax/xml/bind/DatatypeConverter这类玄学错误。用 Maven 初始化一个 SpringBoot 2.7.182023 年最后一个 2.x 维护版项目关键依赖如下!-- pom.xml -- parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version !-- 注意MyBatis-Plus 3.5.x 对应 SpringBoot 2.x -- /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies参数说明spring-boot-starter-data-jpa提供基础 ORM 能力配合 MyBatis-Plus 可兼顾 SQL 灵活性与 CRUD 效率mysql-connector-java版本必须与 MySQL 服务端匹配MySQL 5.7 用8.0.28MySQL 8.0 用8.0.33否则启动报Public Key Retrieval is not allowedspring-boot-starter-validation用于考试系统核心校验如“单选题必须选 1 项”“主观题字数不能少于 50 字”“考试时间不能早于当前时间”。2.2 前端环境Vue 2.6.14 是毕业设计兼容性最优选择Vue 3 的 Composition API 虽好但配套生态如 Element UI 替代品 Element Plus在毕业设计场景下反而增加复杂度。Vue 2.6.14 Vue Router 3.5.3 Vuex 3.6.2 Element UI 2.15.14 这套组合能直接复用源码里现成的ExamPaper.vue、QuestionList.vue组件无需重写路由守卫或状态管理逻辑。安装命令确保 Node.js ≥ 14.17.0≤ 16.20.2# 全局安装 Vue CLI注意不是 vue-cli是 vue/cli npm install -g vue/cli4.5.15 # 创建 Vue 2 项目必须指定 --preset否则默认创建 Vue 3 vue create exam-front --preset vue2 # 进入项目安装 Element UI 和 axios源码中所有 API 请求都基于 axios cd exam-front npm install element-ui2.15.14 axios0.21.4关键配置在main.js中引入 Element UI 并注册全局组件// main.js import Vue from vue import ElementUI from element-ui import element-ui/lib/theme-chalk/index.css import axios from axios Vue.use(ElementUI) Vue.prototype.$http axios // 挂载到原型方便组件内 this.$http.get() // 设置请求拦截器所有请求自动带上 token考试系统登录态核心 axios.interceptors.request.use(config { const token localStorage.getItem(exam_token) if (token) config.headers.Authorization Bearer ${token} return config })2.3 数据库环境MySQL 5.7.44 是高校实验室最常见版本别硬上 8.0MySQL 8.0 默认开启caching_sha2_password认证插件而 SpringBoot 2.7 默认驱动不兼容会报Client does not support authentication protocol requested by server。用 MySQL 5.7.442023 年 12 月发布的稳定版可彻底规避。初始化 SQL 脚本需包含三张核心表对应源码sql/exam_system.sql表名作用关键字段说明exam_paper试卷主表id,title,duration_minutes,status(0未发布,1进行中,2已结束),create_timeexam_question题目表id,paper_id,type(1单选,2多选,3判断,4主观),content,score,sort_orderexam_answer考生作答表id,paper_id,user_id,question_id,answer_text(主观题),answer_options(JSON 数组存多选选项ID),is_correct(0/1)注意exam_answer.answer_options字段类型必须设为JSONMySQL 5.7 支持而非TEXT。否则在 Java 层解析时会因类型不匹配导致Cannot deserialize instance错误。3. 核心功能落地把“在线考试”拆成 5 个可验证模块每个模块附真实代码片段3.1 登录与权限控制JWT Token 角色分离不是简单 if-else考试系统必须区分三种角色管理员增删试卷、教师发布考试、查看成绩、学生参加考试。SpringBoot 中用PreAuthorize注解实现方法级鉴权比 Controller 里写if(role.equals(teacher))更安全、更易测试。// ExamController.java RestController RequestMapping(/api/exam) public class ExamController { PostMapping(/publish) PreAuthorize(hasRole(TEACHER)) // 只有 TEACHER 角色能调用 public Result publishExam(RequestBody ExamPublishDTO dto) { // 发布逻辑 return Result.success(); } GetMapping(/student/{paperId}) PreAuthorize(hasRole(STUDENT) and #paperId authentication.principal.paperId) public Result getStudentPaper(PathVariable Long paperId) { // 学生只能获取自己被分配的试卷 return Result.success(paperService.getPaperForStudent(paperId)); } }关键点说明PreAuthorize依赖 Spring Security需在pom.xml加入spring-boot-starter-securityauthentication.principal.paperId是自定义 UserDetails 实现类中注入的字段表示该学生当前可考的试卷 ID避免学生手动改 URL 窃取其他试卷Token 生成逻辑放在LoginController中使用jjwt-api和jjwt-impl// 生成 Token有效期 2 小时足够一场考试 String token Jwts.builder() .setSubject(user.getUsername()) .claim(role, user.getRole()) // 存角色 .claim(paperId, user.getAssignedPaperId()) // 存可考试卷ID .setExpiration(new Date(System.currentTimeMillis() 2 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, exam-secret-key) .compact();3.2 试卷随机生成用 ThreadLocal 隔离并发请求避免题目重复同一场考试不同学生看到的题目顺序、选项顺序必须不同否则无法防作弊。源码中PaperService.generateRandomPaper()方法采用两级随机题库抽题从题库中按知识点权重随机抽取 N 道题如“Java 基础”占比 40%“SpringBoot”占比 30%选项打乱对每道单选/多选题的options字段JSON 数组用Collections.shuffle()打乱顺序。// PaperService.java public ExamPaper generateRandomPaper(Long templateId) { // 1. 获取模板题库 ListQuestion questions questionMapper.selectByTemplateId(templateId); // 2. 按权重抽题使用 ThreadLocalRandom 避免 Random 实例共享 ListQuestion selected new ArrayList(); ThreadLocalRandom random ThreadLocalRandom.current(); for (Question q : questions) { if (random.nextDouble() q.getWeight()) { // weight 是 0.0~1.0 的浮点权重 selected.add(q); } } // 3. 打乱题目顺序 Collections.shuffle(selected, random); // 4. 对每道题的选项打乱 for (Question q : selected) { if (q.getType() 1 || q.getType() 2) { // 单选/多选 Collections.shuffle(q.getOptions(), random); } } return buildPaperFromQuestions(selected); }为什么用 ThreadLocalRandom普通Random在高并发下会产生相同种子导致多线程生成的随机序列一致。ThreadLocalRandom.current()为每个线程分配独立实例保证每份试卷真正独立。3.3 倒计时与防切屏Vue 前端双保险不依赖后端轮询考试页面倒计时若只靠后端定时任务推送网络延迟会导致误差 3 秒。源码采用“前端定时器 后端心跳校准”双机制!-- ExamPaper.vue -- template div classexam-timer span v-iftimeLeft 0{{ formatTime(timeLeft) }}/span span v-else考试已结束/span /div /template script export default { data() { return { timeLeft: 0, timer: null, lastSync: Date.now() } }, mounted() { this.startTimer() // 每 30 秒同步一次服务端时间修正前端漂移 this.syncInterval setInterval(() { this.$http.get(/api/exam/time-sync).then(res { const serverTime res.data.serverTime const drift serverTime - Date.now() this.timeLeft Math.max(0, this.timeLeft drift) this.lastSync Date.now() }) }, 30000) }, methods: { startTimer() { this.timer setInterval(() { this.timeLeft-- if (this.timeLeft 0) { this.submitExam() // 自动交卷 } }, 1000) }, // 监听浏览器标签切换切走即暂停倒计时防作弊 beforeUnloadHandler() { window.addEventListener(blur, () { clearInterval(this.timer) this.$message.warning(检测到您离开考试页面倒计时已暂停) }) window.addEventListener(focus, () { this.startTimer() this.$message.info(已恢复倒计时) }) } } } /script3.4 主观题富文本提交用quill-editor而非textarea支持图片粘贴学生答题时经常需要插入代码截图、流程图。源码中SubjectiveAnswer.vue组件集成vue-quill-editorQuill 的 Vue 封装关键配置template quill-editor v-modelanswerContent :optionseditorOption changeonEditorChange / /template script import { quillEditor } from vue-quill-editor import quill/dist/quill.core.css import quill/dist/quill.snow.css export default { components: { quillEditor }, data() { return { answerContent: , editorOption: { modules: { toolbar: [ [bold, italic, underline], // 加粗、斜体、下划线 [{ list: ordered }, { list: bullet }], // 有序/无序列表 [image, code-block] // 插入图片、代码块 ], // 图片上传适配后端接口 imageActions: { upload: (file) { const formData new FormData() formData.append(file, file) return this.$http.post(/api/upload/image, formData, { headers: { Content-Type: multipart/form-data } }).then(res res.data.url) // 返回图片 URL } } } } } } } /script后端接收图片上传UploadController.javaPostMapping(/image) public Result uploadImage(RequestParam MultipartFile file) { // 限制大小 2MB if (file.getSize() 2 * 1024 * 1024) { return Result.fail(图片大小不能超过 2MB); } // 保存到 static/upload 目录返回相对路径 String fileName System.currentTimeMillis() _ file.getOriginalFilename(); Path path Paths.get(static/upload/, fileName); Files.createDirectories(path.getParent()); file.transferTo(path); return Result.success(/upload/ fileName); // 前端直接拼接 http://localhost:8080/upload/xxx.png }3.5 客观题自动阅卷用 JSONPath 解析学生答案比字符串匹配更鲁棒学生提交的多选题答案是 JSON 数组如[A,C,D]而标准答案存为ACD。若用answer.equals(ACD)会因顺序不同判错。源码用JsonPath提取并标准化// AnswerService.java public void autoGrade(Long paperId) { ListExamAnswer answers answerMapper.selectByPaperId(paperId); for (ExamAnswer ans : answers) { Question question questionMapper.selectById(ans.getQuestionId()); if (question.getType() 1 || question.getType() 2) { // 单选/多选 // 解析学生答案 JSON 数组 JSONArray studentOptions JSON.parseArray(ans.getAnswerOptions()); String studentCode studentOptions.stream() .map(Object::toString) .sorted() // 排序后拼接确保 ACD 和 CAD 结果一致 .collect(Collectors.joining()); // 标准答案转大写并排序 String correctCode question.getCorrectAnswer().toUpperCase(); char[] chars correctCode.toCharArray(); Arrays.sort(chars); String sortedCorrect new String(chars); ans.setIsCorrect(studentCode.equals(sortedCorrect) ? 1 : 0); } } answerMapper.updateBatch(answers); }4. 避坑指南这 4 个血泪经验让 90% 的同学少花 3 天调试时间4.1 现象Vue 页面空白控制台报Failed to resolve component: router-view原因Vue CLI 4 创建项目时默认main.js中Vue.use(VueRouter)被注释掉或router/index.js未正确导出router实例。解决检查router/index.js是否有export default router并在main.js中确认import router from ./router Vue.use(VueRouter) // 这行不能注释 new Vue({ router, render: h h(App) }).$mount(#app)4.2 现象SpringBoot 启动报java.lang.NoClassDefFoundError: javax/servlet/Filter原因IDEA 中 Maven 导入时勾选了 “Skip Tests”导致spring-boot-starter-web依赖的javax.servlet-api未下载。解决右键项目 →Maven→Reload project取消勾选 “Skip Tests”再重新导入。4.3 现象MySQL 插入中文乱码日志显示???原因MySQL 服务端未设置utf8mb4字符集或 JDBC URL 缺少useUnicodetruecharacterEncodingutf8参数。解决修改 MySQL 配置文件my.cnf[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ciJDBC URL 加参数jdbc:mysql://localhost:3306/exam?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai4.4 现象考试交卷后成绩为 0数据库exam_answer.is_correct全是 null原因MyBatis-Plus 的TableField注解未标注exist true导致is_correct字段被忽略MySQL 列名含下划线Java 字段名isCorrect驼峰映射失败。解决在ExamAnswer.java实体类中显式声明TableField(value is_correct, exist true) private Integer isCorrect; // 不要写成 is_correct保持 Java 命名规范5. 进阶技巧用ScheduledAsync实现考试自动归档省掉手动清理毕业设计答辩前老师常要求“展示系统长期运行能力”。源码中ExamScheduler.java用 SpringBoot 定时任务 异步处理每天凌晨 2 点自动归档已结束考试Component EnableScheduling public class ExamScheduler { Autowired private ExamService examService; Scheduled(cron 0 0 2 * * ?) // 每天凌晨 2 点执行 public void archiveFinishedExams() { // 查找 7 天前已结束的考试 LocalDateTime sevenDaysAgo LocalDateTime.now().minusDays(7); ListExamPaper papers examPaperMapper.selectList( new QueryWrapperExamPaper() .eq(status, 2) // 已结束 .lt(end_time, sevenDaysAgo) ); // 异步归档避免阻塞主线程 papers.forEach(paper - { CompletableFuture.runAsync(() - { try { examService.archivePaper(paper.getId()); // 移动到 exam_paper_archive 表 log.info(归档考试{}, paper.getTitle()); } catch (Exception e) { log.error(归档失败{}, paper.getId(), e); } }); }); } }关键配置启用异步需在启动类加EnableAsync并配置线程池避免默认单线程卡住Configuration EnableAsync public class AsyncConfig { Bean(taskExecutor) public Executor taskExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); // 核心线程数 executor.setMaxPoolSize(10); // 最大线程数 executor.setQueueCapacity(100); // 队列容量 executor.setThreadNamePrefix(archive-task-); executor.initialize(); return executor; } }调用时指定线程池CompletableFuture.runAsync(() - examService.archivePaper(paper.getId()), taskExecutor());这样即使某次归档耗时 2 秒如压缩答题日志也不会影响第二天考试正常发布。我在实际部署时还加了邮件通知归档完成后发一封简报到管理员邮箱用JavaMailSender实现5 行代码搞定。最后说句实在话这套 SpringBoot Vue MySQL 的在线考试系统不是为了拿高分而堆砌技术而是用最短路径把“考试”这件事做闭环——从学生点击“开始考试”到交卷后 3 秒弹出分数再到老师后台一键导出 Excel 成绩单。它不追求高并发百万级但保证 200 人同时考不崩、不丢题、不错判。我带过 3 届毕设凡是把application.yml配对、MySQL 字符集设对、Vue 路由守卫写全的同学答辩时老师问“如果学生断网了怎么办”都能指着localStorage里缓存的答题草稿回答出来。希望帮到你。本文还有配套的精品资源点击获取