SpringBoot+Vue前后端分离在线考试系统毕业设计全流程解析

发布时间:2026/9/7 9:51:35
SpringBoot+Vue前后端分离在线考试系统毕业设计全流程解析 每年到了毕业季都会有一大批计算机专业的同学在选题和实现之间反复纠结。尤其是“在线考试系统”这类听起来不复杂、但真正动手写才发现细节繁多的题目用户角色怎么分试卷怎么生成客观题自动判分怎么设计前端和后端到底怎么“分离”如果你正在找 SpringBoot 方向的 Java 毕业设计项目也正在被这些问题卡住那这篇文章就是为你准备的。我会从系统架构、数据库设计、后端核心代码、前端 Vue 页面到启动排错完整拆解一个基于 SpringBoot 的前后端分离在线考试系统。看完之后你会明白这个项目真正的工作量在哪里哪些点最容易出错以及怎么在毕业答辩中把系统讲清楚。全文以项目实践为主线代码示例尽量保持完整可复制。文中涉及的版本和配置以通用实践为准具体项目可能使用不同版本但整体思路是一致的。1. 在线考试系统到底解决什么问题很多同学把这个题目理解为“做一套能考试的网页”但毕业设计不是做产品 Demo而是要体现你掌握了从需求分析、系统设计、数据库建模、后端开发到前端联调的一整套工程能力。在线考试系统的核心业务并不复杂但它天然包含多个角色、多种状态、多条业务链路非常适合用作 Java 毕业设计学生端注册登录、查看考试列表、参加考试、提交答卷、查看成绩和错题。教师端维护题库、组卷发布、设置考试时间、查看成绩统计、批改主观题。管理员端用户管理、班级管理、考试数据统计、系统基础配置。真正考验开发水平的地方在于业务逻辑的交织一场考试只能从题库中抽取指定数量的题目。学生在考试时间内只能提交一次答卷。客观题要自动评分主观题要支持人工评分或半自动评分。考试状态要区分未开始、进行中、已结束。学生提交后系统需要重新计算成绩并记录答题明细。这些逻辑单看每一个都不难但放在一起就需要合理的表结构设计和清晰的分层编码。这也是为什么“在线考试系统”这个选题虽然常见却很有区分度的原因。从技术栈角度看SpringBoot Vue 的前后端分离方案已经成为当前 Java 毕业设计的主流选择。后端负责业务逻辑、数据存储和安全认证前端负责页面渲染和用户交互。两者通过 RESTful API 进行通信。2. 系统架构与核心技术选型2.1 前后端分离架构“前后端分离”可以理解为前端项目和后端项目是两个独立的工程可以独立开发、独立部署、独立扩展。在还没分离的传统开发模式中前端页面和后端 Java 代码混在同一个 Web 项目中页面由后端模板引擎渲染。问题在于前后端开发耦合度高后端同学改页面样式前端同学改接口逻辑容易冲突。前后端分离之后前端只负责调接口、渲染数据后端只负责处理业务、返回 JSON。这也是企业级项目的主流开发方式写在毕业设计里能明显加分。2.2 后端技术选型后端核心框架选择 SpringBoot理由非常现实SpringBoot 简化了 Spring 的基础配置通过自动配置减少大量 XML 配置。内嵌 Tomcat可以直接通过java -jar或 IDE 启动部署简单。生态成熟集成 MyBatis、Spring Security、JWT、Redis 等组件都非常方便。数据访问层选择 MyBatis 或 MyBatis-Plus。MyBatis-Plus 在毕设项目中非常受欢迎因为内置了单表 CRUD 方法能减少很多重复代码。2.3 前端技术选型前端选择 Vue 2 或 Vue 3 搭配 Element UI / Element Plus。Vue 的核心特点是组件化开发和响应式数据绑定Element UI 提供了表格、表单、弹窗等现成组件能大幅缩短页面开发时间。技术上 Vue 3 是方向但目前很多毕设项目仍然使用 Vue 2。如果你是从零开始建议优先 Vue 3 Element Plus Vite如果你要参考已有的源码就以源码为准。2.4 数据库与认证方案数据库使用 MySQL这是 Java 方向使用最广泛的数据库资料多、坑少、面试常问。认证方案推荐 JWTJSON Web Token。用户登录成功后后端生成一个 Token 返回给前端前端在后续请求中把 Token 放到请求头里后端拦截器校验 Token 合法性。JWT 的好处是无状态、适合前后端分离、扩展性比 Session 好。但要注意 Token 的过期时间和密钥管理。2.5 系统整体流程前端 Vue 页面 | | RESTful APIJSON v SpringBoot 后端 - Controller 层接收请求参数校验 - Service 层处理业务逻辑、事务控制 - Mapper 层数据库访问 | v MySQL 数据库这个分层结构是 SpringBoot 后端项目的经典组织方式也是毕业设计评阅老师最想看到的层次。3. 环境准备与项目初始化3.1 开发环境清单在开始写代码之前先准备好以下环境工具版本建议说明JDK1.8 或 11Spring Boot 2.x 推荐 JDK 8Spring Boot 3.x 需要 JDK 17Maven3.6 以上后端依赖管理Node.js14 以上前端构建运行环境npm6 以上前端依赖管理MySQL5.7 或 8.0数据存储IDEA2020 及以上版本后端和前端开发全用 IDEA 也能胜任具体版本以你实际项目为准。这里给你一个判断逻辑如果你下载的项目是基于 Spring Boot 2.x很大概率用 JDK 8如果是 Spring Boot 3.x那么 JDK 17 是必需的。3.2 后端项目初始化最简单的初始化方式是使用 Spring Initializrstart.spring.io。选择 Maven 工程、Java 版本、Spring Boot 版本然后添加依赖。实际项目里常见的核心依赖如下!-- 文件路径pom.xml -- dependencies !-- Web 启动器内置 Tomcat -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus 的 Spring Boot 适配 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- Lombok简化实体类代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- Java 后端参数校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies如果你使用的是新版 Spring BootMySQL 驱动的 artifactId 推荐写法可能是com.mysql:mysql-connector-j不再推荐mysql:mysql-connector-java。这里以你的实际依赖情况为准。3.3 前端项目初始化Vue 项目的初始化命令按 Vue 版本区分。Vue 2 项目使用 Vue CLInpm install -g vue/cli vue create exam-frontVue 3 项目推荐使用 Vitenpm create vitelatest exam-front -- --template vue初始化完成后安装 UI 组件库npm install element-plus axios前端项目初始化好之后你就可以把一个可运行的空白前端项目先跑起来再逐步填充页面。4. 数据库设计与建表脚本数据库设计决定了业务逻辑的复杂度上限。表设计不好后面写 Service 层代码时会特别痛苦。在线考试系统的核心表可以从“用户、题目、考试、答卷、答题明细”五个维度去设计。4.1 用户表用户表保存所有角色的账号信息通过role字段区分管理员、教师、学生。CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT 加密后的密码, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, role varchar(20) DEFAULT student COMMENT 角色admin/teacher/student, status tinyint(1) DEFAULT 1 COMMENT 账号状态1启用 0禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;注意密码字段绝对不能存明文。实际项目一般使用 BCrypt 加密具体加密方式以原型代码为准。4.2 试题表试题表存储单选题、多选题、判断题、主观题。为了降低设计复杂度很多毕设系统会先实现单选题和判断题等客观题再做主观题扩展。CREATE TABLE exam_question ( id bigint(20) NOT NULL AUTO_INCREMENT, question_type tinyint(4) DEFAULT 1 COMMENT 题型1单选 2多选 3判断 4主观, question_content text COMMENT 题干, options_json text COMMENT 选项JSON例如[A,B,C,D], answer varchar(255) DEFAULT NULL COMMENT 标准答案, score int(11) DEFAULT 5 COMMENT 每题分值, course_id bigint(20) DEFAULT NULL COMMENT 所属课程, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT试题表;字段设计有一个关键点options_json。对于选择题的选项用 JSON 字符串存储整个选项集合而不是为每个选项建一张表。这样实现简单前端解析也容易。4.3 考试表考试表存储一场考试的基本信息名称、学科、时间、状态等。CREATE TABLE exam_info ( id bigint(20) NOT NULL AUTO_INCREMENT, exam_name varchar(100) NOT NULL COMMENT 考试名称, course_id bigint(20) DEFAULT NULL COMMENT 课程ID, exam_duration int(11) DEFAULT 60 COMMENT 考试时长分钟, start_time datetime DEFAULT NULL COMMENT 开始时间, end_time datetime DEFAULT NULL COMMENT 结束时间, total_score int(11) DEFAULT 100 COMMENT 总分, pass_score int(11) DEFAULT 60 COMMENT 及格分, status tinyint(1) DEFAULT 0 COMMENT 状态0未发布 1进行中 2已结束, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考试表;若要支持“从题库随机抽题”需要增加一张关联表来维护考试与题目的关系。CREATE TABLE exam_paper ( id bigint(20) NOT NULL AUTO_INCREMENT, exam_id bigint(20) NOT NULL COMMENT 考试ID, question_id bigint(20) NOT NULL COMMENT 题目ID, question_order int(11) DEFAULT 0 COMMENT 题目顺序, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT试卷题目关联表;4.4 答卷表学生提交答卷后系统需要生成一条考试记录CREATE TABLE exam_record ( id bigint(20) NOT NULL AUTO_INCREMENT, exam_id bigint(20) NOT NULL COMMENT 考试ID, student_id bigint(20) NOT NULL COMMENT 学生用户ID, total_score int(11) DEFAULT 0 COMMENT 总得分, status tinyint(1) DEFAULT 0 COMMENT 状态0未交卷 1已交卷 2已批改, submit_time datetime DEFAULT NULL COMMENT 交卷时间, PRIMARY KEY (id), KEY idx_exam_id (exam_id), KEY idx_student_id (student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考试记录表;这里建议加索引因为考试记录表的数据增长比较快查询学生成绩时会频繁用到student_id和exam_id。4.5 答题明细表每个学生每道题的答题情况要单独记录这是成绩统计和错题回顾的数据基础。CREATE TABLE exam_record_detail ( id bigint(20) NOT NULL AUTO_INCREMENT, record_id bigint(20) NOT NULL COMMENT 考试记录ID, question_id bigint(20) NOT NULL COMMENT 题目ID, student_answer text COMMENT 学生答案, correct_answer text COMMENT 正确答案, is_correct tinyint(1) DEFAULT 0 COMMENT 是否正确, score int(11) DEFAULT 0 COMMENT 本题得分, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT答题明细表;有了这些核心表考试系统的业务闭环就可以跑通了。接下来的代码实现就是围绕这些表展开 CRUD 和业务校验。5. 后端 SpringBoot 核心功能实现5.1 后端项目分层结构我推荐你按照下面的包结构组织代码这也是企业级 SpringBoot 项目的通用做法src/main/java/com/example/exam/ ├── ExamApplication.java // 启动类 ├── common/ // 通用结果封装、异常处理、常量 │ ├── Result.java │ └── GlobalExceptionHandler.java ├── config/ // 配置类CORS、拦截器、MyBatis-Plus ├── controller/ // 控制层 │ ├── AuthController.java │ ├── ExamController.java │ └── QuestionController.java ├── service/ // 业务层接口 │ └── impl/ // 业务层实现 ├── mapper/ // MyBatis-Plus Mapper 接口 ├── entity/ // 数据库实体类 └── dto/ // 前端传入参数对象5.2 统一返回结果封装前后端分离项目中后端接口返回给前端的结构应该保持统一。推荐使用一个Result类封装// 文件路径src/main/java/com/example/exam/common/Result.java package com.example.exam.common; import lombok.Data; Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }这样做有几个好处前端解析统一格式不需要针对不同接口单独处理后端可以在构造返回结果时统一设置 code、message、data 三段信息后续接入 Swagger 接口文档也比较直观。5.3 登录接口实现登录接口是用户进入系统的第一个入口。核心逻辑是接收用户名和密码校验账号是否存在以及密码是否正确然后生成 Token 返回给前端。// 文件路径src/main/java/com/example/exam/controller/AuthController.java RestController RequestMapping(/api/auth) public class AuthController { Autowired private UserService userService; PostMapping(/login) public ResultString login(RequestBody LoginDTO loginDTO) { User user userService.findByUsername(loginDTO.getUsername()); if (user null) { return Result.error(用户不存在); } // 实际项目必须使用 BCrypt 匹配密码这里简化演示 if (!user.getPassword().equals(loginDTO.getPassword())) { return Result.error(密码错误); } if (user.getStatus() ! 1) { return Result.error(账号已被禁用); } // 生成 JWT Token String token JwtUtil.createToken(user.getId(), user.getUsername(), user.getRole()); return Result.success(token); } }在做毕设项目时登录成功后的 Token 请使用 JWT 工具类生成不要在前端暴露用户密码。JwtUtil 可以自己实现加密和解析逻辑也可以用现成的jjwt或hutool工具类。核心思路是把用户 ID、用户名、角色等信息放入 Token并设置过期时间。后端通过拦截器从请求头拿到 Token解析出用户信息后放行。5.4 教师端创建考试创建考试是教师角色的核心操作。接口逻辑通常包含保存考试基本信息。从试题表中按课程、题型、数量条件随机抽取题目。将抽到的题目批量插入exam_paper表。这里最容易踩坑的是数据一致性。保存考试信息和插入试卷题目要么同时成功要么同时失败必须在 Service 方法上声明Transactional事务注解。// 文件路径src/main/java/com/example/exam/service/impl/ExamServiceImpl.java Override Transactional(rollbackFor Exception.class) public void createExamWithQuestions(CreateExamDTO dto) { // 1. 保存考试基本信息 ExamInfo exam new ExamInfo(); exam.setExamName(dto.getExamName()); exam.setCourseId(dto.getCourseId()); exam.setExamDuration(dto.getExamDuration()); exam.setStartTime(dto.getStartTime()); exam.setEndTime(dto.getEndTime()); exam.setStatus(0); this.save(exam); // 2. 按需随机抽题singleCount 为单选数量judgeCount 为判断数量 ListLong singleQuestionIds questionMapper.selectRandomIds( dto.getCourseId(), 1, dto.getSingleCount()); ListLong judgeQuestionIds questionMapper.selectRandomIds( dto.getCourseId(), 3, dto.getJudgeCount()); ListExamPaper paperList new ArrayList(); int order 1; for (Long qid : singleQuestionIds) { ExamPaper item new ExamPaper(); item.setExamId(exam.getId()); item.setQuestionId(qid); item.setQuestionOrder(order); paperList.add(item); } for (Long qid : judgeQuestionIds) { ExamPaper item new ExamPaper(); item.setExamId(exam.getId()); item.setQuestionId(qid); item.setQuestionOrder(order); paperList.add(item); } // 3. 批量插入 paperMapper.insertBatch(paperList); }随机抽题的 SQL 可以这样写!-- 文件路径src/main/resources/mapper/QuestionMapper.xml -- select idselectRandomIds resultTypejava.lang.Long SELECT id FROM exam_question WHERE course_id #{courseId} AND question_type #{questionType} ORDER BY RAND() LIMIT #{limit} /select在数据量不大时ORDER BY RAND()是常见的随机抽取实现。在生产环境大数据量下性能会变差但用于毕业设计是够用的。如果你在答辩时被问到完全可以回答“这套方案的数据量瓶颈在哪里企业级方案一般怎么做”这会成为一个很好的加分点。5.5 学生端提交试卷学生端提交试卷后后端要做的事情包括校验考试是否存在且已发布。校验当前时间是否在开考和结束时间范围内。校验该学生是否已经交过卷防止重复提交。保存答题明细。自动批改客观题累加总分。更新考试记录状态。自动批改的核心逻辑是遍历答卷中的每道题目将学生答案与标准答案进行比对。// 文件路径src/main/java/com/example/exam/service/impl/ExamRecordServiceImpl.java Override Transactional(rollbackFor Exception.class) public void submitExam(SubmitExamDTO dto) { // 1. 查询考试信息 ExamInfo exam examMapper.selectById(dto.getExamId()); if (exam null || exam.getStatus() ! 1) { throw new RuntimeException(考试不存在或未发布); } // 2. 防止重复提交 QueryWrapperExamRecord wrapper new QueryWrapper(); wrapper.eq(exam_id, dto.getExamId()) .eq(student_id, dto.getStudentId()); Long count examRecordMapper.selectCount(wrapper); if (count ! null count 0) { throw new RuntimeException(请勿重复提交试卷); } // 3. 创建考试记录 ExamRecord record new ExamRecord(); record.setExamId(dto.getExamId()); record.setStudentId(dto.getStudentId()); record.setStatus(1); record.setSubmitTime(new Date()); int totalScore 0; int currentOrder 1; ListExamRecordDetail detailList new ArrayList(); // 4. 遍历学生提交的回答列表 for (SubmitAnswer item : dto.getAnswers()) { Question question questionMapper.selectById(item.getQuestionId()); if (question null) { continue; } boolean correct false; // 客观题直接比对答案 if (question.getQuestionType() 1 || question.getQuestionType() 3) { correct question.getAnswer().trim().equalsIgnoreCase( item.getStudentAnswer().trim()); } ExamRecordDetail detail new ExamRecordDetail(); detail.setQuestionId(question.getId()); detail.setStudentAnswer(item.getStudentAnswer()); detail.setCorrectAnswer(question.getAnswer()); detail.setCorrect(correct ? 1 : 0); if (correct) { detail.setScore(question.getScore()); totalScore question.getScore(); } else { detail.setScore(0); } detailList.add(detail); currentOrder; } record.setTotalScore(totalScore); examRecordMapper.insert(record); // 5. 批量插入答题明细detailList 的 recordId 需要重新赋值 for (ExamRecordDetail detail : detailList) { detail.setRecordId(record.getId()); examRecordDetailMapper.insert(detail); } }这段代码结构清晰每一段注释都对应一个业务校验点。在毕设答辩时老师最喜欢问的问题就是“提交试卷时如何防止重复提交”这段代码可以直接回答。5.6 拦截器与跨域配置前端项目和后端项目开发时使用不同的端口会触发跨域问题。后端需要配置 CORS跨域资源共享才能让前端正常请求接口。// 文件路径src/main/java/com/example/exam/config/CorsConfig.java Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:5173) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }实际项目中allowedOrigins的地址要与你前端项目启动后的地址保持一致。Vue 2 默认端口是 8080Vite 项目默认端口是 5173如果你使用的是其他端口需要自行调整。6. 前端 Vue 页面与接口对接6.1 前端项目目录结构Vue 项目拿到手之后推荐先看src目录下的结构src/ ├── api/ // 后端接口调用封装 ├── assets/ // 静态资源 ├── components/ // 公共组件 ├── router/ // 路由配置 ├── store/ // 全局状态管理Vuex/Pinia ├── views/ // 页面组件 │ ├── login/ │ ├── student/ │ ├── teacher/ │ └── admin/ ├── App.vue └── main.js6.2 封装 Axios 请求前端调后端接口的核心是 Axios。项目里一般会对 Axios 做统一封装把 baseURL、请求头、响应拦截器集中管理。// 文件路径src/api/request.js import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动携带 Token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) // 响应拦截器统一处理业务错误 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default request这里的关键点是把 Token 放在请求头里让后端拦截器能够识别用户身份。登录接口返回的 Token 存入localStorage页面刷新后仍能保持登录状态。前端调用接口时页面组件中只需要写业务代码// 文件路径src/api/exam.js import request from ./request // 获取考试列表 export function getExamList(params) { return request({ url: /exam/list, method: get, params }) } // 提交试卷 export function submitExam(data) { return request({ url: /exam/submit, method: post, data }) }6.3 登录页面示例登录页面是前端开发中最典型的一个例子。表单校验、提交接口、路由跳转、Token 存储都集中在这里。!-- 文件路径src/views/login/Login.vue -- template div classlogin-container el-card classlogin-card h2在线考试系统/h2 el-form refloginFormRef :modelloginForm :rulesrules el-form-item propusername el-input v-modelloginForm.username placeholder请输入用户名 / /el-form-item el-form-item proppassword el-input v-modelloginForm.password typepassword placeholder请输入密码 show-password / /el-form-item el-form-item el-button typeprimary stylewidth: 100% clickhandleLogin 登录 /el-button /el-form-item /el-form /el-card /div /template script setup import { ref } from vue import { useRouter } from vue-router import { ElMessage } from element-plus import { login } from /api/auth const router useRouter() const loginFormRef ref() const loginForm ref({ username: admin, password: 123456 }) const rules { username: [{ required: true, message: 请输入用户名, trigger: blur }], password: [{ required: true, message: 请输入密码, trigger: blur }] } async function handleLogin() { await loginFormRef.value.validate() const res await login(loginForm.value) localStorage.setItem(token, res.data) router.push(/dashboard) } /script这段代码使用的是 Vue 3 的组合式 API 写法。如果你使用的是 Vue 2对应的写法是data、methods选项式 API。判断依据仍然是你实际项目的 Vue 版本。6.4 考试列表与参加考试学生登录后在考试列表页面能看到所有已发布且当前时间在考试时间范围内的考试。点击“参加考试”按钮后跳转到答题页面答题页面加载该考试的试卷题目。前端在处理答题页面时有一个关键体验点需要重点处理倒计时。开考后显示剩余时间时间到自动提交。这里通常会设置一个定时器每秒更新剩余分钟数。// 考试倒计时逻辑伪代码 const remainSeconds ref(examDuration * 60) const timer setInterval(() { remainSeconds.value-- if (remainSeconds.value 0) { clearInterval(timer) handleSubmit() } }, 1000)在真实项目中前端倒计时只是表面控制真正的考试时间校验必须在后端做。前端绕过倒计时直接调接口后端也需要拒绝。这个思路在答辩时同样可以展开讲。7. 项目启动与效果验证7.1 后端启动使用 IDEA 打开后端项目等待 Maven 下载完成依赖后在项目根目录执行 Maven 命令启动mvn spring-boot:run或者直接运行主类ExamApplication.java的main方法。启动成功后控制台会看到类似输出Started ExamApplication in 4.3 seconds (JVM running for 4.9) Tomcat started on port(s): 8080 (http)此时说明后端服务已经启动成功。7.2 前端启动使用 IDEA 或 VS Code 打开前端项目在项目根目录执行npm install npm run dev启动成功后控制台会输出访问地址。如果端口是 5173直接访问http://localhost:5173。7.3 验证流程建议在写论文和使用演示时按以下流程验证一遍系统使用管理员账号登录创建班级和用户。创建教师账号并给教师邮箱增加课程和试题。使用教师账号登录创建一张包含单选题和判断题的考试设置好考试时间。使用学生账号登录查看考试列表进入考试页面答题。提交试卷查看成绩和答题明细。回到教师端查看成绩统计。这一套流程走通说明系统核心业务已经闭环也证明代码没有大的逻辑问题。在演示之前建议你准备一个小型题库至少包含 10 道单选题和 10 道判断题这样抽题时效果更明显。8. 常见报错与排查方法在线考试系统在开发过程中最容易遇到以下几类问题。问题现象可能原因排查方式解决方案后端启动失败端口被占用8080 端口已被其他进程占用执行netstat -ano查看端口占用修改application.yml的server.port前端请求接口报跨域错误后端 CORS 配置未生效查看浏览器 Console 和 Network在后端配置 CorsConfig或使用前端代理数据库中文乱码MySQL 连接 URL 未指定编码查看数据库库表字符集URL 后添加characterEncodingutf8MyBatis-Plus 查询结果为空表名或字段名跟实体类不对应打印 SQL 日志检查表结构添加TableName或TableId注解登录接口报 401JWT Token 无效或过期查看拦截器日志检查 Token 解析重新登录获取新 Token提交试卷时一直提示重复提交exam_record表中已有记录检查数据库记录和查询条件删除测试记录或用前端创建考试后再测试如果放大到整个 Java 开发环节最常见的两个坑是 JDK 和 Maven 环境变量配置问题。第一次运行项目时如果报java: 找不到符号或 Maven 依赖无法下载优先检查 JDK 版本是否匹配、Maven 镜像源是否配置。你可以在 Maven 安装根目录的conf/settings.xml中配置阿里云镜像加快依赖下载速度。8.1 版本兼容问题SpringBoot 版本高低会直接影响依赖的写法。搜索材料中也反复出现“springboot版本太高”“springboot版本太高 springboot框架介绍”这类问题。具体来说SpringBoot 2.x 使用 JDK 8MySQL 驱动 artifactId 是mysql-connector-java。SpringBoot 3.x 使用 JDK 17MySQL 驱动 artifactId 是com.mysql:mysql-connector-j且javax包名变为jakarta。Vue 2 使用 Vue CLI 启动Vue 3 使用 Vite 启动。如果你的代码在 SpringBoot 2 中能跑但换到 SpringBoot 3 启动失败优先检查依赖的 groupId、artifactId 和 javax/jakarta 包名。9. 毕业设计项目最佳实践9.1 代码层面的建议第一包结构一定要清晰。Controller 层不要出现 SQL 业务逻辑Service 层不要把大段重复代码直接复制。包结构干净的项目即使功能一般答辩老师的第一印象也会好很多。第二数据库表要有注释。建表语句中每个字段都要写COMMENT这样在数据库设计中能直接生成带说明的文档。写毕业设计说明书时直接引用表格结构就能省很多时间。第三统一接口返回格式。不要把成功时返回对象、失败时返回字符串这种前后端联调会很痛苦。第四核心业务必须加事务。创建考试、提交试卷这两个操作涉及多表写入务必使用Transactional注解。9.2 答辩时的讲解重点在线考试系统答辩时老师大概率会问以下几类问题提前准备会更有底气为什么选 SpringBoot 而不是 SSM 或 Servlet 手写前后端分离和单体项目的区别考试时间控制是怎么实现的如果学生修改前端时间戳能不能绕过Redis 缓存为什么不是必须的如果需要应对大规模在线考试哪些环节需要优化JWT 和 Session 的区别是什么Token 过期了怎么处理建议准备一张类似下面的表格放在毕业设计说明书中能很直观地表达你的设计思路角色主要功能核心表学生注册、查看考试、答题、查看成绩exam_record、exam_record_detail教师题库管理、组卷、发布考试、批阅主观题exam_question、exam_paper、exam_info管理员用户管理、数据统计、系统配置sys_user、exam_info9.3 生产环境部署建议毕业设计一般只要求在本地跑通但很多同学会在答辩前尝试部署到阿里云服务器。如果你有这一步注意三点一是 MySQL 必须修改默认 root 密码不要使用弱密码。生产环境和本地环境的安全基线不同。二是前端打包后放在 Nginx 中后端用java -jar启动前后端通过 Nginx 反向代理/api地址。部署前最好准备一份简明的部署文档答辩时老师问到部署你说不出来会很吃亏。三是部署前要在测试环境完整跑一遍流程尤其注意数据库初始化脚本和前端请求接口地址。搜索材料里也反复出现“阿里云部署前后端分离项目”的搜索词说明这是毕业设计的常见方向值得提前练习。9.4 善用现成资源但不要失去独立能力现在网上的毕业设计资源很丰富也许你手头已经有一份源码了。拿到源码后不要只改个项目名称就交差。建议你花时间做三件事一是把数据库表结构全部理清画出 ER 图。 二是把登录和提交试卷这两条核心链路写清楚最好能不看源码自己讲出来。 三是尝试改一个功能比如增加一种新题型、调整自动评分规则。做到这三件事答辩时你的表现会比大多数照搬项目的同学好很多。10. 总结基于 SpringBoot 的在线考试系统这个选题技术上覆盖了前后端分离、RESTful 接口设计、MySQL 数据建模、JWT 认证、MyBatis-Plus 数据访问、Vue 页面开发、跨域处理、事务管理等多方面的知识点。从毕业设计角度看它的业务复杂度适中不至于做不完也不会简单到没有内容写。这篇文章从环境准备、数据库设计、后端核心实现、前端接口联调、启动验证到常见排错和答辩建议基本覆盖了一个完整毕设项目的落地路径。如果你正在做这个项目建议先把核心表结构和提交试卷这条业务链路吃透再逐步完善题库、用户管理和图表统计等功能。代码跑通只是第一步能在答辩时讲清楚设计思路和关键细节才是真正有价值的产出。