ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot + uniapp 公考学习平台:从架构到上线全解析

Spring Boot + uniapp 公考学习平台:从架构到上线全解析 简介一份基于Spring Boot和uniapp的公考学习平台完整源码针对公务员考试备考场景采用前后端分离架构前端可发布为微信小程序后端提供在线做题、学习资源浏览、用户管理、题库维护与内容发布等接口并自带管理员端功能适合高校学生、Java学习者用于全栈实训、毕业设计或二次开发。技术栈以Java为主后端基于Spring Boot开发支持JDK1.8与MySQL5.7环境前端使用uniapp构建兼容微信小程序端整体工程结构清晰。压缩包共1347个文件、约27.92MB其中包含257个Vue组件、161个SVG图标、160个JS脚本、145个JSON配置、144个Java源码、134个PNG图片、108个Markdown说明文档以及WXML/WXSS小程序页面、SQL数据库脚本、BAT一键部署脚本等基本覆盖从源码、资源到数据库与文档的完整项目组成前端组件、后台逻辑、数据库脚本与说明文档均有独立归类便于按模块阅读和修改。包内还放置了1-install.bat、2-run.bat、3-build.bat分别覆盖环境初始化、项目启动与前端打包场景并附带rar备份包、mp3音频资源及CSS/Map构建产物便于理解从开发到构建的完整流程Markdown说明文档与SQL初始化脚本进一步降低了上手门槛可辅助梳理数据库表结构和接口调用关系。目前已有53人学习下载可作为全栈项目实践与二次开发的重要参考。1. Spring Boot uniapp 的公考学习平台先看它到底能解决什么公考备考最大的痛点其实不是没题刷而是刷题效率低错题不沉淀、同一道题反复错、手机和电脑的进度不通。这套基于 Spring Boot uniapp 的公考学习平台源码把题库管理、刷题、模拟考试、错题本、后台管理做成了一个完整闭环——后端用 Spring Boot 提供接口前端用 uniapp 一套代码编译成微信小程序、H5 和安卓 App数据实时同步。它适合两类人一是想做备考类产品原型验证的独立开发者二是拿它当 Spring Boot uniapp 全栈课设的学生能跑、能改、能讲清原理。这份资源最值钱的地方在于它不是孤立的页面模板而是把「用户登录 → 题目列表 → 答题 → 提交判分 → 错题沉淀 → 模拟考试」这条完整业务链路打通了拿到当天就能跑起来对着表结构改业务省掉从零搭框架的时间。2. 架构与数据设计这套公考平台为什么这么拆2.1 技术选型Spring Boot uniapp 的组合逻辑先说后端。公考学习平台这类业务核心是题库的增删改查和答题判分没有高并发实时计算也没有复杂的分布式事务。Spring Boot 2.x MyBatis-Plus 是这类系统最稳的组合MyBatis-Plus 的代码生成器和 LambdaQueryWrapper 能省掉大量重复 SQL。我一般建议新手优先用 Spring Boot 2.7 而不是 3.x原因很实际3.x 强制依赖 Jakarta EE很多教程和旧依赖都要跟着换对于下载源码来改的人来说2.7 的生态兼容性更省事。前端选 uniapp 的理由更直接公考用户大头在微信小程序但讲师和管理员又需要 H5 管理后台。uniapp 一套 Vue 代码同时编译到微信小程序、H5 和 App不用为每个端单独写一套。需要说明的是uniapp 在 iOS 和安卓原生能力上有差异但题库这种以列表和表单为主的业务绝大部分页面走的是 Vue 语法跨端踩坑面很小。2.2 业务模块拆解整个平台按角色分成两端用户端和后台管理端。用户端主要包含五个模块账号登录、题库浏览、刷题答题、错题本、模拟考试后台管理端包含题目管理、分类管理、用户管理、公告管理。用户端的核心链路是这样的用户登录后拿到 token带着 token 请求题目列表接口选择某道题作答后提交答案后端判分并返回解析答错的题自动进入错题本错题本支持按分类筛选和移除。模拟考试模块则是按题型固定抽取一定数量的题目组成试卷提交后按规则打分并展示正确率。后台管理是很多人容易忽略的部分。公考平台的题目数据是不断更新的没有管理后台题库只能靠 SQL 硬灌这种工程无法持续。这套源码里的管理后台实现了题目的批量导入和分类维护实际用下来导入一份行测题库 CSV 就是一次接口调用的事。数据流基本是单向的管理端写入题目 → 用户端读取题目 → 答题结果写回答题记录和错题表。这里有个设计细节答题记录表只保存题目 id 和用户答案不冗余题目内容这样题目后续修改时历史答题记录不会产生版本冲突。这套源码里权限控制也做了基础处理后台管理接口需要管理员角色user 表有个 role 字段拦截器里解析完 token 后会再查一次用户角色普通用户访问管理接口返回 403。这个设计在你做课设答辩时是实打实的加分点很多同学的毕设连角色权限都只是前端做个判断。2.3 数据库设计要点公考学习平台的表结构不算复杂核心是这几张表表名作用关键字段user用户username, password(BCrypt), nicknamecategory题目分类name, sortquestion题目question_type, option_json, answer, score, category_idanswer_record答题记录user_id, question_id, user_answer, is_correcterror_book错题本user_id, question_id, sourceexam_record模拟考试记录user_id, answer_snapshot, score, duration_seconds题目表的设计要注意两个字段question_type和option_json。question_type区分单选、多选、判断三种题型判分逻辑完全依赖这个字段option_json用 JSON 字符串存选项而不是单独建选项表因为公考题目的选项数量固定四个或五个JSON 存比关联查询简单得多也不会有多表联查的性能问题。错题本表不存冗余数据只持有user_id和question_id两个外键加一个source字段标记错题来源是普通刷题还是模拟考试。这样设计的好处是错题本天然支持去重同一道题反复答错也不会出现多条记录只需要更新update_time。模拟考试记录表稍微特殊一点它需要保存用户提交的完整答案快照因为考试成绩要支持事后回看不能让题目更新影响历史成绩。所以这张表里既有answer_snapshotJSON 存的提交答案也有score和duration_seconds。设计时把「当前数据」和「历史快照」分开是这类系统比较关键的经验。提示下载源码后先看sql/目录下的初始化脚本把表结构建齐再启动后端否则首次启动会因缺表报错。索引方面answer_record 表是数据量增长最快的一定要建 (user_id, question_id) 联合索引error_book 表建议建 (user_id, category_id) 联合索引不然用户刷题量上来后错题列表接口会越来越慢。源码里初始化脚本已经带了这两组索引你如果自己改表结构别把索引弄丢。3. 后端实现登录鉴权与刷题判分的接口细节3.1 工程结构与启动配置源码的后端工程是标准 Maven 结构包名按 controller、service、mapper、entity、config 分层。入口类在Application.java重点看application.yml里面有几个参数是拿到源码后必须改的。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/gongkao?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: gongkao-secret-key-please-change expire-hours: 24serverTimezoneAsia/Shanghai这个参数很关键MySQL 5.7 以上如果缺这个时间字段会有 8 小时偏差。连接池用户名和密码按环境改jwt.secret上线前必须换不然别人能用默认密钥伪造 token。MyBatis-Plus 的逻辑删除配置是源码的一个细节题目表删除用逻辑删除而不是物理删除这样模拟考试回看历史记录时不会因为题目被删而取不到解析。3.2 登录鉴权与拦截器登录接口用的是用户名密码 JWT 方案没接短信验证码因为公考学习平台这种场景下短信验证码需要第三方服务且有费用源码做了简化。密码存的是 BCrypt 加密后的密文。PostMapping(/api/user/login) public ResultUserVO login(RequestBody LoginDTO dto) { User user userMapper.selectOne( new LambdaQueryWrapperUser() .eq(User::getUsername, dto.getUsername())); if (user null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return Result.error(用户名或密码错误); } String token JwtUtil.createToken(user.getId(), user.getUsername()); return Result.ok(new UserVO(user.getId(), user.getNickname(), token)); }逻辑很直观先按用户名查用户查不到或密码校验失败直接返回统一错误。这里用BCrypt.checkpw而不是把密码解密出来对比因为 BCrypt 是不可逆加密只能通过同样的算法验证。JwtUtil.createToken把用户 id 和用户名塞进 token后续接口通过拦截器解析 token 拿到当前用户。拦截器是后端鉴权的核心。在WebMvcConfig里注册一个HandlerInterceptor拦截/api/user/**和/api/exam/**等需要登录的路径白名单放行登录和注册接口。每次请求进来先取 Header 的Authorization解析失败直接返回 401解析成功把 userId 塞进ThreadLocalservice 层随时取。注意ThreadLocal用完要在afterCompletion里 remove不然线程池复用会串用户。3.3 刷题接口与错题本刷题接口是查询密集型的我一般会加缓存。题目是低频变更数据用 Redis 按分类和题型做 key能明显降低数据库压力。缓存更新策略是后台管理端修改题目时删除对应分类的缓存 key。GetMapping(/api/question/list) public ResultPageQuestionVO list( RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) Long categoryId) { PageQuestion p new Page(page, size); LambdaQueryWrapperQuestion qw new LambdaQueryWrapper(); qw.eq(categoryId ! null, Question::getCategoryId, categoryId); qw.orderByAsc(Question::getSort); PageQuestion result questionService.page(p, qw); return Result.ok(convertToVO(result)); }分页参数page和size由前端传categoryId可选。qw.eq(categoryId ! null, Question::getCategoryId, categoryId)是 MyBatis-Plus 的条件拼接categoryId 为空时不拼这条条件。排序用sort字段而不是id因为题库顺序要支持人工调整调整后用户刷题的顺序跟着变更接近真实题序。错题本的两个操作是添加和移除。添加由后端在提交答案且判分为错误时自动写入不需要前端额外调用移除是用户手动操作走删除接口。这里我踩过一个坑一开始把添加错题的时机放在前端结果用户答错后立刻退出页面前端请求没发出去错题就丢了。后来改成后端在判分逻辑里同步写错题表才彻底解决。普通刷题的提交接口在ExamController里路径是/api/exam/submit它和模拟考试提交共用同一套 judge 判分方法只是不生成考试记录、只写答题记录和错题本。你改判分规则时两个接口都会生效。3.4 模拟考试的判分规则模拟考试判分是业务逻辑最集中的地方。它要处理单选、多选、判断三种题型的混合判分还要统计正确率、耗时和得分。public ExamResultVO submitExam(ExamSubmitDTO dto) { ListAnswerItem answers dto.getAnswers(); int correctCount 0; int totalScore 0; for (AnswerItem item : answers) { Question q questionMapper.selectById(item.getQuestionId()); boolean isCorrect judge(q, item.getUserAnswer()); if (isCorrect) { correctCount; totalScore q.getScore(); } else { errorBookService.add(dto.getUserId(), q.getId(), exam); } } return new ExamResultVO(correctCount, totalScore, answers.size() - correctCount); } private boolean judge(Question q, String userAnswer) { if (single.equals(q.getQuestionType()) || judge.equals(q.getQuestionType())) { return q.getAnswer().equals(userAnswer); } if (multiple.equals(q.getQuestionType())) { String[] correct q.getAnswer().split(,); String[] user userAnswer.split(,); if (correct.length ! user.length) return false; SetString set new HashSet(Arrays.asList(correct)); for (String u : user) if (!set.contains(u)) return false; return true; } return false; }判分规则单选和判断必须完全一致才算对多选要求选项集合完全匹配漏选、多选、错选都不给分。这是「严格判分」适合模拟考试。如果你想让用户端体验更友好可以把多选改成「漏选得一半分」但我建议不要直接在源码默认逻辑上改而是抽象一个判分策略否则用户会发现分数浮动不稳定。judge方法里多选判断用了Set做包含检查这里有个关键坑userAnswer里的选项顺序可能和正确答案顺序不一致比如正确答案是 A,C用户答 C,A字符串比较会误判为错误。所以必须拆开后用集合比对源码的处理是正确的。4. uniapp 前端落地跨端编译与小程序打包要点4.1 页面结构与路由设计uniapp 前端的页面按业务模块组织pages目录下每个子目录对应一个页面。首页是题库分类入口做题页承载刷题和模拟考试错题本页展示错题列表我的页面放个人信息和设置。{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 公考学习 } }, { path: pages/exam/exam, style: { navigationBarTitleText: 模拟考试 } }, { path: pages/error-book/error-book, style: { navigationBarTitleText: 错题本 } }, { path: pages/mine/mine, style: { navigationBarTitleText: 我的 } } ] }页面跳转用uni.navigateTo传参通过 URL query。这里有个经验做题页和结果页之间传数据不要试图把整张试卷塞进 URL 参数URL 长度在小程序端有限制。正确做法是只传examId结果页根据 id 重新拉后端接口拿详情。路由配置里有个容易忽略的问题首页的navigationBarTitleText一定要改成自己的产品名。小程序审核时名称和实际内容不符会被驳回。4.2 请求封装与登录态同步uniapp 里请求不能用 axios要用uni.request。源码封装了一个request.js统一处理 baseURL、token 注入、错误提示和 401 跳转。const BASE_URL http://localhost:8080/api; export function request(url, method GET, data {}) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL url, method, data, header: { Authorization: uni.getStorageSync(token) || , Content-Type: application/json }, success: (res) { if (res.statusCode 401) { uni.removeStorageSync(token); uni.navigateTo({ url: /pages/login/login }); reject(res); } else if (res.statusCode 200 res.data.code 0) { resolve(res.data.data); } else { uni.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res); } }, fail: (err) { uni.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); }这个封装有几个关键点。baseURL 要改成后端实际地址本地调试用 localhost真机预览必须换成电脑局域网 IP。uni.getStorageSync(token)从本地存储取 token 注入请求头后端拦截器靠这个头识别用户身份。401 处理是整个封装里最重要的逻辑。返回 401 说明 token 过期或无效这时要清掉本地 token 并跳转登录页否则用户会一直停留在「请求失败」的报错里。很多半成品项目把 401 当普通错误提示体验非常差。4.3 微信小程序打包与体积控制uniapp 打包微信小程序是高频操作但有一个绕不过去的坎微信小程序主包限制 2MB。公考学习平台如果有题目配图或大尺寸 UI 素材很容易超。源码的静态资源尽量精简但如果你加入自己的 logo 和题图还是要主动控制。manifest.json 里需要配mp-weixin的appid不配的话编译能过但真机预览和上传会报错。另外一个配置是 HBuilderX 可视化界面里把「运行环境 → 压缩」勾上能减少一部分包体积。提示小程序编译产物超过 2MB 时优先检查static目录下的图片是否被 base64 内联了其次检查是否直接 import 了整个 uni-ui 组件库。按需引入组件能把体积砍掉大半。打包之后的第一件事不是看效果而是先在微信开发者工具「详情」面板里看「本地代码」体积分布定位是哪个模块占了大头。图片资源一律建议走 CDN 或对象存储外链不要放在前端包里。实例一次项目里把一套行测题目的解析图全部打成本地资源包体积直接爆表改成 OSS 外链后主包从 2.8MB 降到 900KB。5. 避坑实录联调与部署最容易翻车的五个细节这五个问题按出现频率排前两个基本联调第一天就会撞上后三个会在功能测试和上线前夕冒出来。每一条都对应源码里一个具体模块改起来不涉及大重构。5.1 真机预览请求失败这是联调第一天最容易碰到的。现象在微信开发者工具里接口正常但手机扫码预览后所有请求都报request:fail或超时。原因开发者工具默认不校验合法域名但真机环境下微信强制校验本地localhost后端地址在手机上根本不存在必须用局域网 IP另外如果后端接口是http://而非https://真机预览同样会被拦截。解决开发阶段用http://本机局域网IP:8080作为 baseURL并在微信开发者工具「详情 → 本地设置」里勾选「不校验合法域名」真机预览需要在同一局域网正式上线必须配 HTTPS 并在小程序后台把接口域名加入 request 合法域名。5.2 冷启动后登录态丢失现象小程序冷启动后进入页面接口全部返回 401 跳回登录页但用户明明已经登录过。原因uniapp 冷启动时uni.getStorageSync(token)取不到值一般是 token 存错位置了。有些人会把 token 只存在全局变量globalData里冷启动时全局变量重新初始化token 就没了。解决token 必须持久化存储用uni.setStorageSync写入本地缓存不要只挂在globalData。同时在App.vue的onLaunch里做静默鉴权本地有 token 就先调GET /api/user/info验证有效性有效直接进首页无效才跳登录。5.3 多选判分与预期不一致现象用户答了 A、C正确答案是 C、A系统判错或者用户漏选一项系统直接判 0 分。原因前端提交的答案字符串顺序是用户点击顺序后端直接做字符串相等比较时选项顺序不同就会误判。漏选给不给分是产品决策问题源码默认严格判分漏选不算分。解决后端判分时把用户答案和正确答案都按统一字符顺序排序再比较或者用集合比对。如果要改漏选得一半分需要在判分逻辑里增加分支集合包含关系成立且用户选项数少于正确答案数时计一半分数。改之前先确认产品规则否则后台统计得分和前端展示会不一致。另外后台管理端如果提供了题库编辑功能保存题目答案时也要做一次格式校验把答案选项归一化排序否则前端展示的正确答案顺序一变判分结果就跟着变。5.4 模拟考试计时与退出恢复现象用户答题中途退出小程序再进来考试计时还在走甚至考试进度直接丢失。原因前端用setInterval计时页面销毁后计时器没有清除重进页面又新建一个计时器两个叠加导致时间错乱。进度丢失是因为考试中间状态只存在页面 data 里没有同步到后端。解决页面onUnload和onHide里必须clearInterval清理计时器。考试进度在每次作答完成时调用后端接口保存重新进入考试页先请求GET /api/exam/progress恢复。这里有个取舍公考模拟考试通常限时保存进度有利于用户体验但至少记录每题的首次作答时间防止作弊。计时器的问题在小程序端尤其隐蔽页面onHide和onUnload是两个不同生命周期用户切后台再回来会触发onShow如果只在onUnload清理了计时器、onHide没清理一个沉睡的计时器会在用户回来时继续走两个计时器叠加出来的剩余时间是完全乱的。5.5 题库导入乱码与字段错位现象后台管理端导入 CSV 题库中文显示乱码或者题目内容和选项错位。原因CSV 文件编码不是 UTF-8用 Excel 导出的 CSV 默认是 GBK另外题目解析里包含逗号的字段没有加引号导致按逗号分割时字段错位。解决导入前统一转 UTF-8 编码或者后端读取时用InputStreamReader指定编码自动检测。字段分割按标准 CSV 解析规则处理不要简单split(,)选项内容包含逗号时用引号包裹。我一般让管理端先导入 10 条测试数据校验通过后再导全量避免一次导入 5000 题全部错位后只能回滚。如果你打算批量导入的题目包含图片解析CSV 里那个字段应该填图片 URL 而不是文件名否则题目详情页加载图片时会拼出一个不存在的路径前端只显示一个破图。6. 上线前的验证清单从能跑到好用的最后一公里6.1 四个方向的验收项源码能跑起来只是第一步离实用还有段路。我整理了上线前必须走完的一组验证按接口、数据、并发、安全四个方向查。验证方向具体检查项接口层登录、题目列表、提交答案、错题添加/移除、模拟考试提交六组接口各测正常参数、缺参数、非法 token 三种情况数据一致性错题本增删几十次确认列表数量与数据库一致模拟考试得分、正确率、用时手动核算并发与性能JMeter 或 wrk 对题目列表接口压 200 并发观察响应时间和数据库连接池情况安全兜底换掉 jwt.secret管理后台接口加角色鉴权确认 yml 里数据库密码未暴露接口层重点看非法 token 是否返回 401 而不是 500这直接反映拦截器配置是否完整。数据一致性用 10 道题的试卷反复提交几次手动核算得分能发现大部分判分边界问题。并发测试如果发现题库接口没有缓存建议把 Redis 缓存加上压测数据是最好的说服理由。安全兜底里最容易忘的是已经提交过仓库的历史记录密码暴露过就要重置数据库密码而不是只改配置。最后说一个我自己吃过亏的习惯有一回组织模拟考试上线前只验了接口通不通没验 token 过期后的恢复路径结果用户刷了半小时题token 到期后直接被踢出回到登录页还登录不回去因为登录接口在并发下响应超时。从那以后我每次上线前都会强制走一遍「token 过期 → 重新登录 → 恢复上次页面」的完整链路再配合弱网模拟看超时提示是否友好。希望你拿到这套源码后把这些检查项当成默认动作而不是可选项能少走不少弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表