SpringBoot+uni-app仿驾考宝典毕设:前后端分离刷题系统项目拆解

发布时间:2026/10/30 17:21:33
SpringBoot+uni-app仿驾考宝典毕设:前后端分离刷题系统项目拆解 简介一套基于SpringBoot与uni-app开发的仿驾考宝典答题软件是完整的毕业设计项目源码面向计算机相关专业学生可用于课程设计、大作业或毕业设计参考。该项目为个人毕设作品评审分达九十五分代码经调试可运行。资源包共两千个文件体积约123MB包含Java后端代码、Vue前端组件、JavaScript脚本、样式表、配置文件、Markdown说明文档另有大量图片素材、数据库SQL脚本及项目说明便于快速搭建环境。后端采用SpringBoot、MyBatis、MySQL、Redis、JWT、POI、Thymeleaf等主流框架前端使用uni-app与colorui组件库适配H5和App后台管理账号为admin短信验证码可在套餐过期时输入“999999”完成验证腾讯云短信接口可在TxSend处配置。整体功能涵盖后台管理、题库管理、在线答题等核心模块已有八百余人学习下载适合需要掌握前后端分离开发、跨端适配和认证鉴权流程的读者基础较好者可继续扩展统计、社区等新功能。1. 仿驾考宝典的毕设源码SpringBootuni-app 这套项目到底拆开看什么很多人拿到这份基于SpringBootuni-app开发的仿驾考宝典答题软件源码时习惯先找一张程序运行截图看看效果但我更建议你先想清楚一件事这套代码最值钱的不是那几套驾考题库而是把“刷题”这个场景完整地做成了一个业务闭环。后端有用户、题库、答题记录、错题统计前端用 uni-app 包住了 App、H5、小程序三类形态这才是毕设项目真正需要的东西。它能解决的不只是科目一科目四刷题把题库一换它就是考研政治刷题、安全员考证刷题的底子。这篇笔记面向两类人准备拿它做毕设的同学以及想从零跑通一个前后端分离项目的老手。后面所有命令我都按实际调试习惯写你照着做就行。2. 先把项目跑起来数据库脚本导入与 SpringBoot 启动参数核对拿到源码包的第一步不是急着打开 IDEA而是先确认启动顺序。这套项目的结构是典型的前后端分离后端依赖数据库前端依赖后端接口。所以首次启动必须走“先导数据库再起后端最后跑前端”这条链路。顺序错了你会看到后端连不上数据库、前端调不通接口到处都是报错。下面我会把每一步的关键参数和容易忽略的细节都标出来。2.1 先拆压缩包后端、前端、数据库脚本各归其位把压缩包解压后先别急着点进去按工程形态把内容分成三类。后端通常是一个 Maven 工程根目录放pom.xml里面按controller/service/mapper/entity分层前端是 uni-app 工程特征是有pages.json、manifest.json、App.vue这几个文件数据库脚本一般单独放在sql或db目录也可能塞在后端的resources目录下叫schema.sql或data.sql。项目说明通常是 Word 或 PDF和源码放在一起。如果解压后没有找到单独的 SQL 文件去后端src/main/resources里找很多毕设项目会把建表语句放在这个位置。看清楚这些之后再确定 MySQL 版本和 JDK 版本。这套代码在 MySQL 5.7 和 8.0 下都能跑但 8.0 的驱动和连接参数写法有点区别后面会专门说到。前端的 uni-app 部分建议直接用 HBuilderX 打开这样能省去命令行脚手架的环境问题。2.2 导入数据库建库、设置字符集、一次导完所有表数据库脚本导入是第一个分水岭这里翻车的人最多。我一般不会用 Navicat 的“运行 SQL 文件”直接一把梭而是先用命令行把库建好再导数据这样报错能看得更清楚。先登录 MySQL执行下面的建库和导入命令CREATE DATABASE IF NOT EXISTS driving_exam DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE driving_exam; SET NAMES utf8mb4; SOURCE /your/path/to/driving_exam.sql;CREATE DATABASE IF NOT EXISTS确保重复执行不报错这个在毕设答辩现场很有用DEFAULT CHARACTER SET utf8mb4不是随便写的驾考题目里经常出现生僻字和特殊符号老式的utf8字符集会报 1366 错换成utf8mb4才能存得住。COLLATE utf8mb4_general_ci是大小写不敏感的排序规则和后面的 Java 字符串比较逻辑要区分开数据库排序规则只影响ORDER BY和唯一键判断不影响你代码里判卷。SET NAMES utf8mb4是让当前客户端连接的字符集和数据库一致导完以后可以执行下面这条命令验证mysql -u root -p -e USE driving_exam; SHOW TABLES;如果你看到十几张表说明脚本导入完整。常见的报错有两类一是 1044 权限错误说明当前 MySQL 用户没有建库权限换 root 或用授权后的账号二是 1064 语法错误多半是 SQL 文件里带了低版本 MySQL 的写法。这里提示一句需要修改表结构时不要直接改原 SQL 再重新导入那会把现有答题记录清空。正确做法是用ALTER TABLE增量改比如给题目表加难度字段ALTER TABLE t_question ADD COLUMN difficulty TINYINT DEFAULT 1 COMMENT 难度1-3;数据库增删改查只是基本功真正决定项目质量的是表结构设计这个下面专门讲。2.3 配置后端启动参数JDK、Maven、数据源先对齐后端启动前先对着pom.xml看一眼 SpringBoot 版本这决定了你本机要装哪个 JDK。这是一年能遇到十几次的血泪经验SpringBoot 2.7.x 用 JDK 8 或 11 最顺畅SpringBoot 3.x 必须用 JDK 17 或更高。很多人电脑上装了 JDK 21直接跑 2.7 的项目启动就报UnsupportedClassVersionError这就是 springboot 版本太高和本机 JDK 不匹配造成的。先看pom.xml里的 parent 片段parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent确认版本后再去 IDEA 里调整 Project Structure把 Project SDK 切到对应版本。接着打开application.yml核对数据源、端口和时区这三组参数server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/driving_exam?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml type-aliases-package: com.example.entityserver.port8080是后端服务端口前端所有接口请求都往这里打characterEncodingutf8配合 MySQL 的utf8mb4才能保证中文不乱码useSSLfalse是本地开发必须加的否则 MySQL 8.0 会默认尝试加密连接导致告警serverTimezoneAsia/Shanghai解决日期差 8 小时的问题。如果你用新版 IDEARun Configuration 里默认会读application.yml的端口但如果你在 VM options 里手动写了-Dserver.port8081那它会覆盖配置文件排错时先看这里。Maven 构建我用的是 maven 命令IDEA 里双击clean再package也一样mvn clean package -DskipTests java -jar target/your-artifactId.jar-DskipTests是跳过单元测试不是不编译测试代码如果用-Dmaven.test.skiptrue连测试代码编译都跳过速度更快但面试问起来你要能说清楚区别。启动成功后日志里会出现Tomcat started on port(s): 8080这时候后端才算真正立住了。2.4 用 HBuilderX 打开前端确认入口文件再连接口前端是 uni-app 工程标准做法是用 HBuilderX 导入。打开后先认识三个入口文件main.js负责创建应用实例App.vue是全局生命周期pages.json负责页面注册。uni-app 的入口文件和普通 Vue 项目不一样main.js里不能直接createApp(App)而是要导出一个createApp函数这是 uni-app 为跨端做的特殊处理import App from ./App import { createSSRApp } from vue export function createApp() { const app createSSRApp(App) return { app } }这段代码用createSSRApp是 uni-app 的固定写法改成createApp的话微信小程序端可能白屏。接着看pages.json新增页面必须在这里注册才能被编译出来{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 题库列表 } }, { path: pages/exam/exam, style: { navigationBarTitleText: 模拟考试 } }, { path: pages/wrong/wrong, style: { navigationBarTitleText: 错题本 } } ] }pages数组里的第一个元素就是启动页毕设项目通常会把题库列表作为首页。前端这边先不要急着点题目先用浏览器直接访问http://localhost:8080/api/xxx能返回 JSON 再继续。前后端分离开发时 H5 端跨域几乎必然发生后面第三章我会给出后端 CORS 配置这样前端就不用额外做跨域转发了。3. 后端答题模块这样拆题库、答题记录与自动判卷接口后端代码如果只做增删改查答辩老师问两句就露馅。这套项目真正值得讲清楚的是四个能力题库怎么存、答题记录怎么写、答案怎么判、错题怎么算。把它们串在一起才是完整的答题业务闭环。本章我会按表设计、选型、判卷逻辑、跨域处理和统一返回体四个部分来拆每一块都能直接对照你的源码改。3.1 先定表关系再写接口题库、答题记录和错题本怎么建表业务建模是后端的第一步。驾考刷题的核心表有四种用户表、题目表、答题记录表、错题表。我见过很多毕设项目把所有东西塞进一张大表后面统计错题率时 SQL 写得极其痛苦。合理的设计是拆开让每张表只干一件事CREATE TABLE t_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_question ( id BIGINT AUTO_INCREMENT PRIMARY KEY, category VARCHAR(20) NOT NULL COMMENT 科目一、科目四或章节编号, content TEXT NOT NULL, options_json TEXT NOT NULL COMMENT 选项 JSON如 [{key:A,label:...}], answer VARCHAR(10) NOT NULL COMMENT 单选 A/B/C/D多选 ABD判断题 T/F, analysis TEXT COMMENT 题目解析, status TINYINT DEFAULT 1, version INT DEFAULT 1 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_answer_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, question_id BIGINT NOT NULL, user_answer VARCHAR(10) NOT NULL, is_correct TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_question (user_id, question_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_wrong_book ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, question_id BIGINT NOT NULL, wrong_count INT DEFAULT 1, last_wrong_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_question (user_id, question_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;有几个字段设计值得说明。options_json用 TEXT 存 JSON而不是单独建一张选项表这降低了查询复杂度题目展示时前端一次拿到全部选项answer用VARCHAR(10)是为了兼容单选和多选多选答案用逗号分隔比如A,B,Dt_answer_record里建了复合索引idx_user_question因为按用户查答题记录是最频繁的查询t_wrong_book的UNIQUE KEY保证同一道题在错题本里只有一条记录错误次数靠wrong_count累加。数据库层我没有建外键约束这不是偷懒而是毕设项目在开发期经常要删题、改题外键会让这类操作被卡住一致性交给后端 Service 层控制。3.2 持久层选型MyBatis-Plus 和 Spring Data JPA 怎么选这套源码的持久层实现不同版本的项目可能不一样。如果你拿到的版本用的是 MyBatis-Plus那是国内毕设最常见的选型如果是 Spring Data JPA也不用慌张接口路径是一样的。两者的区别主要在这张表里对比维度MyBatis-PlusSpring Data JPA单表 CRUD 开发速度很快内置 BaseMapper很快内置 JpaRepository复杂 SQL 控制力强可写 XML 或注解较弱复杂查询要拼 Specification上手门槛中需要理解 Mapper 扫描低简单继承接口就能用答辩被追问难度容易被追问 SQL你必须会写容易被追问 Hibernate 原理如果你打算拿这套项目去答辩我建议无论源码里是什么持久层都先弄懂 MyBatis-Plus 的常见配置因为面试官问“分页怎么做、条件查询怎么写”的概率很高。SpringBoot 整合 MyBatis-Plus 非常成熟最常见的问题就是 Mapper 扫描没配好。在启动类上加MapperScan通常就解决了SpringBootApplication MapperScan(com.example.mapper) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }MapperScan后面的包路径必须和你的mapper包实际路径一致写错的话启动就报Consider defining a bean of type xxxMapper。还有一种做法是给每个 Mapper 接口加Mapper注解两者选其一即可不要叠加否则可能出现代理冲突。配置完pom.xml里的依赖后先跑一次mvn compile看 Mapper 接口能不能被正确扫描到再来写业务接口。3.3 判卷接口单选、多选和判断题的边界处理自动判卷是整个系统的核心亮点也是面试官最爱问的地方。很多人实现判卷时直接correctAnswer.equals(userAnswer)遇到多选题顺序不同就翻车。我一般会先看答案里有没有逗号有逗号就是多选转成 Set 再比较忽略顺序没有逗号就是单选或判断用忽略大小写的方式比较Transactional public AnswerResultVO submitAnswer(Long userId, SubmitAnswerDTO dto) { Question question questionMapper.selectById(dto.getQuestionId()); String correctAnswer question.getAnswer().trim(); String userAnswer dto.getUserAnswer().trim(); boolean correct; if (correctAnswer.contains(,)) { correct answerSet(correctAnswer).equals(answerSet(userAnswer)); } else { correct correctAnswer.equalsIgnoreCase(userAnswer); } AnswerRecord record new AnswerRecord(); record.setUserId(userId); record.setQuestionId(question.getId()); record.setUserAnswer(userAnswer); record.setIsCorrect(correct ? 1 : 0); answerRecordMapper.insert(record); if (!correct) { WrongBook wrong wrongBookMapper.selectByUserIdAndQuestionId(userId, question.getId()); if (wrong null) { wrong new WrongBook(); wrong.setUserId(userId); wrong.setQuestionId(question.getId()); wrong.setWrongCount(1); wrongBookMapper.insert(wrong); } else { wrong.setWrongCount(wrong.getWrongCount() 1); wrong.setLastWrongTime(new Date()); wrongBookMapper.updateById(wrong); } } else { wrongBookMapper.deleteByUserIdAndQuestionId(userId, question.getId()); } AnswerResultVO vo new AnswerResultVO(); vo.setCorrect(correct); vo.setCorrectAnswer(correctAnswer); vo.setAnalysis(question.getAnalysis()); return vo; } private SetString answerSet(String answer) { return Arrays.stream(answer.split(,)) .map(String::trim) .collect(Collectors.toSet()); }这里有两个容易被忽略的点。一是Transactional因为提交答案要同时写答题记录表和错题本事务保证这两步要么都成功要么都回滚避免出现答题记录写了但错题没更新的情况二是trim()用户从界面上传来的答案可能带空格不处理会导致明明选对了却判错。判断题的答案在表里统一存T或F不要存“对”“错”这种中文不然后端判卷还要做一层映射。数据库里的answer字段设计成逗号分隔就是为了让answerSet这个方法能同时处理多选和单选。3.4 跨域配置、统一返回体和异常处理别让接口调不动前端 H5 跑在 HBuilderX 的随机端口上后端跑在 8080跨域是逃不掉的。有些后端同学的思路是给前端开开发服务器转发但对于 uni-app 这种多端项目最省事的做法是在后端直接配跨域过滤器。注意一个细节allowCredentials(true)时不能再用allowedOrigins(*)否则浏览器会直接拒绝请求必须用allowedOriginPatterns(*)Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }addMapping(/api/**)表示只对/api路径下的接口生效避免把非业务接口暴露出去allowedMethods里必须加上OPTIONS因为浏览器跨域请求前会先发一个预检请求后端不认OPTIONS就会报 403maxAge(3600)是一小时内不用重复预检能减少很多无效请求。配合统一返回体前后端联调时错误信息一目了然public class RT { private Integer code; private String msg; private T data; public static T RT ok(T data) { RT r new R(); r.setCode(200); r.setMsg(success); r.setData(data); return r; } public static T RT fail(Integer code, String msg) { RT r new R(); r.setCode(code); r.setMsg(msg); return r; } }前端统一判断code是否为 200而不是依赖 HTTP 状态码。为什么因为跨域预检失败时 HTTP 状态码可能是 200但浏览器不让你读取响应统一返回体和 CORS 配置配合能够把后端真实错误透传给前端。异常处理建议加一个RestControllerAdvice把未捕获的异常转成R.fail(500, e.getMessage())不然前端拿到一堆堆栈信息体验很差。4. uni-app 刷题前端跨端复用、答题状态与本地缓存设计后端接口齐了前端的核心任务是把刷题体验做好。uni-app 的价值在于写一套 Vue 代码编译到 App、H5、微信小程序三个平台。但“一套代码到处跑”并不是说三个平台没有任何差异而是把差异尽量收敛到 API 层。我在这章里只挑四个最关键的点讲都是实际开发中一定会碰到的。4.1 请求封装与 uni-app 前端登录实现很多毕设项目的前端代码是直接在页面里到处写uni.request维护起来很痛苦。我一般会在utils/request.js里封装一个请求函数统一处理baseURL、token和code判断。uni-app 前端用户登录实现本质就是封装好这个请求后调后端的/api/login接口const BASE_URL http://localhost:8080 export function request(path, method GET, data {}) { return new Promise((resolve, reject) { uni.request({ url: ${BASE_URL}/api${path}, method: method, data: data, header: { Content-Type: application/json, token: uni.getStorageSync(token) || }, success: (res) { if (res.data.code 200) { resolve(res.data.data) } else { uni.showToast({ title: res.data.msg, icon: none }) reject(new Error(res.data.msg)) } }, fail: (err) reject(err) }) }) }BASE_URL是第一个坑。H5 端可以写localhost但微信小程序里localhost指向的是手机自己不是你电脑的后端所以小程序开发时必须改成电脑的局域网 IP。这个参数要单独抽出来根据编译平台切换。header里的token从本地缓存读后端过滤器拿到这个值校验登录状态如果返回 401前端要跳回登录页。登录接口的响应数据里如果有用户昵称、头像也要存到storage方便后面的个人中心直接读取。4.2 答题页用 vue3 ref 管理状态选项点击与下一题联动这套项目的前端如果是 Vue2 版本改成 Vue3 的 uni-app 项目时会用到组合式 API。答题页的几个状态非常适合用ref来管理当前题目索引、当前题用户选的答案、所有题的答案数组。用ref而不是普通对象的原因是模板渲染依赖响应式更新改普通对象不会触发页面重绘这在 uni-app vue3 里是常见的白屏元凶script setup import { ref, computed } from vue import { onLoad } from dcloudio/uni-app import { request } from /utils/request const questions ref([]) const currentIndex ref(0) const selectedAnswer ref() const answers ref([]) const currentQuestion computed(() questions.value[currentIndex.value]) const progress computed(() { if (!questions.value.length) return 0 return Math.round(((currentIndex.value 1) / questions.value.length) * 100) }) onLoad(() { loadQuestions() }) async function loadQuestions() { questions.value await request(/exam/questions, GET) } function choose(option) { if (selectedAnswer.value) return selectedAnswer.value option answers.value[currentIndex.value] option } function next() { if (currentIndex.value questions.value.length - 1) { currentIndex.value selectedAnswer.value answers.value[currentIndex.value] || } else { submitExam() } } async function submitExam() { const result await request(/exam/submit, POST, { answers: answers.value }) uni.showModal({ title: 得分, content: ${result.score} 分 }) } /script这里的currentQuestion用computed计算而不是在模板里写v-ifindex currentIndex加v-for原因很简单计算属性只会在currentIndex变化时重新求值性能更好模板也更干净。choose函数里加if (selectedAnswer.value) return是防止用户在一道题里反复点选项改了答案要做到选了就锁定下一题才解锁。answers数组用来保存所有题的已选答案这样用户回退到上一题时能通过answers.value[currentIndex.value]恢复选项高亮不会丢状态。4.3 本地缓存与错题收集用 storage 做后悔药刷题场景有一个特点用户可能在答到一半时切走应用或者小程序被系统回收。这时候如果不做本地缓存用户的答题进度全丢体验很差。uni-app 提供了uni.setStorageSync和uni.getStorageSync这对同步 API数据量不大时直接同步读写就行不用异步回调function saveAnswer(questionId, answer) { const answers uni.getStorageSync(exam_answers) || {} answers[questionId] answer uni.setStorageSync(exam_answers, answers) } function collectWrong(questionId, answer) { const wrongList uni.getStorageSync(wrongList) || [] const exist wrongList.find(item item.questionId questionId) if (!exist) { wrongList.push({ questionId, answer, count: 1 }) uni.setStorageSync(wrongList, wrongList) } }把错题存到本地而不是每次都请求后端一是减少接口压力二是用户离线也能看错题。但这也带来一个问题本地缓存和远端题库的一致性。如果后端改了题目标题或答案用户手机里存的仍然可能是旧数据。我一般会在后端加一个题库版本号字段前端启动时拉一次版本号发现和本地不一致就清掉旧的答题缓存和错题缓存重新拉题库。这个“后悔药”机制比直接清缓存更优雅既保留用户答题进度又不会让旧题目一直卡在本地。4.4 从 H5 到微信小程序一次编译多端运行在 HBuilderX 里写完整套代码后运行菜单里可以选“运行到浏览器”“运行到手机模拟器”“运行到微信开发者工具”。同样的代码编译目标不同产物也不同。微信小程序端的差异主要体现在三个方面一是没有window和document对象任何操作DOM的第三方库都不能直接用二是不能用localStorage必须用uni.getStorageSync三是网络请求要求域名必须配置到微信公众平台的合法域名列表里开发环境可以在微信开发者工具右上角“详情”里勾选“不校验合法域名”来跳过。做微信小程序开发 uni-app 项目时建议从一开始就把request封装好不要在页面里直接写uni.request不然每个页面都要处理code判断和环境差异。另外如果你要打包安卓 AppHBuilderX 里默认是云打包如果源项目依赖了原生插件需要准备本地离线打包的 Android SDK工作量会大不少。毕设场景一般用云打包就够了不用在本地搭那套环境。5. 常见问题排查与 4 个避坑记录环境、接口、缓存与数据一致性这套项目我帮人排查过不少次真正的问题集中在五块Mapper 扫描不到、H5 跨域失败、多选题判卷错误、题库缓存不刷新、JDK 与 SpringBoot 版本冲突。每一条我都按“现象、原因、解决”写清楚遇到对应情况直接照着处理。5.1 后端启动报 Mapper 扫描不到接口 500现象SpringBoot 启动时报Consider defining a bean of type com.xxx.QuestionMapper in your configuration.但QuestionMapper接口确实存在。原因分两种一是在启动类上没加MapperScan二是加了但包路径写得不对。解决方式是在启动类上补上扫描注解或者在每个 Mapper 接口上加Mapper。加了注解还是报错就去检查resources/mapper目录下的 XML 文件看application.yml里的mybatis-plus.mapper-locations是否指向正确mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml type-aliases-package: com.example.entityclasspath*:后面的路径要和 XML 文件实际位置匹配。如果项目里没有 XML 文件完全可以去掉mapper-locations配置用注解 SQL 也能跑。我的习惯是自定义一个简单的 Mapper 接口做测试注入后直接调用先确认扫描链路通不通再往上写 Service。5.2 H5 端跨域报错但小程序端正常现象同样的接口小程序里调用正常H5 浏览器控制台报CORS error有时还伴随Failed to fetch。原因是浏览器同源策略拦截了跨域响应后端没有在响应头里加Access-Control-Allow-Origin。解决办法是使用第三章里的CorsConfig注意allowCredentials(true)时必须配合allowedOriginPatterns(*)不能用allowedOrigins(*)。这类问题还要看后端是不是用了 Shiro 或 Spring Security如果用了安全框架CORS 过滤器必须在安全过滤器之前注册否则预检请求直接被拦截。H5 端开发期调接口后端这台电脑的防火墙也要放行 8080 端口不然局域网里的手机模拟器访问不到。有人问前端打包后能不能放进 springboot 的 static 目录一起部署可以但对 uni-app 多端项目不推荐小程序端仍然要单独部署 API 服务。5.3 多选题判卷错误ABD 和 DAB 明明一样却判错现象用户选择A,B,D提交到后端后显示答案错误但正确答案确实是A,B,D。原因是字符串equals比较完全按顺序匹配A,B,D和B,D,A在字符串层面不相等。解决方法是后端判卷时把答案拆成Set用Set.equals比较忽略顺序。注意一定要先trim()两端空格因为前端传过来的字符串可能带空字符特别是用户用键盘输入时。这道题的边界点在于单选题答案只有一个字母不需要拆Set直接equalsIgnoreCase更快判断题答案T和F同样用忽略大小写比较。这三类题型一定要分清楚不要一套逻辑通吃。5.4 题库更新后用户端还显示旧题目现象管理员在后台改了一道题的答案用户重启 App 后看到的还是旧答案。原因是前端把题目和答案缓存到了本地 storage没有做版本失效。解决方式是给题库加版本号字段后端提供/api/exam/version接口前端启动时对比本地存储的版本号不一致就清理exam_answers和wrongList再重新拉题库。版本号怎么变化呢我一般用数据库里的时间戳或者每次批量更新题库时手动加一不必用复杂的增量同步。这里要特别注意清理缓存要放在用户答题之前如果你在拉完题库之后再清理用户当前正在做的题就没了体验很差。5.5 JDK 版本太高SpringBoot 2.x 启动直接报 ClassVersionError现象启动时日志出现java.lang.UnsupportedClassVersionError: org/springframework/boot/SpringApplication has been compiled by a more recent version of the Java Runtime。原因就是你本机的 JDK 版本比项目编译版本低或者反过来JDK 版本太高与 SpringBoot 2.x 依赖不兼容。现在很多同学装的是 JDK 17 或 JDK 21拿起来就开跑 2.7 的老项目启动报错的概率非常高。解决方式先看pom.xml里的spring-boot.starter.parent.version2.7.x 用 JDK 8 或 113.x 用 JDK 17。在 IDEA 里打开 Project Structure把 Project SDK 和 Java Compiler 都切到对应版本再重启项目。这里补充一点IDEA 新版本的 Run Configuration 里可以单独给 Spring Boot 配置 JRE 路径不跟随全局 SDK多模块项目尤其容易踩先检查这里。6. 题库热替换与错题强化把毕设刷题系统做成答辩亮点基础功能做完后想让答辩老师眼前一亮我建议你在“题库维护”和“错题复习”这两个方向上做深一点。很多毕设的题库是写死在 SQL 脚本里的用户用起来没问题但你演示改题目时很尴尬总不能现场开数据库改。我在做这类系统时会加一个非常简单的版本表CREATE TABLE t_question_version ( id BIGINT AUTO_INCREMENT PRIMARY KEY, version_no INT NOT NULL DEFAULT 1, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;管理端每次更新题库只做两件事执行UPDATE t_question SET status0 WHERE id?下线旧题再插入新题最后INSERT INTO t_question_version把版本号加一。用户端启动刷题页面时先调/api/exam/version拿远端版本号和本地uni.getStorageSync(questionVersion)比较不一致才清理缓存拉新题。这套逻辑不依赖消息队列也不用定时任务但很好地解决了“题库热替换”的数据一致性问题。错题强化可以再进一步。普通错题本只是把错题列出来答辩时要能说出“为什么这么设计”。我在t_wrong_book里存了wrong_count和last_wrong_time生成强化卷时按错误次数倒序、最近错误时间倒序出十道题。这个排序规则在 SQL 里就是一句话SELECT question_id FROM t_wrong_book WHERE user_id #{userId} ORDER BY wrong_count DESC, last_wrong_time DESC LIMIT 10;每次都把错题本里错误最多的题拿出来配合章节分类就能组成一套“易错专项练习”。这个功能做起来不难但答辩时你能讲清楚“为什么按这两个字段排序”就已经比大多数只做增删改查的毕设高一截。我最初做类似项目时也以为把 CRUD 写完就万事大吉后来才意识到老师真正认可的是你把“刷题”这个场景里的边界都想清楚了。热替换解决了数据维护的痛点错题强化解决了用户刷题效率的痛点这些点不复杂但很加分希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询