☰
Java Web前后端分离知识竞赛系统:SpringBoot+Vue全栈实现
2026/10/15 15:07:37 网站建设 项目流程

做毕业设计和做真实项目最大的区别在于,真实项目可以不断迭代,而毕设必须在有限时间内把完整闭环跑通,还得能讲清楚。我这次整理的信息知识赛系统,就是一个标准的前后端分离竞赛平台:管理者在后端配置题库、创建竞赛,参赛者在网页上完成限时答题,系统自动判分并给出排行榜。整个项目源码、SQL 脚本和接口文档都是齐全的,适合正在准备 Java Web 方向毕设的同学拿来拆解,也适合想快速搭建内部知识竞赛流程的开发者直接复用。

我当时做这个项目时最深的感受是:这类平台表面看就是“题库+答题+成绩”,真正动手才会发现,竞赛流程的状态管理、判分规则的一致性、前后端联调的数据格式,才是吃掉大部分时间的地方。下面这篇文章,我把项目的需求拆解、数据库设计、核心模块实现和部署注意事项一次讲清楚,也给准备拿这套系统做毕设或者二次开发的朋友一个可以直接照着走的路线。

1. 项目定位与需求拆解

1.1 这个系统到底解决什么问题

我见过不少同学拿到“信息知识赛系统”这类题目后,第一反应就是“这不就是个在线考试系统嘛”。真把需求拆开看,竞赛和考试还是有不少区别的。考试更偏向标准化答题和阅卷,而竞赛要额外考虑赛事配置、题型权重、排行榜、参赛者状态流转这些细节。这套系统最大的作用,就是把知识竞赛里最常用的一条闭环落地:管理员在后台维护题库、创建竞赛、控制发布节奏,参赛者在前端浏览赛事、完成限时答题、交卷后立刻看到成绩和排名,整个流程不需要任何人工干预。

选这个题目的好处在于,它几乎覆盖了 Java Web 方向的高频考点:SpringBoot 的接口开发、MyBatis 的数据访问、Vue 的组件化交互、前后端分离联调、MySQL 表设计、JWT 登录状态管理。一套代码做下来,技术栈完整度和工作量都很扎实,无论是做毕设还是课程综合项目,内容都够写、够讲。

从适用人群来说,如果你是刚接触前后端分离项目的学生,可以直接照着源码从零跑通一遍流程;如果你已经有了一定基础,重点去看数据库设计和判分模块的思路,那才是这套系统的核心难点,也是容易在答辩时被追问的地方。

1.2 核心角色与业务主流程

整个系统的角色,我拆成了三类:系统管理员、普通参赛者、以及一个可选的竞赛负责人。管理员负责用户管理、题库管理、竞赛管理;参赛者只需要注册、登录、答题、查看成绩;竞赛负责人可以看成管理员的一个子集,他只能维护自己名下创建的竞赛内容。这样设计的目的是避免权限模型一开始就做得太重,先把核心业务跑通,再考虑细粒度权限。

业务主流程,我总结成五步。第一步,管理员在题库模块录入题目,或者通过 Excel 模板批量导入,一次性把一批竞赛题目灌进去。第二步,创建一场竞赛,配置竞赛名称、开始时间、持续时间、总题数、每题分值。第三步,从题库里按规则选题,形成这场竞赛的固定试卷。第四步,参赛者收到竞赛发布通知后进入答题页,系统从交卷时刻或者倒计时结束时刻计算成绩。第五步,交卷后自动判分,按总成绩生成排行榜,参赛者可以看到自己的得分和排名。

这个流程看起来顺理成章,真正麻烦的是三条约束:竞赛发布前不能改题、答题过程中不能重新出题、交卷后判分必须保证幂等。后面讲到数据库和功能模块的时候,我会把这三条约束怎么落地说清楚。

2. 技术选型与架构解析

2.1 技术栈为什么这么组合

后端选 SpringBoot,与其说是因为它有多新潮,不如说它是 Java Web 场景里最稳妥的选择。SpringBoot 的自动配置把传统 SSM 那套繁琐的 XML 配置省掉了,单独构建一个可运行的服务基本不需要额外折腾。搭配 MyBatis-Plus 使用,单表 CRUD 几乎不用写 SQL,复杂查询再手写 XML,开发效率跟可维护性都能兼顾,这也是现在大部分教学项目和企业项目的常见组合。

前端选 Vue 加 Element 组件库,是因为竞赛平台里管理后台的表格、表单、弹窗操作非常多,Element 的现成组件能省下大量样式和交互的重复工作。而答题页面这种交互密集的场景,Vue 的响应式数据管理让页面状态同步变得很自然。Axios 负责请求封装,统一处理 Token、错误码、加载态,业务组件里就不用堆一堆重复的请求逻辑了。

这里要补充说明一下,这几个选择合在一起,形成的是一套标准的真正的前后端分离架构。前端静态资源可以单独部署到任意 Web 服务,后端只提供 JSON 接口。即使以后想迁移到小程序,后端接口也能直接复用,扩展性要比传统 JSP 方案舒服很多。

2.2 前后端分离的工程结构

后端工程我按常见的分包方式组织:controller 接收请求和参数校验,service 负责业务逻辑,mapper 访问数据库,entity 对应表结构,dto 负责接口层的数据传输,config 存放跨域配置、拦截器、全局异常处理。第一次写这类项目的时候,我特别容易在 controller 里堆业务代码,后来发现一旦业务逻辑变化,改起来非常痛苦,严格分层之后问题反而好定位了。

前端工程我用 Vue CLI 搭建,目录结构按职责拆分:views 放页面组件,router 按页面维度配置路由,store 管理用户信息和全局状态,api 目录按后端接口分组封装请求方法,utils 放请求工具和 Token 存储工具。

这里有一个容易忽略但特别关键的点:vue.config.js 里的 devServer 代理。后端如果跑在 8080,前端开发服务器在 8081,开发时如果不做代理,浏览器直接请求后端接口会触发跨域。我选择在前端把 /api 开头的请求全部代理到后端,这样开发联调环境跟生产环境用 Nginx 反代的方式保持一致,代码里不用写死接口地址。

3. 数据库设计与核心表结构

3.1 用户表、题库表、竞赛表

用户表的核心是角色区分。我设计的字段包括 username、password、real_name、role、status,role 用字符串存 ADMIN 和 USER,判断权限时直接比对字符串,直观也好扩展。password 字段一定要存 BCrypt 加密后的密文,坚决不要明文入库,这是最基础的安全底线。

题库表 decide 了后面组卷的灵活度,字段包括 type(单选、多选、判断)、category、difficulty、content、options、answer、score、analysis。options 字段用 JSON 字符串存选项列表,这样选项数量不固定时不用改表结构。answer 字段对单选存正确选项,对判断存对或错,对多选存选项集合的拼接。这个字段直接决定了判分逻辑的复杂度,后面我会专门讲。

竞赛表 exam 相对直接,字段有 title、description、start_time、end_time、duration、total_questions、total_score、status、creator_id。status 分未开始、进行中、已结束三种,但这里有个设计取舍:状态我并不是由管理员手动去改,而是根据 start_time 和 end_time 动态计算出来的。如果让后台手动维护状态,经常会出现活动时间到了但状态还是“未开始”的尴尬情况,竞赛时间一过还得记着去改状态,很容易漏。

3.2 答题记录、试卷与成绩表的设计要点

答题过程涉及两张核心表,exam_question 和 answer_record。exam_question 存 exam_id、question_id、sort_order,通过这张关联表把一套试卷固化下来。这样做的好处是,即使管理员后来修改了题库里的原始题目,已经创建并发布过的竞赛试卷内容不会跟着变,这就是我前面说的“竞赛发布前不能改题”的硬性约束,本质上是做了试卷快照。进入答题阶段后再有人改题库,也不会影响任何已生成的试卷。

answer_record 表记录每道题的作答明细,字段包括 exam_id、user_id、question_id、user_answer、is_correct、score。participant 表专门存提交记录,字段包括 exam_id、user_id、submit_time、total_score、rank。排名我选择在交卷后即时计算并写入库,而不是每次查看排行榜时临时排序。这样虽然多了一次写操作,但排行页面的读取效率高得多,几百个参赛者同时查看榜单也不会有压力。

3.3 表关系与 SQL 脚本里的坑

表之间主要的关系是:user 一对多 exam,exam 多对多 question,通过 exam_question 关联,participant 多对一 user 和多对一 exam,answer_record 多对一 participant。外键我并没有加物理外键,只建普通索引。物理外键在数据迁移、删除、批量更新时会带来很多额外的锁和约束,竞赛系统这种以读为主的场景,逻辑外键足够可靠,效率反而更好。

SQL 脚本里最容易翻车的是建表顺序。必须先建主表,再建关联表,最后插入测试数据。如果一次性把完整脚本丢进数据库,经常因为关联表引用的主表还没创建而报错,所以脚本里我习惯加上 DROP TABLE IF EXISTS 再建表。另外,存储引擎统一用 InnoDB,字符集统一用 utf8mb4,否则题目内容里出现特殊符号或表情时会有编码错误,显示成乱码或者直接插入失败。

4. 核心功能模块与实现细节

4.1 JWT 登录鉴权与拦截

登录模块用 JWT 保持无状态登录态。用户提交用户名密码,后端校验通过后生成一个包含用户 ID、角色、过期时间的 Token 返回给前端,前端存在 localStorage,之后每次请求在请求头里带 Authorization 字段。后端拦截器解析请求头并校验 Token,校验通过就把用户信息塞进 ThreadLocal,后续业务方法可以直接取用。

这里有一个特别值得注意的点:JWT 防止篡改没问题,但不要过度信任 Token 里的角色字段。后端拿到用户 ID 后,一定要再查一次数据库,确认当前角色和状态。因为如果用户被禁用,或者角色在 Token 有效期内发生了变化,单纯相信 Token 里的字段就会造成越权访问。这个细节在毕设里经常被忽略,但恰恰是答辩老师最喜欢深挖的地方。

前端这边同样要处理 Token 过期。我在 axios 拦截器里统一判断响应码,如果捕获到 401,就清理本地登录态并跳转登录页,同时弹一个“登录已过期,请重新登录”的提示。没有这层处理,用户在操作过程中 Token 悄悄失效,所有请求都会失败,页面还没有任何反馈,体验会非常差。

4.2 竞赛状态机与答题流程

竞赛流程我建议用状态机来控制。参赛者进入竞赛详情页,前端先拿到竞赛状态:未开始的状态下只能看到介绍;进行中的竞赛且该用户未参加过,才显示进入答题按钮;已结束的竞赛则显示成绩和排行入口。但这里要记住,后端在任何一个答题接口上都要二次校验竞赛状态,不能只依赖前端按钮的显隐控制。接口是可以被直接调用的,前端按钮不显示不代表不能发请求。

答题页有两个重点:倒计时和答题中断恢复。倒计时的实现方式是后端在进入答题时返回本次作答的截止时间戳,前端用定时器每秒刷新剩余时间做展示,但交卷时后端必须以服务器时间为准,不能信任浏览器时间。如果让前端把剩余秒数传给后端,用户把系统时间一改,计时就不公平了。答题中断恢复的功能也很有价值:参赛者不小心关掉页面再进来,后端通过 participant 记录判断他已经参加过,只要没交卷并且剩余时间足够,就允许继续答题,而不是重新创建一次作答。

交卷动作要做成幂等操作。所谓幂等,就是不管用户点了多少次交卷,最终只计算一次成绩。实现方法是在交卷接口里先用 user_id 和 exam_id 查 participant 记录,如果状态已经是已交卷,直接返回当前成绩,不再重复计算。没有这一层防护,前端因为网络原因或者用户手滑重复发送交卷请求,后台就会出现多条成绩记录,排行榜也会被污染。

4.3 自动判分与排行榜实现

自动判分针对三种题型分别处理。单选题和判断题最简单:拿用户提交的答案和 question 表里的 answer 字段直接比对,一致得满分,不一致不得分。多选题不能直接用字符串相等判断,因为用户提交的答案顺序很可能跟标准答案不一致。我的做法是先把用户答案字符串按分隔符拆开再排序,然后跟标准答案排序后拼接的字符串比较。

// 多选题判分核心逻辑 String[] userArr = userAnswer.split(","); String[] correctArr = correctAnswer.split(","); Arrays.sort(userArr); Arrays.sort(correctArr); boolean isCorrect = Arrays.equals(userArr, correctArr);

排行榜实现我用了一个比较通用的方案:participant 表在交卷时记录总得分和交卷时间,排行榜查询时按总得分降序、交卷时间升序排序。得分相同的情况下,先交卷的排前面,这是竞赛里比较公认的排名规则。如果想做得再重一点,可以在交卷时直接算出排名写入 rank 字段,查询时直接按 rank 排序。这个方法牺牲了一点写入性能,但排行查询非常稳定,适合在答辩演示时现场看到效果。

5. 接口文档与前后端联调

5.1 接口文档应该怎么组织

项目里的接口文档我按模块分组,认证模块、用户模块、题库模块、竞赛模块、答题模块、排行榜模块,每个模块下再把接口列全。每个接口都必须写清楚请求方法、请求路径、请求参数、返回结构、错误码。返回结构我统一用 code、message、data 三个字段,code 为 0 表示成功,非 0 表示具体的业务错误码。

统一返回结构最大的好处是前端可以集中处理异常。axios 拦截器里拿到响应后判断 code,不为 0 就直接弹出 message 提示,业务组件里只需要关心 data 部分,不用每个接口单独处理错误分支。

具体到接口设计,有两个细节很关键。一个是分页查询接口统一用 pageNum 和 pageSize 参数,返回格式固定为 data.total 和 data.list。另一个是给前端返回的题目数据结构里,绝对不能出现 answer 字段。答题页面只需要题目内容、选项、题型,正确答案是判分时才用的数据。如果接口把 answer 返回了,懂点技术的人打开浏览器控制台就能看到所有答案,竞赛公平性直接崩掉。

接口文档还要标注哪些接口需要登录,哪些接口需要管理员权限,需要登录的接口必须在文档里写明请求头要带 Authorization 字段。毕设项目里的接口文档不要求做得像商业产品那么复杂,用在线文档或者 Markdown 整理都可以,关键是信息完整、示例可以直接复制。

5.2 联调高频问题实录

前后端联调时最常遇到的就是跨域问题。后端如果没有配置跨域,前端浏览器会直接拦截响应,现象就是接口已经发出了,但前端拿不到数据。处理方式我在前面提过,开发期用前端代理,生产期用 Nginx 反向代理。这里有一个容易踩的坑:一旦配置了代理,前端请求的 baseURL 就应该写成相对路径,比如 /api/login,不要写 http://localhost:8080/api/login,否则代理配置会白做。

另一个高频问题是日期格式。后端返回的时间字段默认是时间戳,前端如果不做格式化,页面上显示出来的就是一串数字。我在后端配合 Jackson 的全局配置,把日期统一序列化成 yyyy-MM-dd HH:mm:ss 格式,前端拿到后直接展示,两边不用再各自写转换逻辑。

最后要提一个隐蔽但杀伤力很大的问题:Long 类型 ID 的精度丢失。前端拿到的 ID 末尾几位经常变成 0,原因是 JavaScript 处理超过 2 的 53 次方的数字时会丢失精度。解决办法是,后端返回到前端的 ID 字段统一转成字符串。这个 bug 在联调阶段特别隐蔽,如果主键用的是数据库自增 ID,因为数字通常比较小,可能根本不会触发;但只要后来换了雪花 ID,或者 ID 位数变长,问题就会突然出现。

6. 本地部署与问题排查速查表

6.1 从源码到跑通的完整步骤

拿到源码以后,按照我下面这套步骤,基本能在十几分钟内把本地环境跑起来。第一步,准备 JDK 8 或 11、Maven、MySQL 8、Node.js 14 以上版本。第二步,用数据库客户端执行项目里的 SQL 脚本,建议新建一个独立数据库,别跟其他项目混在一起,不然表名冲突排查起来很烦。第三步,修改后端 application.yml 里的数据库地址、用户名、密码,如果项目里用到 Redis,也要确认地址配好了。第四步,在后端根目录执行 mvn spring-boot:run,看到 8080 端口打印启动日志就说明后端起来了。第五步,进入前端目录执行 npm install,装好依赖再执行 npm run serve,浏览器打开开发服务器地址登录系统。

我在实际跑这类项目时,遇到最多的问题是前端依赖安装失败。一半是网络原因,一半是 node_modules 残留导致安装了旧版本的冲突依赖。解决办法是把 node_modules 整个删掉重新执行 npm install,还不行就去检查 Node 版本是不是太新了,有些前端工程在 Node 18 以上会出兼容问题,降级到 Node 16 往往就稳了。

从源码跑到页面能打开,有一个问题最容易忽视:前端代理。页面能打开,但看不到任何数据,十有八九是 vue.config.js 里的代理没配上,或者后端的跨域配置没有生效。调试的时候先看浏览器 Network 面板里接口请求的状态码,再去后端控制台看日志,能很快区分是前端请求没发出去,还是后端接口报错。

6.2 常见问题与排查方法速查

项目资料整理得再完整,也还是会有人在部署环节卡住。我把接触过的高频问题按出现频率排了一个表,供大家直接对照排查:

现象常见原因排查思路
前端页面能打开但登录报错后端没启动,或代理指向了错误地址先直接访问后端接口地址,确认后端服务是否存活
接口返回 401Token 过期或没有传 Authorization 头检查请求头,确认登录后 Token 是否成功写入 localStorage
数据库中文乱码连接串和库表字符集不一致确保连接 URL 带 characterEncoding=utf8,表用 utf8mb4
答题倒计时不准前端用了浏览器本地时间前端倒计时只做展示,判卷时间以后端返回的截止时间戳为准
交卷后成绩为 0判断题答案格式前后不一致检查题库里判断题 answer 存的是 true/false 还是 0/1,跟判分逻辑保持完全一致
排行榜顺序不对没处理同分时按交卷时间升序排序字段写两个:总得分降序,交卷时间升序
端口被占用8080 或 8081 被其他服务占用找到占用进程,或者直接改掉后端/前端端口配置
前端拿到的 ID 末尾是 0Long 类型超出 JavaScript 安全整数范围后端统一把 ID 转成字符串再返回
多选判分一直不对用户答案顺序和标准答案顺序不一致判分前把两个答案都拆开排序再比较
重新交卷生成了多条成绩交卷接口没有做幂等处理交卷前先查提交记录,已交卷直接返回原成绩

这组排查思路基本覆盖了从部署到答辩演示期间能遇到的绝大多数问题。最后再多说一句,答辩之前,一定要把从导入 SQL 脚本到前端页面完整跑通的流程在自己机器上过两遍。这类系统十有八九翻车都发生在环境上,功能逻辑反而基本不会出问题。先把环境问题解决掉,你在台上讲项目时才能把精力放在设计思路和实现细节上,而不是在现场修 bug。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询