
简介基于 Spring Boot 的考研知识题库微信小程序毕业设计源码面向计算机相关专业毕业生及小程序开发者完整覆盖学生、讲师、科目、视频课程、试题、交流论坛、测卷、考试记录、错题本、轮播图管理等核心模块解决从后端接口到前端交互的一体化实现问题。压缩包共 1306 个文件约 14.52MB主要包含 java 后端、vue 管理端、js 与 wxml/wxss 小程序端代码以及 143 个 vue 组件、103 个 json 配置等各类文件用途清晰便于定位和学习。资源内附安装、运行、构建脚本、数据库 SQL 文件与配置文件可快速搭建环境并跑通全流程。目前已有 270 人学习下载适合需要完整参考项目并做功能扩展的同学可重点研究自动评分、错题自动收集等业务设计。1. 考研知识题库小程序的定位——Spring Boot 底座加小程序前端到了大四后半学期“考研题库”在小程序设计题里几乎年年出现。选 Spring Boot 而不是 JSP 那套老技术是想把后端接口设计和数据库建模这些真正的工作量留下再配一个体验不掉队的小程序端。这样一套前后端分离结构覆盖了后端分层、数据库设计、小程序页面生命周期和微信登录鉴权四块硬技能答辩时也容易讲清楚模块边界。系统的核心不是薄薄的 CRUD 壳而是把「题库—练习—错题—复习」的闭环跑通。后文按 Spring Boot 后端的数据表设计、刷题链路、微信小程序端的页面落地顺序拆开讲末尾补部署和联调时容易踩的雷。看完可以直接把这套架构搬到公务员、教师资格证题库上而不是只换皮。2. Spring Boot 题库后端的模块拆分与数据表设计2.1 按业务域拆分包结构Controller 只做协议层这类毕业设计最常见的翻车姿势是把题目列表、提交答案、错题统计全部塞进一个 Controller启动没问题等联调时改一个字段要同时动三层代码。正确做法是按「用户、题库、练习、错题、首页配置」五个业务域拆包依赖方向统一指向 service不让 controller 直接操作 mapper 返回的实体对象。常用的包结构是下面这个形态直接套用即可com.knowledge.practice ├── config # 跨域、拦截器、MyBatis-Plus 配置 ├── controller # 参数校验 返回统一结果 ├── service # 业务逻辑、事务、幂等控制 ├── mapper # MyBatis 接口基础 CRUD 由 BaseMapper 提供 ├── entity # 数据表实体 ├── dto # 前端入参对象不直接透传 entity ├── vo # 返回给前端的视图对象 ├── common # 统一返回体、业务异常 └── utils # JWT、AES、日期工具依赖方向是 controller → service → mapperentity 只被 mapper 和 service 引用dto 和 vo 隔离在接口层。这样后续换数据库、加 Redis 缓存时controller 基本不动。service 接口里也不要暴露selectList这类裸方法否则写着写着又变回 Controller 直连数据库。2.1.1 登录态在拦截器统一校验业务代码不再判空小程序每个请求头会带Authorization与其在 service 方法里反复判断用户是否登录不如写一个AuthInterceptorpublic class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || !JwtUtil.verify(token)) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:1001,\msg\:\登录态失效\}); return false; } request.setAttribute(userId, JwtUtil.parseUserId(token)); return true; } }verify解析失败就返回 401 并附带业务错误码1001小程序端收到后统一跳转登录页。拦截器只校验 token 格式用户是否被禁用这类状态还是要查一次库不能只信 token。2.2 六张表的职责边界和建表字段参数说明考研题库单表数据量在百万题以内时不用考虑分库分表正常 InnoDB 加联合索引就足够。这里用六张表用户表、学科分类表、题目表、练习记录表、错题本表、首页配置表。有人会额外加收藏表建议先留着等练习记录跑起来再决定否则容易多出一张写代码用不上、答辩讲不清的死表。表名核心职责关键索引user微信用户、管理员账号uk_openidcategory考研学科分类英语、政治、数学、专业课parent_id sortquestion练习题主体含选项、答案、解析idx_category_statusexercise_record每次刷题的结果支撑正确率统计uk_client_nowrong_book错题本错题重做和复习进度uk_user_questionhome_config首页轮播图、公告、客服入口无题目表是最容易被改坏的一张表常见做法是把options拆成option_a/option_b/option_c/option_d。单个看着简单遇到多选题和判断题就不够用。把选项存成 JSON 数组单选题四个选项、判断题两个选项前端一次JSON.parse就能渲染出来。MySQL 8 下常用的建表语句长这样CREATE TABLE IF NOT EXISTS question ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 题目主键, category_id BIGINT NOT NULL COMMENT 所属学科分类ID, question_type TINYINT NOT NULL DEFAULT 1 COMMENT 1单选 2多选 3判断, content TEXT NOT NULL COMMENT 题干, options JSON DEFAULT NULL COMMENT 选项数组如[A,B,C,D], answer VARCHAR(500) NOT NULL COMMENT 正确答案多选题用逗号连接, analysis TEXT COMMENT 答案解析留空则前端隐藏解析区, difficulty TINYINT NOT NULL DEFAULT 2 COMMENT 1简单 2中等 3困难, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_status (category_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考研题目表;三个参数说明一下。options用 MySQL 8 的 JSON 类型切到低版本数据库时直接改成VARCHAR存 JSON 字符串对代码零影响answer用逗号连接多选答案前端提交前统一排序后端不必关心选项顺序difficulty只保留三档后续做智能出题时靠它分层抽样。错题本表给user_id question_id加联合唯一键累加错题次数用INSERT ... ON DUPLICATE KEY UPDATE一次请求完成新增或计数加一不用先查再改。练习记录表要存client_request_no并建唯一索引这是后面做幂等提交的关键。3. Spring Boot 刷题链路核心微信登录、抽题过滤、幂等提交3.1 微信登录的完整链路与配置落点小程序端wx.login()拿到的是临时code后端用 code 换 openid。整个链路需要 appid、secret、code 三个参数其中 secret 一旦泄露等同用户数据泄露必须放在 Spring Boot 的application.yml里绝不能出现在小程序代码包中。wechat: appid: wx1234567890abcdef secret: 只存在后端配置里 jscode2session-url: https://api.weixin.qq.com/sns/jscode2session微信接口只认 HTTPS 调用后端换 openid 用一个轻量 HTTP 客户端即可。拿到 openid 先查 user 表查不到就自动注册再把 userId 和角色塞进 JWT 返回。微信接口同时会返回session_key这个值不要落库日志里也不要打印 openid统一用数据库自增主键做用户标识。登录页不要急着弹昵称头像授权现在微信把头像昵称接口收紧后静默调用失败是常态先让用户进主界面再在个人中心诱导补资料体验会稳很多。后端统一错误码按下面这套设计小程序端只用判断code不需要把错误消息散落到各个页面。错误码场景小程序端应对0请求成功正常渲染数据1001登录态失效或 token 过期清理本地 token跳转登录页1002题目已下架当前题自动跳过1003提交记录重复直接返回上一次提交结果3.2 抽题的随机逻辑与答案过滤题库页按学科分类出 10 题或 20 题一组最直观的是ORDER BY RAND()。数据量到两三万条时这条 SQL 要对整表做排序接口延迟明显。换成先取主键再内存打散public ListQuestion pickRandomQuestions(Long categoryId, int limit) { ListLong allIds questionMapper.selectIdsByCategory(categoryId); if (allIds.size() limit) { return questionMapper.selectListInIds(allIds); } Collections.shuffle(allIds); return questionMapper.selectListInIds(allIds.subList(0, limit)); }selectIdsByCategory只查主键列内存 shuffle 的开销远小于数据库随机排序。selectListInIds底层用 foreach 拼IN查询一批 id 超过 1000 时要拆分否则 MySQL 执行计划可能退化接口延迟反而上去。查询出题目后在 VO 层把answer和analysis字段置空否则小程序端用抓包工具一看就能看到正确答案。3.3 提交答案的幂等控制弱网环境下wx.request超时重发很常见用户点一次交卷后端可能收到两次 POST。service 层对提交做幂等前端每次练习生成一个client_request_no值为 UUID后端把该字段当唯一键第一次请求落库第二次捕获唯一键冲突后直接返回第一次的结果。public SubmitResult submit(SubmitRequest req, Long userId) { ExerciseRecord exist recordMapper.selectByClientNo(req.getClientRequestNo(), userId); if (exist ! null) { return SubmitResult.success(exist); // 重复提交返回旧结果 } try { return SubmitResult.success(recordMapper.insert(req, userId)); } catch (DuplicateKeyException e) { return SubmitResult.success(recordMapper.selectByClientNo(req.getClientRequestNo(), userId)); } }先查再插在并发下会有窗口期但捕获DuplicateKeyException兜底后就能保证结果一致。这个机制同时也是支付回调重复通知、答题重复提交的标准解法答辩时被追问概率很高提前准备两句话解释清楚即可。4. 微信小程序前端落地全局配置、加载页与答题交互4.1 全局窗口配置与顶部导航栏高度适配原生微信小程序的窗口行为由app.json统一管理在window节点设置后所有页面默认继承。把导航栏背景色和标题先定成和主色一致的品牌色避免页面跳转时导航栏颜色闪变。{ pages: [ pages/index/index, pages/quiz/quiz, pages/result/result, pages/wrong/wrong ], window: { navigationBarBackgroundColor: #4A67FF, navigationBarTitleText: 考研题库, navigationBarTextStyle: white, backgroundColor: #F6F7FB } }这里有几个参数容易理解错。navigationBarTextStyle只有white和black两个值不是任意色值navigationBarTitleText会被页面自己的wx.setNavigationBarTitle覆盖不要在每个页面写死标题backgroundColor是窗口下拉露出区域的背景不是页面底色页面底色要在app.wxss里给page选择器统一设置。配置项可选值实际影响navigationBarBackgroundColor十六进制色值顶部导航栏背景色navigationBarTextStylewhite / black导航栏文字颜色只能二选一navigationStyledefault / custom是否启用自定义导航栏backgroundColor十六进制色值下拉露出的窗口底色如果要做渐变或自定义品牌色把navigationStyle设为custom页面顶部要自己预留安全距离。通用计算方式是胶囊按钮底部坐标减去状态栏高度得到 44 到 48 像素左右的导航区域iOS 和安卓结果略有差异真机必须各跑一遍。4.1.1 修改刚进入小程序的加载页先渲染本地缓存小程序默认首屏是pages/index/index最容易在这里翻车的是首页请求时间太长白屏几秒后弹错误。做一个缓存预填让启动速度不再依赖网络。onLoad() { const cached wx.getStorageSync(home_data); if (cached) { this.setData({ banner: cached.banner, categories: cached.categories }); } this.fetchHomeData(); }fetchHomeData成功后再wx.setStorageSync(home_data, data)下次冷启动先展示上一次的首页数据请求失败也不会整页空白。配合骨架屏组件把 banner 区域先占位视觉上就没有白屏到内容突然弹出的生硬感。4.2 分类切换与题目的单选多选交互首页题库分类常做成横向滚动标签用scroll-view时注意scroll-x开启横向滚动、white-space: nowrap让子项不换行、子项display: inline-flex。点击分类用>onSwitchCategory(e) { const id Number(e.currentTarget.dataset.id); if (id this.data.currentCategoryId) return; this.setData({ currentCategoryId: id, questionList: [], pageNo: 1 }); this.loadQuestions(id, 1); }同一分类重复点击不重新请求这是小程序 tab 切换常见的防抖处理。单选题用radio-group包label天然获得互斥逻辑多选题用checkbox-group提交前把选中数组sort()再join(,)顺序统一才能和后端答案比较radio-group bindchangeonChoiceChange label wx:for{{currentQuestion.options}} wx:key*this radio value{{item}} checked{{selectedValue item}} / text{{item}}/text /label /radio-group选中 B 和 A 统一存成A,B比对时就不会因为顺序不一致判错。如果是用 HBuilderX 的 uni-app 版本同套逻辑在manifest.json的mp-weixin节点里设置导航栏页面代码差异很小。4.3 API 封装与登录态过期跳转不要在页面里复制粘贴wx.request统一封装到utils/request.js方便切换开发和生产地址const BASE_URL https://api.example.com // 上线后换成已备案的 HTTPS 域名 const request (url, data, method POST) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${url}, data, method, header: { Authorization: wx.getStorageSync(token) || }, success: res { if (res.data.code 1001) { wx.reLaunch({ url: /pages/login/login }) } else { resolve(res.data) } }, fail: reject }) }) }接口调用全部走 Promise页面用await接收。1001统一跳登录页不必每个请求单独写错误分支。开发阶段用开发者工具勾选「不校验合法域名」可以绕开 HTTPS 限制上线前必须在公众平台把正式域名配进 request 合法域名否则真机上请求会被拦截。5. 微信小程序与 Spring Boot 联调部署自动建表、错误排查与演示数据5.1 项目启动时自动建表换机器不再手工导入 SQL毕业设计经常在实验室、宿舍、答辩教室几台电脑间切换最痛苦的是重建数据库再导脚本。常见做法是用ApplicationRunner在应用启动后检测核心表表不存在就执行schema.sqlComponent public class SchemaInitializer implements ApplicationRunner { Resource private JdbcTemplate jdbcTemplate; Override public void run(ApplicationArguments args) throws Exception { String sql SELECT COUNT(*) FROM information_schema.tables WHERE table_schema DATABASE() AND table_name question; Integer count jdbcTemplate.queryForObject(sql, Integer.class); if (count null || count 0) { ScriptUtils.executeSqlScript( Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection(), new ClassPathResource(db/schema.sql) ); log.info(question 表不存在schema.sql 已自动执行); } } }用information_schema而不是直接查question表是为了避免表不存在时直接把启动流程打断。schema.sql里的每条建表语句都加IF NOT EXISTS重复执行也安全。开发环境配utf8mb4字符集存储题目解析里的中文标点和表情符号不会出乱码。注意自动建表只适合开发和演示环境。生产环境建议用 Flyway 管理 schema 版本既有变更记录又能保证所有实例执行顺序一致。5.2 联调时的检查顺序与微信常见错误码真机联调报错时按三步排查省掉大量盲目调试时间微信开发者工具右上角「详情 - 本地设置」勾选「不校验合法域名、web-view、TLS 版本以及 HTTPS 证书」本地开发用 HTTP 后端必须开这个开关。后端日志出现errcode: 40163说明登录 code 已被使用检查页面是否在onShow里重复调了wx.login。临时凭证只能消费一次。小程序页面空白且接口正常优先看控制台的TypeError多数情况是把res.data.data直接 setData而真正的数据在res.data.result里。微信错误码含义处理方式40029code 无效前端重新调用 wx.login 获取新 code40163code 已被使用避免 onShow 重复登录45011接口调用频率限制降低请求频率或换服务端接口重试5.3 预置演示数据答辩现场不露怯题库空荡荡的演示现场最尴尬。用CommandLineRunner检测question表记录数为 0 时自动插入 50 道样题和两个测试账号Bean public CommandLineRunner demoDataInit(QuestionMapper questionMapper, UserMapper userMapper) { return args - { if (questionMapper.selectCount(null) 0) return; ListQuestion demoQuestions buildDemoQuestions(); questionMapper.insert(demoQuestions); User admin new User(); admin.setUsername(admin); admin.setPassword(new BCryptPasswordEncoder().encode(admin123)); userMapper.insert(admin); log.info(演示数据初始化完成题目 {} 道, demoQuestions.size()); }; }演示数据要选题干短、答案唯一、带完整解析的题目当场演示重点是前后端交互和接口响应速度不是让题达到真题难度。给demoDataInit加上Profile(dev)生产环境启动时不会自动执行自动建表和演示数据两条逻辑都只控制在开发环境里。测试账号密码用BCryptPasswordEncoder加密后再落库答辩被问用户密码为什么不存明文时这段代码就是现成的回答依据。本文还有配套的精品资源点击获取