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

Spring Boot+微信小程序的在线考试系统设计与实践

做了不少课程类项目之后我发现一个特别核心的需求很多学校、机构甚至企业内部培训都需要一套“课程学习在线测试”的系统学生端最好在微信里就能直接用老师端又能方便地管理题库、出卷和查看成绩。围绕这个需求我完整实现了一套基于 Spring Boot 和微信小程序的学习课程在线测试系统从后端接口到手机端页面再到数据库设计、部署上线流程全都跑通了。这篇文章就把这套系统的设计思路、核心实现、部署细节以及我实际开发里踩过的坑一次性和盘托出。如果你是准备做毕业设计、课程设计或者想快速在公司内部搭一套培训考核工具这套东西值得参考。后端用 Spring Boot 接管所有业务逻辑小程序端只负责展示和交互既降低了前端的复杂度又让后续扩展管理后台变得非常方便。源码、数据库脚本、运行视频和配套文档我都整理好了下面先从整体设计开始讲。1. 项目整体设计与技术选型先想清楚再动手1.1 需求拆解三种用户角色与核心业务流程做在线测试系统之前我习惯先画一张简单的用例图把“谁在用、要做什么”理清楚。这套系统里有三类用户学生、教师、管理员。学生端的核心场景是微信小程序里完成整个学习闭环。选一门课程查看课程资料看到老师发布的试卷后在线答题提交后立刻看到客观题的得分还能查看自己的历史考试记录和错题分析。整个流程都必须在小程序里顺畅完成不能依赖PC浏览器这是选微信小程序而不是Web网页的根本原因。教师端和管理员端则偏后台操作我用的是普通Web页面Spring Boot 返回模板或前后端分离的接口方式均可。教师的操作主要有创建课程、维护题库单选题、多选题、判断题、简答题四种题型、手动或自动组卷、发布考试、查看学生的成绩统计。管理员则在教师的基础上增加了系统用户管理、课程审核等权限。核心业务流程可以概括为课程创建 → 题库录入 → 组卷发布 → 学生报名学习 → 在线答题 → 自动判分 → 成绩查看。这里面涉及的关键数据有用户、课程、题目、试卷、考试记录、答题明细数据表的设计会围绕这几类数据展开。1.2 技术选型为什么是 Spring Boot 微信小程序很多同学纠结后端到底用 SSM 还是 Spring Boot。我的经验是直接选 Spring Boot理由很现实SSM 的 XML 配置繁琐光是 spring、springmvc、mybatis 三个框架的整合配置就能折腾半天Spring Boot 内置 Tomcat、自动配置、约定大于配置开发效率要高很多。现在主流项目基本都用 Spring Boot相当于你学了一套直接用得上的技术栈。前端选微信小程序而不是原生 App 或 H5核心原因是“触达成本”低。学生不用下载 App微信里扫码或者搜索就能打开相比 H5小程序在调用微信登录、分享、消息通知等能力上原生支持体验更接近 App。加上现在微信生态非常成熟学校的教务场景和微信小程序结合的案例很多选题方向也很讨喜。数据库我用 MySQL搭配 MyBatis-Plus 作为 ORM 框架。MyBatis-Plus 的代码生成器、内置分页插件、条件构造器LambdaQueryWrapper真的能省掉大量手写 SQL 和 XML 配置特别适合课程设计这种开发节奏快的场景。认证方案选了 JWT无状态、适合前后端分离小程序端每次请求在 header 里带 Token 即可。1.3 数据库设计七张核心表一次理清数据库是整个系统的地基我在设计时重点考虑了题目存储和答题记录的扩展性。核心表包括表名用途关键字段user用户表id, username, password, nickname, openid, role, avatarcourse课程表id, name, description, cover, teacher_id, statusstudent_course选课关系表id, student_id, course_id, create_timequestion试题表id, course_id, type, content, options, answer, difficulty, analysisexam_paper试卷表id, course_id, title, duration, total_score, statuspaper_question试卷题目关联表id, paper_id, question_id, scoreexam_record考试记录表id, student_id, paper_id, score, submit_time, statusanswer_detail答题明细表id, record_id, question_id, user_answer, is_correct, score这里有两个容易踩坑的细节。第一试题的选项我统一用 JSON 字符串存储比如单选四个选项就存[A.选项一,B.选项二,C.选项三,D.选项四]这样不同题型的选项数量可以灵活控制不用为单选题建一张选项表。第二用户表中同时保留openid和username/password目的是让微信登录和后台管理登录共用一张表教师和管理员走账号密码登录学生走微信授权登录一个系统的两种登录方式互不干扰。2. 后端核心实现Spring Boot 项目从骨架到业务闭环2.1 项目初始化版本选择与依赖清单创建 Spring Boot 项目我通常直接到 Spring Initializr 生成基础结构。这里要特别提醒版本问题很多同学一上来就选了最新版 Spring Boot 3.x结果 JDK 还是 8启动直接报错。Spring Boot 3.x 基于 Spring Framework 6要求 JDK 17 起步如果本地环境还是 JDK 8老老实实选 Spring Boot 2.7.x 更稳妥。这套系统的代码也是基于 2.7.x 写的兼容性最好。核心依赖就五个spring-boot-starter-webWeb 能力、mybatis-plus-boot-starter数据库操作、mysql-connector-javaMySQL 驱动、java-jwtJWT 生成与解析、lombok简化实体类代码。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdcom.auth0/groupId artifactIdjava-jwt/artifactId version4.4.0/version /dependency项目的包结构我按controller→service→mapper→entity→config分层中间再加一个common包放统一的返回结果类Result、全局异常处理、JWT 工具类。小项目不用引入复杂的 DDD 设计Controller 只做参数接收和结果封装业务逻辑下沉到 Service 层这样代码清晰答辩时也容易讲清楚。2.2 数据库初始化与自动建表机制系统运行前必须先初始化数据库。我建议项目里放一份init.sql脚本包含建库、建表、插入初始数据的完整 SQL。拿到源码后先在 MySQL 客户端里执行一遍再改application.yml里的数据库连接信息。不过实际操作中大家经常遇到一种情况代码没问题SQL 脚本也执行了但一启动项目就报表不存在。这通常是因为连接的不是同一个库或者数据库名对不上。所以我给这套系统额外加了一个“启动时自动建表”的机制利用 Spring Boot 的SchemaManagement能力或者 MyBatis-Plus 的DbType配置配合spring.sql.init.modealways让项目在启动时自动执行schema.sql里的建表语句。为了保证不覆盖已有数据SQL 里统一用了CREATE TABLE IF NOT EXISTS。spring: sql: init: mode: always schema-locations: classpath:schema.sql这个配置在第一次运行时能省去手动导入脚本的麻烦但如果已经跑过一遍建议把mode改成never避免每次启动都执行一遍。2.3 核心业务逻辑自动组卷、答题判分与成绩统计后端最核心、也最容易被问到“怎么实现”的就是自动组卷和答题判分这两块。自动组卷的逻辑是这样的先定义试卷规则比如“单选题 10 道每题 3 分多选题 5 道每题 4 分判断题 5 道每题 2 分简答题 2 道每题 10 分试卷总分 100 分”。组卷时按题目类型和难度系数,从题库中随机抽取指定数量的题目生成exam_paper和paper_question记录。public ListLong selectRandomQuestions(Long courseId, Integer type, Integer difficulty, Integer limit) { // 简单示例按课程、题型、难度随机抽取 return questionMapper.selectList(new LambdaQueryWrapperQuestion() .eq(Question::getCourseId, courseId) .eq(Question::getType, type) .eq(Question::getDifficulty, difficulty) .orderByAsc(Question::getId) .last(ORDER BY RAND() LIMIT limit)) .stream().map(Question::getId).collect(Collectors.toList()); }答题判分需要区分客观题和主观题。单选题、多选题、判断题都可以直接把用户提交的答案和数据库中存的正确答案比对一致就得分不一致就是 0 分。多选题要额外处理“少选漏选”的情况通常策略是严格模式答案序列完全一致才得分。简答题客观判不了默认给 0 分并标记为待人工复核由教师在后台手动打分。if (question.getType() 3) { detail.setIsCorrect(false); detail.setScore(0); detail.setNeedReview(true); // 简答题待教师复核 }成绩统计部分我提供了“按试卷统计平均分、最高分、及格率”和“按学生统计历史成绩曲线”两种维度。这里用了 MyBatis-Plus 的分页插件把统计 SQL 写在 Mapper XML 里比 Java 里做内存计算高效得多。3. 微信小程序端把后端能力搬进手机里3.1 小程序项目结构与登录鉴权小程序端我用的是微信官方原生开发没有引入 uni-app 或 Taro原因是原生框架对微信 API 的支持最及时调试也最直接。项目结构上pages目录放页面utils目录放请求封装和工具函数components目录放自定义组件例如答题卡组件。登录鉴权走的是微信wx.login换取code然后把code发给后端后端调用微信的jscode2session接口换取用户的openid再用openid判断用户是否已注册。无论新老用户最终后端都会签发一个 JWT Token 返回给小程序小程序把 Token 存到wx.setStorageSync之后每次请求都在 header 里带上。// utils/request.js const BASE_URL https://your-api.example.com/api function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success(res) { if (res.data.code 200) resolve(res.data.data) else reject(res.data.message) }, fail(err) reject(err) }) }) } module.exports { request }3.2 课程学习、考试列表与答题交互实战首页设计很简单就是课程卡片列表数据从后端/api/course/list拉取按课程状态过滤出已上架的课程。进入课程详情页后可以看到课程介绍、教师信息、学习资料列表和考试列表。答题页是整个小程序端工程量最大的地方。题干区、选项区、答题卡区、倒计时区四块布局。重点讲一下单选框的实现很多新手在选择题交互上容易写错radio-group bindchangehandleAnswerChange label wx:for{{currentQuestion.options}} wx:keyindex classoption-item radio value{{item}} checked{{currentAnswer item}} color#1AAD19 / text{{item}}/text /label /radio-grouphandleAnswerChange(e) { const val e.detail.value this.setData({ [answerList[ currentIndex ]]: val }) }这里的关键点在于radio-group的bindchange事件触发后要用e.detail.value拿到当前选中的值并更新到data里的答案数组对应下标这样切题时checked才能正确回显之前的选择。我见过很多新手把答案只存在局部变量里导致切题后已选内容丢失一定要通过setData同步到页面数据源。答题卡区域的实现思路是用一个循环渲染试卷所有题目序号根据answerList里是否有值来判断该题是“已答”还是“未答”已答显示为绿色未答显示为灰色点击序号直接跳转到对应题号。顶部再放一个倒计时组件时间到自动提交答卷。3.3 本地联调与接口对接的坑小程序本地联调最大的坑是域名校验。微信开发者工具默认会校验请求域名必须是 HTTPS 且在公众平台配置过否则会报url not in domain list。开发阶段有个临时解决办法在开发者工具右上角“详情 → 本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。这是官方提供的调试选项仅限本地开发使用上线前必须换成真实的 HTTPS 域名。接口对接时还要注意一个小程序端的“经典问题”请求超时。小程序默认wx.request超时时间是 60 秒但后端如果要做 AI 批改、导出报表等耗时操作很容易超过这个限制。我的处理方式是把耗时操作改成异步任务前端先拿到一个“任务已受理”的结果再通过轮询或 WebSocket 获取最终结果。虽然这套系统暂时用不到但提前设计好总是好的。4. 运行环境与部署上线从本地跑通到云端发布4.1 本地环境搭建JDK、Maven、MySQL、微信开发者工具拿到的源码要跑起来本地环境必须装齐这四样JDK 8或 11、Maven 3.6、MySQL 5.7或 8.0、微信开发者工具。JDK 环境变量配置是老生常谈但依旧高频踩坑的问题。配置完后在命令行输入java -version和javac -version两个都能正常输出版本号才算成功。如果提示“javac 不是内部或外部命令”基本是JAVA_HOME路径配置错了或者path变量里少了%JAVA_HOME%\bin。另外装了多个 JDK 版本的同学记得把path里靠前的那个路径删掉避免优先级冲突。MySQL 安装完成后先用命令行mysql -uroot -p验证能正常登录再执行init.sql脚本完成建库。配置文件里spring.datasource.password要改成你自己的密码serverTimezoneAsia/Shanghai这个参数建议保留否则会有时区报错。后端启动命令mvn spring-boot:run等待出现“Started ExamApplication”字样就说明启动成功了。然后打开微信开发者工具导入小程序前端项目把utils/request.js里的BASE_URL改成http://localhost:8080/api编译后就能看到首页数据。4.2 服务器部署打包、上传、后台运行本地跑通只是第一步上线部署才是完整的项目交付。我采用的方式是传统的云服务器 手动部署虽然不如 Docker 容器化那么新潮但对课程设计阶段的人来说更直观、更容易理解。后端打包mvn clean package -DskipTeststarget目录下会生成exam-server.jar通过scp或者宝塔面板的文件管理上传到服务器。然后后台运行nohup java -jar /opt/exam/exam-server.jar --spring.profiles.activeprod /opt/exam/logs/exam.log 21 这里有个小技巧nohup可以让 Java 进程在 SSH 断开后继续运行日志写到指定文件里排错时直接tail -f /opt/exam/logs/exam.log就能实时看输出。小程序端上线前需要在微信公众平台配置服务器域名request 合法域名必须是 HTTPS然后把小程序上传为体验版扫码测试没问题后再提交审核审核通过后点击“发布”即可。这里提醒一下HTTPS 证书可以直接在云厂商控制台申请免费的 SSL 证书不需要额外购买。4.3 源码、文档和视频怎么配合使用这套项目交付时附带了源码、文档、运行视频和讲解视频我开始以为大家拿到手就会直接写代码后来发现大部分人是先看视频和文档的。合理的使用顺序应该是先花 20 分钟看“运行视频”了解系统有哪些功能、页面长什么样、整体流程怎么走然后打开实践文档按文档里的步骤把环境搭好、把系统跑起来跑通之后再回头对照“讲解视频”重点看核心模块的实现思路最后才是自己去改代码、加功能。文档我建议重点读三部分需求分析章节了解系统边界、数据库设计章节理解表结构、接口文档快速定位接口。不要一上来就逐行读源码那样效率极低。视频则适合在功能“接不上”的时候针对性看比如不知道小程序端怎么登录就跳到登录模块的视频看那几分钟。5. 常见问题排查与避坑指南5.1 运行环境相关Spring Boot 版本、Java 环境、MySQL 连接这套系统我前后跑了不下十次也帮别人排查过不少环境问题最典型的有这么几个第一个是 Spring Boot 版本冲突导致的启动失败。解决办法很简单统一用项目 pom.xml 里锁定的版本不要手动去改。如果你是新建项目想复刻这套系统建议直接用 2.7.x配套的依赖如 MyBatis-Plus、JJWT都有稳定对应的版本不会出现类找不到或方法不存在的问题。第二个是 MySQL 连接失败。报错往往是Access denied for user rootlocalhost或者Communications link failure。前者是用户名或密码错了后者大概率是 MySQL 服务没启动或者端口不是默认的 3306。排查思路就一条先排除 MySQL 本身再用配置连接最后检查防火墙。第三个是“数据库访问中文乱码”。这个大概率是 JDBC 连接串少了characterEncodingutf8或者建表时没有指定utf8mb4字符集。连接串改成jdbc:mysql://localhost:3306/online_exam?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai基本能解决九成问题。5.2 小程序端常见问题请求、渲染与样式小程序端的坑集中在这几类。请求类问题最典型的就是“域名不合法”。我之前已经讲了本地调试的配置方法这里再强调一次上线之前务必在微信公众平台配置 request 合法域名且必须是 HTTPS。如果上线后直接提交代码不配置域名用户端会一直请求失败。渲染类问题是单选、多选回显不准确。这类问题的根因往往是checked属性依赖了错误的数据源。你要确保切换题目的时候把当前题的已选答案从answerList中拿出来赋值给当前题目的checked绑定变量而不是直接复用上一题的答案。还有一个容易被忽略的布局问题是顶部导航栏高度适配。iPhone X 及以后机型有刘海屏小程序官方推荐使用wx.getSystemInfoSync()里的statusBarHeight和胶囊按钮位置来计算导航栏高度。不要硬编码一个固定的 64px不同机型上会错位得很明显。5.3 常见问题速查表问题现象根本原因解决方法javac 不是内部或外部命令JAVA_HOME 或 path 配置错误检查 JAVA_HOME 指向 JDK 根目录确认 path 含%JAVA_HOME%\binmvn spring-boot:run 下载依赖失败Maven 源是国外仓库网络不稳定修改 settings.xml 的 mirror 为阿里云镜像启动报 Communications link failureMySQL 服务未启动或端口不可达先本地登录 MySQL再检查连接串数据库中文乱码连接串或表字符集未用 utf8连接串加 characterEncodingutf8建表用 utf8mb4小程序请求超时或无法连接域名未配置 / 后端未启动本地调试勾选“不校验合法域名”检查后端进程答题页已选答案切题后丢失answerList 与页面数据绑定不同步用 setData 更新数组对应下标多选题少选也算错判分策略选择不一致后端统一按严格模式比对答案序列部署后静态资源/接口 404打包不完整或端口被占用重新 mvn clean package检查 start 端口日志个人经验方面我想多说一句做了这么多套系统之后我的判断标准是“一个在线考试系统好不好用先看两个细节”。第一个是学生交卷那一刻的响应速度如果交个卷要转圈几秒钟体验基本就废了第二个是教师导出成绩的方便程度后台能不能按班级、按试卷快速导出 Excel。这套系统里我把交卷接口设计成只做保存和判分不涉及复杂统计成绩导出做成异步任务老师点击后稍等几秒就能下载文件。另外如果要用这套系统去答辩重点准备三个问题为什么用微信小程序而不是原生 App 或 H5数据表为什么这么设计组卷和判分的具体策略是什么。这三个问题想清楚评审老师基本就认可了。还有一个小技巧源码拿到手后先看/sql目录下的初始化脚本再看application.yml这两个文件看懂了项目的整体情况就掌握了八成比一上来读代码效率高很多。这套系统后续还可以扩展不少方向比如加入错题本功能把答题明细里is_correct 0的题目自动汇总成个人错题集或者引入知识点标签让学生按薄弱点专项练习。结构上后端已经预留了扩展空间改起来并不难。希望能给你省点时间少踩些坑。
分享:

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

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