简介:这是一套面向高校计算机相关专业学生的毕业设计/课程设计完整项目包,主题为考研知识题库微信小程序,采用微信小程序前端与Java后端分离架构,适合正在准备毕业设计、需要项目实战经验或想学习小程序全栈开发的同学参考。压缩包共1243个文件,约14.57MB,涵盖227个png图片资源、192个js脚本、142个vue组件、128个java后端源码、92个wxml与88个wxss小程序页面样式,以及json配置、sql数据库脚本、bat启动脚本和说明文档等,前后端代码、数据库与部署说明一应俱全。目前已有216人学习下载。项目围绕考研题库的浏览、练习与管理场景展开,包含小程序端页面、后台管理界面与Java服务端接口,目录结构清晰,便于按模块阅读与二次开发,可作为毕业设计选题落地的参考方案,也能帮助读者理解小程序与Java后端联调的完整流程。
1. 考研题库小程序:从选题到跑通,一套能交差的毕业设计长什么样
每年到了毕设选题季,「基于微信小程序+Java后端的考研知识题库」这类题目出现频率极高。原因很直接:业务场景清晰(刷题、错题本、收藏、模拟考),技术栈主流(微信小程序 + Spring Boot + MySQL),工作量可控,答辩时老师也挑不出大毛病。但真正动手的人会发现,坑不在业务逻辑,而在环境搭建、前后端联调、数据库设计和答辩文档这四件事上。我带过几届学生的毕设,也自己接过类似的外包,最常见的翻车场景是:小程序端页面写完了,后端接口跑不通;或者接口通了,但登录态、分页、错题记录这些细节一塌糊涂。这篇笔记就按「一个能跑通、能演示、能写进论文」的标准,把整套方案拆开讲清楚,适合正在做计算机毕业设计、想选一个稳妥题目的同学,也适合想快速复现一套前后端分离项目实战的开发者。
2. 技术选型与整体架构:为什么这套组合最适合毕业设计
2.1 小程序端选原生还是 uniapp
这是第一个要做的决定。原生微信小程序开发工具上手快,API 调用直接,调试方便,但代码只能跑在微信里。uniapp 可以一套代码多端发布,但打包到微信小程序时偶尔会遇到样式兼容问题,尤其是顶部导航栏高度、单选框样式这些细节。对于毕业设计来说,我一般建议用原生,原因是:答辩老师不会因为你用了 uniapp 就加分,但会因为小程序跑不起来而扣分。原生方案在微信开发者工具里的报错信息更直观,社区资料也更多。
如果你确实想用 uniapp,注意两个点:一是顶部导航栏高度要用uni.getSystemInfoSync().statusBarHeight动态获取,不要写死;二是打包时在 manifest.json 里正确配置 appid,否则真机预览会失败。
2.2 后端为什么选 Spring Boot + MyBatis
Java 后端做毕业设计,Spring Boot 几乎是默认选项。它把 Tomcat 内嵌了,一个 main 方法就能启动,不需要额外配 web 服务器。持久层用 MyBatis 而不是 JPA,是因为题库类项目的查询条件比较灵活——按科目筛、按年份筛、按题型筛、按关键词搜,MyBatis 的 XML 写动态 SQL 更顺手。数据库用 MySQL 8.0,字符集选 utf8mb4,避免题目里出现特殊符号时乱码。
整体架构就是经典的前后端分离:小程序通过wx.request调用后端 REST 接口,后端返回 JSON,小程序渲染页面。登录态用 JWT 或者简单的 token 表来维护,不建议用 session,因为小程序没有 cookie 机制,session 维护起来很别扭。
2.3 数据库表设计的最小闭环
题库类项目的表不用多,但几张核心表必须设计清楚。下面是我常用的最小表结构:
| 表名 | 作用 | 关键字段 |
|---|---|---|
| user | 用户信息 | id, openid, nickname, avatar, create_time |
| subject | 科目分类 | id, name, sort_order |
| question | 题目主表 | id, subject_id, type, stem, options, answer, analysis |
| wrong_book | 错题本 | id, user_id, question_id, wrong_count, last_wrong_time |
| favorite | 收藏 | id, user_id, question_id, create_time |
| exam_record | 模拟考记录 | id, user_id, score, total, create_time |
题目表里的options字段用 JSON 字符串存选项,比如[{"key":"A","value":"选项内容"}],这样前端解析方便,也避免了多建一张选项表的麻烦。type字段区分单选、多选、判断,用 1/2/3 表示。
3. 后端接口开发:从登录到题库查询的完整实现
3.1 微信登录接口与 token 发放
小程序登录的流程是:前端调用wx.login()拿到 code,传给后端,后端用 code + appid + secret 调用微信接口换 openid,然后生成 token 返回给前端。下面是核心代码:
@PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { // 1. 用 code 换取 openid String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + appId + "&secret=" + appSecret + "&js_code=" + loginDTO.getCode() + "&grant_type=authorization_code"; String response = restTemplate.getForObject(url, String.class); JSONObject json = JSON.parseObject(response); String openid = json.getString("openid"); // 2. 查库,没有就注册 User user = userService.getByOpenid(openid); if (user == null) { user = new User(); user.setOpenid(openid); user.setNickname("用户" + System.currentTimeMillis() % 10000); userService.save(user); } // 3. 生成 token 返回 String token = JwtUtil.generateToken(user.getId()); return Result.success(new LoginVO(token, user)); }这段代码的逻辑说明:第一步用 RestTemplate 调微信接口,返回的是 JSON 字符串,用 FastJSON 解析出 openid。第二步查库,如果用户不存在就自动注册,昵称给一个默认值。第三步用 JWT 工具类生成 token,把 userId 塞进 payload。参数方面,appId 和 appSecret 放在配置文件里,不要硬编码在代码中。token 的有效期一般设 7 天,过期后前端重新走登录流程。
注意:微信接口返回的 session_key 不要返回给前端,它只在后端用来解密用户信息,泄露出去有安全风险。
3.2 题库分页查询与多条件筛选
题库列表是使用频率最高的接口,必须支持分页和多条件筛选。前端传page、size、subjectId、type、keyword这几个参数,后端用 MyBatis 动态 SQL 拼查询条件:
<select id="selectByCondition" resultType="Question"> SELECT * FROM question <where> <if test="subjectId != null"> AND subject_id = #{subjectId} </if> <if test="type != null"> AND type = #{type} </if> <if test="keyword != null and keyword != ''"> AND stem LIKE CONCAT('%', #{keyword}, '%') </if> </where> ORDER BY id DESC LIMIT #{offset}, #{size} </select>逻辑说明:<where>标签会自动处理第一个 AND,避免 SQL 语法错误。offset由(page - 1) * size计算得出。参数方面,size建议限制在 20 以内,太大影响响应速度。关键词搜索用LIKE模糊匹配,数据量超过几万条时需要考虑加索引或者用全文检索,但毕业设计的数据量通常几百到几千条,直接 LIKE 完全够用。
3.3 错题本与收藏的写入逻辑
错题本的逻辑是:用户答错一题,就往wrong_book表插一条记录;如果已经存在,就更新wrong_count和last_wrong_time。这里用INSERT ... ON DUPLICATE KEY UPDATE最简洁:
INSERT INTO wrong_book (user_id, question_id, wrong_count, last_wrong_time) VALUES (#{userId}, #{questionId}, 1, NOW()) ON DUPLICATE KEY UPDATE wrong_count = wrong_count + 1, last_wrong_time = NOW();前提是user_id和question_id要建联合唯一索引,否则 ON DUPLICATE KEY 不会生效。收藏逻辑类似,只是不需要计数,直接插入,重复插入时忽略即可,用INSERT IGNORE。
4. 小程序端页面开发:列表加载、答题交互与状态管理
4.1 题库列表的上拉加载与下拉刷新
微信小程序的列表加载更多,核心是onReachBottom和onPullDownRefresh两个生命周期函数。下面是一个典型的实现:
Page({ data: { questionList: [], page: 1, size: 10, hasMore: true, loading: false }, onLoad() { this.loadQuestions(); }, onReachBottom() { if (this.data.hasMore && !this.data.loading) { this.setData({ page: this.data.page + 1 }); this.loadQuestions(); } }, onPullDownRefresh() { this.setData({ page: 1, questionList: [], hasMore: true }); this.loadQuestions(() => { wx.stopPullDownRefresh(); }); }, loadQuestions(callback) { this.setData({ loading: true }); wx.request({ url: `${baseUrl}/question/list`, data: { page: this.data.page, size: this.data.size, subjectId: this.data.subjectId }, success: (res) => { const list = res.data.data.list; this.setData({ questionList: this.data.page === 1 ? list : this.data.questionList.concat(list), hasMore: list.length === this.data.size, loading: false }); callback && callback(); }, fail: () => { this.setData({ loading: false }); wx.showToast({ title: '加载失败', icon: 'none' }); } }); } });逻辑说明:onReachBottom触发时先判断hasMore和loading,避免重复请求。onPullDownRefresh重置页码和列表,请求完成后调用wx.stopPullDownRefresh()停止刷新动画。loadQuestions里根据当前页码决定是替换还是追加列表。参数方面,size设 10 比较合适,太小请求频繁,太大首屏加载慢。
4.2 答题页的选项状态与提交逻辑
答题页要处理单选和多选两种模式。单选用radio-group,多选用checkbox-group。核心是把用户选中的选项存到data里,提交时和正确答案比对:
submitAnswer() { const { currentQuestion, selectedOptions } = this.data; const correctAnswer = currentQuestion.answer.split(','); const isCorrect = selectedOptions.length === correctAnswer.length && selectedOptions.every(item => correctAnswer.includes(item)); if (!isCorrect) { // 写入错题本 wx.request({ url: `${baseUrl}/wrong/add`, method: 'POST', header: { 'token': wx.getStorageSync('token') }, data: { questionId: currentQuestion.id } }); } this.setData({ showAnalysis: true, isCorrect }); }逻辑说明:正确答案在数据库里存成A,B这种格式,提交时先 split 成数组,再和用户选中的选项比对。长度相同且每一项都包含才算对。答错时调后端接口写入错题本。参数方面,token 放在 header 里传给后端做鉴权。
4.3 顶部导航栏高度适配与单选框样式
微信小程序的顶部导航栏高度在不同机型上不一样,尤其是带刘海屏的 iPhone。获取方法是:
const systemInfo = wx.getSystemInfoSync(); const statusBarHeight = systemInfo.statusBarHeight; const navBarHeight = 44; // 微信标准导航栏高度 const totalHeight = statusBarHeight + navBarHeight;把这个高度存到全局数据里,自定义导航栏时用。单选框的样式在小程序里默认是圆形的,如果想改成方形或者自定义颜色,需要用wxss覆盖默认样式,或者干脆不用原生组件,自己用 view 模拟。
5. 避坑与排查:那些答辩前最容易翻车的地方
5.1 跨域问题:本地调试正常,真机预览失败
现象:微信开发者工具里接口请求正常,但真机预览时所有请求都失败。原因:开发者工具默认关闭了跨域检查,真机不会。解决:后端加 CORS 配置,或者在小程序后台配置合法域名。本地开发阶段可以在开发者工具里勾选「不校验合法域名」,但上线前必须配好。
5.2 分页查询数据重复或丢失
现象:上拉加载时,第二页的数据和第一页有重复,或者中间少了几条。原因:排序字段不唯一,MySQL 分页时顺序不稳定。解决:ORDER BY 后面加一个唯一字段,比如ORDER BY id DESC,不要只用create_time排序。
5.3 登录态过期后接口全部 401
现象:用户用了一段时间后,所有需要登录的接口都返回 401。原因:token 过期了,但前端没有处理。解决:在wx.request的fail或statusCode === 401时,清除本地 token 并重新走登录流程。最好封装一个统一的请求函数,集中处理。
5.4 数据库中文乱码
现象:题目里的中文存进去变成问号。原因:数据库字符集不是 utf8mb4,或者连接 URL 没指定字符集。解决:建库时用CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,连接 URL 加?useUnicode=true&characterEncoding=utf8。
5.5 小程序包体积超限
现象:上传代码时提示包体积超过 2M。原因:图片资源太多,或者引入了不必要的库。解决:图片放 CDN 或者用网络图片,不要打包进小程序;清理未使用的依赖;必要时用分包加载。
6. 进阶技巧:让这套毕设从「能跑」变成「能拿优」
6.1 用拦截器统一处理 token 和日志
后端加一个拦截器,所有需要登录的接口先校验 token,校验通过后把 userId 塞进 ThreadLocal,Controller 里直接取,不用每个方法都解析一遍。同时打印请求日志,方便排查问题:
public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("token"); if (token == null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } Long userId = JwtUtil.getUserId(token); UserContext.set(userId); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); } }逻辑说明:preHandle在请求进入 Controller 之前执行,校验 token 并把 userId 存入 ThreadLocal。afterCompletion在请求结束后清理 ThreadLocal,防止内存泄漏。参数方面,拦截器需要在 WebMvcConfig 里注册,并配置放行路径,比如登录接口和静态资源。
6.2 模拟考成绩的统计与错题回顾
模拟考交卷后,后端要计算得分并生成错题列表。得分计算很简单:总分除以题目数乘以正确数。错题回顾是把这次考试中答错的题目 ID 收集起来,批量查详情返回给前端。这里可以用 MyBatis 的<foreach>标签:
<select id="selectByIds" resultType="Question"> SELECT * FROM question WHERE id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> </select>逻辑说明:foreach把 ID 列表拼成IN (1,2,3)的形式。参数方面,ids列表不要超过 1000 个,否则 SQL 会很长,影响性能。
6.3 答辩演示的准备工作
答辩前一定要做三件事:第一,准备一个干净的数据库脚本,现场导入就能跑;第二,录一段演示视频,防止现场网络出问题;第三,把核心接口用 Postman 跑一遍,截图放进论文附录。我见过太多人代码写得不错,但答辩时环境起不来,最后分数很难看。希望帮到你。
本文还有配套的精品资源,点击获取