ARTICLE DETAIL

资讯详情

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

基于SpringBoot和微信小程序的高校毕业生公考助手系统设计与实现

基于SpringBoot和微信小程序的高校毕业生公考助手系统设计与实现 springboot基于微信小程序的高校毕业生公考助手管理系统——说实话第一次看到这个题目的时候我脑子里首先冒出来的是这些年应届生数量一年比一年夸张的就业现实。公务员、事业单位、国企几乎成了大家眼里稳定的代名词。但真正在高校里做过项目或者带过毕设的人都知道公考备考这事信息差和工具差比想象中要大得多有人拿着好几本厚厚的教材啃有人守着电脑端刷题还有人干脆朋友圈求分享资料学习记录完全靠手记。我当年帮一个学弟做毕设时他提的需求很简单——能不能把刷题、错题、模拟考试都塞进微信里不用装App。这句话基本上就是这个系统的原始起点。这篇文章我打算把整条技术链路完整拆开从需求分析到数据库设计从后端接口到小程序端交互再到部署上线和踩坑记录全程用我实际做过的方案来讲。如果你正打算做类似的基于SpringBoot和微信小程序的毕设或者你想从零自己搭一个完整的前后端分离项目这篇内容应该能帮你少走不少弯路。1. 公考助手的核心价值这个系统到底帮用户解决了什么做系统之前先想清楚一件事市面上刷题软件不少粉笔、腰果、华图在线各个都有题库功能一个高校毕设项目凭什么有存在感这其实决定了你整个项目的功能设计和数据建模方向。1.1 目标用户的真实痛点拆解高校毕业生备考公务员最典型的场景是这样早上在图书馆翻教材中午吃饭时想刷两道题但手机上没有趁手的工具下午参加线下模考晚上回家把错题抄在本子上。整个流程里最大的问题不是缺乏内容而是缺乏连贯性和轻量化。场景一碎片时间无法利用。通勤、排队、等食堂开门的零碎时间打开手机刷几道行测题是最自然的动作但很多网页端题库在手机上体验非常差频繁横竖屏切换、页面缩放根本坚持不下来。场景二错题收集低效。纸质刷题错题要手动抄录Excel记录容易半途而废一旦没有即时沉淀过几天就彻底找不回来了。场景三缺少针对性的练习计划。大多数应届生不是不努力而是不知道今天该练什么蒙头刷题但薄弱环节无法被量化暴露。所以公考助手的定位就应该是一个轻量级的一站式刷题和备考追踪工具而不是又一个考试资讯门户。基于这个定位我把系统拆成了两条主线面向学生的客户端微信小程序和面向管理员的后台管理端Web端。1.2 功能模块的完整划分根据高校毕业生公考备考这个特定场景功能上不是做得越多越好而是要覆盖完整的刷题闭环。我的方案里最终落地的核心模块如下模块功能点关键设计说明题库模块行测数量关系、判断推理、言语理解、资料分析、常识判断申论材料阅读单选、多选、判断三种题型按分类和难度打标签刷题功能顺序刷题、随机刷题、专项练习每道题提交后立即判分并显示解析支持收藏和笔记模拟考试组卷功能限时答题自动评分模拟真实考试时间管控交卷后生成成绩报告错题本自动收录错题支持重做和移除同一道题多次答错则累计出现答对一次后可手动移除学习计划每日刷题目标、连续打卡记录通过简单的任务表打卡机制督促用户持续使用公告资讯招考公告推送、备考经验文章后台发布小程序首页轮播和列表展示个人中心微信授权登录、学习数据统计、成绩曲线用ECharts或Canvas绘制简单的个人成绩变化图后台管理端的主要职责就两点一是题库管理题目的增删改查、批量导入、分类管理二是用户数据查看用户量趋势、每日活跃、错题统计这块可以做成常规的前后端分离管理页面技术栈用Vue2或Vue3配合Element UI都行SpringBoot只负责提供RESTful接口。1.3 系统边界哪些功能宁可砍掉也不做做这类系统最容易跑偏的是想往里面加社区功能、加聊天室、加课程付费。我建议别做原因很实际毕设项目周期有限核心刷题链路打磨一遍已经需要不少时间社区功能会有内容审核风险涉及敏感信息把控对个人开发者来说合规成本很高微信小程序个人主体很多类目不支持开通涉及在线课程、教育直播等需要企业资质审核根本过不了。把这个边界划清楚你在做技术选型和数据库设计的时候就不会被伪需求拖着走。2. 技术选型和整体架构为什么是SpringBoot 微信小程序这个组合这个项目叫springboot基于微信小程序的高校毕业生公考助手管理系统技术栈已经写死了一半但选型背后的理由以及每层技术方案的细节值得展开聊聊因为这会直接影响你后面能不能顺利部署和答辩。2.1 后端为什么选SpringBoot 2.7 MyBatis-PlusSpringBoot这边我强烈建议使用2.7.x系列而不是一上来就追新版本SpringBoot 3.x。原因很简单3.x基于Jakarta EE规范且最低要求JDK 17你在本地开发可能没问题但部署到一些高校服务器或虚拟主机上时JDK版本未必装得了那么高。而且很多老教程、老代码片段都基于SpringBoot 2.x遇到问题搜资料更方便。配套选MyBatis-Plus而不是原生MyBatis主要是基于实际的开发效率考虑。公考管理系统的表结构不算特别复杂但CRUD接口数量多MyBatis-Plus提供BaseMapper通用方法加上分页插件PageHelper用的也是这个生态的经典方案20多张表的基础增删改查基本上不用手写XML。单表查询直接lambdaQuery()搞定多表关联再写XML语句代码量能省下至少30%。依赖版本大致这样固定下来parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies一个很常见的误区是以为选型越新越好。SpringBoot 3.x的AOT编译、GraalVM原生镜像这些东西在中小型管理系统场景里几乎用不到反而会增加部署复杂度。你的核心目标是稳定跑通、逻辑严密不是炫技。2.2 前端为什么选微信原生小程序而不是uni-app这个问题我当时纠结过。uni-app可以一套代码多端复用将来还能编译成H5或App。但实际做完之后我的结论是如果主要目标平台就是微信小程序直接写原生小程序反而更省事。微信开发者工具对原生语法调试支持最好wxml/wxss/js的结构虽然初看别扭但逻辑清晰网上的社区答案也最多。uni-app胜在多端但你一旦用了它提供的自定义导航栏、条件渲染等能力遇到问题时的排查链路会明显变长。这个项目里需要用到微信登录(getUserProfile/uni.login)、个性化组件答题卡、Canvas绘制成绩图表原生实现踩坑更少。当然如果你之前已经熟悉Vue选uni-app也没有问题数据流和组件化的思想基本是相通的。关键不是选什么而是选完之后能不能直接进入开发状态。2.3 整体架构三层分离 统一响应体系统的部署拓扑很简单用户手机上的微信小程序 - HTTPS请求 - Nginx反向代理 - SpringBoot服务 - MySQL数据库。如果有精力可以在后端加Redis做缓存和刷题统计但是对于毕设体量这一层非必须。真正重要的是接口设计规范因为小程序端和后端是两个人或者你自己分两个阶段开发的接口约定不清晰后面联调会非常痛苦。我习惯统一返回给前端的JSON格式{ code: 200, message: 操作成功, data: { } }对应后端自定义一个R类作为统一响应体Data public class RT { private Integer code; private String message; private T data; public static T RT success(T data) { RT r new R(); r.setCode(200); r.setMessage(操作成功); r.setData(data); return r; } public static T RT error(Integer code, String message) { RT r new R(); r.setCode(code); r.setMessage(message); return r; } }另外接口路径按资源命名分类列表用/目录题目操作用/questions答题提交用/answers学习计划用/plans保持RESTful风格且语义清晰。3. 数据库设计真题、错题和答题记录的表结构是全局的核心数据库设计是整个项目的地基。题目错了改起来容易答题记录如果设计不到位后面统计错题、画成绩曲线会各种别扭。我下面把关键几张表的结构和设计理由都列出来可以直接拿着去建库。3.1 核心表题目表和分类表题库表设计时最容易犯的错是把题目正文、选项、答案、解析全部塞进一个长文本字段。这样做短期内没问题但等到你要实现选项单独展示按照题型筛选选项乱序这些功能时就彻底失控了。我这里给出实用设计CREATE TABLE category ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 分类名数量关系/判断推理/言语理解等, parent_id bigint(20) DEFAULT 0 COMMENT 父分类ID0为根分类, sort int(11) DEFAULT 0 COMMENT 排序权重, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT题目分类表; CREATE TABLE question ( id bigint(20) NOT NULL AUTO_INCREMENT, category_id bigint(20) NOT NULL COMMENT 所属分类ID, type tinyint(4) NOT NULL COMMENT 题型1单选 2多选 3判断, content text NOT NULL COMMENT 题干富文本或纯文本, option_a varchar(255) DEFAULT NULL, option_b varchar(255) DEFAULT NULL, option_c varchar(255) DEFAULT NULL, option_d varchar(255) DEFAULT NULL, option_e varchar(255) DEFAULT NULL COMMENT 备用选项, answer varchar(50) NOT NULL COMMENT 标准答案多选用逗号分隔, analysis text COMMENT 题目解析, difficulty tinyint(4) DEFAULT 1 COMMENT 难度1简单 2中等 3困难, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_type (category_id, type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT题目表;用option_a到option_e这种固定字段而不是JSON数组存选项原因是MySQL中JSON字段查询/索引比较麻烦小程序端解析也得多一层处理固定字段配合后端组装逻辑最透明。多选答案直接ABCD这种字符串存虽然有点反范式但在这个表里答案几乎不会被当作查询条件反而是显示时直接返回最方便。3.2 答题记录表如何做到刷题、错题、成绩统计三合一答题记录表是整个系统的账本。每一道题被用户做一次就产生一条记录。这张表设计对了错题本不需要单独建表个人成绩统计也能直接用SQL聚合出来。CREATE TABLE answer_record ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 用户ID, question_id bigint(20) NOT NULL COMMENT 题目ID, exam_id bigint(20) DEFAULT NULL COMMENT 所属模拟考试ID普通刷题为空, user_answer varchar(50) NOT NULL COMMENT 用户提交的答案, is_correct tinyint(1) NOT NULL COMMENT 是否正确0错误 1正确, source tinyint(4) NOT NULL DEFAULT 1 COMMENT 来源1普通刷题 2模拟考试, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_question (user_id, question_id), KEY idx_user_exam (user_id, exam_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT答题记录表;错题本功能可以直接通过查这张表来实现拉取某用户is_correct0且source1的记录按question_id去重并且最近一次还不是正确的题目。这样设计的好处是你不需要一份单独维护的错题列表只要用户重新做对一道题它自然就不再出现在错题里。如果硬要维护错题表就得在重做正确后自动移除和历史错题保留之间做取舍逻辑容易复杂。我的建议是直接查询聚合可靠性最高。3.3 模拟考试表组卷与成绩中间表的关联设计模拟考试模块需要两张表配合exam_paper试卷表 exam_record用户考试记录表。试卷里要包含哪些题用exam_question中间表来管理若干数量的题目。CREATE TABLE exam_paper ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL COMMENT 试卷标题如2024国考行测真题, category_id bigint(20) DEFAULT NULL COMMENT 分类限制, duration int(11) NOT NULL DEFAULT 120 COMMENT 考试时长单位分钟, total_score int(11) NOT NULL DEFAULT 100 COMMENT 总分, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态0下架 1上架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT模拟考试试卷表; CREATE TABLE exam_record ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, paper_id bigint(20) NOT NULL, score decimal(5,1) DEFAULT NULL COMMENT 得分, start_time datetime NOT NULL, submit_time datetime DEFAULT NULL COMMENT 交卷时间, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0考试中 1已交卷 2超时自动交卷, PRIMARY KEY (id), KEY idx_user_paper (user_id, paper_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户考试记录表;判分逻辑在后端做。简单方式多选题全对得分漏选得一半分如果这是考试规则的设定这里完全根据你的业务定义来。我的实现是单选和判断每题1分多选全对2分、漏选1分、有错选0分总分根据实际题目数量动态计算。3.4 用户表设计时的一个额外选项冗余还是关联用户表大字段不再赘述重点说一个取舍每次微信登录拿到的昵称和头像是存数据库还是每次实时调微信接口微信不再提供长期有效的用户信息getUserProfile是临时授权存库是唯一稳妥办法。因此user表里要有nickname和avatar_url字段且允许为空——有些用户拒绝授权时系统要直接用系统默认头像兜底。4. 服务端核心接口实现从登录态到刷题判分的完整链路数据库结构定下来之后服务端就是把这些表变成能被小程序调用的接口。下面几个接口是系统的核心链路必须捋清楚。4.1 微信登录与鉴权方案的实现小程序端点击登录按钮 - wx.login()拿到临时code - 发给后端 - 后端调用微信接口的code2Session接口换取openid和session_key - 后端用openid查用户是否存在不存在则注册 - 生成自定义token返回给前端。这里有两个细节值得注意第一不要用前端传过来的code去数据库查询也不要直接把openid返回给前端。正确的做法是把openid放在token里或服务端session中前端只知道token。token可以使用UUID也可以使用JWT。我推荐JWT因为它天然带有过期时间戳方便小程序端在token过期后自动引导用户重新登录Component public class JwtUtil { private static final String SECRET your-secret-key-change-in-production; private static final long EXPIRE 7 * 24 * 60 * 60 * 1000L; // 7天 public String createToken(Long userId) { return Jwts.builder() .setSubject(String.valueOf(userId)) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public Long parseUserId(String token) { try { Claims claims Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); return Long.parseLong(claims.getSubject()); } catch (Exception e) { return null; } } }第二步后端写一个拦截器HandlerInterceptor把需要登录才能访问的接口拦下来统一校验token避免每个Controller里都写一遍重复的取用户身份代码public class LoginInterceptor implements HandlerInterceptor { Autowired private JwtUtil jwtUtil; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); Long userId jwtUtil.parseUserId(token); if (userId null) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\登录已过期请重新登录\}); return false; } request.setAttribute(userId, userId); return true; } }这样Controller里只需要用request.getAttribute(userId)就能拿到当前用户。4.2 刷题接口随机刷题与专项练习的逻辑刷题接口设计要区分两种场景专项练习前端传入categoryId和type后端返回该分类下的一批未做过的题目。随机刷题前端不传分类后端从所有题目里随机抽取N道。关键点在于未做过过滤。如果数据库题目量大直接用NOT IN子查询可能性能很差我采用的方式是先查询当前用户已做过的question_id集合再传给Java内存去重或者用一条带NOT EXISTS的SQL处理。对于教学项目体量几千道题这两种方式差别不大优先保证逻辑简单// 随机刷题一次返回20道未做过的题 ListQuestion questions questionMapper.selectList( new LambdaQueryWrapperQuestion() .notIn(Question::getId, answeredQuestionIds) .orderByAsc(RAND()) .last(LIMIT 20) );有人可能会质疑ORDER BY RAND()在大表上性能会很差。没错在百万级题库里确实不该这么干但公考题库一般也就几千道RAND()完全可以接受而且这是最直观的随机实现。4.3 答题判分接口的设计即时判分 vs 交卷统一判分普通刷题模式是即时判分用户点完下一题后前端立刻把答案POST到后端后端比对标准答案返回是否正确以及完整解析。模拟考试模式是每道题先暂存答案交卷时统一判分。模拟考试暂存答案时接口设计为批量提交数组而不是一道一道调用这样可以避免拼接大量网络请求PostMapping(/exam/submit) public RString submitExam(RequestBody SubmitExamDTO dto, HttpServletRequest request) { Long userId (Long) request.getAttribute(userId); ExamRecord examRecord examRecordService.getById(dto.getRecordId()); // 校验仅当该试卷记录属于当前用户且状态为考试中时才允许提交 if (!examRecord.getUserId().equals(userId) || examRecord.getStatus() ! 0) { return R.error(400, 非法提交或试卷已交卷); } int score examService.calcScore(dto.getAnswers()); examRecord.setScore(score); examRecord.setStatus(1); examRecord.setSubmitTime(new Date()); examRecordService.updateById(examRecord); return R.success(提交成功); }判分完成后顺便把答题明细写入answer_record表错题本数据就从这里来。4.4 错题本和学习计划一个靠查表一个靠定时任务错题本接口本质上就是根据user_id和is_correct0去查answer_record表然后关联查询题目表和分类表。我认为这里不需要开一张错题表理由前面已经说过。唯一要注意的是分页和去重SELECT q.*, ar.user_answer, ar.create_time as wrong_time FROM answer_record ar INNER JOIN question q ON ar.question_id q.id WHERE ar.user_id #{userId} AND ar.is_correct 0 AND ar.source 1 AND ar.id (SELECT MAX(id) FROM answer_record WHERE question_id ar.question_id AND user_id ar.user_id) ORDER BY ar.create_time DESC这个SQL的思路是对同一道题只看该用户最后一次答题记录如果最后一次是错的就展示在错题本里如果最后一次已做对说明用户已经掌握就不再出现。这个逻辑非常贴近真实刷题场景。学习计划模块更简单一张plan表记录用户设定的每日目标比如每天刷50题一张signin表记录打卡日期。服务端提供一个统计接口返回今日已刷题数 vs 目标进度。如果想要连续打卡天数就用SQL按日期排序后跨界判断在Java里遍历一遍即可数据量不大没必要写复杂的SQL窗口函数。5. 小程序端的关键实现从登录到答题页面的真实交互小程序端是用户直接接触的部分体验好坏决定了这个系统能不能用起来。下面挑几个实现细节详细说明都是容易被忽略但实际开发必须处理的地方。5.1 自定义导航栏和底部TabBar的处理公考助手页面不算多首页、题库、考试、我的四个TabBar页面够用。难点在于微信小程序的navigationStyle默认是系统导航栏它高度在不同手机型号上不一样iPhone X的刘海屏和普通安卓机完全不同。如果你希望顶栏展示自定义标题公考助手并带渐变背景色就必须开启自定义导航栏// app.json 或其他页面json配置 { navigationStyle: custom }开启后要在页面onLoad里计算导航栏高度这个值每个机型不一样计算公式如下const systemInfo wx.getWindowInfo ? wx.getWindowInfo() : wx.getSystemInfoSync(); const menuButtonInfo wx.getMenuButtonBoundingClientRect(); const navBarHeight (menuButtonInfo.top - systemInfo.statusBarHeight) * 2 menuButtonInfo.height; const statusBarHeight systemInfo.statusBarHeight;把statusBarHeight和navBarHeight存到全局变量里页面顶部用占位view撑开再用flex布局放标题。这个方案是目前自定义导航栏的主流做法不要写死某个机型的高度值否则一到全面屏手机上就露馅。5.2 登录态串起整个刷题流程小程序端登录逻辑要设计成无感登录自动续期。我的做法是这样用户第一次打开小程序进入首页不强制登录可以浏览公告和考试列表。一旦点击开始刷题checkLogin函数检查本地storage里有没有token没有token弹窗提示用户进行微信授权登录有token在request请求拦截器里给每个请求头带上Authorization字段后端返回401时拦截器检测到清除本地token并且跳转到登录页。封装请求的代码可以做成一个公共request方法function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 401) { wx.removeStorageSync(token); wx.navigateTo({ url: /pages/login/login }); reject(res.data); return; } if (res.data.code 200) { resolve(res.data.data); } else { reject(res.data); } }, fail: (err) reject(err) }); }); }5.3 答题卡页面题目切换与作答状态回显在模拟考试答题页面中我做了一个二级页面结构顶部显示考试倒计时中间部分是题目卡片底部是上一题/下一题按钮右上角有答题卡入口。答题卡弹出一个半屏抽屉里面是一组小方格每个方格显示题号不同颜色代表不同状态灰色未作答蓝色已作答红色标记为存疑用户想回头再看这里要注意的是index与questionId的映射。前端始终维护着一个answerMap对象key是题号从0开始value是用户选择的答案。切换题目时从answerMap里读当前题号的值回显到选项上点击选项时同时更新answerMap和答题卡状态。交卷时把answerMap转换为数组传给后端。这样前端状态管理非常轻盈不需要引入Vuex或Pinia这类重型方案。5.4 个人成绩曲线的数据可视化用户做完几套模拟卷后个人中心需要显示成绩变化趋势。小程序端画折线图可以直接用Canvas 2D接口不需要额外引入图表库。如果觉得麻烦可以使用ec-canvasECharts小程序版但它会增加包体积而微信小程序主包有2MB限制。我的做法是既然只是成绩单曲线就手写一个简化版的Canvas绘制函数输入是日期数组和成绩数组输出是一条折线加几个坐标点代码不超过100行视觉效果完全够用。6. 部署与联调如何让小程序真正跑在真机上本地开发环境里小程序可以勾选不校验合法域名联调起来很方便。但正式上线或者给老师演示的时候如果还在域名校验上卡住体验会非常糟糕。这一部分我把整个部署链路梳理一遍。6.1 后端打包与服务器环境配置SpringBoot后端打包成jar后直接在服务器上执行nohup java -jar xxx.jar即可。如果服务器内存只有1G或2G记得加上JVM参数限制堆内存nohup java -Xms256m -Xmx512m -jar gongkao-server.jar --spring.profiles.activeprod app.log 21 生产环境的数据源配置要和本地分开用application-prod.yml独立控制server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/gongkao_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: gongkao password: 你自己在生产环境设的密码 driver-class-name: com.mysql.cj.jdbc.Driver redis: # 如果暂时不引入Redis这整段可以不写 host: localhost port: 63796.2 Nginx反向代理和HTTPS证书小程序正式环境要求所有请求域名必须是HTTPS而且域名不能是IP必须是已备案的域名。所以你需要一台云服务器、一个备案域名以及HTTPS证书。Nginx配置的核心就三块内容监听443端口、配置SSL证书、反向代理到SpringBoot的8080。server { listen 443 ssl; server_name api.gongkao.example.com; ssl_certificate /etc/nginx/ssl/gongkao.pem; ssl_certificate_key /etc/nginx/ssl/gongkao.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } server { listen 80; server_name api.gongkao.example.com; return 301 https://$host$request_uri; }小程序后台的服务器域名里只填https://api.gongkao.example.com这一个wss如果没用到就不用配。个人开发者小程序不支持开通微信支付这也是这个项目不涉及支付功能的原因之一申请支付还需要企业资质和微信认证流程繁琐且未必能过审。6.3 文件上传如果要做公告配图和头像上传公告资讯要配图片用户头像也可能需要自定义上传。这个场景不需要引入OSS直接用服务器本地存储即可。SpringBoot里实现一个文件上传接口保存到指定目录然后把访问路径拼接成可访问的URLPostMapping(/upload) public RString upload(RequestParam(file) MultipartFile file, HttpServletRequest request) { String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFilename UUID.randomUUID().toString().replace(-, ) ext; String datePath new SimpleDateFormat(yyyy/MM/dd).format(new Date()); String dir /data/gongkao/upload/ datePath; File dirFile new File(dir); if (!dirFile.exists()) { dirFile.mkdirs(); } file.transferTo(new File(dirFile, newFilename)); return R.success(/upload/ datePath / newFilename); }Nginx里再加一个location把/upload/映射到磁盘目录location /upload/ { alias /data/gongkao/upload/; }这样上传和访问就都通了。注意小程序上传文件用的不是wx.request而是wx.uploadFile两者Header设置方式有些差异很容易被忽略。7. 实战排坑我在开发这个公考系统时踩过的六个坑这部分可能是全文对你最有直接帮助的内容。每一条都是我在实际开发过程中真实遇到过的情况全部整理成了现象 - 原因 - 解决的结构。7.1 坑一微信小程序里textarea层级穿透问题考场答题的主观题比如申论简答要用textarea输入但在微信小程序里textarea是原生组件默认的层级高于普通view。在模拟考试页底部放了一个上一题/下一题的固定条当textarea位于可视区域且高度接近底部固定条时会出现文字穿透覆盖按钮的情况。解决办法有三个方向一是用textarea的adjust-position和fixed属性控制二是当textarea聚焦时隐藏底部按钮条三是直接放弃textarea改用input或view模拟。我的最终方案选择了考试页面干脆不提供主观题输入申论部分只展示材料和题目用户点击查看参考答案直接看完整解析。这本身也符合线上刷题场景的定位主观题本就不适合在小屏幕上敲大量文字考生通常在纸上写完再对答案。7.2 坑二不定项选择题答案比对总是判错多选判分一开始直接用字符串equals比较用户选的顺序是ABD但标准答案存的顺序是DBA结果一个答对被误判成答错。这个问题的根因是存储答案时没有做规范化排序。解决方法是前端提交前把用户答案拆开后排序再合并后端存储标准答案时也从一开始就按字母排好序。双保险后再判断// 多选答案标准化排序 public static String normalizeAnswer(String answer) { char[] chars answer.replace(,, ).replace( , ).toCharArray(); Arrays.sort(chars); return new String(chars); }这个坑在开发阶段不易发现因为自己做的测试数据往往习惯性地按顺序填答案一旦用户实际使用时就会暴露。7.3 坑三真题批量导入时Excel解析失败后台管理要支持题库批量导入于是写了Excel解析。一道真题的内容里往往包含大量特殊字符全角括号、引号、空格、图片在Excel里是嵌入对象。用EasyExcel或POI解析时题干中包含这种嵌入图片时经常会报格式错误。解决方式导入模板中明确注明题干和选项请使用纯文本图片暂时不支持通过Excel导入解析时对每个单元格try-catch出错的行单独记录不让整个文件导入失败。同时开发一个单题添加页面用于处理少量含图的特殊题目题目中的图以URL形式插入。如果你只想给毕设演示题库量不用太大每类30-50道足够通过管理后台手动录入也撑得住。7.4 坑四小程序包体接近2MB开发工具蓝屏警告题库内容如果直接打包进前端代码里几百道题加解析就会让包体积迅速膨胀。正确做法是题目内容全部通过接口动态加载小程序端只保留页面代码和基础样式这样主包通常能控制在1MB以内。如果还超可以把不常用的页面如隐私协议、关于我们改成按需加载配合微信小程序的分包加载功能放在subpackages里。7.5 坑五模拟考试中途退出再次进入状态丢失用户点击开始考试后如果中途切到后台或者小程序被杀掉之前做的题全部丢失会很崩溃。解决方案很简单每道题作答后前端立即调用一个暂存进度的接口把recordId和当前答案Map同步到后端。下次进入考试页时先根据recordId拉取暂存记录回显到界面上。这个功能在开发时容易被忽略但实际使用中一定是高频场景。我把暂存接口设计得很轻量实时性好交卷时再更新最终成绩。7.6 坑六学习打卡的连续天数计算错误打卡功能看似简单但连续打卡的计算很容易在跨月时翻车。比如1月31日打卡2月1日没打2月2日又打了如果只按日期差1判断连续逻辑就乱了。我最终用目标日期偏移法解决先找到用户最后一次打卡日期从该日期往前遍历只要前一天有打卡记录就连续数加一否则中断。遍历过程中用日期字段做精确比对日期函数统一使用LocalDate避免时区转换带来的偏移问题。8. 从答辩到上线给想做这个项目的同学几点实在建议这个项目从需求分析到完整落地我前后用了大概三个星期白天上班、晚上写代码期间踩坑无数。根据我的实际经验有几点体会特别值得说先搭数据库和接口契约再写页面。千万别先做小程序UI做完UI再对后端字段后端改起来心累前端也会跟着返工。我习惯用Apifox先定义好每个接口的请求参数和返回结构前后端照着同一份文档开发效率极高。真题来源要注意。公考题目存在一定的版权边界个人项目里建议只放少量自编模拟题或者标注清楚来源的自使用样本不要大规模搬运商业题库内容。这也是合规意识的一部分。答辩前务必准备一个亮点话术。面试官/评委大概率会问你这个系统相比其他刷题小程序有什么特点我的个人答案是错题本不靠单独维护而是基于答题记录动态生成学习计划自动追踪这个设计既简洁又有说服力。如果你也做了类似的动态统计设计要能清楚讲出来。如果进度紧张优先保证刷题-错题-统计这条主链路完整。模拟考试、公告管理这些模块可以放在第二优先级因为主链路才是这个系统区别于普通CMS的核心。最后再分享一个很实用的小技巧在微信开发者工具的真机调试模式下一定要多测试几台不同机型的展示效果尤其是答题卡弹窗在全面屏和普通屏上的底部安全区适配。这个细节如果能做到位你的项目展示分会有明显提升。真正做完一个完整的全栈项目你学到的远比单纯刷一百道SpringBoot面试题多得多——因为代码只有在真实运行中才会跟你讲真话。
返回列表