微信小程序+Spring Boot刷题系统设计:从表结构到上线避坑

发布时间:2026/10/27 23:14:43
微信小程序+Spring Boot刷题系统设计:从表结构到上线避坑 简介一套基于微信小程序的刷题系统毕业设计源码案例面向在线刷题学习场景整合SpringBoot后端与Vue管理后台适合计算机相关专业学生作为课程设计或毕业设计参考。系统实现用户登录、题目动态加载、在线答题、自动评分、错题记录与成绩统计等核心功能前端采用微信小程序原生框架后端提供用户管理、题库管理、答题逻辑处理等模块并预留智能推荐扩展思路。整个压缩包共773个文件大小25.76MB包含101个Java源文件、93个Vue组件、89个JavaScript脚本、23个wxss样式、22个wxml页面模板以及14个XML配置、SQL脚本、构建批处理与Maven封装文件目录结构完整清晰便于直接导入开发环境学习。目前已有118人学习下载尤其适合希望快速掌握小程序与SpringBoot前后端分离开发流程的读者对照源码理解模块划分与接口调用方式。1. 微信小程序刷题系统这不是“题库展示”而是一条完整的刷题闭环最近很多来问毕设选题的同学一开口就是“想做在线刷题”。如果你也卡在这个标题上——基于微信小程序的刷题系统配一个 Spring Boot 后端我建议你先把定位想清楚刷题系统不是把题目列出来让用户翻而是围绕“做答、判题、错题、重复练习”这四件事把链路跑通。微信小程序解决的是触达和多端同步Spring Boot 解决的是题库管理、判题正确性和学习记录两者拼起来就是一个能演示、能测试、能上线的毕业设计案例。这套选题适合三类人想写课程设计或毕业设计的在校生需要给培训或考试场景做轻量刷题工具的产品同学以及第一次接触前后端分离项目的后端初学者。接下来我会按自己做过的方案把表结构、接口、小程序登录与答题闭环、上线前容易翻车的点一步步给你拆开。2. 技术选型拆解为什么刷题场景离不开微信小程序和 Spring Boot2.1 小程序端的三点优势免安装、原生登录态、发布链路短微信小程序在刷题场景里几乎是绕不开的选择。它比 H5 多一层微信登录态用户点进来不用注册比 App 少一道安装门槛课程群里丢个码就能打开。更重要的是小程序原生组件里 scroll-view、radio-group、textarea 这些都能直接服务于答题交互做单选题、多选题、主观题都不需要额外引 UI 框架。对于 Spring Boot 后端来说前端只要按接口规范对接后端不用关心页面渲染分工很干净。从技术栈的选择上小程序端我一般会坚持“原生 微信开发者工具”而不是 uni-app。原因很实际uni-app 虽然能一套代码跑多端但刷题页面会用到较多的原生交互比如滑动切换题目、答题卡跳转uni-app 在微信端的兼容处理会多出一层黑匣子毕业设计答辩时老师更愿意看到你对原生小程序生命周期、setData、缓存机制有真实理解。如果你毕业设计文档里要写“前端技术选型”原生小程序也比跨端框架更好讲清楚。顺着这个思路后端选 Spring Boot 也不是因为它“热门”而是因为它能帮你把“用户身份”“题库管理”“判题服务”拆成清晰的分层模块。Spring Boot 的自动化配置能把一个 Web 服务在十几分钟内拉起来加上 MyBatis-Plus 操作数据库、Redis 做会话和排行榜缓存整个刷题系统后端可以控制在六到八张表以内规模刚好适合作为毕业设计又不至于像纯 CRUD 那样缺少技术深度。2.2 后端工程骨架controller、service、mapper 三层与启动流程刷题系统的后端如果按“能展示、能答辩、能扩展”三个标准来搭我一般会分成四个部分controller 层只接收参数、调用 service、返回统一结果不写业务判断。service 层承载判题逻辑、错题生成、刷题记录的更新策略。mapper 层通过 MyBatis-Plus 的 BaseMapper 直接完成单表操作多表统计再写 XML。common 层统一返回体 Result、全局异常处理、JWT/Token 工具类。实际工程里新建一个 Spring Boot 项目后我第一件事不是写代码而是先改 pom.xml把依赖范围固定下来。一个刷题后端最常用的依赖组合是这样的dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies注意这个组合里没有放 spring-boot-starter-security因为刷题系统不需要复杂的密码流程登录态用 JWT 自己管理更直观。MyBatis-Plus 3.5.3 这套版本我用得比较多和 Spring Boot 2.7 配合稳定如果你新建项目时选了 Spring Boot 3.x 或更高版本要注意 javax.servlet 已经迁到 jakarta.servlet第 5 章避坑部分会专门讲。依赖确定后工程目录建议按功能包名组织com.example.exam ├── ExamApplication.java ├── common │ ├── Result.java │ └── GlobalExceptionHandler.java ├── controller │ ├── AuthController.java │ ├── QuestionController.java │ └── RecordController.java ├── service │ ├── UserService.java │ ├── QuestionService.java │ └── RecordService.java ├── mapper │ ├── UserMapper.java │ ├── QuestionMapper.java │ └── RecordMapper.java └── entity ├── User.java ├── Question.java └── Record.java这个包结构看起来很常规但它的好处在于答辩和后续替换都非常顺手。比如你想引入 Redis 做热点题排行榜只需要在 service 层加一个缓存组件controller 不用动。想给小程序端加一个章节练习题只需要在 controller 层新增一个接口前端按新的接口对接。2.3 前后端联调约定统一返回体和 Token 让小程序端不猜接口小程序端和后端之间最容易扯皮的就是返回体不一致。后端返回一个 Map小程序端就得在每次请求里判断 data 下面是什么结构。我的做法是后端统一给出一个 Result 类每个接口都必须返回这个结构public class ResultT { private Integer code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.message success; r.data data; return r; } public static T ResultT error(int code, String message) { ResultT r new Result(); r.code code; r.message message; return r; } }这里的参数含义分别是code 用来区分业务状态200 代表成功401 代表登录过期其他值按业务自己扩展message 给前端直接弹提示用data 才是真正的业务数据。小程序端在封装 request 方法时只需要统一判断 code 是否为 200再返回 data页面层拿到的永远是干净的数据。Token 的交互链路上我没有用微信提供的 session_key 直接做鉴权而是让后端用自己的密钥签发一个 JWT。流程是小程序调用 wx.login 拿到 code后端拿着 code 去微信接口换取 openid然后生成 token 返回给小程序小程序后续请求在 header 里带 Authorization后端用一个拦截器统一解析。这样做的优势有两个第一后端对用户身份的判定不依赖微信 session 的有效期第二token 过期时间可以由你控制刷题系统里我一般设 7 天用户一周内不用重复登录。3. 后端核心实现题库表结构、判题逻辑与刷题接口参数3.1 三张核心表题目表、选项表、刷题记录表怎么设计刷题系统的数据模型不需要很复杂但表关系要足够支撑“一套题反复刷”。我见过不少同学把题目字段塞在一张表里选项用逗号分隔存成一个字符串这样写 demo 很快但一旦做错题记录、统计正确率就会非常别扭。我一般会把题库拆成三张核心表题目表、选项表、刷题记录表。第一张是题目表 question 表它只存题目本身的描述、类型、正确答案和难度分数不存选项文本。第二张是选项表用 question_id 关联题目保存 A/B/C/D 文本。第三张是刷题记录表记录某个用户某次作答的答案、对错和耗时。为什么选项要单独建表因为一套卷子里的选项个数不固定单选题四个选项、判断题只有对错、多选题五个选项选项作为独立表才能在不改表结构的情况下扩展题型。直接给一份可以抄作业的建表 SQLCREATE TABLE t_question ( id bigint NOT NULL AUTO_INCREMENT COMMENT 题目ID, type tinyint NOT NULL DEFAULT 1 COMMENT 题型 1单选 2多选 3判断, content text NOT NULL COMMENT 题干, correct_answer varchar(255) NOT NULL COMMENT 正确选项多选用逗号分隔, analysis text COMMENT 答案解析, difficulty tinyint DEFAULT 1 COMMENT 难度 1-5, score int DEFAULT 1 COMMENT 分值, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT题目表; CREATE TABLE t_option ( id bigint NOT NULL AUTO_INCREMENT, question_id bigint NOT NULL COMMENT 题目ID, option_key varchar(16) NOT NULL COMMENT 选项标识 A/B/C/D, option_text varchar(500) NOT NULL COMMENT 选项内容, sort_order int DEFAULT 0 COMMENT 排序, PRIMARY KEY (id), KEY idx_question_id (question_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT选项表; CREATE TABLE t_record ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 用户ID, question_id bigint NOT NULL COMMENT 题目ID, user_answer varchar(255) NOT NULL COMMENT 用户作答, is_correct tinyint NOT NULL COMMENT 0错误 1正确, duration int DEFAULT 0 COMMENT 耗时秒数, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_question (user_id,question_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT刷题记录表;这里有几个参数要说明type 字段用 tinyint 而不是字符串是为了让后端在判断题型时直接用整数比较避免“单选”“多选”这类中文值在前后端对接时出现编码问题correct_answer 在多选题里用逗号分隔比如 “A,C”这样判断题正确答案是 A 或 B也能复用同一个字段。t_record 里故意加了 duration 字段它看上去不起眼但统计用户平均做题速度、生成学习报告都需要它。索引方面t_record 的联合索引 idx_user_question 是关键否则用户刷到一万条题目后查询“这道题我做过没有”就会很慢。3.2 题目接口设计列表接口、提交接口与判题的三个边界条件接口设计上刷题系统至少需要五个接口登录、获取题目列表、提交答案、获取错题本、清空错题。这里重点看两个最常见的接口一个是带条件的题目列表接口一个是提交答案的判题接口它们的参数设计直接影响小程序端工作量。题目列表接口我一般设计成GetMapping(/question/list) public ResultPageResultQuestionVO list( RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 20) Integer size, RequestParam(required false) Integer type, RequestParam(required false) Integer difficulty) { return Result.ok(questionService.pageQuery(page, size, type, difficulty)); }参数说明page 和 size 控制分页默认 1 和 20两者都加上限校验size 最大不能超过 50防止小程序端一次拉取过多题目type 和 difficulty 是筛选条件不传就查全部。注意不要把小程序的页面滚动深度当成分页参数传过来直接内存里维护当前页号更稳。提交答案接口是整个系统的核心它的判题逻辑看起来只是“比对字符串”但实际有三个边界条件容易出错多选题顺序、判断题的表现形式、以及重复提交。这三个点都会让判题结果前后不一致。我常用的实现方式是把答案规范成有序字符串再比较PostMapping(/record/submit) public ResultSubmitResult submit(RequestBody SubmitRequest request, RequestAttribute Long userId) { Question question questionService.getById(request.getQuestionId()); if (question null) { return Result.error(404, 题目不存在); } boolean correct compareAnswer(question.getCorrectAnswer(), normalizeAnswer(request.getAnswer())); Record record new Record(); record.setUserId(userId); record.setQuestionId(question.getId()); record.setUserAnswer(normalizeAnswer(request.getAnswer())); record.setCorrect(correct); record.setDuration(request.getDuration()); recordService.save(record); SubmitResult result new SubmitResult(); result.setCorrect(correct); if (!correct) { result.setCorrectAnswer(question.getCorrectAnswer()); result.setAnalysis(question.getAnalysis()); } return Result.ok(result); } private boolean compareAnswer(String correct, String userAnswer) { if (correct null || userAnswer null) { return false; } String[] correctArr correct.split(,); String[] userArr userAnswer.split(,); if (correctArr.length ! userArr.length) { return false; } Arrays.sort(correctArr); Arrays.sort(userArr); return Arrays.equals(correctArr, userArr); } private String normalizeAnswer(String answer) { if (answer null) { return ; } String[] arr answer.toUpperCase().split(,); Arrays.sort(arr); return String.join(,, arr); }这段代码能跑通单选、多选和判断三类题型。逻辑说明前端传来的答案是用户点选的选项标识组成的字符串比如多选题选了 A、C 就传 “A,C”。后端先 normalizeAnswer 做强转大写并按字母排序再比较。这里的用意很深多选题不管用户点击顺序是 A-C 还是 C-A排序后都会一致不会出现“明明选对了却判错”。correctAnswer 为空时compareAnswer 会返回 false这样库里的脏数据至少不会产生异常。SubmitResult 里只对错误答案返回正确选项和解析比直接把正确答案暴露给前端更适合刷题记忆。提交接口的参数表如下参数类型必填说明questionIdLong是题目 IDanswerString是用户作答多选用逗号分隔如 A,CdurationInteger否做题耗时秒数用于统计学习报告顺带一提错题本接口不用单独建错题表从 t_record 里查 is_correct0 记录聚合即可。很多毕业设计为了显得功能多会单独建一张错题表反而导致每次提交要同时维护两张表容易出现错题记录和刷题记录不一致。我建议错题本就用查询来实现等有“收藏错题”需求时再单独落表。正确率统计直接用一条 SQL 就能拿下来SELECT COUNT(*) AS total, SUM(is_correct 1) AS correct_count FROM t_record WHERE user_id #{userId};4. 小程序端从登录到答题的最小闭环wx.login、题目渲染与答案提交4.1 登录链路wx.login 换 code后端再换 openid 并返回 JWT小程序端要做的第一件事就是登录。很多初学者会直接在 setData 里存 openid甚至把 openid 放到请求参数里这是非常危险的。openid 等于用户唯一身份标识一旦被拿到别人可以直接伪造请求。正确流程是用户进入小程序后先调 wx.login 获取临时 code把这个 code 发给后端后端用 code 去微信的登录凭证校验接口换 openid后端生成 token 返回给小程序小程序把 token 存到 storage后续请求在 header 里带头。整个过程 openid 始终不经过前端落盘。对应的小程序端登录代码const login () { wx.login({ success: async (res) { if (!res.code) { wx.showToast({ title: 登录失败, icon: none }); return; } const loginResult await request({ url: /api/auth/login, method: POST, data: { code: res.code, nickname: 微信用户 } }); if (loginResult.code 200) { wx.setStorageSync(token, loginResult.data.token); wx.setStorageSync(userInfo, loginResult.data.userInfo); } else { wx.showToast({ title: loginResult.message, icon: none }); } } }); };这里有两个参数要注意code 是临时凭证有效期只有五分钟所以后端必须马上拿去换 openid不能把 code 存库nickname 是可选字段如果小程序端不做头像昵称填写后端就用默认值创建用户这样不会影响后续刷题记录的用户关联。request 函数里还需要统一带上 Authorization 头封装方式如下const request (options) { return new Promise((resolve, reject) { const token wx.getStorageSync(token); wx.request({ url: getApp().globalData.baseUrl options.url, method: options.method || GET, data: options.data || {}, header: { Authorization: token ? Bearer ${token} : , Content-Type: application/json }, success: (res) { if (res.data.code 401) { wx.removeStorageSync(token); login(); return; } resolve(res.data); }, fail: (err) reject(err) }); }); };这段封装里值得留意的是 401 处理token 过期后不是弹个错误就完事而是清掉本地 token 后重新调用 login用户几乎感觉不到登录被中断。Content-Type 必须写成 application/json否则 Spring Boot 的 RequestBody 拿不到参数这是小程序端最常见的联调翻车点之一。如果你所在的项目是培训机构、驾考这类需要手机号的场景可以用微信的“手机号快捷验证组件”拿手机号那需要在微信公众平台开通对应接口权限个人主体不支持一般毕业后你所在的公司主体才有。毕业设计阶段不建议在这上面花时间用 openid token 已经足够演示完整流程。4.2 刷题页面一题一屏的交互与答题状态管理题目页面我建议做成“一题一屏”而不是一页长列表。这样刷题时专注度高交互也简单滑动或点击“下一题”切换点击选项选中点击提交由后端判题。页面结构大致是顶部显示答题进度中间是题干和选项列表底部是提交按钮。这里的关键状态有三个当前题目索引 currentIndex、用户选择 answerMap、题目数据 questions。每次点击选项先更新当前题的选中项切换题目时再把选中项填入 answerMap。用原生小程序语法实现如下Page({ data: { questions: [], currentIndex: 0, currentQuestion: {}, selectedKeys: [], answerMap: {}, totalCount: 0 }, async onLoad() { const res await request({ url: /api/question/list?page1size20, method: GET }); if (res.code 200) { const questions res.data.records; this.setData({ questions, totalCount: questions.length, currentQuestion: questions[0] }); } }, onOptionTap(event) { const key event.currentTarget.dataset.key; const q this.data.currentQuestion; let selected this.data.selectedKeys; if (q.type 1) { selected [key]; } else { const idx selected.indexOf(key); if (idx -1) { selected.splice(idx, 1); } else { selected.push(key); } } this.setData({ selectedKeys: selected }); }, onNext() { const nextIndex this.data.currentIndex 1; if (nextIndex this.data.totalCount) { wx.showToast({ title: 已是最后一题, icon: none }); return; } const answered Object.assign({}, this.data.answerMap); answered[this.data.currentQuestion.id] this.data.selectedKeys.join(,); this.setData({ currentIndex: nextIndex, currentQuestion: this.data.questions[nextIndex], selectedKeys: answered[this.data.questions[nextIndex].id] ? answered[this.data.questions[nextIndex].id].split(,) : [], answerMap: answered }); }, onSubmit() { const q this.data.currentQuestion; if (!this.data.selectedKeys.length) { wx.showToast({ title: 请先选择答案, icon: none }); return; } request({ url: /api/record/submit, method: POST, data: { questionId: q.id, answer: this.data.selectedKeys.join(,), duration: 20 } }).then((res) { if (res.code 200) { wx.showModal({ title: res.data.correct ? 回答正确 : 回答错误, content: res.data.correct ? 继续加油 : 正确答案${res.data.correctAnswer}\n${res.data.analysis || }, showCancel: false, success: () this.onNext() }); } }); } });这段代码里最容易出错的是单选和多选共用状态数组 selectedKeys。我的处理方式是单选时把数组重置为只包含当前点击的那一个多选时先判断已经存在就移除。onOptionTap 里的 event.currentTarget.dataset.key 是选项的标识需要在 wxml 的选项 view 上通过>async loadWrongQuestions() { const res await request({ url: /api/record/wrong, method: GET }); if (res.code 200) { const groupMap {}; res.data.forEach((item) { const dateKey item.createdAt.substring(0, 10); if (!groupMap[dateKey]) { groupMap[dateKey] []; } groupMap[dateKey].push(item); }); this.setData({ wrongGroups: groupMap }); } }这里按日期分组只是展示层的处理数据还是来自后端的 t_record 查询。groupMap 这种对象结构在 setData 时需要整体赋值注意最好不要直接改 this.data.wrongGroups 再 setData而是先构造好新对象再一次性赋值。如果错题本数据量很大可以改成只加载最近 20 条并加一个“加载更多”按钮分页拉取但这个功能的优先级不高毕业设计答辩时能把错题展示出来已经足够完整。5. 上线前必看的避坑清单域名校验、并发覆盖与版本兼容5.1 小程序请求一直失败明明用开发者工具能打开网页request 却报 10002现象小程序真机预览时所有请求都失败console 提示 request:fail 或 url not in domain list错误码 10002。原因微信小程序对请求域名有强制校验正式体验版只能请求已在 mp 后台配置的合法域名而且必须是 HTTPS。本地开发时能打开是因为开发者工具里勾选了“不校验合法域名”。这个机制让很多第一次写小程序的同学卡好多天。解决开发阶段在微信开发者工具的“详情-本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。部署上线前在小程序管理后台把后端接口域名加到 request 合法域名。如果你没有备案域名本地联调阶段可以用局域网 IP 加端口配合“不校验域名”到了生产环境必须换成 HTTPS 域名否则审核必被拒。这个问题和 Spring Boot 后端本身没有关系但最容易让前后端互相甩锅。5.2 Spring Boot 3.x 的 javax 与 jakarta 冲突启动直接 ClassNotFound现象用 Spring Initializr 生成最新版 Spring Boot 3.4 项目引入自己的工具类后启动失败异常里出现 javax.servlet.ServletException 或 ClassNotFoundException: javax.servlet.Filter。原因Spring Boot 3 把 Servlet API 从 javax 迁到了 jakarta 命名空间。网上很多资料和老项目代码还是 import javax.servlet.*。直接把旧代码复制进来Maven 可能同时引入 javax.servlet-api 和 spring-boot-starter-web两个容器相关的 API 并存导致类加载冲突。很多同学看到“springboot版本太高”相关的报错就不知所措其实根因就是命名空间迁移。解决如果不想改代码最简单的办法是创建项目时把 Spring Boot 版本降到 2.7.x并使用 JDK 8 或 11。如果非要在 Spring Boot 3.x 上做就把所有 import javax.servlet.* 改成 import jakarta.servlet.*。还有一个容易忽略的地方拦截器里用到 HttpServletRequest 的也全部要换成 jakarta 的包。刷题系统里的登录拦截器正好会踩到这个写的时候要注意。5.3 并发提交导致记录覆盖同一道题提交两次错题状态被“洗掉”现象用户快速连点提交按钮数据库里同一个 (user_id, question_id) 出现多条记录或者后一次提交把前一次的正确状态覆盖了统计正确率时数据对不上。原因如果 service 层用“先查后改”的方式写记录两个请求同时到达会出现丢失更新请求 A 查到 is_correct0请求 B 也查到 is_correct0A 改为正确写回B 也把同一条改为错误写回最终结果被最后提交覆盖。这不是微信小程序端的 bug而是后端接口没有做幂等。解决两种做法。第一给 t_record 加唯一索引 (user_id, question_id, created_at 的分钟级时间戳)从数据库层面限制同一秒重复提交第二更推荐的做法是提交接口幂等同一题只允许保存一次重复提交时走 UPDATE 而不是 INSERT。伪代码如下int count queryCount(userId, questionId); if (count 0) { recordService.save(record); } else { recordService.updateLatest(userId, questionId, correct, userAnswer); }注意这里 queryCount 和 save 之间仍有微小窗口真正严谨的做法是加唯一索引兜底。毕业设计如果想体现“并发意识”可以在答辩文档里写清楚这个幂等策略并配合前置唯一键说明。5.4 数据库时间少了 8 小时刷题记录时间全部偏移现象小程序端展示的“上次做题时间”比实际少了 8 小时或者 t_record 里 created_at 存的是本地时间但查出来却是 UTC 时间。原因MySQL 的 JDBC 连接串里没有写 serverTimezoneSpring Boot 2.x 又默认把数据库时间按 JVM 时区转换。多数国内服务器是 Asia/Shanghai但本地开发机可能是其他时区导致写入和读取各偏一次。这个问题不怪 MySQL也不怪 Spring Boot纯粹是连接参数没配全。解决在 application.yml 的数据库连接参数里显式指定时区url: jdbc:mysql://localhost:3306/exam?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse并把 MySQL 的全局时区设为 08:00SET GLOBAL time_zone 08:00;这两步做完时间字段前后就一致了。另外时间字段统一用 datetime不要用 timestamp避免 2038 年问题。虽然比较远但答辩时提一句能加分。5.5 页面白屏或渲染卡顿setData 一次塞了太多数据现象题目列表加载后页面白屏或者滑动很卡打开调试面板发现 setData 的数据结构里有大量嵌套字段。原因微信小程序的 setData 有性能和大小限制单次 setData 数据过大或者路径层级太深都会导致渲染失败。直接用后端返回的嵌套对象比如把 options 塞进 question 再塞进 questions 数组一次 setData数据嵌套就会越来越深。解决数据进入小程序端后先做一次“扁平化”。每题只保留 id、type、content、optionList 四个字段option 里的 sort_order、question_id 等字段全部删掉。展示时用“当前题”单独 setData而不是把整个数组替换进页面。上面第 4 章的代码里我用的就是这个策略questions 存全量数据但 currentQuestion 单独控制渲染这样页面数据流很干净也不会出现白屏。6. 交付前的最后一步接口验证、HTTPS 部署与一个小技巧系统开发完不要直接点“上传”我会先做一轮接口验证。刷题系统的核心链路是登录、选题、提交、查错题这四个动作先用 curl 跑通再上小程序端能省下一大半联调时间。比如提交答案的接口在本地直接用测试 token 验证curl -X POST http://localhost:8080/api/record/submit \ -H Content-Type: application/json \ -H Authorization: Bearer test-token \ -d {questionId:1,answer:C,A,duration:12}返回里能看到 correct 字段是 true 还是 false以及答案规范化后是否和正确选项一致。这一步过了再打开开发者工具调页面。部署时我习惯用 profile 区分环境。application-dev.yml 放本地数据库和日志application-prod.yml 放云数据库和线上日志。构建命令固定为mvn clean package -DskipTests拿到 jar 后放到云服务器用 nohup 启动nohup java -jar exam.jar --spring.profiles.activeprod exam.log 21 nginx 里只需要一条反向代理规则把 /api 指到后端服务location /api { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }小程序端的 baseUrl 可以在 app.js 里做一个环境开关开发环境指向局域网 IP生产环境指向 HTTPS 域名这样切换环境时不用改业务代码。我自己做这类系统时最后总会花十分钟用开发者工具的“真机调试”把答题闭环过一遍确认切题、提交、错题弹窗、再登录这四条路径没问题再走提审。刷题系统的技术难点不多但每一环都值得在交付前亲手验证一遍。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询