拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于微信小程序与Spring Boot的刷题系统设计与实现详解

简介在线刷题系统是当前教育培训、考试备考和企业内训场景中不可或缺的轻量级应用其核心价值在于打破传统纸质刷题的时空限制实现题库统一管理、错题自动沉淀和答题数据可视化。这类系统通常采用微信小程序承载用户端借助其无需安装、即用即走的特性配合Spring Boot后端框架快速构建RESTful API形成典型的前后端分离架构。在实现过程中通过微信授权登录获取用户唯一标识结合JWT保障接口安全题库表采用灵活的数据结构以适配多种题型并借助MyBatis-Plus高效完成数据持久化。错题本、学习统计等核心模块不仅提升了学习效率也为管理者提供了精准的学情分析依据。当系统面临高并发时还可引入Redis缓存和异步判分机制。本文从工程实践角度出发系统拆解了刷题系统的需求分析、模块设计、接口规范、数据库建模及常见问题排查为开发者提供一套可落地的完整方案。1. 项目概述1.1 核心需求解析这个项目做的是微信小程序刷题系统后端搭配Spring Boot压缩包名是weixin273基于微信小程序的刷题系统的设计与实现springboot.rar。这类项目在高校毕业设计和企业内训场景里特别常见核心解决的是考生刷题分散、错题难追踪、答题数据难统计这三个痛点。站在开发者的角度看它其实就是一个轻量级的在线考试练习平台使用者在微信里打开小程序就能直接刷题不需要额外装APP这个体验门槛很低。从技术栈选择上前端用微信小程序原生开发后端用Spring Boot这几乎是当前国内小型前后端分离项目最主流、性价比最高的组合。微信小程序天然承载了用户端Spring Boot承担接口服务、业务逻辑、数据持久化。数据库方面该项目通常采用MySQL配合MyBatis-Plus这类ORM框架能够快速把CRUD和数据统计做扎实。这套系统的适用人群很明确一是需要刷题备考的学生或职场人二是需要批量管理题库并实时掌握学员答题情况的机构或老师三是对Java后端和小程序开发感兴趣、想完整走一遍前后端联调全流程的开发者。如果你正打算做类似题目或者在工作中接到帮我做个刷题小程序的需求这篇拆解能帮你把全貌梳理清楚少走很多弯路。1.2 技术选型背后的工程逻辑我仔细看过这类项目的实现方式技术上用到的东西不算复杂但每一步选择都有明确的目的性。前端选微信小程序而非H5或App理由很实际微信小程序不用安装打开即用能通过微信授权快速拿到用户身份信息省去了自建账号体系的注册登录流程。对于刷题这类高频但单次使用时长不长的场景小程序是最合适的容器。而且小程序的本地缓存能力wx.setStorageSync可以把章节练习记录、错题数据缓存在用户手机上在弱网环境下依然能保持答题体验。后端选Spring Boot核心价值在于开箱即用的生态整合能力。Spring Boot内置了Spring MVC、嵌入式Tomcat配合Spring Data JPA或MyBatis-Plus数据层的操作被大量简化。更重要的是Spring Boot对参数校验、全局异常处理、跨域配置等都有成熟方案这对一个需要在短时间内输出完整可演示项目的场景来说能省下大量的工程配置时间。这里补充一句项目里实际还会用到Lombok来消除实体类的getter/setter样板代码用Hutool或Apache Commons工具类来简化日期处理和字符串操作。这些选型看起来很基础但在实际开发中确实能显著提升迭代效率属于小项目里藏着工程化思维的典型体现。1.3 系统角色与核心流程梳理一个标准的刷题系统角色划分非常简单清晰。普通用户考生是使用频次最高的一方他们关注的是选题、刷题、查看解析、收藏错题、查看学习统计管理员是维护方负责维护题库、管理用户、查看整体数据报表。核心业务流转过程是这样的用户打开小程序通过微信授权登录后端签发一个Token返回给小程序保存。用户从首页选择科目或章节进入答题页面。答题过程中每题提交后立即判对错并展示答案解析。答错的题自动进入错题本用户可随时回顾重练。每次答题结束后系统记录本次得分、用时、答题数并更新用户的累计学习数据。管理员在后端管理页面或者通过API维护题目、分类、用户状态。这套流程看上去不复杂但真正做起来数据库表设计、接口边界划分、题目选项的数据结构设计都需要提前规划好。下一节我会把核心模块的拆解思路详细展开。2. 核心细节解析与实操要点2.1 功能模块全面拆解整个刷题系统的功能可以按照用户端和管理端两条线来梳理。用户端微信小程序主要包含以下核心模块微信授权登录模块调用wx.login获取临时code把code发送到后端后端再通过微信接口换取openid。这里有一个设计要点后端拿到openid后应该先查库如果用户不存在则自动注册存在则直接登录并生成Token返回给小程序。题库分类与选择模块按科目或章节展示题目分类支持用户选择一个分类进入刷题。我见过不少项目这里用的是最简单的一级分类实际体验不够好更合理的方案是二级分类——先选科目再选章节或者同时展示XXT类型的练习模式。刷题模块支持两种模式练习模式题目做完后立即显示对错和解析和考试模拟模式答题过程中不显示正确答案全部提交后统一判分。这两种模式考察的知识点是一样的但交互逻辑差异很大后端接口设计上需要区分开。错题本模块记录用户所有答错的题目支持重新练习、移除错题、按科目筛选。错题本是这个系统的灵魂功能如果只是存个列表就太肤浅了我会在后续章节详细展开数据表和接口的设计思路。收藏夹模块用户可以在做题过程中收藏重要题目形成自己的重点题集。学习统计模块展示累计做题数、正确率、连续学习天数、每日做题趋势。这个模块在毕业设计和企业版里都很加分能直观体现项目的完整度。管理端Web管理后台或API接口包含题库管理对题目的增删改查、批量导入、题目分类维护。用户管理查看用户列表、禁用/启用用户账号。数据统计查看每日活跃用户数、各科目做题量、整体正确率等。2.2 项目目录结构与代码架构设计我看过的同类优秀项目目录结构通常按照经典的分层架构来组织既保持结构清晰又便于后期扩展。下面给出一个典型的目录结构参考src/main/java/com/example/exam/ ├── controller/ # 控制层接收小程序请求返回JSON数据 │ ├── AuthController.java # 登录授权接口 │ ├── QuestionController.java # 题目相关接口 │ ├── ExamController.java # 答题考试接口 │ └── UserController.java # 用户信息接口 ├── service/ # 业务层处理核心业务逻辑 │ ├── UserService.java │ ├── QuestionService.java │ ├── ExamRecordService.java │ └── WrongBookService.java ├── mapper/ # 数据访问层MyBatis-Plus的Mapper接口 │ ├── UserMapper.java │ ├── QuestionMapper.java │ ├── ExamRecordMapper.java │ └── WrongBookMapper.java ├── entity/ # 实体类User, Question, ExamRecord等 ├── common/ # 通用类Result封装、异常处理、工具类 │ ├── Result.java # 统一返回对象 │ ├── GlobalExceptionHandler.java │ └── JwtUtil.java # JWT工具类 └── config/ # 配置类CORS跨域、拦截器配置这种结构的核心好处是分层清晰前端只需要和controller层打交道业务逻辑集中在service层数据操作在mapper层完成。如果后期需要增加新的功能模块只需要按这个模式增加对应的类即可不需要破坏已有结构。2.3 核心API接口设计与规范一个完整的刷题系统后端接口的设计质量直接决定前端联调的效率。我整理了一套比较成熟的接口设计方案需要注意的点都会标注出来。用户模块POST /api/auth/login微信登录接口。参数是微信小程序端传来的code返回用户信息Token。GET /api/user/info获取当前用户信息。请求头携带Token。PUT /api/user/info更新用户资料比如昵称、头像。题库与题目模块GET /api/category/list获取所有题目分类科目、章节。GET /api/question/list?categoryIdxxxpage1size10分页获取指定分类下的题目。GET /api/question/detail?idxxx获取题目详情含选项、答案、解析。POST /api/question/add管理员新增题目。PUT /api/question/update管理员修改题目。DELETE /api/question/delete?idxxx管理员删除题目。答题模块POST /api/exam/submit提交一份答卷。参数包含分类ID、题目列表每题选择的选项ID和答题用时。后端在所有题目提交后统一评分返回得分、正确题数、错误题数和详细答题报告。POST /api/exam/practiceSubmit练习模式提交单题答案返回判题结果和解析。GET /api/exam/record?page1size10分页获取用户的答题历史记录。错题本与收藏模块GET /api/wrongBook/list?page1size10获取当前用户错题列表。POST /api/wrongBook/remove从错题本移除某道题当用户重新答对后。POST /api/favorite/add收藏题目。DELETE /api/favorite/remove取消收藏。GET /api/favorite/list获取收藏列表。统计模块GET /api/statistics/overview获取用户累计做题数、累计正确率、学习天数。GET /api/statistics/trend?days7获取近N天的做题趋势图数据。这里要特别说明统一返回格式的问题。我强烈建议定义一个Result类格式为{ code: 200, message: success, data: ... }code为200表示成功其他值表示业务异常前端只需要统一判断code即可。这个看似简单的设计在联调阶段能节省大量沟通成本。同样的道理实体类序列化时要注意日期格式统一为yyyy-MM-dd HH:mm:ss避免前后端因日期格式不一致而反复调试。2.4 题目数据结构特别设计题库表question的设计是整个系统的地基字段定义如下id主键自增。category_id所属分类ID关联分类表。question_type题目类型1表示单选题2表示多选题3表示判断题。content题干内容TEXT类型。options选项内容JSON格式存储。例如{A:选项A内容,B:选项B内容,C:选项C内容,D:选项D内容}。answer正确答案。单选题存A多选题存A,B,C判断题存T或F。analysis答案解析。difficulty难度系数1~5用于后续的智能组卷。create_time、update_time时间字段。选项用JSON字符串存储是一个很实用的方案。如果按传统关系型思维做四列option_a, option_b, option_c, option_d一旦题目改成六选项表结构就废了。JSON存储让选项数量完全灵活而且查询时也不需要额外关联子表。我在实际开发中一直用这个方案尤其在题目结构变化频繁的项目里能有效避免反复改表。3. 实操过程与核心环节实现3.1 微信小程序端核心实现前端部分的小程序页面结构是这样的miniprogram/ ├── pages/ │ ├── index/ # 首页分类展示、刷题入口 │ ├── exam/ # 刷题页核心页面 │ ├── examResult/ # 考试结果页 │ ├── wrongBook/ # 错题本页 │ ├── favorite/ # 收藏页 │ ├── statistics/ # 学习统计页 │ └── mine/ # 个人中心页 ├── utils/ │ ├── request.js # 封装wx.request请求 │ └── auth.js # Token管理 ├── app.js # 全局逻辑登录入口 └── app.json # 全局配置先说说请求封装这是小程序的第一个大坑。直接使用wx.request会有很多重复代码而且Token过期后的自动处理逻辑难以复用。我一般会封装一个request.js统一处理baseURL、请求头、Token注入和401状态码跳转// utils/request.js const BASE_URL http://localhost:8080/api; function request({ url, method GET, data {} }) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { Content-Type: application/json, Authorization: Bearer ${wx.getStorageSync(token)} }, success: (res) { if (res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { // Token失效或未登录重新走登录流程 wx.removeStorageSync(token); login(); // 重新登录 reject(new Error(登录已过期)); } else { wx.showToast({ icon: none, title: res.data.message || 请求失败 }); reject(new Error(res.data.message)); } }, fail: (err) { wx.showToast({ icon: none, title: 网络异常请稍后重试 }); reject(err); } }); }); } module.exports { request };另一个核心封装是登录流程。微信登录不能每次打开小程序都调一次wx.login那样会频繁刷新用户的登录态。合理的做法是启动时先检查本地是否有Token、Token是否过期没有或过期才触发登录。// app.js App({ onLaunch() { const token wx.getStorageSync(token); if (!token) { this.login(); } }, login() { wx.login({ success: (res) { if (res.code) { wx.request({ url: http://localhost:8080/api/auth/login, method: POST, data: { code: res.code }, success: (resp) { const { token, userInfo } resp.data.data; wx.setStorageSync(token, token); wx.setStorageSync(userInfo, userInfo); } }); } } }); } });在刷题页面的uni-app或原生小程序实现中有一个点需要特别注意答题时一旦用户切到后台应该在该页面的onHide或onUnload中记录答题进度并保存到本地缓存。同时给用户弹出中途退出将保存当前进度的提示这是体验细节的重要一环。3.2 刷题页核心逻辑实现刷题页是整个小程序中最复杂、也最能体现项目质量的地方。以练习模式为例核心操作流程是加载题目列表 → 展示当前题目 → 用户点击选项 → 立即判题 → 展示解析 → 用户点击下一题 → 重复。这里有几个值得展开说明的细节。第一单题判题逻辑。用户点击选项后前端需要判断选中项是否等于正确答案。严格来说判题逻辑应该由后端来做因为前端代码可以被逆向分析答案会暴露。但考虑到这是一个刷题系统用户体验要求即时反馈如果每做一题都发一次请求网络开销太大。我的折中方案是练习模式下后端把题目列表一次性返回包含答案前端本地判题并展示解析考试模拟模式下后端返回题目时不包含答案用户全部答题结束后统一提交给后端判分。这样既保证了练习的流畅性又确保了模拟考试不被作弊。第二题目的本地缓存和预加载。用户在练习模式下每次加载下一题时不要每次都发请求。正确的姿势是进入分类时后端一次性返回该分类下的所有题目ID列表前端再以5道题为一组后台静默加载用户阅读题干的时间就能覆盖下一组题目的加载延迟。这样用户感知到的就是翻页无等待体验会顺滑很多。第三进度记录。每次用户答到第N题时写入微信本地缓存wx.setStorageSync(exam_ categoryId, currentIndex)。用户下次进入时直接弹窗询问是否从上次进度继续。这个功能实现成本很低但在做完整个系统演示时非常加分。3.3 后端核心功能实现后端接口从整个项目角度看有相当多的内容是模板化程度很高的CRUD。真正有含量、需要仔细设计的我认为有三处微信登录换取openid、题目数据读取与脱敏、答卷判分逻辑。微信登录的后端实现逻辑比较固定关键是理解code换session的完整流程PostMapping(/auth/login) public Result login(RequestBody LoginRequest request) { // step1: 通过code请求微信接口获取openid String url https://api.weixin.qq.com/sns/jscode2session?appid WX_APPID secret WX_SECRET js_code request.getCode() grant_typeauthorization_code; String result restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(result); String openid json.getString(openid); // step2: 如果openid对应账号不存在就自动注册 User user userMapper.selectByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(微信用户 openid.substring(0, 8)); user.setAvatar(https://cdn.example.com/default-avatar.png); userMapper.insert(user); } // step3: 生成Token返回前端 String token JwtUtil.createToken(user.getId()); return Result.success(new HashMap() {{ put(token, token); put(user, user); }}); }这段代码需要注意的点是wx.login获取的code只能使用一次且有效期很短5分钟所以后端拿到code后要立即请求微信接口不能做任何缓存。此外openid是用户的唯一标识必须要有数据库唯一索引防止多端并发登录时产生重复用户。题目列表接口和考试接口都可以考虑做数据脱敏。练习模式下返回题目时包含答案和解析考试模式下同样的接口后端就应该把answer和analysis字段置为null。很多新手不知道这个区分同一个查询接口既给练习用又给考试用导致考试模拟可以直接看到答案。判分逻辑是考试模式的核心我给出一个完整的实现思路public ExamResult submitExam(Long userId, Long categoryId, ExamSubmitDTO dto) { // 1. 查出该用户提交的所有题目ID ListLong questionIds dto.getQuestions().stream() .map(SubmitItem::getQuestionId) .collect(Collectors.toList()); // 2. 查出所有题目真实答案 ListQuestion questions questionMapper.selectBatchIds(questionIds); MapLong, Question questionMap questions.stream() .collect(Collectors.toMap(Question::getId, q - q)); // 3. 循环判分 int correctCount 0; ListAnswerDetail details new ArrayList(); for (SubmitItem item : dto.getQuestions()) { Question q questionMap.get(item.getQuestionId()); boolean isCorrect checkAnswer(q, item.getSelectedAnswer()); if (isCorrect) correctCount; // 记录每道题的对错供前端展示详情 details.add(new AnswerDetail(q.getId(), q.getCategoryId(), item.getSelectedAnswer(), q.getAnswer(), isCorrect, q.getAnalysis())); } // 4. 保存考试记录 ExamRecord record new ExamRecord(); record.setUserId(userId); record.setCategoryId(categoryId); record.setTotalCount(dto.getQuestions().size()); record.setCorrectCount(correctCount); record.setScore(correctCount * 100 / dto.getQuestions().size()); record.setDuration(dto.getDuration()); examRecordMapper.insert(record); // 5. 把错题自动写入错题本 details.stream() .filter(detail - !detail.isCorrect()) .forEach(detail - wrongBookService.addWrongRecord(userId, detail.getQuestionId())); // 6. 返回结果 ExamResult result new ExamResult(); result.setRecordId(record.getId()); result.setScore(record.getScore()); result.setCorrectCount(correctCount); result.setTotalCount(dto.getQuestions().size()); result.setDetails(details); return result; }checkAnswer方法里有个细节单选题直接比较字符串多选题却要把用户提交的答案排序后用逗号拼接再比较否则选A、B提交A,B和B,A会得到不同的结果。这个坑很隐蔽我曾经在这里翻过车。3.4 数据库建表实战数据表的设计直接关系到整个系统后续的扩展能力。核心表关系设计如下用户表userCREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL COMMENT 微信openid, nickname varchar(50) DEFAULT COMMENT 昵称, avatar varchar(255) DEFAULT COMMENT 头像URL, status tinyint(4) DEFAULT 1 COMMENT 状态1正常0禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;题目分类表category和题目表question的关系是典型的一对多这里不再赘述。重点看一下答题记录表和错题本表CREATE TABLE exam_record ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 用户ID, category_id bigint(20) NOT NULL COMMENT 分类ID, total_count int(11) DEFAULT 0 COMMENT 总题数, correct_count int(11) DEFAULT 0 COMMENT 正确数, score int(11) DEFAULT 0 COMMENT 得分百分制, duration int(11) DEFAULT 0 COMMENT 用时秒, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 答题时间, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_category_id (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT答题记录表;这个表的重要作用不仅仅是记录答题历史它还是学习趋势统计的数据源。想要查询用户近7天每天做题数量只需按DATE(create_time)分组统计即可SELECT DATE(create_time) AS date, COUNT(*) AS count FROM exam_record WHERE user_id #{userId} AND create_time DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY DATE(create_time) ORDER BY date;错题本表的独特设计在于重复错题的处理方式CREATE TABLE wrong_book ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, question_id bigint(20) NOT NULL, wrong_count int(11) DEFAULT 1 COMMENT 错误次数, last_wrong_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 最近错误时间, status tinyint(4) DEFAULT 1 COMMENT 状态1在错题本0移除, PRIMARY KEY (id), UNIQUE KEY uk_user_question (user_id, question_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT错题本表;这里加了唯一索引uk_user_question当用户再次答错同一道题时执行的是更新操作wrong_count1, last_wrong_timeNOW()而不是插入新记录。这样设计的好处是错题本列表不会出现重复题而且通过wrong_count可以识别出反复错的高频薄弱题为后续智能推荐提供数据基础。如果做项目答辩把这一点讲出来会非常亮眼。4. 实操中值得注意的细节与边界问题4.1 微信小程序登录态与Token管理从小程序端到后端Token的传递和安全是绕不开的核心话题。整个链路是这样的小程序端在登录后拿到Token所有需要认证的请求都必须在header中带上Authorization: Bearer token。后端需要在Spring Boot中配置拦截器或者过滤器HandlerInterceptor / OncePerRequestFilter对所有需要登录的接口做Token解析和校验。我在项目中用过最顺手的方式是自定义一个拦截器public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 String uri request.getRequestURI(); if (uri.contains(/auth/login)) { return true; } // 从header中获取Token String authHeader request.getHeader(Authorization); if (authHeader null || !authHeader.startsWith(Bearer )) { throw new BusinessException(401, 未登录); } // 解析Token String token authHeader.substring(7); try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); } catch (Exception e) { throw new BusinessException(401, Token无效或已过期); } return true; } }Token本身的生成建议使用JWT但不是强制要求。对小项目来说存Redis的方案和JWT方案都行差别在于JWT是无状态的不占存储但很难主动失效Redis存储可以在服务端秒级封禁用户。考虑到微信小程序手机端的使用习惯是常驻后台我倾向于使用较短的过期时间比如1天配合Redis中的黑名单机制。如果项目工期紧直接用JWT加2小时过期时间也够用但千万要做续签逻辑否则用户每两个小时就要重新登录一次体验会很差。4.2 小程序数据缓存策略微信小程序自带wx.setStorageSync和wx.getStorageSync这个能力被很多初学者低估了。在刷题场景中合理的缓存策略能大幅降低对后端的压力同时提升用户体验。我常用的缓存策略是题目分类列表几乎不变化一旦拉取成功就缓存24小时下次启动直接读缓存。首页展示的统计数据如累计刷题数量今日打卡可以缓存5分钟。用户已经完成的答题记录下拉刷新时重新拉取平时从缓存读取。错题本数据每次进入页面时强制刷新因为这个数据对实时性要求高。重要提示小程序的storage容量上限是10MB题目正文如果包含大量图片会很占空间。所以在缓存题目列表时要么只缓存图片URL不缓存本地文件要么对缓存条数设置上限比如最多缓存100道题的完整内容超出部分丢弃。如果你在开发过程中发现小程序启动越来越慢大概率就是storage里堆积了太多旧数据解决方式是给缓存统一增加时间戳版本号加载时校验过期即可删除。4.3 管理员端题库批量导入在很多实际项目中题目投入实际使用的方式不是一条条在后台手动输入而是通过Excel批量导入。一个300道题的题库如果靠手动录入大约需要一千多分钟如果通过Excel批量导入配合模板规范几分钟就能完成。我建议在后端提供一个Excel导入接口接收标准格式的Excel文件逐行读取后校验并入库。常用的方式是使用EasyExcel或Apache POI。这里有一个非常关键的细节导入的题目答案必须做格式校验。如果题目类型的单选题有5个选项正确答案却填了E那这条数据必须报错而不是静默跳过。最稳妥的做法是将校验失败的行单独收集起来导入完成后返回给管理员一个错误报告列出每行失败的原因由管理员修正后重新导入。这个能力在项目的实际落地阶段有极高的价值能让人力成本从几天压缩到几十分钟演示效果也非常好。4.4 数据统计模块的计算口径统计模块看似简单但算法口径如果不统一前后端联调时很容易扯皮。我遇到过前端说我做了100道题为什么显示99这样的问题原因就是统计的时候没有统一口径。统计维度建议这样定义累计做题数所有exam_record表中的total_count累加。累计正确数所有exam_record表中的correct_count累加。正确率累计正确数 / 累计做题数保留一位小数。连续学习天数以最近一次有做题记录的日期为参照向前追溯连续的日期天数。今日学习时长当天所有exam_record表中的duration累加。这里要注意练习模式下每次提交单题也会产生一条耗时记录但考试模式下整个答卷可能持续20分钟单题的耗时依然按答案提交时间计算否则时长会膨胀得很离谱。5. 常见问题与排查技巧实录5.1 微信小程序request请求失败的真正常见原因这个坑我见过太多次几乎每个做小程序联调的新手都会踩。小程序开发工具里访问http://localhost:8080看起来没问题但真机一测就全部请求失败。原因有两层第一层小程序要求所有请求域名必须是HTTPS的合法域名。在开发阶段可以在开发者工具中勾选不校验合法域名来跳过检查但真机上这个开关是无效的。所以要么把后端部署到有HTTPS证书的服务器要么在真机调试时选择预览模式同时打开开发工具的真机调试功能。第二层localhost在手机上指向的是手机自己不是开发者的电脑。正确做法是在后端配置文件中把server.address设为0.0.0.0然后手机和小程序开发者使用的电脑连同一个局域网WiFi在request.js的BASE_URL中填写电脑的局域网IP地址如http://192.168.1.100:8080/api。排查这类问题有一个高效的思路不要直接用小程序测而是先用Postman、Apifox等接口调试工具请求后端接口。接口能用Postman测通才说明后端本身没问题这样可以快速把问题定位在前端。如果Postman也通不了那问题大概率出现在后端的端口占用、防火墙、CORS配置上。实际项目中约八成的前后端联通问题是后端没有配置CORS跨域导致的Spring Boot解决方案如下Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }加了这个配置后直接的小程序跨域请求问题基本都能解决。5.2 微信登录有时失败code2Session接口报40029在联调微信登录接口时我遇到过几次code2Session返回40029 invalid code的情况。排查到最后原因不是方法错误而是code被使用了两次。调试的时候可能前端代码发起了两次登录请求第一次请求成功之后code就已经失效第二次就报错。排查方法在后端日志中打印每次调用code2Session的code然后用日志对比前端调用的次数。如果再仔细一点看一般还会发现另一个常见原因——开发者工具中的AppID与后端配置的AppID不一致。小程序端和后端是用两个不同的小程序测试号做的后端拿到的code是用AppID A申请的却用AppID B去换openid微信自然会拒绝。5.3 答题页面在部分安卓手机上样式错乱这是纯前端问题但后端通常无法帮忙而且常在演示时发作。具体表现为在iPhone上正常的页面在部分Android机型上出现底部按钮上移、选项换行错乱等问题。主要是由于微信小程序的rpx单位在低端机型上换算时出现小数加上部分安卓浏览器对Flex布局的老旧支持差异导致的。解决方案通常有两个一是避免过度依赖rpx做精细的单位控制尺寸较大或需要精确对齐的元素直接使用固定值加百分比页面整体使用flex布局并在根节点设置flex: 1加上box-sizing: border-box。二是给滑块容器设置min-height而非height给选项checkbox的外层包裹一层view用width: 100%和flex-wrap: wrap保证换行规则一致。这个问题的深度排查可以在Android真机上打开开发者工具远程调试但成本较高。大多数情况下采用保守的布局方案不用绝对定位、不用flex-direction: column加上overflow-y: auto的嵌套就能完美规避。5.4 Token过期后小程序行为异常Token过期是系统上线后最影响体验的问题之一。小程序端如果对401响应没有做统一处理用户会看到接口报错这个很模糊的提示然后什么都点不了只有杀掉小程序重开才能继续用。避免这个问题的方式我已经在前面提到过就是封装request.js时统一拦截401状态码。但真正的难点不在于怎么拦截而在于自动续期。一种方案是后端提供一个POST /auth/refresh接口用旧的但尚在有效期内的Token换取新的Token另一种方案最简单也最直接——前端检测到401时直接清除本地Token并重新走wx.login登录流程。对于刷题系统这种对安全要求不那么极端的业务第二种方案简单有效实现成本最低。5.5 常见问题速查表问题现象可能原因排查思路与解决方案小程序里请求后端接口一直失败域名未配置合法域名、CORS未配置开发工具勾选不校验域名后端配置CorsFilter真机上无法请求本地后端localhost指向了手机自身改用电脑局域网IP三方设备同一WiFiwx.login的code换不到openidAppID不一致、code被重复使用核对前后端AppID是否一致每次登录只调用一次wx.login考试模拟模式能看到答案后端接口没有做数据脱敏考试接口单独查询答案字段置null返回多选题判分一直不对选项顺序不一致导致字符串比较失败提交前对选项排序再拼接比较错题本里出现重复题目缺少唯一索引表设计增加UNIQUE KEY uk_user_question(user_id, question_id)小程序启动越来越慢本地缓存太多旧数据给缓存加版本号定期清理限制题目缓存条数安卓机上面样式错乱rpx换算误差、flex兼容性问题使用百分比宽度压缩flex嵌套层级设min-height避坑5.6 项目答辩/汇报时的加分点这个压缩包很可能用于毕业设计或项目展示演示环节要注意几个关键点。演示的彩蛋主要体现在以下几个方面第一展示错题本和答对自动移除的交互这个流程需要精心准备1~2道肯定会答错的题目现场演示错题入本、重练答对后自动移除的完整闭环效果很直观。第二展示学习统计页面的近7天做题趋势。为了演示效果好看可以提前准备几天散落分布的真实做题数据让折线图走势起伏明显。切忌演示一个全零或全满的图形会显得很假。第三展示管理员通过Excel批量导入题目的流程。准备一个几十行的Excel文件现场导入、现场展示题库数量刷新效率对比强烈是行政加分非常有力的环节。回答评委的问题时建议主动讲清楚一个问题这个系统的用户量如果从100人涨到10万人你会怎么优化。思路可以从数据库表索引、题库加Redis缓存、答题接口异步化这几个维度展开。不需要真的做但要知道方向这说明你不只是照着跑通而是有架构层面的思考。6. 我的实操心得与后续扩展建议6.1 两点痛并快乐的心得这类系统做下来我最想分享的第一点体会是刷题系统的核心不在代码写得有多花哨而在做题的闭环逻辑是否完整。练习、判分、错题沉淀、统计分析这四个环节任何一个断了系统价值就大打折扣。很多同类项目只做到了前两步错题本只是简单列表而不支持重新练习和移除统计模块完全没做这等于把最能让用户产生粘性的功能砍掉了一大半。第二点体会是前后端联调阶段的时间一定要预留充足。后端接口写好了不代表前端能顺利对接。字段命名不一致、空值处理不当、返回格式不统一这些问题在联调阶段会集中爆发。越早定义好统一的返回结构和字段命名规范联调就越顺畅。建议在接口设计阶段就编写一份简单的API文档用Apifox、Swagger都可以前后端按文档开发能省掉大半的沟通成本。6.2 后续可以扩展的方向如果现在拿到的项目还要继续升级我认为有三个方向很值得做而且实现起来不那么困难第一是加一个每日一练功能。每天定时从题库中随机抽取10道题推送给用户配合小程序订阅消息提醒能显著提升用户日活。后端只需要一个定时任务Spring Boot的Scheduled和简单的随机取题逻辑。第二是加入错题重练的智能算法。目前错题本是静态的谁错谁进答对了就移除。可以做进阶版本结合wrong_count字段错误次数对错误次数超过2的题目在一周后自动推送给用户重新测试实现基于遗忘曲线的复习提醒。这个功能一加系统的专业性立刻上一个台阶。第三是管理端的数据可视化大屏。虽然项目本身的管理功能可以做成简单表格但接入一个ECharts组件把每日活跃用户、题目正确率分布、分类热度做成图表整个项目的完成度和展示效果会有质的提升。ECharts接入Spring Boot后端非常成熟前端通过接口拉取统计JSON数据渲染图表即可。这些都是实际开发中延伸出来的思路如果你的项目阶段或者工作场景正好需要这些能力可以从这里起步去扩展。希望这篇拆解能帮你把整个刷题项目的脉络理清楚顺利跑通自己的版本。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门