ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring Boot + Vue在线考试系统开发全流程实战

2026/9/10 19:54:34 拓冰建站 浏览量
Spring Boot + Vue在线考试系统开发全流程实战 如果你正在为毕业设计选题发愁或者想从零完整走一遍前后端分离项目的全流程基于Spring Boot Vue的在线考试系统是一道很经典、也很能锻炼人的题。它覆盖了Web开发里最常见的硬骨头角色权限、题库管理、自动组卷、在线答题、自动评分还牵扯到前后端怎么联调、怎么打包成一个可运行的jar。这套技术栈IDEA Maven Spring Boot Vue几乎是目前毕业设计和中小型团队项目的标准组合跑通一遍你对整个Java全栈开发的理解会有一个质的提升。这篇文章不会只给你贴代码我会把这套系统从需求分析、表设计、后端实现、前端页面到最后的部署排坑按照实际操作顺序完整拆开讲。文章面向的是那些已经学过Java基础和前端三件套、但还没有独立完成过完整项目的同学。你跟着走一遍不仅能交出一个能演示、能答辩的系统更重要的是知道每个环节为什么这么做踩到坑的时候懂得怎么定位问题。1. 在线考试系统选型为什么是Spring Boot Vue Maven IDEA1.1 在线考试系统到底要解决哪些真实问题我见过不少同学拿到在线考试系统这个题目之后第一反应就是做几个页面能登录能考试就行。真动手才发现光是把需求理顺就要花不少时间。一个能称得上完整的在线考试系统至少要回答这几个问题谁来用考什么怎么考怎么判使用者分三类管理员、教师、学生。管理员管用户、管课程和考试信息教师管题库、出卷、发布考试、批主观题学生选考试、答题、看成绩。这三类角色的操作权限完全不同这就逼着你必须处理登录认证和权限拦截。考试流程上教师要能手动选题也能自动组卷学生考试时要有倒计时、提交限制考完之后客观题要自动判分主观题留给教师人工评判。这些需求一点点拆下来功能点其实相当多。我见过有人用JSPServlet硬写的代码几千行糊成一团后面改一个需求要动十几个文件。这也是为什么现在大家一致转向Spring Boot Vue这套前后端分离方案。1.2 从SSH到Spring Boot框架选型的实际理由早期Java Web开发用SSHStruts Spring Hibernate或者SSMSpring SpringMVC MyBatis配置文件的复杂程度劝退了很多人。Spring Boot最大的价值在于自动配置和极简启动你只需要在pom.xml里引入spring-boot-starter-web写一个SpringBootApplication主类就能跑起一个Web服务。内嵌Tomcat让你不需要单独装Web容器打包直接用java -jar运行这在部署时比传统方式省太多事。对于在线考试系统这类CRUD密集的业务系统Spring Boot提供的spring-boot-starter-data-jpa或MyBatis-Plus能极大简化数据访问层的代码。再加上它有成熟的生态JWT认证、Redis缓存、Excel导入导出都能找到对应的starter一键接入。如果你用JDK 8认准Spring Boot 2.7.x版本就好这是最稳妥的组合Spring Boot 3.x要求JDK 17很多跟着课程走、还在用JDK 8的同学一上来就卡在版本不兼容上。1.3 为什么前端必须上Vue而不是继续用模板引擎以前用Thymeleaf或JSP做服务端渲染每个页面都是一整份HTML前后端代码混在一个工程里改个样式要重启服务页面跳转刷新体验也差。Vue这类前端框架解决了什么问题组件化和响应式数据。页面拆成一个个组件数据变了视图自动更新不用手动操作DOM配合Vue Router管理路由切换页面不用刷新体验接近原生App。更重要的是前后端分离之后后端只写接口返回JSON前端专注页面交互两边可以并行开发。我做这套系统时先定好接口文档前端同学调mock数据开发页面后端写Controller最后联调的时候对接一下就行。这个协作模式也是目前企业里真正在用的提早适应没有坏处。2. 业务模块拆解与数据库设计先画清楚再动代码2.1 三种角色、三类核心流程把需求翻译成功能数据库设计之前我建议先把角色和流程画出来。不要一上来就建表先弄清楚每个角色有哪些操作每个操作涉及哪些数据不然建出来的表不是缺字段就是多一张没用的表。管理员端的功能用户管理教师和学生账号的增删改查、考试管理创建考试、设定考试时间、关联试卷、成绩总览。教师端的功能题库管理单选、多选、判断、简答四种题型的维护、试卷管理手动组卷和自动组卷、考试发布、主观题批阅、成绩导出。学生端的功能查看可参加的考试、在线答题、查看考试成绩和答题详情。把功能列完你会发现核心数据实体非常清晰用户、角色、试题、试卷、考试、答题记录、成绩。接下来就是怎么把这些实体变成表。2.2 表结构设计从用户表到答题流水表我在这套系统里设计的主要表大概有这些表名用途关键字段sys_user用户表id, username, password(BCrypt加密), real_name, rolequestion试题表id, type, content, options, answer, score, difficulty, course_idpaper试卷表id, name, total_score, duration, course_idpaper_question试卷题目关联表id, paper_id, question_id, scoreexam考试表id, name, paper_id, start_time, end_time, creator_id, statusexam_record答题记录表id, exam_id, user_id, question_id, user_answer, is_correct, scoreexam_result成绩表id, exam_id, user_id, total_score, submit_time有几点要特别说明。question表的options字段我用JSON字符串存选项这样单选、多选、判断题都能兼容前端解析也方便省得为每种题型建一张子表。answer字段对判断题存对/错对多选题存选项集合的JSON评分时再解析。paper_question表里冗余了一份score字段因为同一道题在不同试卷里分值可以不同。2.3 试卷快照机制为什么答题记录要冗余一份题目内容这里我要重点讲一个很多初学者不会注意、但真实项目里非常关键的设计答题记录里必须冗余题目快照。你可能觉得答题记录表存了question_id到时候关联question表查题目内容不就行了问题在于试卷和题目是可以被教师修改的。学生考完试之后教师如果修改了某道题的题干甚至答案历史答题记录再关联查询就会出现当时考的题和现在显示的不一样的严重数据不一致。成绩单里的题目内容、答案、得分全对不上。解决办法就是在exam_record表里把题目内容、选项、正确答案、用户答案、得分全部冗余存储。学生提交答案的那一刻查出题目快照连同作答内容一起写入记录表。这样哪怕后来题库改了历史成绩单查出来依然是学生当时真正看到的题目。这个快照思路在很多业务系统里都会用到比如订单要存商品快照、合同要存条款快照本质都是防止关联数据变动导致历史记录失真。2.4 自动组卷规则随机抽题不是简单order by rand自动组卷的逻辑看起来很简单按题型和难度随机从题库抽题。网上很多代码直接SELECT * FROM question ORDER BY RAND() LIMIT 10但数据量大了这种写法性能很糟糕。更合理的方式是按条件分组抽题。我的方案是让教师在创建试卷时先设定规则单选10题每题2分、多选5题每题3分、判断10题每题1分、简答2题每题10分。后端拿到规则后对每种题型分别查询符合条件的题目ID然后从ID列表里随机取指定数量。控制层代码大致是这样// 按题型难度课程筛选题目ID再随机抽取 ListInteger ids questionMapper.selectIdsByTypeAndDifficulty(type, difficulty, courseId); Collections.shuffle(ids); ListInteger selected ids.subList(0, Math.min(needCount, ids.size()));抽题时还要加一个校验如果题库里满足条件的题不够要返回给前端一个明确提示告诉教师单选题库不足当前只有8道需要10道而不是默默用不够数量的题生成试卷。这个细节在演示和答辩时非常加分说明你考虑到了真实使用场景。3. Spring Boot后端实战从工程骨架到安全访问3.1 工程初始化Maven坐标、Spring Boot版本和JDK版本的三角关系创建工程之前先把三个版本的关系搞清楚。这是新手问得最多的坑。我用一张表把对应关系列清楚Spring Boot版本最低JDK要求常用JDK推荐度2.7.xJDK 8JDK 8 / 11最稳适合课程设计和毕业设计3.0.xJDK 17JDK 17新项目可以用但环境要求高如果你用IDEA自带Spring Initializr创建项目它默认选的可能是最新版本3.x而你的电脑装的是JDK 8一运行就报错Unsupported class file major version。解决方式有两种在创建时把Spring Boot版本改成2.7.18或者卸载JDK 8换成JDK 17。我强烈建议如果只是做课程设计直接选2.7.x配合JDK 8最省事因为很多机房电脑、旧服务器还是JDK 8你答辩演示时环境兼容性最好。Maven这边国内必做的操作是配置阿里云镜像否则下载依赖会慢到怀疑人生。在maven/conf/settings.xml里加mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror另外在settings.xml里确认localRepository指向的本地仓库路径IDEA中设置Maven home path和settings.xml位置User settings file要指向你改过的settings.xml否则改了镜像也不生效。3.2 Controller-Service-Mapper三层代码怎么组织才不混乱工程建好后的包结构我建议这样组织com.example.exam ├── controller // 接收请求返回Result ├── service // 业务逻辑接口 ├── service.impl // 业务逻辑实现 ├── mapper // MyBatis-Plus的Mapper接口 ├── entity // 数据库表对应的实体类 ├── dto // 接收前端参数的模型 ├── vo // 返回前端数据的模型 ├── config // 配置类CORS、拦截器、MyBatis-Plus分页 ├── common // 统一结果Result、异常处理 ├── util // JWT工具类等 └── ExamApplication.java三层架构的核心思想是请求从Controller进业务逻辑在Service处理数据库操作在Mapper。Controller只做参数接收和结果封装不写任何SQLService里写业务判断和事务控制用Transactional保证数据一致性Mapper对应SQL或MyBatis-Plus的BaseMapper方法。这里特别推荐MyBatis-Plus它把单表CRUD、分页、条件构造器都封装好了能少写一大半样板代码。比如用户管理的分页查询PageUser page userMapper.selectPage(new Page(current, size), new LambdaQueryWrapperUser().like(StringUtils.hasText(name), User::getName, name));3.3 JWT登录认证与角色权限拦截的落地方式考试系统的三个角色权限不同登录之后不同角色能访问的接口不一样这就必须做认证和授权。我选的是JWTJSON Web Token方案无状态、适合前后端分离。实现思路分三步。第一步用户登录成功后后端把用户ID和角色放进Token里生成JWT返回给前端String token Jwts.builder() .setSubject(user.getId().toString()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 24)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact();第二步前端拿到Token存到localStorage每次请求在header里带Authorization: token。第三步后端写一个拦截器拦截非放行路径的请求校验Token解析出用户信息放到请求上下文。拦截器里要放行的路径包括登录接口、注册接口、静态资源。放行的配置我用一个白名单数组维护其他接口全部校验。校验通过后还要做角色判断比如教师端接口要求role teacher学生端接口要求role student管理员操作需要role admin。我是在拦截器里解析出角色后对比当前访问路径所属的角色权限。一个更精细的做法是配合Spring Security或自定义注解但课程设计用拦截器完全够用代码量少且逻辑透明。密码存储这边千万别明文存入数据库。用Spring Security里的BCryptPasswordEncoder或者独立的spring-security-crypto依赖对密码做BCrypt哈希存储登录时做比对。就算数据库泄露拿到的也是哈希值安全性好很多。3.4 自动评分逻辑客观题对答案、主观题留给人工考试提交之后评分逻辑分两条线。单选题和判断题直接对比用户的答案和标准答案字符串一致得满分不一致得0分。多选题要麻烦一点标准答案是ABD用户答ABD算对用户答AB算漏选按规则可能得部分分用户答ABC算错选0分。我实现的评分规则是多选答案用户必须完全匹配才得分这是最常见也最好解释的方案。简答题没法自动判我得把它从自动评分里剔出来。学生提交考试后正确做法是把主观题答案单独存到exam_record表里成绩表先创建一个status 待批阅的记录教师端列表里能看到待批阅的考试点进去给每道简答题打分输入得分后汇总更新总成绩。这里用到了Transactional教师提交所有简答题分数时循环更新记录分然后重新汇总总分写回成绩表整个过程要么全部成功要么全部回滚防止出现批了一半服务器崩了成绩不对的情况。4. Vue前端开发考试流程页面实现的关键细节4.1 项目初始化与跨域代理配置前端用Vue 3 Vite Element Plus这套组合。初始化命令很简单npm create vitelatest exam-web -- --template vue cd exam-web npm install npm install vue-router4 pinia axios element-plus工程创建好、依赖装完第一件事是配开发环境的代理。开发时前端跑在5173端口后端跑在8080端口两者端口不同浏览器直接发请求会被跨域挡住。解决开发环境跨域最优雅的方式是在vite.config.js里配置proxyexport default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端请求/api/user/loginVite开发服务器自动转发到http://localhost:8080/api/user/login浏览器看到的还是同源请求跨域问题消失。注意请求路径里要带/api前缀后端Controller的RequestMapping也统一以/api开头两边约定好联调时省心。4.2 路由守卫和用户状态管理用户登录后的Token和角色信息要全局共享我放在Pinia里管理。设计一个userStore保存token、username、role提供login和logout方法配合localStorage做持久化刷新页面后重新读取用户不会掉线。路由守卫控制页面访问。考试系统有几个典型场景未登录用户访问任何页面都跳回登录页学生用户不能进入教师管理页教师用户不能进入学生考试页。用Vue Router的全局前置守卫实现router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() } else if (!token) { next(/login) } else { // 校验角色是否匹配路由meta里要求的角色 const requiredRole to.meta.role const userRole localStorage.getItem(role) if (requiredRole requiredRole ! userRole) { next(/403) } else { next() } } })路由定义里给每个页面标上meta.role如meta: { role: teacher }。这套逻辑写完之后前端权限控制基本成型再配合后端的JWT拦截器双保险。4.3 考试页面的倒计时、防切屏与自动交卷考试页是整个系统交互最复杂的页面也是演示时最出彩的部分。倒计时直接起步从考试总时长剩余秒数开始每秒减1显示成剩余时间23分45秒。倒计时到0时强制提交所有已答答案。提交按钮也会确认一次确认交卷交卷后无法修改防止误触。防切屏这个功能很能体现系统的实用性。我用document.visibilitychange事件监听页面是否被切换当页面隐藏超过一定次数比如3次自动交卷并记录违规。实现逻辑document.addEventListener(visibilitychange, () { if (document.visibilityState hidden) { this.switchCount if (this.switchCount 3) { this.submitExam() // 自动交卷 } else { ElMessage.warning(检测到切屏第${this.switchCount}次超过3次将自动交卷) } } })注意这个功能要在考试开始后启用并提示学生浏览器不能切走。它防不住拿着手机拍照的高级作弊但能把网页上复制粘贴答案这种低级作弊挡住答辩时讲清楚思路就很加分。单选题目的数据回显也有一点要留心用户点击选项时把选项值存到本地一个对象里题目是单选还是多选决定是直接赋值还是push到数组。题目切来切去再切回来答案不能丢。用一个answers对象以questionId为key存储性能最好也方便最后提交时整包传给后端。4.4 表单校验和编辑回显中的坑题库的增删改查页面表单校验有自己的规矩。Element Plus的el-form配合rules实现校验不难比较坑的是编辑场景的数据回显。我遇到过最典型的问题是编辑一道选择题打开弹窗后表单里的选项没显示一看是v-model绑定的数据层级太深或者从父组件传入的prop对象被直接修改却没有触发响应式。正确的做法是编辑弹窗打开时重新从后端获取完整题目数据再浅拷贝一份到表单model。不能直接把父组件的对象拿来改否则取消编辑时原数据已经被污染了。用reactive定义表单对象打开编辑时Object.assign(form, res.data)关闭弹窗时Object.assign(form, initForm())重置。这个小细节处理好了页面操作流畅很多不好好弄就会出现取消编辑但列表里题目已经被改了这种尴尬bug。5. 联调、打包与部署把前后端凑成完整系统5.1 跨域问题的三种解法对比开发环境我们用Vite proxy解决跨域但生产环境部署时前端是静态文件后端是Java服务跨域问题仍然存在。常见的解法有三种我对比一下方案实现方式适用场景注意点后端CORS后端配置CrossOrigin或全局CorsFilter开发环境快速联调不能配置成*要指定前端地址前端代理Vite/webpack proxy仅开发环境生产环境无效Nginx反向代理Nginx把/api转发到后端服务生产环境最推荐还顺便处理静态资源生产环境部署时我强烈建议用Nginx。前端打包出的dist目录直接扔给Nginx托管Nginx里配置一条规则把所有/api开头的请求转发到http://localhost:8080。这样前后端完全是同域都是Nginx的端口不存在跨域问题还顺带解决了前端路由history模式刷新404的问题Nginx配置try_files $uri /index.html。5.2 Vue打包产物如何与Spring Boot集成进同一个jar如果你没有独立的前端服务器比如课程设计只申请了一台云服务器可以把Vue打包产物塞进Spring Boot的src/main/resources/static目录这样最终打出来的jar自带页面和接口一个进程跑完。操作步骤# 前端打包 npm run build # 将dist目录下的内容复制到后端static目录 cp -r dist/* ../exam-server/src/main/resources/static/ # 后端打包 mvn clean package -DskipTests # 运行 java -jar exam-server.jar启动后访问http://localhost:8080Nginx层都不用装直接就是前端页面/api接口由同一个服务提供。这个方式对演示和答辩最友好拷一个jar文件到任何装了JDK的机器上就能跑。需要提醒的是每次前端改了代码重新build之后一定要复制到static里再重新打包很多人忘了复制导致演示时还是旧页面非常尴尬。5.3 部署上线检查清单每次部署新环境我习惯按这个清单过一遍能省下大量排查时间服务器JDK版本和打包时的JDK版本一致不一致容易报UnsupportedClassVersionErrorMySQL数据库初始化脚本执行过表结构完整application.yml里数据库地址、用户名、密码改成环境对应的值检查MySQL驱动版本MySQL 8要用com.mysql.cj.jdbc.Driver连接串带serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8端口没有被占用firewall-cmd或安全组放行对应端口如果需要对外开放数据库不要用root账号单独建一个最小权限账号6. 踩坑实录六类高频问题定位与修复6.1 数据库连接失败别只盯着报错第一行数据库连接失败是出现频率最高的报错形式五花八门。我排错时会看异常信息的核心段别被大片堆栈吓到Access denied for user rootlocalhost用户名或密码错了或者是这个账号不允许从当前主机连接Unknown database exam_db数据库不存在检查数据库名或者还没执行建库脚本Communications link failure数据库服务没启动或者IP端口不对The server time zone value Öйú±ê׼ʱ¼ä时区问题连接串加serverTimezoneAsia/ShanghaiPublic Key Retrieval is not allowedMySQL 8的认证插件问题连接串加allowPublicKeyRetrievaltrueuseSSLfalse排查顺序建议是服务启动与否 → 网络通不通ping telnet IP端口 → 账号密码 → 数据库存在性 → 连接串参数。很多人一上来就改代码其实多半是配置文件里的地址或者账号写错了。6.2 接口返回404和405先分清谁挡住了请求接口调用不通先看状态码。404和405是两种完全不同的错误。404说明请求路径没有被任何Controller接收。原因可能是路径写错了比如前端请求/api/user/list后端RequestMapping是/api/user/getList对不上也可能是Controller类没有被Spring扫描到比如主类在com.example.exam而Controller类放在了com.example.controller包超出了主类扫描范围。405说明路径找到了但请求方法不对。前端用POST后端写的是GetMapping自然进不来。还有一个隐蔽点RequestBody接收JSON时如果前端Content-Type没设置成application/json或者请求体格式不对会抛出HttpMessageNotReadableException也容易误判成404。我的排查套路是先在浏览器控制台看请求的完整URL和方法去后端Controller里搜这个路径确定路径没问题后再看方法注解。还不行就打断点调试别靠猜。6.3 前端页面白屏与首屏加载慢前端白屏的原因主要在路由和打包配置上。如果用了createWebHistoryhistory模式路由后端服务器如果没有把所有路由都重定向到index.html直接访问/exam-detail这类深层路径就会404白屏。解决方式开发时后端代理加一条rewrite生产时Nginx配置try_files $uri $uri/ /index.html。如果不想管这些直接改用createWebHashHistoryhash模式URL里带#永远不会404但就是不够美观。首屏加载慢多半是打包后的JS文件太大。我的常规优化三板斧第一路由懒加载每个页面组件单独打成chunk按需加载第二Element Plus按需引入别整个import ElementPlus from element-plus全量引入第三大图标库用CDN或按需引入。做完这些首屏加载从好几秒能降到一秒以内。6.4 本地能跑、服务器跑不了的环境错位还有一种崩溃是我本地明明是好的。这类问题十有八九是环境不一致。我遇到过几次典型的Windows本地代码用GBK编码提交到Linux服务器上中文全变乱码。解决方案是统一用UTF-8编码IDEA右下角把文件编码改好pom.xml里project.build.sourceEncoding设置成UTF-8。本地JDK 8打包服务器装了JDK 17运行时不兼容。反过来也可能本地用IDEA默认JDK 17开发打成class文件是Java 17的字节码服务器JDK 8根本读不了直接UnsupportedClassVersionError。还有一种项目里写了本地绝对路径比如文件上传后存到了D:/upload服务器上根本没这个目录。解决办法是配置文件里用相对路径或者系统属性比如${user.dir}/upload部署时再通过配置项指定。6.5 考试进行中途Token过期学生提交失败这个坑我也是实战中被学生反馈之后才发现的。JWT的过期时间我一开始只设置了30分钟结果一场考试60分钟学生答到一半Token过期所有请求全部401交卷直接失败。前端跳回登录页就更惨了重新登录后考试状态全丢。解决思路要分两层。第一层把Token的有效期设置成比最长考试时长更长比如24小时保证考试周期内不会过期。第二层在前端axios响应拦截器里遇到401时先尝试用refreshToken刷新刷新成功就重发原请求。对课程设计来说第一层就够用但从设计角度能讲出第二层的思路说明你考虑到了真实场景下的边界情况。6.6 并发提交导致成绩重复插入最后说一个并发场景。学生快速点击交卷按钮前端连续发两次提交请求后端如果没有处理成绩表里就会插入两条记录总分统计翻倍。用Transactional只能保证单次事务防不住两个并发请求同时进来。解决方式有三个层次。最简单的是前端加锁点击提交后把按钮禁用同时用一个isSubmitting标记挡住第二次请求。后端再加一层幂等提交接口里先根据examId userId查成绩表如果已经存在直接返回已有成绩不再重复插入。数据库层面再给exam_result表加唯一索引(exam_id, user_id)三重保险。我实际只用了前两层就够稳了但把第三层的思路写进设计文档里答辩时非常加分。个人项目体会在线考试系统还能怎么扩展这套系统完整做完其实已经是一个很扎实的项目了。但如果你还有时间我建议在几个方向上加码成本和收益都很高。一是给成绩汇总加个Redis缓存考试结束后把成绩写入Redis列表查询走缓存展示性能提升明显二是用EasyExcel加一个成绩导出功能教师一键导出Excel这个功能几乎每个真实考试系统都需要三是如果课程是信息安全方向的可以给管理员操作加日志审计谁在什么时间改了哪道题全部留痕。这几个功能都不复杂但对项目质量和答辩说服力提升非常明显。我的体会是这类管理系统的技术难点从来不在某个单一技术上而在数据的一致性、权限的边界、异常场景的处理这些细节里。你把这些细节一个个补齐从能跑到能用再到敢给别人用这个过程中获得的能力比项目本身值钱得多。希望这篇整理能帮你少踩几个坑顺利把系统做出来。