☰
SpringBoot+Vue健美操评分系统:核心逻辑与部署实践全解析
2026/10/23 5:26:07 网站建设 项目流程

1. 项目概述:这个系统到底在解决什么问题

最近帮一个学弟调了他毕业设计的源码,SpringBoot+Vue的健美操评分系统管理平台,Java+MySQL的技术栈。说实话,这类题目在毕设里非常常见,但真正能跑通、能演示、能过答辩的完整项目,比想象中要少得多。这个项目好的地方在于它不是一个单纯的“增删改查”堆积,而是围绕一场健美操比赛的全流程管理来做:赛事创建、队伍报名、裁判评分、成绩汇总、排名公示,业务链路是完整的,非常契合课程设计和毕业设计的选题需求。

先说清楚这套系统能做到什么程度。管理员登录后可以创建一场比赛,配置比赛名称、时间、参赛队伍名单和裁判名单;比赛开始后,每位裁判在自己的账号里对每个参赛队伍打分,评分维度按健美操项目特点拆成动作完成度、艺术表现力、难度水平三个方向;所有裁判提交完毕后,系统自动去除最高分和最低分,对剩余分数计算平均值,作为该队伍最终得分,然后生成排名。这个过程基本复刻了实际健美操竞赛的评分流程,业务逻辑上站得住脚。

适合拿它来做什么,也很明确:如果你是准备毕业设计的学生,这个项目可以直接作为系统实现部分的支撑;如果你在准备课程设计,它的代码量、难度、技术栈覆盖面都处在“认真做完能学到东西”的档位;如果你只是单纯想学习前后端分离开发,它也是一个很好的完整案例——从前端Vue页面到后端SpringBoot服务,再到MySQL存储,每一层都有代码可以读。

我自己在梳理这套系统代码的时候最大的感受是,它把很多毕设项目容易忽略的“业务完整性”做出来了。比如裁判提交评分之前系统会校验该裁判是否已经对该队伍打过分数,防止重复评分;比如赛事存在“未开始、进行中、已结束”的状态流转,不同状态下评分接口的开放权限不一样。这些细节不是炫技,而是真正在解决一场比赛流程中必然出现的问题。对拿它做毕设的学生来说,这样的逻辑在答辩时非常加分。

2. 整体设计拆解:技术选型背后的考量

2.1 为什么是SpringBoot+Vue这个组合

前后端分离已经是当前Java项目的主流形态,SpringBoot负责后端接口,Vue负责前端页面,两者通过JSON交互。这个组合在毕设里的优势非常明显:一方面SpringBoot的生态成熟,MyBatis-Plus操作数据库非常简洁,网上资料多,遇到问题好排查;另一方面Vue的组件化开发让页面复用变得容易,评分页、队伍管理页、成绩排名页都可以独立开发互不干扰。

在这个项目里,后端按常见的三层结构拆包:Controller层负责接收请求,Service层处理业务逻辑,Mapper层操作数据库。评分计算的业务全部放在Service层,Controller只做参数接收和结果返回,这种分层让代码读起来很舒服。前端按页面拆成若干Vue组件,配合Vue Router做路由跳转,Axios做接口请求。

2.2 角色权限怎么设计的

整个系统有管理员和裁判两种账号角色,这是业务上最核心的区分,一个不能少。

管理员负责赛事配置和数据处理:创建比赛、维护参赛队伍、指定裁判名单、查看最终成绩、可以手动进行成绩导出或审核。裁判则是纯粹的评分角色,登录后只能看到被分配给自己打分的队伍列表,提交完一个队伍的评分后不能再修改。

这个权限模型在后端通过一个简单的用户表中增加角色字段来实现,前端再根据登录用户的角色动态渲染路由和菜单。实现上用的是JWT做登录鉴权,用户登录成功后拿到一个包含角色信息的token,后续请求在请求头带上token,后端拦截器解析token并判断角色权限。这个机制在毕设答辩时是很好讲的点,因为它是真实业务系统的常见做法,而不是单纯的数据库字段硬编码。

2.3 数据库表结构:评分表是核心

数据库设计上,这个项目最值得关注的是评分表和外键关系。我梳理了一下核心表的设计:

表名用途关键字段
user表用户登录信息id、username、password、role
competition表赛事信息id、name、status、start_time
team表参赛队伍id、name、competition_id、score
judge_score表裁判评分记录id、competition_id、team_id、judge_id、score_quality、score_artistry、score_difficulty、total_score

这里有个字段设计细节值得注意:score表对应的就是judge_score,里面存了多个评分维度的单项分数,又冗余存了一个total_score字段,也就是该裁判给这个队伍的总分。之所以保留total_score,是因为在计算最终成绩时,需要先按总评分做排序,才能去除最高分和最低分,而各维度分数也会被单独保留,方便展示详细的评分明细,比如每个裁判给了多少完成度分。

另一个非常关键的细节是judge_score表建议加一个联合唯一索引,字段组合为competition_id + team_id + judge_id。这个索引的作用是确保同一个裁判不会对同一支队伍重复评分,从数据库层面兜底防止脏数据。如果没做这个设计,代码里的校验逻辑一旦有漏洞,就会出现同一裁判对同一队伍有两笔评分记录,最终成绩计算就乱了。

3. 核心业务实现:评分规则的落地

3.1 健美操评分规则怎么变成代码

健美操评分的核心规则是去掉一个最高分和一个最低分,其余评分取平均作为最终成绩。这个规则在答辩时一定会被问到,所以代码实现要能说清楚。

最终成绩计算过程分为两步。第一步,取出某个队伍在某场赛事下的所有裁判评分记录,按总评分排序,去掉排名第一和排名最末的记录;第二步,对剩下的评分记录,分别计算动作完成度、艺术表现力、难度水平的平均值,再合成最终总分展示在排名页。

后端Service层的核心代码大致是这个思路:

public BigDecimal calculateFinalScore(Long competitionId, Long teamId) { List<JudgeScore> scoreList = judgeScoreMapper.selectList( new LambdaQueryWrapper<JudgeScore>() .eq(JudgeScore::getCompetitionId, competitionId) .eq(JudgeScore::getTeamId, teamId) .orderByAsc(JudgeScore::getTotalScore) ); if (scoreList.size() <= 2) { // 少于等于两个裁判时,直接取平均值,避免全部被去掉 return average(scoreList); } // 去掉头尾,即去掉最高和最低分 List<JudgeScore> validScores = scoreList.subList(1, scoreList.size() - 1); // 各维度分别求平均 double avgQuality = validScores.stream().collect(averagingDouble(JudgeScore::getScoreQuality)); double avgArtistry = validScores.stream().collect(averagingDouble(JudgeScore::getScoreArtistry)); double avgDifficulty = validScores.stream().collect(averagingDouble(JudgeScore::getScoreDifficulty)); return BigDecimal.valueOf(avgQuality + avgArtistry + avgDifficulty) .setScale(2, RoundingMode.HALF_UP); }

这里有一个细节容易写错:去掉最高最低后,剩余记录数量可能是0。比如一场比赛只有3个裁判,去掉一个最高一个最低,还剩1个有效分数;但如果只有2个裁判,去掉之后就没有有效分数了,所以代码里专门做了size小于等于2时的兜底处理,直接按所有裁判的平均值计算。这是我在看很多类似项目代码时,发现最容易漏掉的边界情况,但答辩老师往往就盯着这种逻辑漏洞问。

3.2 赛事状态流转如何控制评分权限

一场比赛从创建到结束,状态管理是整个流程不被搞乱的关键。这个项目里赛事状态分为三个:未开始、进行中、已结束。

  • 未开始状态:管理员可以编辑赛事信息、调整队伍和裁判名单,评分接口不开放。
  • 进行中状态:评分接口开放,裁判可以提交或查看自己的评分,管理员可以实时查看已提交评分数量和比赛进度。
  • 已结束状态:所有评分锁定,不允许再修改,系统自动计算最终成绩并生成排名。

后端实现时,我比较推荐用一个拦截器统一校验赛事状态,而不是在Service层里每次手写一遍状态判断。因为评分相关的接口可能有好几个:提交评分、查询某队伍已给出的分数、修改评分等,统一拦截的好处是可以保证任何接口都不会漏掉状态校验,也更方便维护。

比如定义一个简单的自定义注解,标在需要校验赛事状态的Controller方法上,拦截器里判断当前赛事状态是否符合要求:

if (competition.getStatus() == 0) { throw new BusinessException("比赛尚未开始,无法评分"); }

这个方案代码量不大,但能体现“对业务规则的统一管理”,在答辩讲解的时候是一个很好的加分点。

3.3 前端评分页面的交互设计

前端评分页面是整个系统里最核心的交互界面。裁判登录后进入评分页,页面上展示待评分队伍列表,点击某个队伍进入评分表单。表单里包含三个评分维度:动作完成度、艺术表现力、难度水平,每个维度满分为10分,分值精确到小数点后一位。

前端在提交前至少要做三个校验:分数不能为空、分数范围必须在0到10之间、三项分数都有效后才能点击提交。提交后前端通过Axios向后端发送请求,后端返回成功后,该队伍从待评分列表中移除,并在界面上显示“已评分”标识。

我自己在调整这个前端页面的时候,觉得一个值得注意的交互细节是小数输入的处理。健美操评分允许0.1的精度,如果使用普通的数字输入框,浏览器默认的步进是1,用户想输入9.5会很麻烦。最简单的解决方式是在输入框上设置step属性值为0.1,同时在表单校验时判断小数位数。这个细节很小,但实际演示的时候非常影响评委老师的体验感受。

另一个细节是计时提示。健美操比赛有规定时长,超时会扣分,所以评分表单里通常会放一个赛事总时长的倒计时提示,帮助裁判在时间维度上做参考。实现上用一个setInterval定时器,倒计时归零后仅做提示,不强制干预评分过程,保持裁判的评分独立性。

4. 实操过程:从源码到跑通的完整记录

4.1 环境准备与版本选型

先把环境准备好,这是最容易踩坑的第一步。这个项目需要的核心环境如下:

  • JDK:1.8或以上,推荐使用JDK 8或JDK 11
  • Maven:3.6及以上,用于后端依赖管理
  • MySQL:5.7或8.0均可,8.0需要注意时区配置
  • Node.js:建议使用16版本,对Vue2项目兼容性最好
  • 前端包管理器:npm或yarn均可

版本选型为什么单独拿出来说,因为这里坑太明显了。Vue2的很多依赖,尤其是sass相关的库,在新版Node下编译会直接报错,我记得有段时间在Node 18环境下装旧项目的依赖,node-sass会下载失败或者编译失败,解决方案要么是把Node版本切回16,要么在package.json里把sass相关依赖替换为dart-sass版本。前一种方案更稳,直接装一个Node版本管理工具,切换版本方便一些。

4.2 后端启动步骤

第一步,初始化数据库。项目源码的sql目录下会有一份数据库初始化脚本,里面包含建库建表语句和基础测试数据。在MySQL中执行这份脚本,执行完成后数据库里会有user、competition、team、judge_score等数据表,并且通常会预置一个管理员账号和若干测试裁判账号,方便登录演示。

第二步,修改数据库连接配置。打开后端的application.yml文件,找到数据源配置:

spring: datasource: url: jdbc:mysql://localhost:3306/aerobics_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver

注意url里的serverTimezone参数,MySQL 8.0版本如果不设置时区,启动时大概率会报错误,提示无法识别服务器时区。这是一个非常经典的报错,答案就是加serverTimezone=Asia/Shanghai。

第三步,启动后端服务。在项目根目录执行:

mvn spring-boot:run

后端默认端口为8080,启动成功后控制台会打印Spring Boot的启动日志,最后一行显示端口启动信息。

4.3 前端启动步骤

先安装依赖。切换到前端目录,执行:

npm install

这一步耗时取决于网络状况,耐心等待完成。如果遇到上面说过的node-sass编译失败问题,记得先检查Node版本。

依赖安装完成后,需要确认前端代理配置。打开前端项目的vue.config.js文件,里面通常配置了开发环境代理,将/api路径的请求转发到后端地址:

module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }

配置代理的目的是解决开发环境下的跨域问题。前端页面运行在8081端口,后端接口在8080端口,两个端口不同,浏览器会拦截跨域请求,代理就是让前端开发服务器帮转发去请求后端接口,绕开浏览器的跨域限制。

最后启动前端:

npm run serve

启动完成后浏览器访问http://localhost:8081,应能看到系统登录页面。

4.4 模拟一次完整的评分流程

系统跑通后,我建议按下面的流程完整走一遍,既验证功能,也为答辩演示准备流程:

用管理员账号登录,进入赛事管理页面,创建一场比赛,添加三到五个参赛队伍,分配五名裁判账号。然后退出管理员账号,切换到一个裁判账号登录,进入评分页面,对每个队伍打分并提交。重复这个步骤,把五个裁判的分数都提交完毕。最后切回管理员账号,结束比赛,查看最终排名。

这个流程走完,基本覆盖了系统的主要功能。还有一个细节要做:故意试一下重复提交评分。比如某个裁判对某个队伍已经打过分,尝试再次提交,这时候页面应给出“已经评分”的提示,后端也会进行同样的校验。演示这个场景能给答辩老师留下“边界情况处理过”的印象。

5. 常见问题与排查技巧

5.1 启动阶段的报错排查

我实际调试这个项目的时候,遇到过几次典型的启动失败,整理成速查表:

报错现象原因处理方法
数据库连接超时,Access deniedMySQL账号密码不对,或数据库未初始化检查application.yml配置,确认数据库中已有数据表
Server returns invalid timezoneMySQL 8.0时区问题数据库连接url加上serverTimezone=Asia/Shanghai
Port 8080 was already in use端口被其他进程占用修改后端配置里的server.port,或关闭占用进程
npm install时报node-sass相关错误Node版本过高,与旧Sass依赖不兼容切换Node版本到16,或将node-sass替换为sass
前端请求接口404前端代理未生效或代理配置错误检查vue.config.js中proxy配置,以及请求路径是否以配置的/api开头

其中一个端口占用的问题看起来很小,但在答辩现场特别容易出状况。有一次演示的时候后端一直报端口被占用,排查发现是后台还挂着一个别的Java服务没关掉。建议启动前先执行netstat命令确认端口状态,这种30秒能解决的问题,现场遇到就会非常尴尬。

5.2 运行阶段的逻辑问题排查

运行阶段报错更隐蔽,有些问题不报错但结果不对。最典型的是评分计算出来数据明显不对,比如最终得分算出来是0分,或者排名顺序错乱。

排查这类问题,我给你一个通用的思路。先去数据库里直接查judge_score表,看这个队伍对应的评分记录是不是完整的——有没有缺失,有没有重复记录。如果重复了,检查联合唯一索引是否建上;如果缺失,检查赛事的提交流程是否在某个环节中断了。然后再算一遍最终得分,核对代码里的去最高最低逻辑是否符合预期。

没有报错但功能异常的情况,在前端也常遇到。比如提交评分后列表中的队伍状态没有更新成“已评分”,大概率是后端返回数据后前端没有刷新状态,也就是数据一致性问题。解决方式是提交成功后再次调用查询接口刷新列表,而不是仅依赖前端本地状态。

5.3 答辩演示前的准备建议

项目代码本身没问题,但演示环节翻车往往是因为数据准备不充分。我发现很多同学喜欢用空数据库现场演示,打开页面一堆空表格,评委感受不好,还容易在操作中暴露出逻辑问题。建议提前准备一套完整的演示数据:一场已经结束的比赛,包含完整的队伍评分记录和排名;一场进行中的比赛,保留部分评分记录,用于展示评分中的状态;一场未开始的比赛,用于展示管理员创建赛事的流程。

另外,别忘了准备好各个角色账号的密码,并把账号信息抄在演示文档的第一页。答辩现场紧张起来很容易手滑,输错三次密码账号被锁,虽然有系统设置因素,但演示流畅度都会大打折扣。我总结的经验是:管理员账号、裁判账号各准备两个,一个主用,一个备用,防现场出意外。

最后还有一个小经验,我在帮学弟调试这个项目的过程中体会特别深。整个系统的业务主链路就一条完整的评分闭环,答辩时讲解顺序也按这条主线走,不要一上来先讲数据库表结构。先让评委看到页面和功能,再深入到评分规则和权限控制,最后才展开表设计和代码细节,观众的接受度是最高的。这也是这个项目在设计上比较合理的地方——多个模块之间层次清楚,业务主线清晰,讲起来很顺手。

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

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

立即咨询