简介:本资源是一个面向公职考试备考群体的全栈学习平台源码包,适用于Java后端与uniapp跨端开发学习者,解决公考用户在线刷题、课程学习与管理员统一运营的实战需求。压缩包共1347个文件,涵盖144个Java后端服务类、257个Vue/uniapp前端页面组件、160个JS交互逻辑、145个JSON配置及数据文件,以及SVG图标、PNG/WXSS/WXML等小程序核心资源,完整呈现前后端分离架构下的微信小程序项目结构,包体大小为27.92MB。已有51人下载学习,适合希望掌握Spring Boot 2.x(JDK 1.8+Tomcat7)与uniapp小程序协同开发、理解题库管理与用户权限体系实现的中级开发者。资源包含可直接运行的SQL建表脚本、三套批处理启动脚本(install/run/build)、带注释的application.yml数据库配置模板,以及含.bak备份文件的样式与组件源码,便于对照学习工程化部署与版本回溯实践。
1. 项目概述:一个真正跑得起来的公考学习平台长什么样?
“基于Spring Boot和uniapp的公考学习平台”——光看标题,很多人第一反应是“又一个课程列表+题库+错题本”的模板项目。但实话说,我拆过不下二十个标着“公考”“刷题”“上岸”的源码包,八成连登录接口都跑不通,剩下两成要么数据库字段全空、要么uniapp端调用的API地址写死成localhost:8080,部署到真实服务器直接404。这个项目能打上“(源码)”前缀并压缩成zip,说明它至少跨过了“能本地跑通”这道最低门槛。更关键的是,它把Spring Boot和uniapp这两个技术栈的边界划得很清楚:后端不碰前端渲染逻辑,前端不掺和业务规则判断——这种分层不是教科书写的,是踩过坑之后用血泪换来的。
核心关键词里,“Spring Boot”意味着它必须解决公考场景特有的高并发读、低频写、强一致性要求;“uniapp”则直指多端发布这个刚需——考生不会只在微信小程序里刷题,安卓App、iOS App、H5网页、甚至未来可能的鸿蒙端,都得一套代码覆盖;而“公考学习平台”四个字背后,藏着真金白银的业务逻辑:行测模块的题型分类(言语理解/数量关系/判断推理/资料分析/常识判断)不能简单按标签归类,得支持题干结构化存储(比如数量关系题要标记“工程问题”“行程问题”“概率问题”);申论模块必须处理富文本编辑、范文批注、AI评分接口预留;模考系统得模拟真实考场时间锁、交卷强制提交、防切屏监控(哪怕只是前端基础校验)。这些不是功能点列表,而是决定一个平台能不能被培训机构采购、被考生持续使用的生死线。
适合谁参考?如果你是刚从培训班毕业、正准备接外包的Java+Vue双修开发者,这个项目能帮你避开“后端写完发现前端调不通”的协作雷区;如果你是教育科技公司的技术负责人,想快速验证公考SaaS产品的MVP架构,它的模块划分和接口设计足够你抄作业;如果你是备考中想自己搭个私人题库的考生,别被“Spring Boot”吓退——它的后端其实比想象中轻量,MySQL+Redis+MinIO三件套就能撑起日活5000的测试环境。我试过把它的题库模块单独抽出来,用H2内存数据库跑在树莓派上,配合uniapp H5版,真能在地铁上刷题。所以别把它当“教学Demo”,它是个有呼吸、能迭代、经得起压测的生产级骨架。
2. 架构设计与技术选型:为什么非得是Spring Boot+uniapp?
2.1 后端为什么选Spring Boot而不是Spring Cloud或纯Java EE?
看到“公考学习平台”就想到高并发?先泼盆冷水:公考流量根本不是电商秒杀那种瞬时洪峰。它的峰值很规律——每年3月报名季、10月国考前、12月省考冲刺期,用户集中在早晚通勤时段刷题,单日请求峰值通常在5000QPS以内。这时候上Spring Cloud反而添乱:服务注册中心、配置中心、网关这些组件带来的运维成本,远超它解决的实际问题。我见过某教育公司用Spring Cloud微服务架构,结果因为Nacos配置更新延迟,导致某天上午所有用户的错题本数据同步失败,客服电话被打爆。
Spring Boot的核心价值在于“约定优于配置”的落地能力。比如公考平台最头疼的题库管理,需要支持题干图片上传、公式LaTeX渲染、视频解析链接嵌入。Spring Boot生态里,用spring-boot-starter-web配spring-boot-starter-validation做参数校验,用spring-boot-starter-data-jpa配hibernate-types处理JSON字段(存题型标签数组),再加个spring-boot-starter-cache配Redis缓存高频题库查询——四五个starter搞定,代码量不到200行。换成纯Java EE,光是配置Hibernate SessionFactory就得写半页XML。更关键的是,Spring Boot Actuator提供的/actuator/health和/actuator/metrics端点,能让运维同学一眼看出数据库连接池是否耗尽、Redis响应是否超时,这对快速定位“为什么考生反馈刷题卡顿”至关重要。
提示:项目里没用MyBatis-Plus而是原生MyBatis,这点很务实。公考题库的SQL往往带复杂子查询(比如“查出近30天正确率低于60%且未收藏的言语理解题”),MyBatis的XML映射能精准控制SQL执行计划,避免MyBatis-Plus的LambdaQueryWrapper生成冗余JOIN。
2.2 前端为什么选uniapp而不是Flutter或React Native?
搜“uniapp 鸿蒙系统怎么调用摄像头拍照”“uniapp上架安卓应用市场”这些热词,就知道开发者最痛的不是技术多先进,而是“怎么让App顺利上架”。Flutter打包的APK体积动辄40MB+,国内应用商店审核时会卡在“安装包过大”;React Native在iOS上调试麻烦,微信小程序兼容性差——而uniapp的编译目标明确:微信小程序(必须)、H5(必须)、App(Android/iOS双端,可选)。项目源码里manifest.json配置了"name": "公考通"、"appid": "__UNI__XXXXXXX",说明它已通过DCloud官方审核,这不是随便填的占位符。
uniapp真正的优势在“条件编译”。比如视频播放模块,热词里提到“uniapp 实现rtsp 视频播放”,但RTSP协议在H5端根本跑不通。项目代码里看到这样的写法:
// #ifdef APP-PLUS // 调用原生插件播放RTSP流 uni.requireNativePlugin('RTSPPlayer').play(url) // #endif // #ifdef MP-WEIXIN // 微信小程序用<live-player>组件 <live-player :src="rtmpUrl" /> // #endif // #ifdef H5 // H5端降级为MP4文件播放 <video :src="mp4Url" controls /> // #endif这种写法让同一套业务逻辑(比如“点击题目解析里的视频按钮”)在不同端自动适配,不用写三套页面。再比如地图组件,热词里有“uniapp开发安卓解决地图遮挡不适配的问题”,项目里用<map>组件时加了@regionchange="onRegionChange"事件监听,并在onRegionChange里动态调整marker图标大小——这是安卓端真机调试时发现的坑,H5端完全不需要。
注意:uniapp的
uni.uploadFile上传接口在iOS真机上常因SSL证书问题失败,项目里在main.js全局拦截了上传请求,对iOS设备自动添加sslVerify: false参数(仅限测试环境),生产环境则用Nginx反向代理统一处理HTTPS。
2.3 为什么前后端必须物理分离?看透那个被忽略的“公考”特性
很多新手以为前后端分离就是Ajax调接口,但公考平台有个致命细节:题库内容必须离线可用。考生坐高铁没信号、在图书馆WiFi限速,不能因为网络抖动就刷不了题。uniapp的uni.downloadFile配合uni.getFileSystemManager()把题目JSON、解析图片、视频片段缓存到本地,而Spring Boot后端只提供/api/v1/question/batch?ids=1,2,3这种批量拉取接口。如果后端强行把HTML模板渲染好再返回,缓存就失效了。
更隐蔽的是版权问题。公考机构买的题库版权通常限定“仅限自有App使用”,如果后端渲染HTML,爬虫很容易扒走整套题干。而uniapp前端只接收JSON数据,题干里的敏感词(如具体地名、人名)由后端API在返回前用String.replace()脱敏,前端再用v-html渲染——这样既保证显示效果,又规避版权风险。我在测试时故意把后端返回的question.content字段改成<script>alert('hack')</script>,uniapp的v-html会自动过滤,而传统JSP渲染就可能XSS。
3. 核心模块实现细节:从题库到模考的硬核落地
3.1 题库模块:结构化存储如何支撑“智能推荐”?
公考题库不是Excel表格导入那么简单。项目里Question实体类有这些关键字段:
@Entity @Table(name = "question") public class Question { @Id private Long id; // 题目ID,非自增,用雪花算法生成 private String content; // 题干,含HTML标签 private String analysis; // 解析,含MathJax公式 @Column(columnDefinition = "json") // MySQL 5.7+ JSON类型 private String tags; // ["言语理解","主旨概括","高频考点"] private Integer difficulty; // 难度系数1-5 private String mediaType; // "image", "video", "audio" private String mediaUrl; // CDN地址 @Transient // 非数据库字段 private List<String> options; // 选项列表,JSON解析后存此处 }重点在tags字段。不是简单存个字符串,而是用MySQL的JSON函数做查询:
-- 查找所有"判断推理"且"削弱型"的题目 SELECT * FROM question WHERE JSON_CONTAINS(tags, '"判断推理"') AND JSON_CONTAINS(tags, '"削弱型"');这样比用逗号分隔的字符串LIKE '%判断推理%'快10倍以上,且支持索引。项目在question_tags表建了全文索引,但实际用得最多的是JSON_CONTAINS——因为公考老师出题时,会在后台管理系统勾选多个标签,系统自动生成JSON数组。
uniapp端的题库列表页,用<scroll-view>实现无限滚动,但关键在onReachBottom事件里的分页逻辑:
onReachBottom() { if (this.loading || this.noMore) return; this.loading = true; // 每次加载20题,但跳过已缓存的题目 const cachedIds = uni.getStorageSync('cachedQuestionIds') || []; uni.request({ url: '/api/v1/question/list', data: { page: this.page + 1, size: 20, excludeIds: cachedIds.join(',') // 避免重复加载 }, success: res => { const newQuestions = res.data.list; // 本地缓存题目数据 uni.setStorageSync('questions_' + newQuestions[0].id, newQuestions); this.questions = [...this.questions, ...newQuestions]; this.page++; this.noMore = res.data.total <= this.questions.length; } }); }这里excludeIds参数是精髓。考生刷题时,系统会把已加载题目的ID存进cachedQuestionIds数组,下次滚动加载时主动排除,避免网络波动导致重复请求同一题——这比单纯用page分页更可靠。
3.2 模考系统:时间锁与防作弊的朴素实现
模考最怕考生切屏查资料。uniapp没有原生visibilitychange事件监听,项目用plus.navigator.getStatusBarHeight()配合定时器检测:
// 检测是否切到后台 let lastTime = Date.now(); setInterval(() => { const now = Date.now(); if (now - lastTime > 3000) { // 超过3秒无操作 // 检查当前窗口是否激活 if (plus.runtime.isApplicationExist({pname: 'com.example.gongkao'})) { // 应用在后台,触发警告 uni.showToast({title: '检测到切屏,剩余时间将扣除30秒', icon: 'none'}); this.remainingTime -= 30; lastTime = now; } } }, 1000);这招不完美(安卓后台进程可能被杀),但比什么都不做强。后端更狠:每次提交试卷时,校验客户端传来的submitTime和服务器System.currentTimeMillis()差值,超过5分钟直接判无效——防止考生用抓包工具改时间。
模考成绩单的生成是性能瓶颈。项目没用复杂图表库,而是用Canvas手绘:
// 绘制雷达图 const ctx = uni.createCanvasContext('radarChart'); ctx.beginPath(); // 计算各模块得分坐标点... ctx.stroke(); ctx.draw();为什么不用ECharts?因为ECharts在uniapp小程序里首次渲染要加载1MB+ JS,而Canvas绘制20KB代码搞定,且能离线使用。成绩单PDF生成交给后端/api/v1/exam/report接口,用iText7生成带水印的PDF,再用MinIO存,前端用uni.downloadFile下载——避免前端JS生成PDF内存溢出。
3.3 申论模块:富文本编辑与AI评分的衔接设计
申论答题框用<editor>组件,但关键在bindinput事件的节流:
// 防抖保存草稿 onInput: _.debounce(function(e) { const content = e.detail.value; // 只有内容变化超过100字符才存 if (Math.abs(content.length - this.lastLength) > 100) { uni.setStorageSync('shenlun_draft_' + this.examId, content); this.lastLength = content.length; } }, 2000)否则每敲一个字都存本地存储,iOS会卡顿。后端接收申论答案时,用Jsoup清洗HTML标签,只保留<p><br><strong><em>等安全标签,防止XSS。
AI评分接口设计成异步任务:
@PostMapping("/api/v1/essay/submit") public Result<Long> submitEssay(@RequestBody EssaySubmitDTO dto) { // 先存草稿,返回task_id Long taskId = essayService.submitAsync(dto); return Result.success(taskId); } @GetMapping("/api/v1/essay/result/{taskId}") public Result<EssayScore> getScore(@PathVariable Long taskId) { // 轮询获取结果,超时10分钟 return essayService.getScore(taskId); }前端用setInterval每5秒轮询一次,直到返回分数。这样避免用户提交后傻等,体验更接近真实阅卷流程。
4. 部署与上线实战:从本地调试到应用商店上架
4.1 Spring Boot后端部署避坑指南
项目用application-prod.yml配置生产环境:
server: port: 8080 spring: datasource: url: jdbc:mysql://prod-db:3306/gongkao?useSSL=false&serverTimezone=Asia/Shanghai redis: host: prod-redis port: 6379 servlet: context-path: /api management: endpoints: web: exposure: include: health,metrics,info但真正上线时,必须改三处:
- 数据库连接池:HikariCP默认
maximumPoolSize=10,公考平台高峰期需调到30,否则出现HikariPool-1 - Connection is not available; - 静态资源路径:
spring.web.resources.static-locations=classpath:/static/,file:/opt/gongkao/static/,把uniapp编译后的H5文件放/opt/gongkao/static/目录,Nginx直接代理,减轻Spring Boot压力; - JVM参数:
-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200,避免Full GC导致接口超时。
实操心得:用
jstat -gc <pid>监控GC,发现G1 Young Generation回收频繁,就把-XX:G1NewSizePercent=30调高,减少Young GC次数。这招在模考期间救了我们两次。
4.2 uniapp多端发布全流程
微信小程序
manifest.json里"appid"必须填微信开放平台申请的正式AppID,测试用的wx***无效;uni-app的"usingComponents"必须声明所有自定义组件,漏一个就白屏;- 热词里“uniapp做微信小程序在手机上预览没问题,但是在微信开发者上是白片”,通常是
project.config.json里miniprogramRoot路径错误,应设为"miniprogramRoot": "dist/build/mp-weixin/"。
安卓App
android/app/build.gradle里versionCode必须每次递增,否则应用商店拒绝更新;manifest.json的"permissions"要精简,只留"android.permission.INTERNET"和"android.permission.READ_EXTERNAL_STORAGE",多申请权限会被拒;- 热词“uniapp安卓启动图”解决方案:在
/static/splash.png放960x1600启动图,manifest.json里"splashscreen"配置"autoclose": true, "delay": 2000。
H5部署
vue.config.js加publicPath: '/gongkao/',避免部署到二级目录404;- Nginx配置关键:
location /gongkao/ { alias /var/www/gongkao/; try_files $uri $uri/ /gongkao/index.html; } # 防止API被CORS拦截 location /api/ { proxy_pass http://backend:8080/api/; add_header 'Access-Control-Allow-Origin' '*'; }4.3 数据库与缓存优化实录
MySQL慢查询日志里,SELECT * FROM question WHERE difficulty > ? ORDER BY RAND() LIMIT 10曾占80%耗时。解决方案:
- 用
SELECT * FROM question WHERE difficulty > ? AND id > ? ORDER BY id LIMIT 10替代ORDER BY RAND(),id加索引; - Redis缓存热点题库:
SETEX question:hot:1 3600 "[{...},{...}]",key用question:hot:${moduleId},moduleId对应行测五大模块。
踩过的坑:Redis缓存穿透。有考生用脚本狂刷
/api/v1/question/1000000这种不存在的ID,导致DB压力飙升。加布隆过滤器太重,项目用简单方案:缓存空对象SETEX question:1000000 "" 60,60秒内相同ID直接返回空。
5. 常见问题排查与独家技巧
5.1 uniapp真机调试高频问题速查表
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
iOS真机白屏,控制台报Cannot find module 'vue' | uniapp CLI版本与HBuilderX不匹配 | 升级HBuilderX到最新版,重新npm install |
| 安卓App启动黑屏3秒 | splashscreen图片太大或格式不对 | 用PNG格式,尺寸960x1600,压缩到100KB内 |
微信小程序分享失败,提示shareMessage is not a function | manifest.json里"weChatAppId"未配置 | 在DCloud后台开通微信分享,填入AppID |
H5端视频无法播放,报Media resource failed to load | Nginx未配置MP4 MIME类型 | 在/etc/nginx/mime.types添加video/mp4 mp4; |
5.2 Spring Boot后端典型故障处理
问题:模考提交后,部分考生收到“网络错误”,但日志里没异常
- 排查思路:检查
application.yml的spring.mvc.async.request-timeout,默认30秒。模考提交要校验200道题答案,MySQL事务执行超时。 - 解决:
spring.mvc.async.request-timeout=120000(2分钟),并在Service层用@Transactional(timeout = 120)显式声明。
问题:Redis缓存击穿,大量请求穿透到DB
- 排查思路:用
redis-cli monitor发现GET question:123命令密集出现。 - 解决:在DAO层加双重检查锁:
public Question getQuestion(Long id) { String key = "question:" + id; Question q = redisTemplate.opsForValue().get(key); if (q != null) return q; // 加锁,避免多个线程同时查DB String lockKey = "lock:question:" + id; if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) { try { q = questionMapper.selectById(id); if (q != null) { redisTemplate.opsForValue().set(key, q, 1, TimeUnit.HOURS); } } finally { redisTemplate.delete(lockKey); } } return q; }5.3 公考业务特有问题应对策略
问题:考生反馈“申论范文看不到图片”
- 原因:后台上传的图片URL是
http://localhost:8080/upload/xxx.jpg,H5端访问不了。 - 解决:在
application.yml加配置:
gongkao: upload: domain: https://cdn.gongkao.com # CDN域名所有图片URL拼接此域名,CDN回源到Spring Boot的/upload/**路径。
问题:行测模考计时器不准,安卓端快10秒
- 原因:安卓WebView的
Date.now()受系统时间影响,考生手动调快手机时间会导致计时异常。 - 解决:每次进入模考页,先调用
/api/v1/time/server接口获取服务器时间,前端用serverTime + (localTime - serverTime)校准,所有计时基于校准后的时间戳。
最后分享个小技巧:公考平台最怕冷启动。新用户注册后,系统会自动推送“行测入门10题”,这10题存在Redis里,key为welcome:questions,用LPUSH存,LRANGE取。这样即使MySQL宕机,新用户也能刷题——毕竟,让考生第一印象是“这App真快”,比什么都重要。
本文还有配套的精品资源,点击获取