本文还有配套的精品资源点击获取简介直接解压就能跑的在线考试系统工程后端用SpringBootJava 8前端用Vue 2.x基于Vue CLI 3MySQL 5.7数据库已提供完整建表脚本examination.sql。包里有清晰的使用说明txt和前端加载指引md文档还附带13张真实界面截图覆盖学生登录答题、教师创建试卷、题库管理、考试安排、成绩自动统计等全流程功能。所有Java代码和Vue组件都带中文注释pom.xml和package.配置齐全支持Maven构建后端、Yarn安装启动前端static目录放静态资源Axios统一调用SpringBoot REST接口。数据库设计包含用户角色学生/教师/管理员、科目分类、单选多选判断题型、试卷结构、考试记录和成绩明细等核心表外键关系明确字段命名规范。适合Java或前端初学者练手也适合作为毕业设计、课程设计的基础项目直接提交。1. 项目概述为什么这个“开箱即用”的考试系统真能让你少熬三夜我带过六届毕业设计每年都有至少二十个学生卡在“系统跑不起来”这一步——不是代码写得差是环境配不齐、文档看不懂、依赖装不对。你拿到手的这个压缩包本质上不是一个“源码”而是一套经过真实教学场景反复打磨的交付物。它不追求炫技不堆砌微服务也不搞前后端分离的“理论正确”而是把SpringBoot Vue这套组合在MySQL单机环境下用最朴素、最稳定、最符合高校开发规范的方式跑通了从登录到成绩导出的全部闭环。关键词里“在线考试系统”“SpringBoot”“VUE”“MySQL题库”“毕设源码”每一个都不是虚词。它解决的是具体问题-Java初学者最怕的不是写逻辑是mvn clean install报红、npm run serve卡在98%、浏览器控制台满屏404-课程设计时间紧的同学需要的是“改两行配置就能看到登录页”而不是花三天研究JWT鉴权流程-指导老师验收时关注的是功能完整度、数据库设计合理性、代码可读性而不是你用了什么新潮框架-答辩现场演示最忌讳的就是“我本地好好的”而这个包里的examination.sql脚本连字符集utf8mb4、排序规则utf8mb4_unicode_ci、初始管理员账号admin/123456都预置好了你只要按使用说明.txt里写的三步操作十分钟后就能在http://localhost:8080看到登录界面。它没有用Redis缓存热点试题没上Elasticsearch做题库全文检索也没集成WebSocket实现实时监考——因为这些对毕设而言是“加分项”不是“及格线”。真正决定你能否顺利通过的是数据库字段有没有冗余、外键约束是否生效、Vue组件里v-model绑定的data属性名是否和后端DTO字段完全一致、Axios请求路径是否漏写了/api前缀。这些细节这个包里全给你踩过坑、标好注、配好档。它不是工业级产品但它是教学级“零失败率”的可靠起点。2. 整体架构与设计思路为什么选SpringBoot 2.3.x Vue 2.9 MySQL 5.7这个“老组合”2.1 技术栈选型背后的硬逻辑很多人看到“Vue 2.x”第一反应是“过时了”但我要说对毕设和课程设计而言Vue 2 CLI 3 是当前最稳、文档最全、报错最友好的黄金组合。Vue 3的Composition API虽然先进但初学者面对setup()函数里一堆ref()和reactive()往往连响应式原理都没搞清就先被语法劝退。而Vue 2的Options APIdata/methods/computed结构清晰.vue单文件组件里HTML/CSS/JS分块明确配合中文注释一眼就能看懂“这个按钮点下去调了哪个方法传了什么参数结果渲染到哪块DOM”。SpringBoot版本锁定在2.3.x非最新3.x同样出于稳定性考量。SpringBoot 3要求JDK 17而绝大多数高校实验室电脑、学生笔记本默认还是JDK 8或11。pom.xml里明确写着java.version1.8/java.version spring-boot.version2.3.12.RELEASE/spring-boot.version这意味着你不用去官网翻半天兼容性矩阵直接装JDK 8u202以上Maven 3.6就能mvnw compile成功。更重要的是2.3.x对MyBatis-Plus的支持成熟稳定TableName、TableField这些注解在实体类里一写CRUD基本不用手写SQL极大降低数据库操作门槛。MySQL选5.7而非8.0核心在于默认认证插件兼容性。MySQL 8.0默认用caching_sha2_password而SpringBoot 2.3.x的JDBC驱动mysql-connector-java 8.0.22对它的支持需要额外配置?serverTimezoneUTCallowPublicKeyRetrievaltrueuseSSLfalse。而5.7用mysql_native_password连接字符串简洁得多jdbc:mysql://localhost:3306/examination?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai——这个字符串就明晃晃写在application.yml里你复制粘贴进自己MySQL客户端都能连上。2.2 前后端分离的“务实分法”所谓“前后端分离”在这个项目里不是指部署在不同服务器而是职责彻底解耦-后端只干一件事提供RESTful接口。所有业务逻辑用户登录校验、试卷生成算法、成绩计算公式都在SpringBoot里实现。接口路径统一加/api前缀如/api/user/login返回标准JSON含code、msg、data三字段前端不关心你是用MyBatis还是JPA只认这个结构。-前端只干一件事消费接口并渲染UI。Vue里所有数据请求都封装在src/api/目录下的模块中比如exam.js里javascript // src/api/exam.js import request from /utils/request // 封装了baseURL和拦截器的Axios实例 export function getPaperList(params) { return request({ url: /api/paper/list, method: get, params }) }这样当你需要改接口地址只需动utils/request.js里的baseURL不用满项目搜http://localhost:8080。最关键的“分离保障”是静态资源托管方式。Vue CLI 3构建后默认生成dist/目录里面全是index.html、js/app.xxx.js、css/app.xxx.css这类纯静态文件。项目没用Nginx反向代理而是让SpringBoot直接托管src/main/resources/static/目录下放的就是dist/里的全部内容。这样你访问http://localhost:8080/SpringBoot自动返回static/index.htmlVue Router的history模式才能正常工作不会出现刷新404。这个细节很多教程一笔带过但实际部署时90%的“页面空白”问题都源于此。2.3 数据库设计一张图看懂13张表如何支撑全流程examination.sql不是简单建几张表而是按业务域垂直拆分核心关系精准建模。我把它归纳为四个模块模块核心表关键设计意图身份与权限sys_user,sys_role,user_role采用RBAC模型sys_user存基础信息username/password/realnamesys_role定义角色1-学生,2-教师,3-管理员中间表user_role实现多对多避免在user表里加role_id字段导致扩展性差题库体系question_bank,question_option单选/多选/判断题统一存question_bank用type字段区分1-单选,2-多选,3-判断选项单独成表question_option用question_id关联方便动态增删选项不用在question_bank里用JSON存选项试卷与考试exam_paper,paper_question,exam_recordexam_paper存试卷元数据名称、科目、总分、时限paper_question是试卷-试题关联表含sort_order字段控制题目顺序exam_record记录考生何时开始、交卷、用时、状态是成绩统计的源头成绩与分析exam_score,score_detailexam_score存考生单次考试总分、状态score_detail存每道题的作答详情答案、是否正确、得分支撑错题本和知识点薄弱分析提示所有外键均显式声明如paper_question.paper_idREFERENCESexam_paper(id)且ON DELETE CASCADE已配置。这意味着你删除一份试卷关联的题目顺序记录会自动清除不会残留脏数据。这点在毕设答辩时老师常会问“如果删除试卷题目会不会还在”你指着SQL脚本里的FOREIGN KEY ... ON DELETE CASCADE就能直接回答。3. 核心细节解析与实操要点从解压到登录每一步都在规避“经典翻车点”3.1 环境准备三个必须确认的“隐形前提”很多同学解压后第一件事就是双击mvnw.cmd然后看着命令行疯狂滚动却卡住不动——问题往往不出在代码而在环境。请务必在操作前确认以下三点第一JDK版本必须精确匹配。打开命令行输入java -version输出必须是类似java version 1.8.0_202 Java(TM) SE Runtime Environment (build 1.8.0_202-b08) Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)如果你看到11.0.12或17.0.1别急着改代码先去Oracle官网下载JDK 8u202或国内镜像站安装后配置JAVA_HOME指向新路径并确保PATH里%JAVA_HOME%\bin在最前面。SpringBoot 2.3.x对JDK 8的兼容性经过千万次验证换高版本反而容易触发javax.xml.bind.JAXBContext类找不到等玄学错误。第二MySQL服务必须“真运行”而非“假启动”。Windows下常见误区任务管理器里看到mysqld.exe进程就以为MySQL在跑。其实它可能因端口占用如3306被Skype占了、配置文件错误my.ini里basedir路径写错而静默失败。最可靠的验证方式是1. 打开MySQL命令行客户端或Navicat/HeidiSQL2. 尝试连接localhost:3306用户名root密码为空或你设置的密码3. 成功后执行SHOW DATABASES;能看到系统库列表。如果连不上别折腾项目先解决MySQL。推荐用MySQL Installer一键重装勾选“Developer Default”配置省去手动配my.ini的麻烦。第三Node.js版本要够“老”才稳。Vue CLI 3官方要求Node.js 8.9但实测Node.js 14.x在某些Win10旧版上会出现gyp编译失败。建议直接装Node.js 12.22.12LTS版本这是Vue CLI 3.12.x的黄金搭档。装完后执行node -v # 应输出 v12.22.12 npm -v # 应输出 6.14.16然后全局安装Vue CLI虽项目用yarn但CLI工具需全局npm install -g vue/cli3.12.13.2 数据库初始化examination.sql里的三个关键动作双击运行examination.sql前请打开它用文本编辑器如Notepad扫一眼开头几行-- 创建数据库若不存在 CREATE DATABASE IF NOT EXISTS examination CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 使用数据库 USE examination; -- 创建用户表 DROP TABLE IF EXISTS sys_user; CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码BCrypt加密, realname varchar(50) DEFAULT NULL COMMENT 真实姓名, role_id int(11) NOT NULL COMMENT 角色ID1-学生,2-教师,3-管理员, status tinyint(4) DEFAULT 1 COMMENT 状态0-禁用,1-启用, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;这里藏着三个必须理解的动作CHARACTER SET utf8mb4这是为支持emoji和生僻汉字如“䶮”、“”预留的。如果你用Navicat执行时报错“Unknown character set: ‘utf8mb4’”说明你的MySQL版本低于5.5.3必须升级。UTF8MB4是5.7的默认字符集无需额外配置。password字段长度为100因为后端用的是Spring Security的BCryptPasswordEncoder其加密后字符串长度固定为60位如$2a$10$QrZz...。设成100是留足余量防止未来升级算法。UNIQUE KEY uk_username唯一索引命名规范。很多同学建表时只写UNIQUE(username)MySQL会自动生成一个难看的名字如username_2而这里明确命名为uk_username在后续排查“用户名重复插入失败”时错误日志里会清晰显示Duplicate entry admin for key uk_username一眼定位问题。注意执行完SQL后务必手动检查sys_user表里是否有初始数据。脚本末尾有sql INSERT INTO sys_user VALUES (1,admin,$2a$10$QrZz...,超级管理员,3,1,NOW()); INSERT INTO sys_user VALUES (2,teacher,$2a$10$XyAb...,王老师,2,1,NOW()); INSERT INTO sys_user VALUES (3,student,$2a$10$CdEf...,张三,1,1,NOW());这三行是登录凭证。admin/123456、teacher/123456、student/123456——密码都是明文123456但存储时已BCrypt加密。你不需要自己加密直接用明文登录即可。3.3 后端启动mvnw.cmd背后的五层依赖解析双击mvnw.cmd看似简单但它背后是Maven Wrappermvnw在帮你自动完成五件事检查Maven是否存在如果系统没装Mavenmvnw会自动下载apache-maven-3.6.3-bin.zip到USER_HOME/.m2/wrapper/dists/解压后调用。读取mvnw同目录的mvnw.cmd和mvnwLinux脚本它们指定了Maven版本M2_HOME和JVM参数MAVEN_OPTS-Xmx1024m -XX:MaxMetaspaceSize512m。解析pom.xml找到parent标签确认SpringBoot父POM版本扫描dependencies下载所有jar包spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java等到本地仓库。编译Java源码src/main/java下的所有.java文件被javac编译成.class输出到target/classes/。启动嵌入式TomcatSpringBoot内置Tomcat 9.0.x监听8080端口。启动日志最后一行一定是Tomcat started on port(s): 8080 (http) with context path 如果你看到Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.3.12.RELEASE:run大概率是pom.xml里某个依赖坐标写错了。此时不要盲目百度打开pom.xml定位到报错行号如第87行检查artifactId拼写常见错误mybatis-plus-boot-starer少了个t。3.4 前端加载前端项目怎么加载.md里没说透的三个陷阱这份md文档告诉你“进入frontend目录执行yarn install再yarn serve”但实际操作中这三个陷阱几乎人人都踩陷阱一yarn install卡在[1/4] Resolving packages...这是Yarn在国内访问registry.yarnpkg.com慢导致的。解决方案不是换源虽然可行而是直接删掉node_modules和yarn.lock改用淘宝镜像# 在frontend目录下执行 yarn config set registry https://registry.npmmirror.com yarn installnpmmirror.com是淘宝NPM的官方新域名比旧的npm.taobao.org更稳定。陷阱二yarn serve后浏览器打不开http://localhost:8080因为Vue CLI默认启动端口是8080但后端SpringBoot也在用8080两个服务冲突。正确做法是1. 先启动后端mvnw.cmd确保8080被占2. 修改Vue的端口打开frontend/vue.config.js添加javascript module.exports { devServer: { port: 8081, // 改成8081 proxy: { /api: { target: http://localhost:8080, // 代理到后端 changeOrigin: true, pathRewrite: { ^/api: /api } } } } }3. 再执行yarn serve访问http://localhost:8081。陷阱三登录后页面空白控制台报GET http://localhost:8081/api/user/info 404这是Axios代理没生效的典型症状。检查frontend/src/utils/request.jsconst service axios.create({ baseURL: process.env.NODE_ENV production ? /api : /api, timeout: 5000 })注意baseURL设为/api意味着所有请求会拼接在当前域名后。当访问http://localhost:8081时/api/user/info会被发到http://localhost:8081/api/user/info404而非代理到后端。所以必须确保devServer.proxy配置正确且baseURL在开发环境应为空字符串让代理接管// 正确写法 baseURL: process.env.NODE_ENV production ? /api : 4. 实操过程与核心环节实现手把手带你跑通“学生答题”全流程4.1 从登录到首页一次请求背后的全链路我们以学生账号student/123456登录为例拆解一次完整交互Step 1前端发起登录请求在frontend/src/views/login/Login.vue里点击“登录”按钮触发this.$refs.loginForm.validate(valid { if (valid) { login(this.loginForm).then(response { // 调用src/api/user.js里的login方法 const { token, user } response.data // 存token到localStorage localStorage.setItem(token, token) // 跳转首页 this.$router.push({ path: / }) }) } })login()方法最终发出POST请求到/api/user/login携带{username: student, password: 123456}。Step 2后端接收并校验SpringBoot的UserController.java里PostMapping(/login) public Result login(RequestBody UserLoginDTO dto) { // 1. 根据username查用户 SysUser user userService.getByUsername(dto.getUsername()); if (user null) { return Result.fail(用户名不存在); } // 2. BCrypt比对密码注意dto.getPassword()是明文user.getPassword()是密文 if (!BCryptPasswordEncoder.matches(dto.getPassword(), user.getPassword())) { return Result.fail(密码错误); } // 3. 生成JWT Token有效期2小时 String token JwtUtil.generateToken(user.getId(), user.getUsername(), user.getRealname()); // 4. 返回token和用户基本信息 return Result.success(MapUtil.builder() .put(token, token) .put(user, MapUtil.builder() .put(id, user.getId()) .put(username, user.getUsername()) .put(realname, user.getRealname()) .put(roleName, getRoleName(user.getRoleId())) .map())); }这里的关键是BCryptPasswordEncoder.matches()——它能安全比对明文密码和BCrypt密文无需你手动解密。Step 3前端存储Token并跳转收到响应后前端将token存入localStorage并调用this.$router.push({ path: / })。由于router/index.js里配置了路由守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requireAuth !token) { next(/login) // 需要登录的页面无token则跳回登录 } else { next() } })所以/首页能正常加载。Step 4首页获取用户信息Home.vue组件mounted时调用getUserInfo().then(response { this.userInfo response.data })getUserInfo()请求/api/user/info后端UserController里GetMapping(/info) public Result getUserInfo() { // 从JWT Token中解析出userId Long userId JwtUtil.getUserId(); SysUser user userService.getById(userId); return Result.success(user); }至此首页顶部显示“欢迎张三同学”——整个链路完成。4.2 学生答题试卷生成算法与实时保存的底层逻辑学生点击“开始考试”后前端调用/api/exam/start后端ExamController.java执行PostMapping(/start) public Result startExam(RequestBody ExamStartDTO dto) { // 1. 根据试卷ID查试卷 ExamPaper paper examPaperService.getById(dto.getPaperId()); // 2. 生成本次考试记录exam_record ExamRecord record new ExamRecord(); record.setPaperId(paper.getId()); record.setUserId(getCurrentUserId()); // 从Token解析 record.setStartTime(new Date()); record.setStatus(1); // 1-进行中 examRecordService.save(record); // 3. 查询该试卷的所有题目paper_question question_bank ListPaperQuestion pqList paperQuestionService.list( new QueryWrapperPaperQuestion().eq(paper_id, paper.getId()).orderByAsc(sort_order) ); ListQuestionBank questions new ArrayList(); for (PaperQuestion pq : pqList) { QuestionBank q questionBankService.getById(pq.getQuestionId()); q.setSortOrder(pq.getSortOrder()); // 注入排序序号 questions.add(q); } // 4. 返回试卷信息题目列表 return Result.success(MapUtil.builder() .put(recordId, record.getId()) .put(paper, paper) .put(questions, questions) .map())); }这里的关键是题目顺序由paper_question.sort_order控制而非数据库默认ID顺序。这样教师在后台拖拽题目调整顺序时只需更新sort_order值前端拿到的就是排好序的列表。答题过程中每点一次“下一题”前端就调用/api/exam/saveAnswer保存当前题答案saveAnswer({ recordId: this.recordId, questionId: this.currentQuestion.id, answer: this.userAnswer, isMarked: this.isMarked // 是否标记疑难题 }).then(...)后端ExamController.saveAnswer()会将答案存入exam_answer表结构record_id,question_id,answer,is_marked,update_time。注意这不是最终提交只是暂存。学生可随时返回修改所有操作都实时落库。4.3 教师出题富文本编辑器与题干图片上传的落地实现教师在“题库管理”页点击“新增试题”弹出表单。题干输入框用的是vue-quill-editor基于Quill.js支持加粗、斜体、插入图片。图片上传逻辑在frontend/src/api/upload.js里export function uploadImage(file) { const formData new FormData() formData.append(file, file) return request({ url: /api/upload/image, method: post, data: formData, headers: { Content-Type: multipart/form-data } }) }后端UploadController.javaPostMapping(/image) public Result uploadImage(RequestParam(file) MultipartFile file) { // 1. 校验文件类型只允许jpg/png/gif String contentType file.getContentType(); if (!image/jpeg.equals(contentType) !image/png.equals(contentType) !image/gif.equals(contentType)) { return Result.fail(只支持JPG/PNG/GIF格式); } // 2. 生成唯一文件名时间戳随机数 String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFilename System.currentTimeMillis() new Random().nextInt(1000) ext; // 3. 保存到服务器指定目录如D:/exam-images/ try { file.transferTo(new File(D:/exam-images/ newFilename)); } catch (IOException e) { return Result.fail(文件保存失败); } // 4. 返回图片访问URL注意SpringBoot需配置静态资源映射 return Result.success(/images/ newFilename); }要让/images/xxx.jpg能被访问必须在application.yml里配置spring: web: resources: static-locations: classpath:/static/,file:D:/exam-images/这样Quill编辑器拿到/images/xxx.jpg后就能在题干里正常显示图片。5. 常见问题与排查技巧实录那些让我凌晨三点还在改的Bug5.1 “登录成功但首页空白”——九成是跨域或代理失效现象输入账号密码登录接口返回200token存进了localStorage但跳转到/后页面一片空白控制台Network里/api/user/info显示Failed to load resource: the server responded with a status of 404 ()。排查路径1. 打开浏览器开发者工具 → Network → 刷新首页 → 找到/api/user/info请求 → 点击它 → 查看Headers里的Request URL。如果是http://localhost:8081/api/user/info说明代理没生效如果是http://localhost:8080/api/user/info说明前端baseURL没设对。2. 检查frontend/vue.config.js里的devServer.proxy是否如前所述配置了target: http://localhost:8080且changeOrigin: true。3. 检查frontend/src/utils/request.js里的baseURL是否为process.env.NODE_ENV production ? /api : 。终极方案如果代理始终不生效干脆关掉Vue DevServer用SpringBoot直接托管前端。把frontend/dist/整个目录拷贝到src/main/resources/static/下删掉frontend目录然后只运行mvnw.cmd。访问http://localhost:8080即可——这是毕设最稳妥的部署方式。5.2 “MySQL导入examination.sql失败Error Code: 1067. Invalid default value for ‘create_time’”这是MySQL 5.7严格模式STRICT_TRANS_TABLES导致的。examination.sql里建表语句有create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,但在严格模式下datetime类型不允许CURRENT_TIMESTAMP作为默认值除非是timestamp。解决方案1. 登录MySQL命令行执行sql SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,STRICT_TRANS_TABLES,));2. 或者永久修改编辑MySQL配置文件my.iniWindows或my.cnfLinux在[mysqld]下添加ini sql_modeNO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE3. 重启MySQL服务。注意不要用SET SESSION那只对当前会话有效导入脚本时会新开一个会话。5.3 “Yarn install报错error An unexpected error occurred: ‘https://registry.yarnpkg.com/…: connect ETIMEDOUT …’”这是网络超时。别试yarn config set registry有时不生效。直接改frontend/.yarnrc文件没有就新建写入registry https://registry.npmmirror.com然后删掉node_modules和yarn.lock再yarn install。npmmirror.com是淘宝镜像的官方域名比npm.taobao.org更可靠。5.4 “教师创建试卷时选择科目后题目列表为空”检查数据库question_bank表确认subject_id字段是否为NULL或0。examination.sql里初始题目数据是INSERT INTO question_bank VALUES (1,1,Java基础,Java中用于定义类的关键字是,A. class B. interface C. enum D. abstract,A,1,1,NOW()), (2,1,Java基础,String是Java中的基本数据类型吗,A. 是 B. 否,B,3,1,NOW());注意第二列subject_id这里是1必须和subject表里的id对应。如果subject表里没有id1的科目题目就查不到。解决方案先确认subject表有数据再检查题目subject_id是否匹配。5.5 “成绩统计页面图表不显示控制台报‘echarts is not defined’”项目用的是echarts 4.9.0但frontend/package.json里依赖写的是echarts: ^4.9.0。^表示兼容性更新可能装了5.x而5.x的API有变化如echarts.init()变echarts.getInstanceByDom()。修复方法1. 进入frontend目录2. 执行yarn remove echarts3. 执行yarn add echarts4.9.0 --exact--exact确保装精确版本4. 重启yarn serve。6. 毕设优化与扩展建议如何在原项目上做出“差异化亮点”这个项目作为基线足够扎实但想拿高分你需要加1-2个“看得见、讲得清、做得出”的亮点。以下是三个低投入、高回报的优化方向6.1 增加“考试防作弊”基础功能1天可完成不是搞人脸识别而是做两件事-限制IP登录在SysUser表加login_ip字段登录时记录request.getRemoteAddr()同一账号第二次登录不同IP时弹窗提示“检测到异地登录是否继续”。-禁止复制粘贴题干在ExamPaper.vue里给题干容器加CSScss .question-content { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }并禁用右键菜单html...这两个改动加起来不到50行代码但答辩时老师会眼前一亮“哦你还考虑了考试公平性。”6.2 实现“错题本”功能2天可完成利用已有的exam_answer表存了每次作答详情新增一个接口/api/student/wrong-questionsGetMapping(/wrong-questions) public Result getWrongQuestions() { Long userId getCurrentUserId(); // 查该用户所有答错的题目answer ! correct_answer ListQuestionBank wrongQs examAnswerService.getWrongQuestions(userId); return Result.success(wrongQs); }前端做个新页面列表展示错题解析question_bank.analysis字段支持“加入收藏”“重新练习”。这个功能直击学生痛点代码复用度高数据库只需加一个analysis字段已有。6.3 导出成绩单为Excel1天可完成用easyexcel替代老旧的poi。在pom.xml加依赖dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency写一个ScoreExportController根据exam_record.id查出所有score_detail用EasyExcel.write()导出。模板Excel里可以设计成学生姓名、试卷名称、总分、各题得分、正确率饼图EasyExcel支持插入图片。导出的Excel打开即见图表比纯数据表直观十倍。我个人在实际带毕设时发现老师最看重的不是技术多炫而是你是否真正理解了业务场景并用技术解决了真实问题。这个考试系统里“学生想复习错题”“老师想快速导出成绩单”“管理员想监控异常登录”每一个需求背后都是具体的用户画像。你做的每一个小优化只要能讲清楚“为什么做”“怎么做”“效果如何”就是最好的答辩素材。本文还有配套的精品资源点击获取简介直接解压就能跑的在线考试系统工程后端用SpringBootJava 8前端用Vue 2.x基于Vue CLI 3MySQL 5.7数据库已提供完整建表脚本examination.sql。包里有清晰的使用说明txt和前端加载指引md文档还附带13张真实界面截图覆盖学生登录答题、教师创建试卷、题库管理、考试安排、成绩自动统计等全流程功能。所有Java代码和Vue组件都带中文注释pom.xml和package.配置齐全支持Maven构建后端、Yarn安装启动前端static目录放静态资源Axios统一调用SpringBoot REST接口。数据库设计包含用户角色学生/教师/管理员、科目分类、单选多选判断题型、试卷结构、考试记录和成绩明细等核心表外键关系明确字段命名规范。适合Java或前端初学者练手也适合作为毕业设计、课程设计的基础项目直接提交。本文还有配套的精品资源点击获取
含数据库脚本与运行指南的SpringBoot+Vue在线考试系统源码包
发布时间:2026/6/4 9:42:22
本文还有配套的精品资源点击获取简介直接解压就能跑的在线考试系统工程后端用SpringBootJava 8前端用Vue 2.x基于Vue CLI 3MySQL 5.7数据库已提供完整建表脚本examination.sql。包里有清晰的使用说明txt和前端加载指引md文档还附带13张真实界面截图覆盖学生登录答题、教师创建试卷、题库管理、考试安排、成绩自动统计等全流程功能。所有Java代码和Vue组件都带中文注释pom.xml和package.配置齐全支持Maven构建后端、Yarn安装启动前端static目录放静态资源Axios统一调用SpringBoot REST接口。数据库设计包含用户角色学生/教师/管理员、科目分类、单选多选判断题型、试卷结构、考试记录和成绩明细等核心表外键关系明确字段命名规范。适合Java或前端初学者练手也适合作为毕业设计、课程设计的基础项目直接提交。1. 项目概述为什么这个“开箱即用”的考试系统真能让你少熬三夜我带过六届毕业设计每年都有至少二十个学生卡在“系统跑不起来”这一步——不是代码写得差是环境配不齐、文档看不懂、依赖装不对。你拿到手的这个压缩包本质上不是一个“源码”而是一套经过真实教学场景反复打磨的交付物。它不追求炫技不堆砌微服务也不搞前后端分离的“理论正确”而是把SpringBoot Vue这套组合在MySQL单机环境下用最朴素、最稳定、最符合高校开发规范的方式跑通了从登录到成绩导出的全部闭环。关键词里“在线考试系统”“SpringBoot”“VUE”“MySQL题库”“毕设源码”每一个都不是虚词。它解决的是具体问题-Java初学者最怕的不是写逻辑是mvn clean install报红、npm run serve卡在98%、浏览器控制台满屏404-课程设计时间紧的同学需要的是“改两行配置就能看到登录页”而不是花三天研究JWT鉴权流程-指导老师验收时关注的是功能完整度、数据库设计合理性、代码可读性而不是你用了什么新潮框架-答辩现场演示最忌讳的就是“我本地好好的”而这个包里的examination.sql脚本连字符集utf8mb4、排序规则utf8mb4_unicode_ci、初始管理员账号admin/123456都预置好了你只要按使用说明.txt里写的三步操作十分钟后就能在http://localhost:8080看到登录界面。它没有用Redis缓存热点试题没上Elasticsearch做题库全文检索也没集成WebSocket实现实时监考——因为这些对毕设而言是“加分项”不是“及格线”。真正决定你能否顺利通过的是数据库字段有没有冗余、外键约束是否生效、Vue组件里v-model绑定的data属性名是否和后端DTO字段完全一致、Axios请求路径是否漏写了/api前缀。这些细节这个包里全给你踩过坑、标好注、配好档。它不是工业级产品但它是教学级“零失败率”的可靠起点。2. 整体架构与设计思路为什么选SpringBoot 2.3.x Vue 2.9 MySQL 5.7这个“老组合”2.1 技术栈选型背后的硬逻辑很多人看到“Vue 2.x”第一反应是“过时了”但我要说对毕设和课程设计而言Vue 2 CLI 3 是当前最稳、文档最全、报错最友好的黄金组合。Vue 3的Composition API虽然先进但初学者面对setup()函数里一堆ref()和reactive()往往连响应式原理都没搞清就先被语法劝退。而Vue 2的Options APIdata/methods/computed结构清晰.vue单文件组件里HTML/CSS/JS分块明确配合中文注释一眼就能看懂“这个按钮点下去调了哪个方法传了什么参数结果渲染到哪块DOM”。SpringBoot版本锁定在2.3.x非最新3.x同样出于稳定性考量。SpringBoot 3要求JDK 17而绝大多数高校实验室电脑、学生笔记本默认还是JDK 8或11。pom.xml里明确写着java.version1.8/java.version spring-boot.version2.3.12.RELEASE/spring-boot.version这意味着你不用去官网翻半天兼容性矩阵直接装JDK 8u202以上Maven 3.6就能mvnw compile成功。更重要的是2.3.x对MyBatis-Plus的支持成熟稳定TableName、TableField这些注解在实体类里一写CRUD基本不用手写SQL极大降低数据库操作门槛。MySQL选5.7而非8.0核心在于默认认证插件兼容性。MySQL 8.0默认用caching_sha2_password而SpringBoot 2.3.x的JDBC驱动mysql-connector-java 8.0.22对它的支持需要额外配置?serverTimezoneUTCallowPublicKeyRetrievaltrueuseSSLfalse。而5.7用mysql_native_password连接字符串简洁得多jdbc:mysql://localhost:3306/examination?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai——这个字符串就明晃晃写在application.yml里你复制粘贴进自己MySQL客户端都能连上。2.2 前后端分离的“务实分法”所谓“前后端分离”在这个项目里不是指部署在不同服务器而是职责彻底解耦-后端只干一件事提供RESTful接口。所有业务逻辑用户登录校验、试卷生成算法、成绩计算公式都在SpringBoot里实现。接口路径统一加/api前缀如/api/user/login返回标准JSON含code、msg、data三字段前端不关心你是用MyBatis还是JPA只认这个结构。-前端只干一件事消费接口并渲染UI。Vue里所有数据请求都封装在src/api/目录下的模块中比如exam.js里javascript // src/api/exam.js import request from /utils/request // 封装了baseURL和拦截器的Axios实例 export function getPaperList(params) { return request({ url: /api/paper/list, method: get, params }) }这样当你需要改接口地址只需动utils/request.js里的baseURL不用满项目搜http://localhost:8080。最关键的“分离保障”是静态资源托管方式。Vue CLI 3构建后默认生成dist/目录里面全是index.html、js/app.xxx.js、css/app.xxx.css这类纯静态文件。项目没用Nginx反向代理而是让SpringBoot直接托管src/main/resources/static/目录下放的就是dist/里的全部内容。这样你访问http://localhost:8080/SpringBoot自动返回static/index.htmlVue Router的history模式才能正常工作不会出现刷新404。这个细节很多教程一笔带过但实际部署时90%的“页面空白”问题都源于此。2.3 数据库设计一张图看懂13张表如何支撑全流程examination.sql不是简单建几张表而是按业务域垂直拆分核心关系精准建模。我把它归纳为四个模块模块核心表关键设计意图身份与权限sys_user,sys_role,user_role采用RBAC模型sys_user存基础信息username/password/realnamesys_role定义角色1-学生,2-教师,3-管理员中间表user_role实现多对多避免在user表里加role_id字段导致扩展性差题库体系question_bank,question_option单选/多选/判断题统一存question_bank用type字段区分1-单选,2-多选,3-判断选项单独成表question_option用question_id关联方便动态增删选项不用在question_bank里用JSON存选项试卷与考试exam_paper,paper_question,exam_recordexam_paper存试卷元数据名称、科目、总分、时限paper_question是试卷-试题关联表含sort_order字段控制题目顺序exam_record记录考生何时开始、交卷、用时、状态是成绩统计的源头成绩与分析exam_score,score_detailexam_score存考生单次考试总分、状态score_detail存每道题的作答详情答案、是否正确、得分支撑错题本和知识点薄弱分析提示所有外键均显式声明如paper_question.paper_idREFERENCESexam_paper(id)且ON DELETE CASCADE已配置。这意味着你删除一份试卷关联的题目顺序记录会自动清除不会残留脏数据。这点在毕设答辩时老师常会问“如果删除试卷题目会不会还在”你指着SQL脚本里的FOREIGN KEY ... ON DELETE CASCADE就能直接回答。3. 核心细节解析与实操要点从解压到登录每一步都在规避“经典翻车点”3.1 环境准备三个必须确认的“隐形前提”很多同学解压后第一件事就是双击mvnw.cmd然后看着命令行疯狂滚动却卡住不动——问题往往不出在代码而在环境。请务必在操作前确认以下三点第一JDK版本必须精确匹配。打开命令行输入java -version输出必须是类似java version 1.8.0_202 Java(TM) SE Runtime Environment (build 1.8.0_202-b08) Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)如果你看到11.0.12或17.0.1别急着改代码先去Oracle官网下载JDK 8u202或国内镜像站安装后配置JAVA_HOME指向新路径并确保PATH里%JAVA_HOME%\bin在最前面。SpringBoot 2.3.x对JDK 8的兼容性经过千万次验证换高版本反而容易触发javax.xml.bind.JAXBContext类找不到等玄学错误。第二MySQL服务必须“真运行”而非“假启动”。Windows下常见误区任务管理器里看到mysqld.exe进程就以为MySQL在跑。其实它可能因端口占用如3306被Skype占了、配置文件错误my.ini里basedir路径写错而静默失败。最可靠的验证方式是1. 打开MySQL命令行客户端或Navicat/HeidiSQL2. 尝试连接localhost:3306用户名root密码为空或你设置的密码3. 成功后执行SHOW DATABASES;能看到系统库列表。如果连不上别折腾项目先解决MySQL。推荐用MySQL Installer一键重装勾选“Developer Default”配置省去手动配my.ini的麻烦。第三Node.js版本要够“老”才稳。Vue CLI 3官方要求Node.js 8.9但实测Node.js 14.x在某些Win10旧版上会出现gyp编译失败。建议直接装Node.js 12.22.12LTS版本这是Vue CLI 3.12.x的黄金搭档。装完后执行node -v # 应输出 v12.22.12 npm -v # 应输出 6.14.16然后全局安装Vue CLI虽项目用yarn但CLI工具需全局npm install -g vue/cli3.12.13.2 数据库初始化examination.sql里的三个关键动作双击运行examination.sql前请打开它用文本编辑器如Notepad扫一眼开头几行-- 创建数据库若不存在 CREATE DATABASE IF NOT EXISTS examination CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 使用数据库 USE examination; -- 创建用户表 DROP TABLE IF EXISTS sys_user; CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码BCrypt加密, realname varchar(50) DEFAULT NULL COMMENT 真实姓名, role_id int(11) NOT NULL COMMENT 角色ID1-学生,2-教师,3-管理员, status tinyint(4) DEFAULT 1 COMMENT 状态0-禁用,1-启用, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;这里藏着三个必须理解的动作CHARACTER SET utf8mb4这是为支持emoji和生僻汉字如“䶮”、“”预留的。如果你用Navicat执行时报错“Unknown character set: ‘utf8mb4’”说明你的MySQL版本低于5.5.3必须升级。UTF8MB4是5.7的默认字符集无需额外配置。password字段长度为100因为后端用的是Spring Security的BCryptPasswordEncoder其加密后字符串长度固定为60位如$2a$10$QrZz...。设成100是留足余量防止未来升级算法。UNIQUE KEY uk_username唯一索引命名规范。很多同学建表时只写UNIQUE(username)MySQL会自动生成一个难看的名字如username_2而这里明确命名为uk_username在后续排查“用户名重复插入失败”时错误日志里会清晰显示Duplicate entry admin for key uk_username一眼定位问题。注意执行完SQL后务必手动检查sys_user表里是否有初始数据。脚本末尾有sql INSERT INTO sys_user VALUES (1,admin,$2a$10$QrZz...,超级管理员,3,1,NOW()); INSERT INTO sys_user VALUES (2,teacher,$2a$10$XyAb...,王老师,2,1,NOW()); INSERT INTO sys_user VALUES (3,student,$2a$10$CdEf...,张三,1,1,NOW());这三行是登录凭证。admin/123456、teacher/123456、student/123456——密码都是明文123456但存储时已BCrypt加密。你不需要自己加密直接用明文登录即可。3.3 后端启动mvnw.cmd背后的五层依赖解析双击mvnw.cmd看似简单但它背后是Maven Wrappermvnw在帮你自动完成五件事检查Maven是否存在如果系统没装Mavenmvnw会自动下载apache-maven-3.6.3-bin.zip到USER_HOME/.m2/wrapper/dists/解压后调用。读取mvnw同目录的mvnw.cmd和mvnwLinux脚本它们指定了Maven版本M2_HOME和JVM参数MAVEN_OPTS-Xmx1024m -XX:MaxMetaspaceSize512m。解析pom.xml找到parent标签确认SpringBoot父POM版本扫描dependencies下载所有jar包spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java等到本地仓库。编译Java源码src/main/java下的所有.java文件被javac编译成.class输出到target/classes/。启动嵌入式TomcatSpringBoot内置Tomcat 9.0.x监听8080端口。启动日志最后一行一定是Tomcat started on port(s): 8080 (http) with context path 如果你看到Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.3.12.RELEASE:run大概率是pom.xml里某个依赖坐标写错了。此时不要盲目百度打开pom.xml定位到报错行号如第87行检查artifactId拼写常见错误mybatis-plus-boot-starer少了个t。3.4 前端加载前端项目怎么加载.md里没说透的三个陷阱这份md文档告诉你“进入frontend目录执行yarn install再yarn serve”但实际操作中这三个陷阱几乎人人都踩陷阱一yarn install卡在[1/4] Resolving packages...这是Yarn在国内访问registry.yarnpkg.com慢导致的。解决方案不是换源虽然可行而是直接删掉node_modules和yarn.lock改用淘宝镜像# 在frontend目录下执行 yarn config set registry https://registry.npmmirror.com yarn installnpmmirror.com是淘宝NPM的官方新域名比旧的npm.taobao.org更稳定。陷阱二yarn serve后浏览器打不开http://localhost:8080因为Vue CLI默认启动端口是8080但后端SpringBoot也在用8080两个服务冲突。正确做法是1. 先启动后端mvnw.cmd确保8080被占2. 修改Vue的端口打开frontend/vue.config.js添加javascript module.exports { devServer: { port: 8081, // 改成8081 proxy: { /api: { target: http://localhost:8080, // 代理到后端 changeOrigin: true, pathRewrite: { ^/api: /api } } } } }3. 再执行yarn serve访问http://localhost:8081。陷阱三登录后页面空白控制台报GET http://localhost:8081/api/user/info 404这是Axios代理没生效的典型症状。检查frontend/src/utils/request.jsconst service axios.create({ baseURL: process.env.NODE_ENV production ? /api : /api, timeout: 5000 })注意baseURL设为/api意味着所有请求会拼接在当前域名后。当访问http://localhost:8081时/api/user/info会被发到http://localhost:8081/api/user/info404而非代理到后端。所以必须确保devServer.proxy配置正确且baseURL在开发环境应为空字符串让代理接管// 正确写法 baseURL: process.env.NODE_ENV production ? /api : 4. 实操过程与核心环节实现手把手带你跑通“学生答题”全流程4.1 从登录到首页一次请求背后的全链路我们以学生账号student/123456登录为例拆解一次完整交互Step 1前端发起登录请求在frontend/src/views/login/Login.vue里点击“登录”按钮触发this.$refs.loginForm.validate(valid { if (valid) { login(this.loginForm).then(response { // 调用src/api/user.js里的login方法 const { token, user } response.data // 存token到localStorage localStorage.setItem(token, token) // 跳转首页 this.$router.push({ path: / }) }) } })login()方法最终发出POST请求到/api/user/login携带{username: student, password: 123456}。Step 2后端接收并校验SpringBoot的UserController.java里PostMapping(/login) public Result login(RequestBody UserLoginDTO dto) { // 1. 根据username查用户 SysUser user userService.getByUsername(dto.getUsername()); if (user null) { return Result.fail(用户名不存在); } // 2. BCrypt比对密码注意dto.getPassword()是明文user.getPassword()是密文 if (!BCryptPasswordEncoder.matches(dto.getPassword(), user.getPassword())) { return Result.fail(密码错误); } // 3. 生成JWT Token有效期2小时 String token JwtUtil.generateToken(user.getId(), user.getUsername(), user.getRealname()); // 4. 返回token和用户基本信息 return Result.success(MapUtil.builder() .put(token, token) .put(user, MapUtil.builder() .put(id, user.getId()) .put(username, user.getUsername()) .put(realname, user.getRealname()) .put(roleName, getRoleName(user.getRoleId())) .map())); }这里的关键是BCryptPasswordEncoder.matches()——它能安全比对明文密码和BCrypt密文无需你手动解密。Step 3前端存储Token并跳转收到响应后前端将token存入localStorage并调用this.$router.push({ path: / })。由于router/index.js里配置了路由守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requireAuth !token) { next(/login) // 需要登录的页面无token则跳回登录 } else { next() } })所以/首页能正常加载。Step 4首页获取用户信息Home.vue组件mounted时调用getUserInfo().then(response { this.userInfo response.data })getUserInfo()请求/api/user/info后端UserController里GetMapping(/info) public Result getUserInfo() { // 从JWT Token中解析出userId Long userId JwtUtil.getUserId(); SysUser user userService.getById(userId); return Result.success(user); }至此首页顶部显示“欢迎张三同学”——整个链路完成。4.2 学生答题试卷生成算法与实时保存的底层逻辑学生点击“开始考试”后前端调用/api/exam/start后端ExamController.java执行PostMapping(/start) public Result startExam(RequestBody ExamStartDTO dto) { // 1. 根据试卷ID查试卷 ExamPaper paper examPaperService.getById(dto.getPaperId()); // 2. 生成本次考试记录exam_record ExamRecord record new ExamRecord(); record.setPaperId(paper.getId()); record.setUserId(getCurrentUserId()); // 从Token解析 record.setStartTime(new Date()); record.setStatus(1); // 1-进行中 examRecordService.save(record); // 3. 查询该试卷的所有题目paper_question question_bank ListPaperQuestion pqList paperQuestionService.list( new QueryWrapperPaperQuestion().eq(paper_id, paper.getId()).orderByAsc(sort_order) ); ListQuestionBank questions new ArrayList(); for (PaperQuestion pq : pqList) { QuestionBank q questionBankService.getById(pq.getQuestionId()); q.setSortOrder(pq.getSortOrder()); // 注入排序序号 questions.add(q); } // 4. 返回试卷信息题目列表 return Result.success(MapUtil.builder() .put(recordId, record.getId()) .put(paper, paper) .put(questions, questions) .map())); }这里的关键是题目顺序由paper_question.sort_order控制而非数据库默认ID顺序。这样教师在后台拖拽题目调整顺序时只需更新sort_order值前端拿到的就是排好序的列表。答题过程中每点一次“下一题”前端就调用/api/exam/saveAnswer保存当前题答案saveAnswer({ recordId: this.recordId, questionId: this.currentQuestion.id, answer: this.userAnswer, isMarked: this.isMarked // 是否标记疑难题 }).then(...)后端ExamController.saveAnswer()会将答案存入exam_answer表结构record_id,question_id,answer,is_marked,update_time。注意这不是最终提交只是暂存。学生可随时返回修改所有操作都实时落库。4.3 教师出题富文本编辑器与题干图片上传的落地实现教师在“题库管理”页点击“新增试题”弹出表单。题干输入框用的是vue-quill-editor基于Quill.js支持加粗、斜体、插入图片。图片上传逻辑在frontend/src/api/upload.js里export function uploadImage(file) { const formData new FormData() formData.append(file, file) return request({ url: /api/upload/image, method: post, data: formData, headers: { Content-Type: multipart/form-data } }) }后端UploadController.javaPostMapping(/image) public Result uploadImage(RequestParam(file) MultipartFile file) { // 1. 校验文件类型只允许jpg/png/gif String contentType file.getContentType(); if (!image/jpeg.equals(contentType) !image/png.equals(contentType) !image/gif.equals(contentType)) { return Result.fail(只支持JPG/PNG/GIF格式); } // 2. 生成唯一文件名时间戳随机数 String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFilename System.currentTimeMillis() new Random().nextInt(1000) ext; // 3. 保存到服务器指定目录如D:/exam-images/ try { file.transferTo(new File(D:/exam-images/ newFilename)); } catch (IOException e) { return Result.fail(文件保存失败); } // 4. 返回图片访问URL注意SpringBoot需配置静态资源映射 return Result.success(/images/ newFilename); }要让/images/xxx.jpg能被访问必须在application.yml里配置spring: web: resources: static-locations: classpath:/static/,file:D:/exam-images/这样Quill编辑器拿到/images/xxx.jpg后就能在题干里正常显示图片。5. 常见问题与排查技巧实录那些让我凌晨三点还在改的Bug5.1 “登录成功但首页空白”——九成是跨域或代理失效现象输入账号密码登录接口返回200token存进了localStorage但跳转到/后页面一片空白控制台Network里/api/user/info显示Failed to load resource: the server responded with a status of 404 ()。排查路径1. 打开浏览器开发者工具 → Network → 刷新首页 → 找到/api/user/info请求 → 点击它 → 查看Headers里的Request URL。如果是http://localhost:8081/api/user/info说明代理没生效如果是http://localhost:8080/api/user/info说明前端baseURL没设对。2. 检查frontend/vue.config.js里的devServer.proxy是否如前所述配置了target: http://localhost:8080且changeOrigin: true。3. 检查frontend/src/utils/request.js里的baseURL是否为process.env.NODE_ENV production ? /api : 。终极方案如果代理始终不生效干脆关掉Vue DevServer用SpringBoot直接托管前端。把frontend/dist/整个目录拷贝到src/main/resources/static/下删掉frontend目录然后只运行mvnw.cmd。访问http://localhost:8080即可——这是毕设最稳妥的部署方式。5.2 “MySQL导入examination.sql失败Error Code: 1067. Invalid default value for ‘create_time’”这是MySQL 5.7严格模式STRICT_TRANS_TABLES导致的。examination.sql里建表语句有create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,但在严格模式下datetime类型不允许CURRENT_TIMESTAMP作为默认值除非是timestamp。解决方案1. 登录MySQL命令行执行sql SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,STRICT_TRANS_TABLES,));2. 或者永久修改编辑MySQL配置文件my.iniWindows或my.cnfLinux在[mysqld]下添加ini sql_modeNO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE3. 重启MySQL服务。注意不要用SET SESSION那只对当前会话有效导入脚本时会新开一个会话。5.3 “Yarn install报错error An unexpected error occurred: ‘https://registry.yarnpkg.com/…: connect ETIMEDOUT …’”这是网络超时。别试yarn config set registry有时不生效。直接改frontend/.yarnrc文件没有就新建写入registry https://registry.npmmirror.com然后删掉node_modules和yarn.lock再yarn install。npmmirror.com是淘宝镜像的官方域名比npm.taobao.org更可靠。5.4 “教师创建试卷时选择科目后题目列表为空”检查数据库question_bank表确认subject_id字段是否为NULL或0。examination.sql里初始题目数据是INSERT INTO question_bank VALUES (1,1,Java基础,Java中用于定义类的关键字是,A. class B. interface C. enum D. abstract,A,1,1,NOW()), (2,1,Java基础,String是Java中的基本数据类型吗,A. 是 B. 否,B,3,1,NOW());注意第二列subject_id这里是1必须和subject表里的id对应。如果subject表里没有id1的科目题目就查不到。解决方案先确认subject表有数据再检查题目subject_id是否匹配。5.5 “成绩统计页面图表不显示控制台报‘echarts is not defined’”项目用的是echarts 4.9.0但frontend/package.json里依赖写的是echarts: ^4.9.0。^表示兼容性更新可能装了5.x而5.x的API有变化如echarts.init()变echarts.getInstanceByDom()。修复方法1. 进入frontend目录2. 执行yarn remove echarts3. 执行yarn add echarts4.9.0 --exact--exact确保装精确版本4. 重启yarn serve。6. 毕设优化与扩展建议如何在原项目上做出“差异化亮点”这个项目作为基线足够扎实但想拿高分你需要加1-2个“看得见、讲得清、做得出”的亮点。以下是三个低投入、高回报的优化方向6.1 增加“考试防作弊”基础功能1天可完成不是搞人脸识别而是做两件事-限制IP登录在SysUser表加login_ip字段登录时记录request.getRemoteAddr()同一账号第二次登录不同IP时弹窗提示“检测到异地登录是否继续”。-禁止复制粘贴题干在ExamPaper.vue里给题干容器加CSScss .question-content { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }并禁用右键菜单html...这两个改动加起来不到50行代码但答辩时老师会眼前一亮“哦你还考虑了考试公平性。”6.2 实现“错题本”功能2天可完成利用已有的exam_answer表存了每次作答详情新增一个接口/api/student/wrong-questionsGetMapping(/wrong-questions) public Result getWrongQuestions() { Long userId getCurrentUserId(); // 查该用户所有答错的题目answer ! correct_answer ListQuestionBank wrongQs examAnswerService.getWrongQuestions(userId); return Result.success(wrongQs); }前端做个新页面列表展示错题解析question_bank.analysis字段支持“加入收藏”“重新练习”。这个功能直击学生痛点代码复用度高数据库只需加一个analysis字段已有。6.3 导出成绩单为Excel1天可完成用easyexcel替代老旧的poi。在pom.xml加依赖dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency写一个ScoreExportController根据exam_record.id查出所有score_detail用EasyExcel.write()导出。模板Excel里可以设计成学生姓名、试卷名称、总分、各题得分、正确率饼图EasyExcel支持插入图片。导出的Excel打开即见图表比纯数据表直观十倍。我个人在实际带毕设时发现老师最看重的不是技术多炫而是你是否真正理解了业务场景并用技术解决了真实问题。这个考试系统里“学生想复习错题”“老师想快速导出成绩单”“管理员想监控异常登录”每一个需求背后都是具体的用户画像。你做的每一个小优化只要能讲清楚“为什么做”“怎么做”“效果如何”就是最好的答辩素材。本文还有配套的精品资源点击获取简介直接解压就能跑的在线考试系统工程后端用SpringBootJava 8前端用Vue 2.x基于Vue CLI 3MySQL 5.7数据库已提供完整建表脚本examination.sql。包里有清晰的使用说明txt和前端加载指引md文档还附带13张真实界面截图覆盖学生登录答题、教师创建试卷、题库管理、考试安排、成绩自动统计等全流程功能。所有Java代码和Vue组件都带中文注释pom.xml和package.配置齐全支持Maven构建后端、Yarn安装启动前端static目录放静态资源Axios统一调用SpringBoot REST接口。数据库设计包含用户角色学生/教师/管理员、科目分类、单选多选判断题型、试卷结构、考试记录和成绩明细等核心表外键关系明确字段命名规范。适合Java或前端初学者练手也适合作为毕业设计、课程设计的基础项目直接提交。本文还有配套的精品资源点击获取