ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

微信小程序与Spring Boot刷题系统实战:架构拆解与避坑指南

微信小程序与Spring Boot刷题系统实战:架构拆解与避坑指南 简介一套基于微信小程序与Spring Boot开发的刷题系统完整项目源码适合Java后端开发者、小程序前端学习者及毕业设计参考。项目实现了题库管理、在线刷题、用户反馈等核心功能前端使用小程序原生组件与Vue管理端后端提供RESTful API并配套SQL数据库脚本。资源包共1049个文件约16MB涵盖png/svg静态资源、js/vue/wxml/wxss前后端页面、java后端逻辑、json配置及sql脚本等类型目录结构清晰便于按模块查阅与二次开发。已有26人学习下载。包内含后台管理端Vue页面、Spring Boot后端工程、小程序前端源码与部署脚本并附带运行视频与反馈通道可帮助学习者快速理解前后端交互流程掌握从环境搭建到接口调试的完整思路也可作为课程设计与求职作品的基础模板。1. 基于微信小程序的刷题系统 Spring Boot这套代码到底能省多少事拿到weixin273这个压缩包时我先确认了一件事它不是一个“老师布置的作业”级别的半成品而是一个能直接跑起来的小程序刷题系统——小程序端负责选题、答题、看错题Spring Boot 后端负责题库管理、答题判定、数据统计整套链路是通的。对于想自己写一个在线答题小程序、又不想从零抠登录态和接口设计的开发者来说这份资源能省下至少一周的搭建时间。但它也带着不少实操门槛你得会改 MySQL 配置、看得懂微信小程序的 request 封装、还得知道端口和域名校验这些坑。这篇笔记就按我拆包复现的顺序把它说透。2. 拆包看架构小程序端、后端接口与数据库表是怎么分工的2.1 项目目录结构谁在前端谁在后端解压后目录大致是这样的根目录下会有miniprogram或者叫wechat-app和springboot或者直接是jar包和src目录两个主要文件夹另外一般还有db或sql目录放初始化脚本以及一份简单的设计说明文档。别小看这个分类很多新手第一步就栽在把后端代码往小程序开发工具里拖。miniprogram里是标准的微信小程序项目app.json、pages、utils一目了然。pages下通常有index题库列表、exam答题页、result结果页、wrong错题本这些子目录。utils里会有一个request.js负责统一封装wx.request这是整套前端和后端通信的喉咙后面我会专门拆它。springboot那边是标准的 Maven 项目结构src/main/java下面按controller、service、mapper、entity分层。如果这个包是早期生成的你可能还会看到dao目录而不是mapper这不影响理解核心是 Controller 接 HTTP 请求Mapper 碰数据库。数据库脚本通常在database/shuati.sql这类路径下用 Navicat 或命令行执行就能建库建表。2.2 后端 Spring Boot 分层Controller-Service-Mapper 的套路这套资源的技术栈基本是 Spring Boot MyBatis MySQL合情合理因为刷题系统的数据模型简单MyBatis 写 SQL 比 JPA 直观多了。接口设计上我整理了一份典型的接口清单你对照源码找同名文件就行功能接口路径常见请求类型作用微信登录/api/user/loginPOST接收 code调 wx.code2Session 换 openid生成 token获取题库列表/api/bank/listGET分页返回题库列表获取题目/api/question/listGET按题库 ID 分页获取题目提交答案/api/record/submitPOST保存答题记录并判定对错错题列表/api/wrong/listGET查询当前用户错题统计结果/api/statistics/summaryGET返回做题总数、正确率Controller 层很薄基本就是收参数调 ServiceService 层处理业务比如迭代题目、解析答案Mapper 层只写 SQL。这套代码的亮点是“答案不在接口里”而是在数据库question表的answer字段里提交时用用户答案和字段比对这种做法比前端判断要稳因为别人绕过页面直接调接口也刷不了分。2.3 数据库核心表结构数据库结构我整理成五张核心表源码里的sql脚本基本都有表名用途关键字段user用户表id, openid, nickname, avatar, create_timequestion_bank题库表id, bank_name, description, question_countquestion题目表id, bank_id, question_type, content, option_a, option_b, option_c, option_d, answer, analysisanswer_record答题记录表id, user_id, question_id, bank_id, is_correct, answer_content, create_timewrong_question错题表id, user_id, question_id, wrong_count, last_wrong_time注意question表的question_type一般 1 表示单选题2 表示多选题。如果这是你第一次看这种表建议先弄清answer字段的存法单选是A多选可能是ABC甚至用逗号分隔。别拿到多选当单选判后面我把这个坑展开说。3. 从 .rar 到能跑起来环境准备、导入、配置与启动全流程3.1 环境清单与版本组合建议先把环境备齐。这套代码是 Spring Boot 项目JDK 用 1.8 最稳Maven 3.6 以上就行MySQL 5.7 或 8.0 都兼容微信开发者工具用稳定版。这里我不建议你直接用 Spring Boot 3.x 去试因为老项目的javax包名和 Spring Boot 3 的jakarta不兼容你会编译出一堆红色报错纯属折腾。除了运行环境你最好准备一个 MySQL 客户端Navicat 或 DBeaver 都行和一个能看 JSON 的接口测试工具。为什么强调这两个因为后端跑起来以后你要用接口工具先打http://localhost:8080/api/bank/list看返回数据确认后端正常再调小程序否则你根本分不清问题出在哪一层。3.2 数据库初始化别急着改application.yml先导脚本打开数据库脚本先创建一个名为shuati或项目自带的名字的数据库字符集选utf8mb4排序规则选utf8mb4_general_ci然后导入sql文件。导入后别急着跑先看几行表结构和几条数据确认题目表里确实有内容。我见过不止一次脚本本身没有CREATE DATABASE你直接跑就全执行在默认库里了后面对不上。接下来看application.yml或application.properties改数据源。常见配置长这样server: port: 8080 servlet: context-path: /api spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/shuati?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.shuati.entity这里有两个参数最容易改错。第一context-path是/api那你所有接口都带/api前缀小程序端request.js里的 baseUrl 如果没带就会 404。第二serverTimezone必须要设置MySQL 8 默认时区和本地不同不配可能直接报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。JVM 里的user.timezone不一定管用直接在连接串写死最省心。3.3 后端启动两种方式本地开发建议用 IDEA如果你装了 IDEA直接 File - Open 选springboot目录等 Maven 把依赖下载完然后找到主类SpringBootApplication注解的那个右键 Run。这种方式的好处是你能在控制台看到完整的启动日志排查问题最方便。如果你更习惯命令行也可以进到目录里执行mvn clean package -DskipTests java -jar target/shuati-0.0.1-SNAPSHOT.jar启动成功的标志不是“没有报错”而是看到类似Tomcat started on port 8080和Started Application in XX seconds。注意-DskipTests是跳过测试如果项目里有测试类但你本地环境缺东西不跳会卡在测试阶段跳过它能让你更快看到结果。3.4 小程序端导入与配置先跑通模拟器再上真机用微信开发者工具导入miniprogram目录导入时它会要求填 AppID。如果你只是本地验证可以直接点“测试号”不需要自己注册小程序账号。这一步很多新手不知道导致卡在 AppID 上其实测试号完全够用。导入后第一件事打开utils/request.js找到类似这样的地方const BASE_URL http://localhost:8080/api; function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method || GET, data: data || {}, header: { Content-Type: application/json }, success: resolve, fail: reject }); }); } module.exports { request };BASE_URL就是后端地址。我用的是localhost:8080/api这个在微信开发者工具里没问题但真机调试时手机访问不到电脑的 localhost要改成你电脑在局域网里的 IP比如http://192.168.1.5:8080/api。另外Content-Type用application/json如果你的后端方法签名是接收实体类这个没问题如果后端接收的是表单参数这里就得改成application/x-www-form-urlencoded这是前后端联调最常见的分歧点。4. 刷题核心链路题目加载、答题判定、错题记录与进度统计的实现拆解4.1 小程序端请求封装与登录态代码的喉咙口刚才我提了request.js这里再深入一点。刷题系统里所有页面都会调这个封装它通常不只是wx.request的简单包装还会带 token。常见的写法是在请求头里塞一个自定义字段比如Authorizationfunction request(url, method, data) { const token wx.getStorageSync(token); return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method || GET, data: data || {}, header: { Content-Type: application/json, Authorization: token ? Bearer ${token} : }, success: (res) { if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/login }); reject(res); } else { resolve(res.data); } }, fail: reject }); }); }为什么强调这个文件因为这套系统里“用户身份”完全靠这个 token 识别。你在小程序端登录一次后端返回 token存进 storage之后所有接口都带它。如果后端有拦截器校验 token你直接调接口不带 header返回的就不是题目而是 401。所以排查“为什么接口全报错”时我一般先看这里。4.2 题库与题目列表的分页加载别一次捞全量数据刷题系统的题目量可能不大但设计上通常会做分页。后端接口常见的实现是 MyBatis 加上 PageHelper 分页插件。先看pom.xml里有没有pagehelper-spring-boot-starter有的话Service 层代码大概长这样Override public PageInfoQuestion getQuestionList(Long bankId, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); ListQuestion questions questionMapper.selectByBankId(bankId); return new PageInfo(questions); }PageHelper.startPage(pageNum, pageSize)是分页开关它写在查询语句前面底层会在执行 SQL 前自动拼接LIMIT。注意这里有个经典坑startPage只对紧接着的下一条查询生效如果你在它和select之间塞了别的查询分页就跑到别的语句上去了结果页面怎么翻都是同一批数据。小程序端对应的处理也简单onReachBottom里把pageNum加 1再请求下一页把返回的数组concat到原有列表后。微信小程序的页面没有浏览器滚动事件用onReachBottom是最现成的方案。4.3 提交答案与判定逻辑比对答案的时机和事务题目提交是这套系统的核心。我看过的代码里判定逻辑大致有两种一种是前端判就是提交时直接比对option字段这种最简单但容易作弊另一种是后端判把用户答案传到后端后端从库里取标准答案再比对。这份资源大概率是后者代码类似Transactional public SubmitResult submitAnswer(AnswerSubmitDTO dto) { Question question questionMapper.selectById(dto.getQuestionId()); if (question null) { throw new ServiceException(题目不存在); } boolean correct question.getAnswer().equalsIgnoreCase(dto.getAnswer()); AnswerRecord record new AnswerRecord(); record.setUserId(dto.getUserId()); record.setQuestionId(dto.getQuestionId()); record.setBankId(question.getBankId()); record.setIsCorrect(correct ? 1 : 0); record.setAnswerContent(dto.getAnswer()); answerRecordMapper.insert(record); if (!correct) { WrongQuestion wrong new WrongQuestion(); wrong.setUserId(dto.getUserId()); wrong.setQuestionId(dto.getQuestionId()); wrong.setWrongCount(1); wrong.setLastWrongTime(new Date()); wrongQuestionMapper.insert(wrong); } return new SubmitResult(correct, question.getAnswer(), question.getAnalysis()); }参数说明AnswerSubmitDTO里必须有userId、questionId、answer三个字段question.getAnswer()是标准答案equalsIgnoreCase是为了让你填a或A都算对。Transactional放在这里很关键——如果插答题记录成功但插错题记录失败事务回滚不会出现“这道题明明做错了但错题本里没有”的脏数据。再看WrongQuestion的插入。很多项目的错题表是有唯一约束的用user_id和question_id做联合唯一键。如果这套代码里有那插入错题前应该先查一下是否已存在存在就update wrong_count而不是继续insert否则第二次错同一题就主键冲突。你看源码时专门找这个逻辑就行。4.4 错题本与统计接口SQL 的聚合和懒加载错题列表接口不复杂就是SELECT * FROM wrong_question WHERE user_id ?。但如果你点进错题详情想看到题目内容后端会再做一次question_mapper.selectById。这时候要注意 N1 查询问题如果错题有 50 条代码在循环里查 50 次题接口响应会明显变慢。正常做法是查出错题后收集所有question_id再用WHERE id IN (...)一次查回来在内存里组装。统计接口更依赖 SQL 聚合。比如要算正确率可能会写这样的 Mapperselect idselectCountByUserId resultTypemap SELECT COUNT(*) AS total, SUM(CASE WHEN is_correct 1 THEN 1 ELSE 0 END) AS correct FROM answer_record WHERE user_id #{userId} /select这里SUM(CASE WHEN ...)是在 MySQL 里做条件计数返回一个MapService 层拿到total和correct算出正确率。注意total有可能是 null没有记录时SUM返回 null拿到 Java 里要先判空再转 int否则拆箱直接NullPointerException。这段逻辑在小程序端统计页面能直接看到效果也是我最常建议新手先看的代码。5. 避坑与排查把 Spring Boot 刷题系统跑起来最常见的五个翻车点5.1 小程序请求报错request: fail url not in domain list现象后端启动正常接口工具也能通但在微信开发者工具里点刷新控制台报url not in domain list。原因微信小程序对网络请求有域名白名单校验开发工具默认不校验但如果你勾了“校验合法域名”它会拦所有http://的本地地址。解决在微信开发者工具右上角“详情 - 本地设置”里勾上“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。这是本地调试的后悔药真机预览时再按需配置个人域名的 HTTPS 证书。5.2Tomcat started on port 8080但接口全 404现象启动日志没有报错可访问/api/bank/list返回 404而不是预期的 JSON。原因多半是context-path没对上。后端配置是/api但你在浏览器里访问的是http://localhost:8080/bank/list或者小程序端BASE_URL写成http://localhost:8080漏了/api。接口工具调试时最容易踩这个因为请求地址是拼接的少一段就看不出问题。解决统一三处的路径application.yml里的servlet.context-path、小程序端BASE_URL、接口工具的请求前缀。排查时先看浏览器访问http://localhost:8080/api/bank/list是否通如果通就专心改前端不通就去看 Swagger 文档里接口的 RequestMapping 有没有额外前缀。5.3 数据库连上了但中文全显示成?问号现象小程序端题目内容里的中文变成一串问号或者后端日志插入 MySQL 时出现Incorrect string value报错。原因数据库表字符集是latin1或者连接参数没加characterEncodingutf8。老项目的sql脚本里可能没有DEFAULT CHARSETutf8mb4导入时继承了数据库的默认字符集。解决建库时记住用utf8mb4连接串写死characterEncodingutf8。如果表已经建了执行ALTER TABLE question CONVERT TO CHARACTER SET utf8mb4;改表和字段的字符集。注意改完好重新导入数据否则表中已存坏的问号不会自己恢复。5.4 模拟器能跑真机一打开就白屏或接口超时现象开发者工具里一切正常点“真机预览”后手机上小程序打开页面加载不出列表过一会超时。原因手机访问localhost:8080访问的是手机自己不是你的电脑。你电脑上后端监听 8080手机必须用电脑的局域网 IP 才能访问同时 Windows 防火墙默认会挡掉来自局域网的入站连接。解决先用ipconfig查电脑的 IPv4 地址把request.js里的BASE_URL改成http://局域网IP:8080/api再检查防火墙或者直接在弹窗里允许 Java 通过专用网络。同一 Wi-Fi 下手机和电脑必须在一个网段公司网络如果有 AP 隔离就改用手机热点来真机调试。5.5 微信登录报invalid appid或code2Session失败现象首次进小程序就卡在登录页网络请求到/api/user/login后端日志显示code2Session返回errCode: 40013。原因微信小程序的 AppID 和密钥是绑定的你用测试号发起登录后端却用自己申请的 AppID 去调微信接口或者wx.login返回的 code 失效了。解决如果你只是验证功能就统一用测试号测试号的 AppID 不能调真实微信登录接口但后端可以做一步“模拟登录”直接把code当作openid来用比如openid test_ code。这套系统的源码里通常留着这个开关你搜code2Session或mock注释就能找到把 mock 开关打开就行。要是你接到了真实项目就一定得配成你注册的小程序 AppID 和 Secret并且把app.js里的 AppID 也改成同一个。6. 进阶给这套刷题系统加“错题重刷”和“模拟考试”两个小功能6.1 错题重刷把错题表当成随机题库错题本里的题如果只是列表展示练的意义就打折了。我一般会给后端加一个randomWrongList接口从错题表关联题目表随机取 N 道GetMapping(/wrong/random) public ListQuestion randomWrong(RequestParam Long userId, RequestParam int limit) { return questionMapper.selectRandomWrong(userId, limit); }对应 Mapper 里的 SQL 关键词就是ORDER BY RAND()加LIMIT。题目量不大时这样写没毛病量大了要换随机算法但演示项目完全够用。小程序端刷完这批错题还可以在提交时调用同一个提交接口答对了就从错题表删掉形成“错题清零”的闭环。6.2 模拟考试前端倒计时与超时交卷模拟考试的核心是限制时间。前端页面进入时设置一个总时长比如 30 分钟用setInterval每秒减 1到 0 时触发提交。提交时把所有答案组装成一个数组批量调提交接口。注意时间组件卸载时要clearInterval不然页面跳走了定时器还在跑会出现跳转后依然弹窗的玄学问题。let timer null; const startExam () { let left 30 * 60; timer setInterval(() { left--; if (left 0) { clearInterval(timer); submitAllAnswers(); } }, 1000); };这套系统本身是完整的但它的代码风格比较传统适合学习也适合二次改。你可以顺着它往里面加“每日一题”“排行榜”“刷题日历”这些模块接口和页面的套路都一样。说句心里话像这种带前后端完整闭包的项目最值得做的不是原样跑通而是先在本地把这条链路摸熟再按自己的业务改。我每次拿到一个 Spring Boot 小程序的项目都会强制自己先把request.js的 baseUrl、数据库脚本、启动日志三步走完再谈改功能。希望帮到你。本文还有配套的精品资源点击获取
返回列表