资讯动态

基于Spring Boot+Vue的在线知识竞赛系统:从数据库设计到自动判分全解析

发布时间:2026/10/15 15:07:40 来源:尧图企业网站定制
1. 项目全景这个竞赛平台到底做了什么信息知识赛系统平台名字听起来像是个“比赛工具”但真把它当毕设做下来你会发现它其实是一个完整的在线竞赛管理闭环从创建题库、配置试卷、发布竞赛到选手报名参赛、在线答题再到自动判分、成绩排行、数据导出一整条链路全都覆盖了。我当初选这个题目核心就看中两点一是技术栈不偏门后端 Spring Boot 是 Java 方向的绝对主流前端 Vue 也是如今企业里用得最多的框架之一随便翻开一个招聘 JD 都能撞见这两个词二是业务模型清晰、可延展知识竞赛这个场景本身有明确的实体关系——用户、题目、试卷、答卷、成绩——做数据库设计时特别好讲道理答辩时能聊的东西也多。先拆一下系统里到底有哪几类人。这套平台我设计成三种角色管理员、出题教师、参赛选手。管理员管的是系统级别的配置比如用户管理、角色分配、竞赛分类出题教师负责维护题库往里面加单选题、多选题、判断题同时能创建试卷、发起竞赛选手就是注册登录后参加比赛、看成绩、查排名。三者的权限从数据层面就隔离了后端每个接口都做了角色校验前端也按权限渲染菜单和页面。这一层不做扎实后面答辩被问“你的权限是怎么控制的”就会很尴尬。功能模块再往细了分大致是这么一块地图系统管理用户、角色、菜单、题库管理题目增删改查、批量导入、按题型筛选、试卷管理手工组卷、按模板随机抽题、竞赛管理发布竞赛、配置时长、开放报名、锁定试卷、在线答题倒计时、逐题作答、自动保存、交卷判分、成绩管理自动评分、榜单、正确率统计、成绩导出、个人中心参赛记录、错题回顾。每一块都不算难但组合起来就是一个五脏俱全的全栈项目非常适合拿来撑起一份毕设的体量。这里我想多说一句选题的思路。很多同学做毕设喜欢挑那种“看起来很炫”的项目但其实越是复杂的业务界面越容易被细枝末节缠住最后技术讲不清、功能做不完。竞赛平台这种题目有一个天然优势它的每个功能点都能对应到一个明确的技术问题——随机抽题考你算法、倒计时考你前端交互、判分考你事务一致性、数据导出考你工具类使用。每一条都能在论文和答辩里说出一二三四来这才是毕设最该有的样子。2. 数据库设计整个项目最值得反复打磨的部分很多初学者拿到这种项目第一反应是先写代码哪天用到哪张表再临时建哪张表。我的建议正好反过来把三天时间花在数据库设计上后面你能省下三个星期。竞赛系统的数据模型关系不算复杂但有几个点特别容易被忽略我一个个说。2.1 用户、题库、答卷三张核心表的字段设计用户表sys_user建议使用标准的用户字段id、username、passwordBCrypt加密存储、real_name、role用数字标识1管理员、2教师、3选手、status启用/禁用、avatar、create_time。不要用明文密码这是答辩时很加分的点——你可以在文档中说明使用了 BCrypt 加盐哈希保证密码以密文形式入库。角色这里有两种设计路线一种是直接用一个字段区分适合角色少于四类的系统另一种是引入角色表和用户角色关联表走 RBAC。竞赛平台的角色是固定的我用的是单字段方案更省事查用户列表时不用 JOIN 一堆表。但如果你的扩展需求明确比如以后要加“裁判”“评委”RBAC 才是更稳的选择。这个取舍建议写进项目的接口文档里让答辩老师看到你考虑过这个问题。题库表question_bank是内容核心。字段大概有id、category_id题目所属分类/学科比如“信息技术基础知识”、question_type1单选、2多选、3判断、content题干TEXT类型、optionsJSON字符串存 A/B/C/D 选项及内容、answer标准答案单选存 A多选存 A,B,D判断题存 T/F、analysis答案解析、difficulty难度系数1到5、create_by、create_time。我把选项存成 JSON 而不是单独建一张选项表是刻意为之——竞赛题目的选项不会单独被查询或修改内嵌在题目表里反而省去大量 JOIN也方便前端直接渲染。你要是选多表方案也不是不行但要对规范化程度的收益和成本有清晰认知。答卷表exam_record是所有比赛数据流的中枢。字段有id、contest_id、user_id、paper_id、total_score、objective_score客观题得分、correct_count、wrong_count这俩字段在做错题统计时很顺手、status1未交卷、2已交卷、3已判分、submit_time、duration_seconds实际用了多长时间。还有一个关键的子表answer_record记录选手每一道题的作答情况——question_id、selected_answer用户选的答案、is_correct是否正确、score该题得分判断题部分给分时用得上。为什么要把每道题的作答单独落表因为成绩列表、错题回顾、逐题判分这些功能都需要它。你要是只保存一个总分那后面什么都做不了。2.2 试卷表与题目关系的“中间层”设计试卷这块是数据库里最容易踩坑的地方。很多新手会把题目直接塞进试卷表比如加一个“question_ids”字段存一堆 ID 逗号分隔看起来很省事但一旦要用题目分数、顺序、题型来做统计时就成了灾难。正确的做法是建一张试卷题目关联表 paper_question至少三个字段id、paper_id、question_id、score该题分值、sort_order题目顺序。这个设计的价值在做“固定组卷”和“随机组卷”两种模式时立刻体现出来。固定组卷就是出题教师手工从题库里挑若干题目拼成一张卷子paper_question 里把选中的题一行行插入随机组卷则更灵活设置“单选题10题每题2分、多选题5题每题4分、判断题10题每题1分”系统在发布竞赛那一刻从题库里按分类和题型随机捞题目捞完自动写入 paper_question。你日常题库里有四百道题每次竞赛生成的就是一份全新的卷子选手看到的题目顺序和内容都可能不一样。有一个小细节竞赛表contest里我加了一个字段 question_strategy值为 FIXED 或 RANDOM同时配了 random_configJSON字符串用来描述随机组卷的规则。每次发布竞赛时根据这个配置去动态生成 paper_question 数据。这种“数据驱动配置”的设计在接口文档里体现出来很加分因为这是真实业务系统里常见做法不像课程作业那样硬编码。2.3 SQL脚本里应该提前塞哪些数据题目里提到项目附带 SQL 脚本。脚本里除了建表语句强烈建议把种子数据也准备好。SSH 连接数据库后可以看到需要配置的初始数据至少一个管理员账号admin/admin123密码是 BCrypt 哈希一个教师账号两个测试选手账号一千道左右的示例题目——我当时的做法是写了一段 Python 脚本批量生成随机题目题干从技术词汇池里拼答案随机分布一次性插进去四百道单选题、三百道多选题、三百道判断题覆盖“信息技术基础”“网络安全常识”“编程语言基础”等分类。这些题目让系统演示时立刻有真实感也能用实际数据验证随机组卷效果。SQL 脚本的组织也建议分三份schema.sql全部建表语句、data.sql种子数据、cleanup.sql清库脚本。另外在脚本顶部写好版本注释、字符集设置utf8mb4、存储引擎InnoDB。这是别人拿到你脚本后第一直观印象排版干净会留下非常多的好感。接口文档里我也专门写了初始化操作先把 SQL 引入然后启动项目两步就能跑起来。3. 后端核心链路登录鉴权、组卷逻辑与自动判分后端我用的是 Spring Boot 2.3.7 MyBatis-Plus 3.4 JWTjjwt 0.9.1 Lombok Hutool接口文档用的 Knife4jSwagger 增强版界面好看中文友好。这套组合是 2020 前后 Java 毕设项目里非常成熟的一套踩坑的人多、参考资料也多做起项目来会很顺手。3.1 JWT 登录鉴权的实现边界登录接口的思路不复杂但有几个边界要处理好。逻辑流程是前端提交 username password也可以要求加上验证码后端从数据库查出用户用 BCrypt 校验密码通过后生成一段 JWT Token有效期我设了 48 小时返回给前端前端把 token 存到 localStorage每次请求在 Axios 拦截器里往请求头加 Authorization: Bearer 。后端拦截我用的是自己写的一个 TokenInterceptor 加 Spring 拦截器注册没有上 Spring Security。不是 Spring Security 不好而是这种角色只有三类的系统自己写拦截器逻辑反而更直白——实现 HandlerInterceptor在 preHandle 里放行登录接口、Swagger 文档路径和前端静态资源其他所有 /api/** 请求都校验 token。校验通过后顺手把 userId、userRole 塞进 request 的 attribute 里后续 Controller 通过一个自定义注解 LoginUser 直接取当前用户体验很顺。其实框架选型可以把这个逻辑放在接口文档中的“技术选型说明”章节里讲清楚为什么放弃 Security——你的时间有限实际需求没那么重与其被过滤链和配置类绕晕不如十行代码解决问题。这是真实业务中很常见的“杀鸡不用牛刀”思路答辩说出来不会减分反而给人务实感。3.2 随机组卷如何保证抽题不重、结构合理随机组卷是我觉得整个项目里最花心思的地方。需求指出来每次比赛前系统按配置从题库里随机抽题同一次比赛中不能出现相同的题题型配额和总分要精准匹配。我实现的方式是在“发布竞赛”时调用组卷服务首先根据分类和题型从题库查出所有可用题目的 id 列表再通过一个巧妙的 SQL —— SELECT id FROM question_bank WHERE category_id ? AND question_type ? ORDER BY RAND() LIMIT ?——直接用数据库乱排序取前 N 条。数据量在几千条时这种写法完全没性能压力也省去手动打乱数组的代码。随后逐题算出分数写入 paper_question 表同时校验总分是否等于预期值——所以创建竞赛时“总分”这个字段不是让用户手填的而是系统根据配置自动计算的这是一个很好的防呆设计。这里有一个细节值得强调同一场竞赛如果同时开放给选手并发报名都触发组卷就可能产生重复试卷。为了解决这个问题我把组卷动作放在“发布竞赛”这一操作中而不是放在选手“开始答题”时才生成。每次竞赛的状态从“草稿”变“已发布”时系统一次性生成试卷选手只是消费这份已被固定好的题目。这样实现最简单还能保证所有选手面对的是同一套题——这就是知识点竞赛最公平的形态。3.3 判分逻辑与事务边界一次交卷的完整旅程交卷接口值得写好因为答题流程的终点、成绩排名、错题回顾都挂在它上面。我的实现思路是前端逐题提交或本地缓存作答结果交卷时一次性把答案数组发给后端。后端接收数据后做这么几件事校验答卷状态防止重复交卷status 字段判断。逐题遍历答案记录比对标准答案。单选判断直接对比多选我用的是全对才给分策略——先把两边答案转成 List 排序后 compare相同得满分有任何差异得 0 分。这个策略建议在文档中写清楚因为判分策略本身就是业务规则不同比赛有不同诉求。统计得分更新 exam_record 表和每题的 answer_record 表。更新用户战绩、竞赛的参赛人数统计等。这里必须强调事务边界。交卷涉及多张表的写入一旦判到一半报错会导致用户成绩丢了但状态已改成已交卷整个流程就会卡死。我的做法是在 Service 方法上加了 Transactional(rollbackFor Exception.class)并且注意同一个类内部的调用不要自调用——Spring 事务代理有个经典坑同类内 this.method() 调用不会经过代理事务会失效。我第一次写的时候就在类内直接调了内部方法结果事务静默失效数据半写状态。这类经验值得写进项目的“踩坑记录”里。判分有一个容易被忽视的性能点多选判分时答案是以字符串存的如A,C,D。每次解析都要 split当一份试卷有 50 道题、同时有三四百人交卷时这个字符串操作量不小。虽然实测性能还好但在答题入口用 JSON 格式传会更快前端序列化、后端用 Hutool 的 JSONUtil 解析代码也更干净。我最终选了 JSON 方案。4. 前端实现要点Vue 侧的关键交互和工程化细节前端部分我用的 Vue 2.6 Vue CLI 4 Vue Router Vuex Axios Element UI。选 Vue 2 而不是 Vue 3不是因为新版本不好而是为了和大量成熟组件库生态完全兼容且毕设场景下稳定优先网上参考资料也更多。4.1 权限路由与菜单的动态渲染三套角色共用一套前端代码登录后看到的菜单和页面必须分开。我的实现是在登录接口返回用户信息包含 role后前端根据角色过滤出允许访问的路由表通过 router.addRoutes 动态注册菜单同理用 v-if 配合路由 meta 里的 roles 字段控制显示。具体到路由配置我会这样设计meta 里放 roles: [ADMIN, TEACHER] 表示哪些角色能进这个页面如果有页面不在其中访问时会被路由器守卫重定向到 403 页面。后端接口层面也要做双保险——不能只靠前端挡一下Controller 的方法上我加了自定义注解 RequireRole用 AOP 或拦截器统一校验做到即使有人越过前端直接调接口也无法越权操作。这个“前防后挡”的思路特别适合在答辩时讲给老师听。4.2 答题页面的交互细节倒计时与防作弊答题页是整个项目里前端最紧张的部分。计时器我选 setInterval 每秒钟更新一次剩余时间同时把剩余时间同步存到 Vuex 里分钟秒数格式化展示屏幕上红字提醒。倒计时归零后直接自动调用交卷接口把当前已保存的答案交上去——这一步必须在后端判分前校验时间是否真的到了防止有人改前端代码绕过倒计时。后端的校验逻辑就是取竞赛配置的比赛时长和试卷开始时间判断当前时间是否超出了 deadline。还有一个比较容易忽视的交互点切换下一题的时候要保存当前题已选择答案。我的做法是实时把用户答题状态写进 Vuex 里的一个 MapquestionId - answer然后在交卷时统一批量提交。中间如果用户刷新页面此时 Vuex 状态会丢所以我加了一层防抖自动保存每 10 秒把答案快照存到 localStorage页面重新加载时从 localStorage 恢复。这个“本地草稿箱”的体验很受欢迎实际答辩演示时如果刚好演示了刷新恢复老师会觉得你考虑问题很全面。题目选项的渲染也比较讲究。单选和判断用 el-radio-group多选用 el-checkbox-group。提交时多选结果我用选中项按字母序排序后拼接成 A,C,D保持一致的后端解析逻辑。前端拼数据时如果有任意一道题未作答交卷二次确认模态框要指出来“你还有 N 题未作答确定交卷吗”。人性化交互其实是毕设里特别容易被量化的加分点。4.3 成绩可视化用 ECharts 让数据自己说话竞赛榜单和成绩分析页是体现你前端能力的好地方。成绩发布后选手个人页可以展示最近十次参赛成绩的折线趋势管理员端可以展示每场竞赛的平均分、最高分、通过率、各分段人数分布这些我用 ECharts 实现。后端提供统计接口返回按分数段分组的人数列表前端拉数据后直接 setOption 渲染柱状图。在做统计接口时有一个推荐的 SQL 写法SELECT FLOOR(score/10)10 AS score_bucket, COUNT() FROM exam_record WHERE contest_id? GROUP BY score_bucket ORDER BY score_bucket。这条语句能一次查出 0-10、10-20、20-30 等分段的数据免去内存循环分组。接口文档里我把这个统计思路单独写了一段自认为是整个项目中“性能优化”意识最好的体现之一虽然本身只是个小技巧但能说明你有数据库分组查询的能力。5. 部署运行指南从零到一跑起来5.1 本地环境准备清单建议本机环境是 JDK 1.8项目用的 Java 8新版本 JDK 可能有兼容问题、Maven 3.6、Node 14、MySQL 8.0、Redis 6我做了验证码登录功能验证码存 Redis 并设置过期时间。Redis 不是必选项如果你不想引入 Redis把验证码存内存 ConcurrentHashMap 也行不过 Redis 更贴近真实生产环境。以下是启动项目的官方顺序我在接口文档里是这么写给人看的。5.2 SQL 脚本初始化步骤先启动 MySQL用 Navicat 或命令行新建数据库CREATE DATABASE knowledge_contest DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;。然后执行 schema.sql 建表再执行 data.sql 导入初始数据。然后改后端配置文件 application.yml 中的数据源和三方配置数据库地址、账号密码、Redis 地址、JWT 密钥若用 JWT 可自定义一个足够长的随机字符串。executing 这些步骤时有一个小提示如果首次启动报“时区错误”在数据库连接 URL 后面加上 ?serverTimezoneAsia/Shanghai 就好这是很常见的坑。5.3 前后端打包与部署前端在项目根目录执行 npm install然后 npm run serve 起开发环境默认 8080 端口访问即可看到界面。后端在 IDEA 里直接运行 Application 主类端口 8081。实际开发中我是同时跑前后端的后端把接口开放跨域在配置类里加 CorsFilter前端通过 axios 代理把请求转发到 http://localhost:8081/api。用代理而不是直连的一个重要原因是直连会导致请求直接发到 8081会产生 CORS 跨域预检代理模式下前后端同源跨域问题彻底消失。开发环境用代理解决跨域是正确姿势部署时才走 Nginx 把 /api 反向代理到后端服务。生产部署建议打 jar 包前端执行 npm run build 生成 dist 文件夹后端在 mvn clean package 打成 knowledge-contest.jar。把 dist 整个丢到 Nginx 的 html 目录下Nginx 配置里写location /api/ { proxy_pass http://localhost:8081/api/; }并且单页应用要加 location / { try_files $uri $uri/ /index.html; }防止前端路由刷新后 404。这两条配置在 Linux 服务器上直接贴就能用最终效果是同一端口同时托管前端页面和后端 API。5.4 接口文档的呈现与目录组织接下来讲讲接口文档本身。我用 Knife4jSwagger 增强版做接口文档访问 /doc.html 可以看到一个很清爽的接口列表每个接口都能在线调试。工程代码里用 Api、ApiOperation、ApiImplicitParam 注解做好注释。但光有 Swagger 还不够我在项目根目录放了一份自己整理的接口文档.md按模块列了每个接口的 URL、请求方式、请求参数、响应结果示例。为什么两份都要因为第一份给评委看系统“有规范”第二份给开发者看系统“可接入”。尤其答辩时老师可能不会现场连环境用文档翻两页就能快速了解系统结构这一招是好用的。6. 毕设避坑指南这些坑我替你踩过了做这个项目最耗费我时间的是以下几个问题提前知道能帮你省下不少回头路。第一个坑是跨域配置不当。开发环境用 Vite 或 vue-cli 代理就好别在后端放开所有跨域。生产环境就更简单了前后端同源部署后端还有可能存在 dev 环境的 CorsFilter不要直接删改用配置文件开关控制。否则你的安全配置在演示时可能会暴露问题。第二个坑是日期格式化。MySQL 的 datetime 字段经过 Jackson 序列化后默认返回时间戳或标准格式前端 element table 显示时经常成英文格式。统一做法是在后端 application.yml 里配置 spring.jackson.date-formatyyyy-MM-dd HH:mm:ss 和 time-zoneGMT8简单粗暴全项目生效。第三个坑是文件上传和导出。成绩导出我用了 EasyExcel 工具监听器模式一条条写入 Excel避免大数据量时 OOM。这个在答辩演示时非常带感现场导出一个 excel 表格成绩和排名清清楚楚。第四个坑是缓存一致性。排行榜如果每次访问都实时统计数据库压力较大但缓存太久成绩刚公布榜单不更新。我的折中方案是成绩排名用 Redis 缓存 30 秒交卷时主动删除排行榜缓存让下一次查询重新计算。对并发量不高的毕设项目来说这已经足够优雅。第五个坑是枚举和状态值的管理。竞赛状态未开始/进行中/已结束在前端渲染、后端判断、数据库存储三个层面都要有同一个映射千万别在几处散写汉字否则后续改需求会是一场噩梦。我在后端定义枚举类 ContestStatus前端定义常量对象用 type 值做关联派生保证不会对不上。第六个坑是异常处理。一个统一的全局异常处理器 RestControllerAdvice 是必需的否则 MyBatis 抛出的异常会直接带出一大坨英文堆栈前端的响应也乱七八糟。我用统一响应体 Result 所有 Controller 都返回它异常处理器把业务异常转成 code message前端 Axios 拦截器里根据 code 统一弹提示。这个风格让前端联调时非常省心也代表着你的代码命名清晰。7. 答辩怎么讲才能把四个月的工作说明白论文和答辩 PPT 不要平铺直叙列功能而是把一个“知识点竞赛”的业务场景从头到尾串成一个故事。我的答辩结构大概如下先讲为什么做——传统线下知识竞赛组织成本高、人工判卷慢、数据易出错再讲方案怎么做——数字化解决全流程并配业务流程图然后讲技术架构——前后端分离、后端分模块、数据库五张核心表再挑几个亮点深入讲——随机组卷避免泄题、自动判分保证公平、ECharts 数据可视化让结果一目了然最后演示系统从建题库到发布竞赛到选手答题交卷到成绩榜单一气呵成。演示时要提前准备一个“标准答案”建一个只有三个题目的小试卷答完立刻出分让老师看到核心链路是通的、可复现的。别上来就在几百道题的大题库里演示太容易出意外。答辩时老师大概率会问这么几个问题我建议提前准备回答角度问为什么用 JWT 而不用 Session答前后端分离架构下的天然选择无状态、易扩展、跨域友好Session 需维护服务器状态在分布式部署时还要引入共享存储。问多选判分策略是什么答全对才得分严格模式已留出扩展点可调整策略实现部分得分在接口文档中已注明。问随机组卷题库数据量增大后性能怎么保证答当前千级数据用 ORDER BY RAND 没有压力后续可升级为应用层随机算法或引入 Redis 的 SRANDMEMBER并提前生成多套备用试卷。问密码明文存储答绝不会BCrypt 哈希存储天然带盐爆破成本极高。问项目有什么不足答还缺图片/富文本上传仅仅支持客观题后续可扩展主观题人工批卷功能。坦率承认不完美但可演进比强行说“已经完美”可信得多。我个人的实际体会是这个系统给你的每一处代码都能成为答辩时的武器。像自动组卷、事务交卷、全局异常管理、EasyExcel 导出、ECharts 可视化这些都是面试和答辩场景中最高频的词汇。你不需要讲得多深但当你能够现场演示一次完整流程时老师和面试官对项目的认可度远超背诵八股文。最后再分享一个小技巧给项目写 README 的时候第一段就用一句话说清楚系统干什么——例如“基于 Spring Boot Vue 的通用知识竞赛全流程管理平台覆盖从题库创建到自动判分的完整链路”然后在 How to run 里从零开始教别人启动你的项目。这套文档做完了你不光拥有一个高完成度的毕设也在学习“如何交付一个别人能看懂的系统”这件事上先走了一步。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑