Spring Boot + 微信小程序考研复习平台:毕业设计选题与完整实现指南 每年三四月总有人来找我问毕业设计该做什么题目。聊了一圈下来我发现今年最值得推荐的方向就是基于 Spring Boot 微信小程序的考研复习辅助平台。这个题目好在哪里它不是一个空壳管理系统而是真正围绕考研人怎么规划每天的学习去设计功能复习计划、每日任务、打卡统计、题库刷题、错题整理、学习数据报表全都对应真实使用场景。对做毕设的学生来说它的技术栈足够完整——后端用 Spring Boot 接 MySQL前端用微信小程序至少覆盖了 Java Web、数据库设计、移动端开发、接口联调这些环节论文能写的东西也多对答辩老师来说题目天然带实际需求比千篇一律的XX管理系统有说服力得多。这篇文章就按我实际做这一类项目的路子把题目拆解、数据库设计、后端接口、小程序端实现、文档交付和踩坑经验完整地走一遍写给正在准备毕设或者想快速搭一套同类系统的同学参考。1. 题目先想清楚这类辅助平台比管理系统好在哪1.1 别再做图书管理会议室预约了很多同学挑毕设题目的时候有个误区哪个框架熟就套哪个场景。于是每年能看到大量基于 XX 的图书管理系统基于 XX 的会议室预约系统功能翻来覆去就是增删改查答辩时自己都讲不出为什么需要这个系统。考研复习辅助平台不一样它的用户是考研学生需求非常明确距离考试还有多少天、每周各科怎么安排、今天该完成哪些任务、刷过的题错了哪些、连续打卡了多少天。这些功能每一个都能说清楚解决什么问题也就让毕设从技术演示变成了有实际价值的应用。我习惯把这个题目的功能拆成几个层次。底层是用户体系和基础数据微信登录、个人信息、科目管理。中间层是复习核心流程制定总体计划比如距离考试 N 天、按周/日拆分任务、完成任务打卡、记录学习时长。上层是提升体验的功能题库刷题与错题自动收录、手动添加错题、每日学习统计、连续打卡日历、订阅消息提醒。实际做的时候底层和中层是必须完成的上层按时间取舍。这样一个题目可深可浅基础版就是一个计划打卡工具进阶版可以做成带刷题和错题分析的学习闭环完全能撑起一篇合格的毕设论文。1.2 技术选型为什么是 Spring Boot 微信小程序先说后端。Spring Boot 几乎是当前 Java 毕业设计的事实标准理由很实际内嵌 Tomcat 让部署变得极其简单写好一个mvn package就能跑Spring MVC 的注解式路由清晰接口写起来顺手配合 MyBatis-Plus 做数据访问连单表 CRUD 的 SQL 都不用手写再加上社区资料多遇到问题搜索一下基本都能解决。同类的 SSM 框架不是不行但配置繁琐对毕设来说没必要。前端选微信小程序而不是纯网页后台关键原因是使用场景。考研学生用手机是常态小程序扫码即用、不用装 App而且微信生态自带的订阅消息正好用来发今日任务提醒。对毕设来说小程序还多了一层能展示的技术含量原生页面生命周期、组件通信、本地缓存、真机调试、域名配置这些都是论文里可以详细展开的素材。如果你之前只做过 Web 前端也不用慌小程序的知识体系接近 Vue 的语法风格数据绑定、列表渲染、事件绑定上手成本不算高。我用下面的表格给一个快速的技术栈清单后面所有章节都按这套来模块技术选型说明后端框架Spring Boot 2.7稳定版资料多数据访问MyBatis-Plus MySQL 5.7/8.0单表 CRUD 少写 SQL权限认证JWT 拦截器小程序端 header 传 token前端框架微信小程序原生避免额外编译链UI 组件Vant Weapp表单、日历、按钮直接用部署环境云服务器 Nginx后端接口走 HTTPS 域名这套组合还有一个隐形的优势每一层都能单独写论文章节。比如数据库设计可以写 E-R 图和表结构优化后端可以写 RESTful 接口设计和 JWT 拦截器实现小程序端可以写页面交互与缓存策略联调阶段可以写接口测试和部署环境搭建。论文根本不愁没内容。2. 数据库设计先想清楚用户每天打开小程序会做什么2.1 核心实体拆分与关系做数据库设计的时候我不建议直接打开 Navicat 建表而是先写用户故事。就拿这个平台来说用户的故事线是打开小程序登录 - 查看距离考研还有多少天 - 查看今天的复习任务 - 完成英语阅读任务并打卡 - 记一笔学习时长 - 刷了几道数学题错的进错题本 - 睡前看一眼今天的统计数据和连续打卡天数。跟着这条线走核心实体自然就出来了用户表useropenid、昵称、头像、目标院校/专业选填、考试日期科目表subject数学、英语、政治、专业课可配置计划表plan用户创建的复习计划时间段、总天数、每日学习时长目标任务表task从计划拆分出的每日任务关联科目、计划日期、完成状态学习记录表study_record每次开始学习/结束学习或手动打卡的时长记录题库表question_bank题目内容、选项、答案、解析、科目、难度错题本表wrong_question用户、题目、错误答案、记录时间这些表之间的关系不算复杂一个用户有多条计划和多个错题记录一个计划下面有多个任务一个任务归属于某个科目。用 MySQL 外键也可以但我通常只在逻辑上保留关联字段比如 task 表里存 subject_id、plan_id物理上不建外键约束这样删除和改数据的时候灵活接口性能也更好。答辩如果被问为什么不用外键可以回答外键约束在高并发插入和批量删除时会带来额外开销业务层面用代码保证数据一致性这是互联网应用常见的做法。2.2 几张关键表的字段设计与取舍用户表除了基本字段重点要考虑登录方式的兼容。小程序端用微信登录首次登录时拿 openid 建用户但答辩现场演示很可能没有小程序 appid 或者网络受限所以我通常再加一套手机号密码的账号登录后端用 JWT 签发 token。这样即使微信登录走不通演示也能正常继续。这个细节在答辩时是加分项。CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, openid varchar(64) DEFAULT NULL COMMENT 微信openid, phone varchar(20) DEFAULT NULL COMMENT 手机号, password varchar(128) DEFAULT NULL COMMENT BCrypt加密后的密码, nickname varchar(64) DEFAULT NULL, avatar varchar(255) DEFAULT NULL, exam_date date DEFAULT NULL COMMENT 考研日期, target_school varchar(128) DEFAULT NULL, daily_target_minutes int DEFAULT 180 COMMENT 每日学习目标(分钟), create_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;任务表是核心中的核心。我给它加的字段包括计划 ID、科目 ID、任务日期、任务内容、预计时长、完成状态0 未完成 1 已完成、完成时间。这里有个容易踩的坑不要把任务内容做成外键关联到另一个任务模板表那样查询要多一次 JOIN而且考研科目的每日任务本来就灵活——今天做英语阅读明天可能是背作文模板直接存文本字段最省事也方便前端直接渲染展示。CREATE TABLE task ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, plan_id bigint(20) DEFAULT NULL, subject_id bigint(20) NOT NULL, task_date date NOT NULL COMMENT 任务日期, content varchar(255) NOT NULL COMMENT 任务描述, expect_minutes int DEFAULT 0, actual_minutes int DEFAULT 0 COMMENT 实际花费分钟, status tinyint DEFAULT 0 COMMENT 0未完成 1已完成, finish_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_user_date (user_id, task_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;错题本表采用的是冗余题目快照方案除了 question_id我还把题目的题干、选项、正确答案、解析都冗余了一份。原因是错题本应该是一个独立、稳定的记录如果用户把某道题从题库删了或者题库表被重置错题本仍然要原样展示另外错题列表页经常要做分页和条件过滤冗余字段可以少 JOIN 一张大表查询快得多。3. Spring Boot 后端把复习逻辑做成清晰可用的接口3.1 接口风格与鉴权设计前后端分离之后后端最重要的是接口边界清晰。我习惯按资源来划分子模块auth负责登录和 tokenplan负责计划管理task负责每日任务record负责学习记录和统计question负责题库与错题。每个模块都遵循标准的 Controller-Service-Mapper 三层结构Controller 只做参数接收和简易校验业务逻辑全在 Service 层。这样答辩被问到某一处逻辑怎么实现的你直接说在 Service 层里逻辑是……就能展开讲清楚。鉴权这块我用的是最经典的 JWT 方案登录成功后后端生成一个 token里面带上 userId 和过期时间小程序端每次请求在 header 里带Authorization: Bearer xxx。后端用一个拦截器统一校验从请求头解析 token解析成功就把 userId 放入 ThreadLocal或者 request attributeController 层直接从上下文拿到当前用户。这样业务代码里不需要每个接口都重复一遍取 userId的逻辑也避免前端伪造身份。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String auth request.getHeader(Authorization); if (auth null || !auth.startsWith(Bearer )) { throw new BusinessException(401, 未登录); } String token auth.substring(7); Long userId JwtUtil.parseToken(token); if (userId null) { throw new BusinessException(401, 登录已过期); } request.setAttribute(currentUserId, userId); return true; } }一个小细节拦截器注册时要注意排除路径比如/api/auth/login、/api/auth/wx-login不能拦截否则用户根本没法登录。我经常看到有人把这个顺序写反导致登录接口 401排查半天才发现是拦截器配置问题。3.2 复习计划生成最简单的算法也能讲清楚根据考研倒计时自动生成每日任务是这个项目最值得讲的功能。很多同学一听到算法两个字就紧张其实这里的核心逻辑非常简单但答辩时非常出彩。我的实现思路是用户设置考试日期和每天目标学习时长比如 3 小时。后端计算从今天到考试前一天的总天数 N。按科目权重分配各科的每天时长。比如数学 40%、英语 30%、政治 20%、专业课 10%权重可以在计划设置里调整。每天生成若干条任务任务内容是科目默认描述如数学完成高数第三章习题也可以允许用户每天自己编辑。批量写入 task 表按 user_id task_date 建索引前端按日期查询。生成计划的核心方法大致是这样public Long createPlan(PlanCreateDTO dto, Long userId) { LocalDate start LocalDate.now(); LocalDate end dto.getExamDate(); long totalDays ChronoUnit.DAYS.between(start, end); if (totalDays 0) { throw new BusinessException(考试日期必须晚于今天); } // 保存计划主记录 Plan plan new Plan(); plan.setUserId(userId); plan.setStartDate(start); plan.setEndDate(end); plan.setDailyMinutes(dto.getDailyMinutes()); planService.save(plan); // 按天生成任务 for (int i 0; i totalDays; i) { LocalDate taskDate start.plusDays(i); for (SubjectWeight sw : dto.getWeights()) { Task task new Task(); task.setUserId(userId); task.setPlanId(plan.getId()); task.setSubjectId(sw.getSubjectId()); task.setTaskDate(taskDate); task.setContent(sw.getSubjectName() 今日复习任务); task.setExpectMinutes(sw.getWeight() * dto.getDailyMinutes() / 100); task.setStatus(0); taskService.save(task); } } return plan.getId(); }如果总天数太长产生几千条任务批量插入要改成saveBatchMyBatis-Plus 自带别一条条循环插入否则接口响应会肉眼可见地变慢。这个优化细节论文里也可以写属于数据量大时的性能优化。3.3 学习统计接口连续打卡与学习时长的计算统计模块是这类平台最容易出效果的地方。我做了两个接口一个返回用户某天/某段时间的总学习时长和任务完成数另一个返回连续打卡天数。连续打卡天数的计算有一个常见的坑不能只数 task 表里今天完成的任务数还要判断昨天是否打卡否则周日断了、周一又打卡连续天数会被错误延续。我的做法是先查出最近 30 天每天的打卡日期列表然后从今天往前推遇到断档就停止public int calcContinuousDays(Long userId, LocalDate today) { ListLocalDate activeDates studyRecordMapper.findDistinctDatesAfter(userId, today.minusDays(30)); SetLocalDate dateSet new HashSet(activeDates); int count 0; LocalDate cursor today; // 今天没打卡从昨天开始算 if (!dateSet.contains(cursor)) { cursor cursor.minusDays(1); } while (dateSet.contains(cursor)) { count; cursor cursor.minusDays(1); } return count; }这个算法不复杂但很能体现考虑边界情况的工程思维答辩时值得拿出来讲。4. 微信小程序端页面不多但这几个交互细节必须做好4.1 项目结构与请求封装小程序部分我建议直接用原生框架不要在这个阶段引入 uni-app 之类的高度封装方案原因很简单毕设更重要的是把逻辑讲清楚原生框架的页面结构、生命周期、wx.request一目了然遇到问题也好定位。如果对样式要求高组件库接一个 Vant Weapp 就够了。全局结构我通常这样组织miniprogram/ pages/ index/ # 首页倒计时今日任务 plan/ # 复习计划设置 task/ # 任务打卡 question/ # 题库/刷题 wrong/ # 错题本 stats/ # 统计图表 mine/ # 个人中心 utils/ request.js # 封装 wx.request自动带 token auth.js # 登录态管理 app.js app.jsonrequest.js是重中之重。它要做三件事自动在 header 里塞 token遇到 401 时清理本地登录态并跳转到登录页后端返回的统一结构{ code, message, data }在这里解包页面只管取data不用每个页面都重复判断 code。const request (url, method, data) { return new Promise((resolve, reject) { wx.request({ url: getApp().globalData.baseUrl url, method: method || GET, data: data || {}, header: { Content-Type: application/json, Authorization: Bearer wx.getStorageSync(token) }, success: (res) { if (res.data.code 0) { resolve(res.data.data); } else if (res.data.code 401) { wx.removeStorageSync(token); wx.reLaunch({ url: /pages/login/login }); reject(res.data); } else { wx.showToast({ title: res.data.message, icon: none }); reject(res.data); } }, fail: (err) reject(err) }); }); };4.2 首页倒计时与今日任务列表首页是用户打开小程序第一眼看到的东西我设计的信息优先级是考研倒计时最大字号- 今日完成 X/Y 个任务 - 连续打卡天数 - 今天的任务清单。倒计时直接在后端计算返回days字段前端拿到后渲染即可不要在端上自己算因为客户端时间可能不准。任务列表项上有一个完成打卡按钮点击后调用后端接口更新任务状态同时把实际学习分钟数一并提交。这里我建议加一个二次确认弹窗Vant 的 Dialog选本次学习多少分钟这样学习时长数据就不会靠用户脑补统计报表因此更真实。4.3 刷题与错题本页面再简单也要注意状态同步题库刷题页面本质上是一个按科目加载题目 - 单选/多选 - 提交答案 - 判对错 - 错题自动进错题本的流程。有两个容易忽略的同步问题。第一个是用户在刷题页面删除了一道错题返回错题本列表时要刷新否则列表里还残留我的做法是错题本页面在onShow里重新拉数据而不是只在onLoad里加载。第二个是已做过的题要有一个本地标记否则用户反复刷同一套题每次都像新题一样。用wx.setStorageSync(done_ questionId, true)就能解决简单且高效。5. 容易被忽略的交付环节文档、演示与远程调试5.1 论文文档怎么才能写满又写得真很多学生代码写完了发现论文憋不出来。这通常是因为开发的时候没有同步留素材。我在开发阶段就会随手做三件事一是把每个接口的请求/响应示例存到一个 Markdown 文件里后面写接口设计章节直接复制二是把每次遇到 Bug 的现象和解决办法记在 Git 提交说明里后面变成系统测试与问题分析章节的第一手资料三是数据库设计阶段画 E-R 图哪怕手画的都行后面整理成论文配图。一份合格的毕设文档至少要包含需求分析用例图、功能列表、非功能需求、系统设计总体架构图、功能模块图、数据库 E-R 图与表结构说明、接口设计、系统实现核心功能页面截图和关键代码说明、系统测试测试用例表、测试结果、问题修复记录。这些内容只要开发过程真实做过整理出来并不难。怕的是框架搭好了但没做细节论文自然空洞。5.2 远程调试环境从 localhost 到手机上看效果毕设验收和答辩最常见的要求是能在手机/真机上运行给我看看。这里必须提前解决两个问题后端接口部署在哪里小程序怎么访问到它。如果后端还在 localhost小程序真机调试完全连不上真机上没有 localhost 这个概念如果用云服务器就必须先把后端打成 jar 包部署到服务器上再用 Nginx 反代一下同时配上 HTTPS 证书。小程序 request 的合法域名要求 HTTPS证书用免费的就行没必要花钱。我习惯的部署路径是服务器安装 JDK 和 MySQL导入 SQL 脚本。后端配置application-prod.yml数据库连接、日志级别改成生产环境。mvn clean package打出 jar用nohup java -jar xxx.jar 启动或者用 systemd 管理。Nginx 配置 HTTPS 证书把/api/转发到本地的8080端口。小程序开发者工具里把baseUrl改成自己的域名打开不校验合法域名方便调试真机预览时正式校验域名。这个流程看起来长但做一次之后就是机械操作。如果实在没有服务器也可以用内网穿透工具把本地端口暴露成公网地址临时演示但我还是建议至少把后端部署到服务器上稳定性完全不同而且我部署到了云服务器本身也是一条答辩加分项。5.3 演示翻车点提前排练比开发更花时间远程调试环境搭好以后至少自己完整走三遍演示流程。我遇到的翻车情况大多是这些演示时微信扫码登录结果小程序是测试版后端返回的 openid 和上次缓存的用户对不上导致数据错乱演示现场网络慢题库分页接口首屏加载转圈超过 3 秒老师以为卡死了演示中途 token 过期接口 401 弹窗然后跳登录页观感非常差。这三个问题都可以提前规避测试版/体验版的用户数据单独建一套演示数据题库接口默认加载 10 条加缓存token 有效期设成 7 天答辩当天重新登录一次就行。6. 开发调试记录这几个坑我几乎每次都踩6.1 小程序合法域名与本地联调的冲突第一个坑发生在最开始的联调阶段。小程序开发者工具默认会校验 request 的域名必须是 HTTPS 且在小程序后台配过如果你后端跑在本地http://localhost:8080直接请求会报https://...不在合法域名列表。新手往往在这里卡很久。解决办法有两个本地调试时在开发者工具右上角把不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书勾上真机预览时再用正式域名。注意这只是调试设置真机正式版一定要有备案域名和证书。6.2 JWT 拦截器与跨域的叠加问题第二个坑是跨域。小程序端发请求不算严格意义上的浏览器跨域但如果你在答辩前临时加了一个 Web 管理后台比如用 Vue 写的同一个后端接口就得处理 CORS。我的建议是在后端写一个全局 CORS 过滤器放行所有来源和方法开发阶段省心上线前再收窄到自己的域名。还有一个更隐蔽的问题拦截器里抛出业务异常时如果异常没有被全局RestControllerAdvice捕获前端拿到的会是 Spring Boot 默认错误页而不是统一的 JSON 结构。所以记得配一个全局异常处理器把BusinessException转成{ code, message, data }前端request.js才能统一处理。6.3 跨午夜打卡与同日多段学习的数据边界第三个坑在学习时长统计。用户可能晚上 23:50 开始学习学到第二天 00:20这在业务上算昨天的学习还是今天的我的处理方式是让前端在打卡时把开始时间和结束时间一起传上来后端用开始时间归属日期结束时间只用来算时长。如果同一天有多条学习记录统计接口里按日期分组求和不要简单 count 记录条数。另外有一类边界情况是用户手动补卡我会在 study_record 表里加一个source字段1 自动计时 2 手动补卡统计连续打卡时允许手动补卡记录参与计算但统计专注时长时手动补卡不计入避免数据虚高。这类细节当时排查了很久但理顺之后统计精确性反而变成答辩中能讲的亮点。最后一个想说的小技巧是开发这类项目时一定会出现功能简单但细节繁杂的阶段——比如日历翻页、打卡状态回显、错题删除后的列表刷新它们都不难但每一个都考验你有没有耐心把状态管理想清楚。我自己的习惯是每完成一个页面就立刻在真机上把完整流程走一遍不要攒到最后一起测试。一个页面十分钟攒到最后可能变成三个小时。这个习惯比任何技术方案都更能保证毕设按时交付。