ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Vue评分系统开发实战:健美操管理平台全流程解析

SpringBoot+Vue评分系统开发实战:健美操管理平台全流程解析 每年到毕设季SpringBootVue 健美操评分系统管理平台源码这类题目都会被反复翻出来。Java 做后端、MySQL 存数据、Vue 写页面听起来是标准三件套可真正开始做时你会发现光是把“多人打分、去掉最高最低、算总分”这套流程理清楚就需要对项目结构、接口设计、数据库表关系都有一定把握。它适合毕业设计、课程设计也适合刚学完 SSM 或 SpringBoot 基础、想完整走一遍项目流程的同学。这篇文章不堆概念按我实际做这个项目的思路从设计、编码到部署一条线讲清楚。1. 项目定位与整体设计思路1.1 为什么是 SpringBootVue而不是别的组合SpringBoot 负责处理业务逻辑和提供接口Vue 负责页面渲染和数据展示MySQL 保存选手、评分、赛事等结构化数据。选择这套组合核心原因是分工清楚、开发效率高、学习资料多。SpringBoot 最大的价值是自动配置和约定优于配置不像早期 SSM 那样要花大量时间写 XMLVue 的组件化开发又能把评分页、管理页拆成独立组件后面维护不用在一大堆 JS 里找逻辑。对比传统 JSPServlet 或 Thymeleaf 服务端渲染前后端分离更容易调试。前端只需要调用/api/xxx接口后端接口可以用 Swagger 或 Postman 单独测试部署时前端打成静态文件扔到 SpringBoot 里一套 jar 就能跑起来。对毕设和课设来说这套组合既能体现“会 Java”“会数据库”“会前端”又不会因为技术太偏导致老师看不懂。1.2 业务场景与角色拆解先把人理清楚做任何管理系统第一步都不是写代码而是弄清谁会用它。健美操评分平台至少要有三类角色角色核心操作对应页面管理员维护用户、赛事分组、裁判分配、评分规则配置系统管理、赛事管理裁判进入指定轮次给选手打各维度分数评分录入页选手/观众查看成绩与排名不需要登录或只读账号成绩查询页每个角色对应不同的菜单和接口权限这是系统架构里最重要的边界。如果一开始没把权限定义清楚后面会出现裁判能进后台删数据的尴尬情况。场景上比赛前管理员创建赛事和轮次把选手分组给每个轮次安排裁判比赛时裁判登录后只能看到分配给自己的选手名单逐项打分比赛结束或每一轮结束后系统自动计算总分、生成排名。这个流程跟体操、跳水等项目也能复用所以“健美操”本质上是一个典型的赛事评分业务模型。在业务设计上我建议把“评分规则”独立成一张配置表而不是写死在代码里。因为不同学校、不同级别的比赛对艺术分、完成分权重定义可能不一样。把维度代码、维度名称、权重存到数据库管理员可以在界面上调整后期不用改代码重新部署。这也是容易被答辩老师问到的点提前做了会加分。2. 核心功能与评分逻辑实现2.1 分值构成与计分算法不是简单平均分健美操评分通常分成几个维度艺术分、完成分、难度分以及违规扣分。实际学校比赛可能不同但都可以抽象成“多维度评分”。一种常见规则是每个维度由多位裁判打分去掉一个最高分和一个最低分剩下的取平均值然后各个维度按权重加权汇总最后减去违规扣分得到最终成绩。为什么要去掉最高最低最直接的原因是避免某一个裁判打分偏高或偏低把整体分数带偏。这个规则虽然简单但实现时要注意边界裁判人数至少要有 3 个否则去掉最高最低后只剩一个分数平均值就失去了意义。代码里要加一个校验裁判人数小于 3 时直接提示“无法计算”。public BigDecimal averageAfterTrim(ListBigDecimal sourceScores) { if (sourceScores null || sourceScores.size() 3) { throw new IllegalArgumentException(至少需要3个裁判评分); } ListBigDecimal sorted new ArrayList(sourceScores); Collections.sort(sorted); BigDecimal sum BigDecimal.ZERO; for (int i 1; i sorted.size() - 1; i) { sum sum.add(sorted.get(i)); } int count sorted.size() - 2; return sum.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP); }这里要强调一点计算必须用BigDecimal不要用double。评分场景对精度敏感double 的二进制浮点误差在 9.99 这种数值上会出现类似 9.990000001 的问题加上总分要保留两位小数必须统一使用 BigDecimal 并指定舍入模式。最终总分公式可以抽象成totalScore artScore * artWeight completionScore * completionWeight difficultyScore * difficultyWeight - deduction这些权重和扣分值都从配置表读取。这样当老师问“如果规则改成 5 个裁判怎么处理”时可以直接回答打分列表长度改成 5其余逻辑不变。2.2 后端评分接口怎么设计权限、事务、重复提交评分接口是系统最核心的接口。一个典型的打分请求可以这样定义{ roundId: 1, playerId: 101, judgeId: 5, dimensionCode: ART, score: 8.50 }后端要做三件事第一权限校验这个裁判是否被分配到这个轮次、是否有权给这个选手打分第二分数范围校验不能低于 0、不能超过满分第三事务处理保存明细后立刻重算该选手的总分。PostMapping(/api/scores/submit) public ResultString submitScore(RequestBody ScoreSubmitDTO dto) { boolean authorized judgeService.canScore(dto.getJudgeId(), dto.getRoundId(), dto.getPlayerId()); if (!authorized) { return Result.fail(当前裁判无权为该选手打分); } if (dto.getScore() null || dto.getScore().compareTo(BigDecimal.ZERO) 0 || dto.getScore().compareTo(new BigDecimal(10.00)) 0) { return Result.fail(分数必须在0到10之间); } scoreService.submitScore(dto); return Result.ok(提交成功); }Service 层要用Transactional(rollbackFor Exception.class)保证明细表和汇总表同时更新。我之前见过不少代码只写了明细表排名页面靠实时查明细计算数据少时没问题但赛事轮次一多查询越来越慢而且如果某个裁判分数录入错误去改明细排名数据会出现临时的不一致。更合理的做法是维护一张汇总表每次裁判提交分数后重算该选手当前轮次的成绩写回汇总表这个动作和写入明细必须放在同一事务里。还有一个容易忽略的点如果允许裁判修改打错的分数提交接口要先判断明细表里是否存在该裁判、该轮次、该选手、该维度的记录。存在就执行更新不存在就插入。为了防止并发重复提交数据库层面还需要唯一索引兜底。2.3 数据库设计要点成绩明细表不能省核心表可以这样规划sys_user登录账号、角色标识competition比赛信息round轮次或分组player_info选手基本信息judge_assignment裁判与轮次的分配关系score_detail每个裁判给每个选手每个维度打的分score_summary选手在当前轮次的最终得分和排名重点看一下打分明细表CREATE TABLE score_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, round_id BIGINT NOT NULL, player_id BIGINT NOT NULL, judge_id BIGINT NOT NULL, dimension_code VARCHAR(20) NOT NULL, score DECIMAL(5,2) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_round_player_judge_dim (round_id, player_id, judge_id, dimension_code) );这条唯一索引的意义是在数据库层面防止同一个裁判对同一个选手的同一个维度重复提交。但要注意如果允许裁判修改分数不能简单依赖这个唯一索引做“阻止重复插入”而是在提交接口里先查记录是否存在存在就更新不存在就插入。汇总表用round_id player_id做唯一约束保证一个选手在一个轮次只有一条最终成绩。维度在明细表里用dimension_code描述比单独存维度名称好扩展。管理员配置新的维度后只要代码合法数据库不需要改表结构。实际项目里表数量控制在 6~8 张就够不要贪多否则写起来累答辩也讲不清。3. 从零搭建前后端项目实操记录3.1 后端SpringBoot 初始化和依赖选择创建项目时建议选 Spring Boot 2.7.x 版本。原因到第 4 部分再展开3.x 需要 JDK17而很多毕设环境还是 JDK8换成 2.7.x 可以少踩坑。依赖选择 Spring Web、MySQL Driver、Lombok然后手动加 MyBatis-Plus。pom.xml 加 MyBatis-Plusdependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.2/version /dependency为什么选 MyBatis-Plus 而不是裸 MyBatis因为单表 CRUD 不需要写 SQL实体类加注解就能操作数据库评分明细的插入、汇总表的更新都不需要维护一堆 XML。对课设项目来说这是提高开发效率的关键。后端目录结构我习惯这样分controller // 接口层 service // 业务逻辑 mapper // 数据访问 entity // 数据库实体 dto // 前端请求参数 config // 跨域、拦截器配置application.yml里重点看数据源spring: datasource: url: jdbc:mysql://localhost:3306/aerobics?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.DriveruseSSLfalse是开发环境关掉 SSL 校验serverTimezoneAsia/Shanghai解决 MySQL 8 的时区报错。很多源码里没有 SQL 脚本需要自己用 Navicat 把建表语句执行一遍这一步跑通了后端基本就成功了一半。3.2 前端Vue 项目初始化和路由规划前端我用 Vue 2 Element UI老项目多、案例多、遇到问题几乎都能搜到。用 Vue CLI 创建vue create aerobics-web npm install element-ui axios vue-router路由按业务划分const routes [ { path: /login, component: Login }, { path: /, component: Layout, children: [ { path: dashboard, component: Dashboard }, { path: players, component: PlayerList }, { path: rounds, component: RoundList }, { path: score-entry, component: ScoreEntry }, { path: ranking, component: Ranking } ] } ]如果要做权限菜单可以在登录返回的 userInfo 里带上 roles前端用addRoutes动态添加对应路由避免裁判看到管理菜单。这个功能能体现对 Vue Router 的掌握属于加分项第一天不用急着写。页面规划不需要太复杂登录页、主页布局、选手列表页、轮次管理页、评分录入页、成绩排名页。评分录入页是最核心的页面建议做成“左侧选手列表右侧分数表单”这样裁判可以快速切换选手不用反复返回列表。Element UI 里el-table和el-form足够覆盖这些场景。3.3 联调与部署开发代理、打包合并、刷新404开发环境最常见的跨域问题可以在vue.config.js里配置代理module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样前端请求/api/scores/submit会被代理到后端 8080浏览器没有跨域问题。生产环境适合毕设演示的方式是前端打包后直接放进 SpringBoot 静态目录变成单 jar。做法是npm run build把生成的dist目录里的文件复制到后端的src/main/resources/static下然后正常启动 SpringBoot。此时访问http://localhost:8080就是前端页面。需要注意如果 Vue Router 用了 history 模式直接访问子路径如/score-entry会返回 404因为后端没有这个路径。最简单的处理是用 hash 模式路由地址会带上#如果想用 history 模式后端需要写一个 forwardController 把所有非/api的前端路由转发到 index.html。毕设答辩时我建议用 hash 模式省事、不会出错。Controller public class PageForwardController { RequestMapping(value {/score-entry, /ranking, /players, /rounds}) public String forward() { return forward:/index.html; } }4. 常见问题排查与避坑指南4.1 MySQL 连接异常SSL、时区、驱动版本很多同学第一次跑源码卡在数据库连接是正常的。常见报错和解决方式整理成一张表报错或现象原因解决办法Communications link failure数据库没启动、连接串写错、驱动没有加载先确认 MySQL 服务和端口再检查driver-class-nameThe server time zone value ...MySQL 时区问题URL 加serverTimezoneAsia/ShanghaiSSL connection errorMySQL 8 的连接默认开启 SSL本地环境可能不兼容URL 加useSSLfalsePublic Key Retrieval is not allowedMySQL 8 密码加密方式导致URL 加allowPublicKeyRetrievaltrueAccess denied for user用户名或密码错误单独用 Navicat 测试连接后再试这里要提示useSSLfalse是本地开发时方便生产环境不建议关 SSL。但毕设演示本地怎么方便怎么来。4.2 SpringBoot 版本太高引发的奇怪报错Spring Boot 3.x 发布后很多同学用 Spring Initializr 默认选了 3.x结果本地 JDK 是 8启动直接报UnsupportedClassVersionError。如果跟着网上的教程写代码还会发现 3.x 里javax变成了jakarta大量老代码不能直接跑。我的建议是不是新版本不好而是项目要稳定。毕设和课设用 Spring Boot 2.7.x、JDK 8 或 11、MyBatis-Plus 3.5.x这套组合已经被验证过无数次遇到问题也容易搜到答案。如果确实要用 Spring Boot 3需要把 MyBatis-Plus 换成mybatis-plus-spring-boot3-starter同时注意 Java 版本和 Jakarta 注解的变化。这些都算额外工作量刚入手时没必要硬上新版本。4.3 前后端联调时的几个细节坑第一时间格式不一致。后端默认返回的 LocalDateTime 可能序列化成一大串数组前端展示很难看。可以在application.yml里配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai第二接口返回结构不统一。建议所有接口都返回{ code, message, data }这种统一结构前端 axios 响应拦截器统一处理。否则每个页面都要写res.data.data很容易乱。第三后端数据校验不能省。很多新手只在页面做if (score 10)这种校验后端接口完全裸奔。答辩老师随便用 Postman 提交一个 100 分系统也能接受这就很尴尬。后端一定要做参数校验可以用Validated也可以在 Service 里手动判断至少不能依赖前端。第四小数精度问题。后端如果接口里用 double前端显示会踩精度坑。数据库DECIMAL(5,2)Java 用BigDecimal前端按字符串或 Number 显示整套链路就不会有问题。5. 几个能让项目更出彩的扩展点5.1 权限控制从“有”到“能用”我见过不少评分系统源码登录之后所有接口都能调甚至直接在浏览器控制台调接口就能改分数。这个问题在答辩时很减分。至少要做一个可用的权限控制后端写一个拦截器从请求头取 token解析出当前用户角色管理员接口检查 ADMIN裁判接口检查 JUDGE。实现不复杂但能说明你理解权限模型。如果技术储备够可以用 Spring Security JWT。具体做法是登录接口签发 token前端 axios 在请求拦截器里加Authorization头后端配置安全过滤器。这样整体才像一个正经平台。5.2 成绩导出 Excel 和打印评分表评委打分完成后管理员常常需要一张可打印的评分表。这个功能建议直接用 EasyExcel加入依赖后一行代码就能导出EasyExcel.write(response.getOutputStream(), ScoreSummaryVO.class) .sheet(成绩表) .doWrite(list);前端只需要用window.location.href请求一个文件下载接口。工作量不大但是演示效果很好评委和老师都会觉得系统完整。5.3 选手照片和参赛视频的扩展如果选手列表要上传证件照、比赛视频可以考虑引入对象存储。MinIO 是一个很轻量的方案在 SpringBoot 里引入 SDK配置桶名和地址上传接口把文件流写入 MinIO再把返回的 URL 存到选手表。这个扩展能体现对文件存储的认识但不是必需功能时间不够可以不加。我自己在实际项目里的做法是先把照片路径放在服务器本地磁盘用 Nginx 映射访问实现简单等系统稳定后再接 MinIO。毕设阶段没必要一上来就追求分布式文件存储把核心业务做好更重要。最后分享一点个人的实战体会。评分系统这类项目最关键的是把“业务规则”和“技术实现”对应起来。如果你拿到源码别急着把项目跑起来先看数据库设计再顺着“管理员建比赛 → 分配裁判 → 裁判打分 → 系统算分 → 公布排名”这条线读代码这样评审时问任何流程都能接上。我自己在这个项目里踩过最大的坑就是改分事务先改了 score_detail忘记重算 score_summary导致排名一直不对。后来在 Service 里加了一个Transactional方法把删除旧分、插入新分、重算汇总放到同一个事务里问题才算彻底解决。这个思路你在做任何带“计算汇总”的项目时都可以直接用上。
返回列表