十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot 3 + Vue3 在线考试系统实战解析

SpringBoot 3 + Vue3 在线考试系统实战解析 简介这是一套基于Spring Boot后端与Vue3前端的在线考试系统完整开发源码面向Java全栈初学者、课程设计学生及Web项目实践者解决传统考试系统开发门槛高、前后端分离不清晰、数据库集成不完整等常见教学与实训痛点。资源共308个文件涵盖92个Java后端核心类、35个Vue组件页面、23个TypeScript逻辑文件、120个XML配置与依赖描述以及SQL建表脚本、环境配置.env.development/.production、样式文件SCSS/CSS和基础静态资源SVG/JPG压缩包仅1.12MB轻量易部署。已有508人学习下载适合快速理解MVC分层架构、RESTful接口对接、JWT鉴权流程与考试业务闭环含登录、题库管理、组卷、在线作答、自动阅卷等模块。资源附带SQL初始化脚本与结构化目录便于本地一键启动并深入调试各层交互逻辑。1. 为什么一个叫online_exam.zip的 SpringBoot Vue3 项目比你上周改的三个“后台管理系统”更值得深挖很多刚接手online_exam.zip这类压缩包的同学第一反应是又一个毕设级考试系统点开发现后端用 SpringBoot 2.7.x或 3.1前端是 Vue3 Vite TypeScript路由守卫、试卷生成、实时交卷、防切屏逻辑全在代码里——它不是模板而是在线考试场景下对前后端协同边界的完整压测现场。它解决的不是“怎么展示题目”而是“如何让 500 人同时作答时数据库不锁表、WebSocket 不断连、浏览器不缓存上一题答案、监考端能秒级感知异常行为”。适合两类人一是正在做教育类 SaaS 的后端工程师需要快速复用其分布式锁控制并发交卷的写法二是前端同学想搞懂 Vue3 的onBeforeUnmount怎么和visibilitychange事件配合实现防切屏检测。别急着解压跑起来先看清它在技术栈缝隙里埋了哪些真问题。2. 拆解online_exam.zip的核心分层为什么必须用 SpringBoot 3.x Vue3 组合而不是 SpringBoot 2.6 Vue22.1 后端选型SpringBoot 3.x 的 Jakarta EE 9 与考试系统的强约束关系online_exam.zip中的pom.xml显示其依赖spring-boot-starter-web版本为3.1.12或3.2.6而非常见的2.7.18。这不是版本升级的惯性而是由考试系统特有的事务边界决定的。例如试卷提交接口/api/exam/submit必须在同一数据库事务内完成三件事更新考生答题记录、扣减剩余考试时长、触发监考日志写入。SpringBoot 2.x 默认使用javax.transaction.*包而 MySQL 8.0.33 驱动已弃用该包若强行降级会在Transactional注解处抛出ClassNotFoundException。online_exam.zip的ExamSubmitService.java第 47 行明确使用jakarta.persistence.Entity这是 SpringBoot 3.x 强制要求的迁移点。提示若你本地 JDK 是 17但 IDEA 仍提示Cannot resolve symbol jakarta请检查 Maven 的settings.xml是否启用了阿里云镜像源https://maven.aliyun.com/repository/public并确认spring-boot-dependencies的 BOM 版本与项目声明一致。常见错误是spring-boot-starter-parent声明为3.2.6但子模块exam-api的pom.xml却漏写了parent标签导致编译时加载旧版hibernate-core。2.2 前端选型Vue3 的ref响应式与实时监考数据流的硬匹配online_exam.zip的src/views/monitor/MonitorView.vue使用const activeUsers refMonitorUser[]([])接收 WebSocket 推送的监考数据而非 Vue2 的data() { return { activeUsers: [] } }。关键差异在于当后端每 3 秒推送一次{ userId: U1001, status: switching-tab, timestamp: 1717023456 }时Vue3 的ref能保证activeUsers.value的每次赋值都触发v-for列表重渲染且不会因对象引用未变而跳过更新Vue2 的data对象需this.$set才能响应。online_exam.zip的useMonitor.ts自定义 Hook 中第 22 行activeUsers.value [...newActiveUsers]正是利用此特性实现毫秒级状态同步。2.2.1 Vue3 环境配置的三个必调参数非vite.config.ts默认值参数默认值online_exam.zip实际值为什么必须改build.targetmoduleses2015监考页面需兼容 Chrome 80 / Edge 90es2015保证Promise.allSettled等 API 可用server.hostlocalhost0.0.0.0本地调试时需让局域网内监考平板通过http://192.168.1.100:5173访问resolve.alias{}{ : path.resolve(__dirname, src) }online_exam.zip中api/exam.ts大量使用import { submitExam } from /api/exam无 alias 将报错# 在项目根目录执行验证 Vue3 环境是否就绪 npm run dev # 观察控制台输出 # ➜ Local: http://localhost:5173/ # ➜ Network: http://192.168.1.100:5173/ ← 这行存在说明 server.host 生效 # 打开浏览器开发者工具 → Application → JavaScript Context输入 window.__VUE_DEVTOOLS_GLOBAL_HOOK__返回对象则 Vue Devtools 已激活2.3 前后端分离的临界点online_exam.zip如何用CrossOrigin和proxy同时解决开发与生产跨域online_exam.zip的跨域处理不是简单加个CrossOrigin(origins *)。它在开发阶段npm run dev通过 Vite 的server.proxy将/api请求代理到http://localhost:8080而在生产部署时npm run build后 Nginx 静态托管后端 SpringBoot 主动开启 CORS// ExamWebMvcConfig.java Configuration public class ExamWebMvcConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(https://exam.yourcompany.com, https://admin.exam.yourcompany.com) // 严格限定域名 .allowedMethods(GET, POST, PUT, DELETE) .maxAge(3600) .allowCredentials(true); // 支持 Cookie 传递 sessionId } }注意若你将online_exam.zip部署到子路径如https://yourdomain.com/exam/必须同步修改两处① Vite 的base: /exam/② SpringBoot 的server.servlet.context-path/exam。否则/api/login会变成https://yourdomain.com/api/login404而非https://yourdomain.com/exam/api/login。3. 从解压到可运行online_exam.zip的最小启动路径与五个高频失败点3.1 解压后必须立即执行的三步校验检查后端 JDK 版本兼容性进入backend/目录运行mvn -v输出中Java version必须为17.0.x或21.0.x。若显示1.8.0_3XX需在 IDEA 的Project Structure → Project → Project SDK中切换至 JDK 17。验证前端 Node.js 版本在frontend/目录执行node -v结果必须 ≥18.17.0Vite 4.5 要求。低于此版本会报ERR_REQUIRE_ESM错误。确认数据库初始化脚本路径online_exam.zip的backend/src/main/resources/sql/下应有init_schema.sql和init_data.sql。前者建表含exam_paper,exam_question,exam_record后者插入管理员账号admin/123456。若缺失启动时DataSourceHealthIndicator将报Table exam.exam_paper doesnt exist。3.2 后端启动application.yml的四个关键参数及其危险值online_exam.zip的backend/src/main/resources/application.yml中以下参数直接影响系统能否通过健康检查参数安全值示例危险值示例后果spring.datasource.urljdbc:mysql://localhost:3306/exam?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltruejdbc:mysql://localhost:3306/exam缺少serverTimezone导致java.sql.SQLException: The server time zone value XXX is unrecognizedspring.redis.host127.0.0.1localhostDocker 环境下localhost指向容器自身而非宿主机 Redisexam.jwt.secretExamSystem2024SecureKey!123456JWT 签名密钥过短jjwt-api会抛IllegalArgumentException: The specified signing key is too weakserver.port808080Linux 下非 root 用户无法绑定 1-1023 端口启动直接失败# application.yml 关键片段需手动核对 spring: datasource: url: jdbc:mysql://localhost:3306/exam?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_mysql_password redis: host: 127.0.0.1 port: 6379 exam: jwt: secret: ExamSystem2024SecureKey! # 至少 16 字符含大小写字母数字符号 server: port: 80803.3 前端启动绕过online_exam.zip中vite-plugin-pages的路由生成陷阱online_exam.zip使用vite-plugin-pages自动生成路由其规则是src/pages/下的.vue文件自动注册为路由。但新手常犯两个错误误删src/pages/index.vue该文件是/路由的入口删除后访问http://localhost:5173显示404控制台无报错因路由表为空。在src/pages/下新建ExamDetail.vue却无法访问/exam/detailvite-plugin-pages默认将文件名转为 kebab-caseExamDetail.vue→/exam-detail而非/exam/detail。正确做法是创建src/pages/exam/detail.vue注意小写斜杠。# 验证路由是否生成成功 npm run dev # 浏览器打开 http://localhost:5173/__open-in-editor Vite 插件 # 查看自动生成的 routes.ts确认包含 // { path: /login, component: () import(../pages/login.vue) } // { path: /exam/detail, component: () import(../pages/exam/detail.vue) }3.4 数据库初始化用flyway替代手动执行 SQL 的标准流程online_exam.zip的backend/pom.xml包含flyway-core依赖因此不应手动执行sql/init_schema.sql。正确流程是# 1. 确保 MySQL 已启动且 exam 库已创建无需建表 mysql -u root -p -e CREATE DATABASE IF NOT EXISTS exam CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 2. 启动后端Flyway 自动扫描 src/main/resources/db/migration/ 下的 V1__init.sql mvn spring-boot:run -Dspring-boot.run.profilesdev # 3. 观察控制台输出 # Flyway Community Edition 9.22.2 by Redgate # INFO Successfully validated 1 migration # INFO Current version of schema exam: Empty Schema # INFO Migrating schema exam to version 1 - init # INFO Successfully applied 1 migration to schema exam提示若 Flyway 报错Found non-empty schemaexamwithout schema history table说明库中已有表但无flyway_schema_history表。此时执行mysql -u root -p exam -e DROP TABLE IF EXISTS flyway_schema_history;清理后重试。4. 真实考场压力下的三个关键优化防刷题、防切屏、防重复提交4.1 后端防刷题用 Redis 分布式锁控制单题请求频次online_exam.zip的QuestionController.java中获取题目详情的接口/api/question/{id}并非简单查库而是加了基于 Redis 的限流GetMapping(/{id}) public ResultQuestionVO getQuestion(PathVariable Long id, HttpServletRequest request) { String ip getClientIp(request); // 获取真实 IP处理 Nginx 转发 String lockKey question:rate: ip : id; Boolean isLocked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, Duration.ofSeconds(5)); if (Boolean.FALSE.equals(isLocked)) { return Result.fail(操作过于频繁请5秒后再试); } // ... 查询数据库逻辑 }参数说明lockKey以IP题目ID为粒度避免全局锁影响并发Duration.ofSeconds(5)是冷却时间防止暴力遍历题库setIfAbsent原子操作避免多线程重复写入。注意若 Redis 未启用此逻辑会退化为isLocked false导致限流失效。启动前务必确认spring.redis.host可连通且redisTemplateBean 已注入。4.2 前端防切屏Vue3 的document.visibilityState与onBeforeUnmount联动online_exam.zip的src/views/exam/ExamPaper.vue实现了监考级防切屏script setup import { onBeforeUnmount, onMounted } from vue let visibilityHandler () {} onMounted(() { visibilityHandler () { if (document.visibilityState hidden) { // 发送切屏事件到后端 api.reportAbnormal({ type: TAB_SWITCH, examId: props.examId }) // 弹窗警告仅首次 if (!localStorage.getItem(tabSwitchWarned)) { alert(检测到您切换了浏览器标签页本次操作将被记录) localStorage.setItem(tabSwitchWarned, true) } } } document.addEventListener(visibilitychange, visibilityHandler) }) onBeforeUnmount(() { document.removeEventListener(visibilitychange, visibilityHandler) // 页面卸载前强制交卷防止用户直接关浏览器 if (examStatus.value ONGOING) { api.submitExam({ examId: props.examId, answers: userAnswers.value }) } }) /script关键点onBeforeUnmount在组件销毁前触发确保用户关闭标签页时仍能调用submitExamlocalStorage标记避免重复弹窗提升用户体验api.reportAbnormal是独立接口与交卷逻辑解耦便于后续接入风控系统。4.3 前后端协同防重复提交RepeatSubmit注解 前端按钮置灰online_exam.zip的防重复提交是双向的前端按钮点击后立即disabled后端用自定义注解拦截// 后端ExamSubmitController.java PostMapping(/submit) RepeatSubmit // 自定义注解基于 Redis Token 校验 public ResultString submitExam(RequestBody SubmitDTO dto) { // 业务逻辑 } // 前端ExamPaper.vue const handleSubmit async () { submitBtnDisabled.value true // 按钮置灰 try { const res await api.submitExam(formData) ElMessage.success(提交成功) } catch (err) { ElMessage.error(err.message || 提交失败) } finally { submitBtnDisabled.value false // 恢复按钮 } }RepeatSubmit工作原理前端在提交前调用/api/token获取唯一submitToken提交时将submitToken放入请求 Header后端RepeatSubmitAspect切面校验该 token 是否存在于 Redis有效期 2 秒存在则删除并放行否则拒绝保证同一用户 2 秒内只能成功提交一次即使网络超时重试也无效。5. 部署上线前的终极验证用 curl 模拟真实考生行为链不要依赖 UI 点击测试。online_exam.zip的健壮性必须通过命令行链路验证。以下是一套覆盖登录→选试卷→答题→交卷→查成绩的curl脚本可直接粘贴到 Linux/macOS 终端执行假设后端运行在localhost:8080前端在localhost:5173# 1. 获取登录凭证模拟考生 TOKEN$(curl -s -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:student001,password:123456} | jq -r .data.token) # 2. 获取可考试卷列表 curl -s -X GET http://localhost:8080/api/exam/paper/list \ -H Authorization: Bearer $TOKEN | jq .data[0].id paper_id.txt # 3. 开始考试获取题目 PAPER_ID$(cat paper_id.txt) curl -s -X POST http://localhost:8080/api/exam/start \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {\paperId\:$PAPER_ID} | jq -r .data.examId exam_id.txt # 4. 提交答案模拟答完所有题 EXAM_ID$(cat exam_id.txt) curl -s -X POST http://localhost:8080/api/exam/submit \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {\examId\:$EXAM_ID,\answers\:[{\questionId\:1,\answer\:\A\},{\questionId\:2,\answer\:\B\}]} | jq .message # 5. 查询成绩验证数据落库 curl -s -X GET http://localhost:8080/api/exam/record/latest \ -H Authorization: Bearer $TOKEN | jq .data.score预期输出步骤 1 返回token字符串长度 ≥ 100步骤 4 返回提交成功步骤 5 返回具体分数如85证明exam_record表已写入。提示若步骤 4 返回{code:403,message:重复提交}说明RepeatSubmit生效若返回{code:500,message:exam not found}检查EXAM_ID是否有效可能考试已结束。此脚本可集成到 CI/CD 的test.sh中作为上线前的冒烟测试。本文还有配套的精品资源点击获取
返回列表