资讯动态

基于Spring Boot的田径运动会管理系统:从报名到成绩排名全流程

发布时间:2026/10/15 18:38:46 来源:尧图企业网站定制
简介这是面向运动会组织场景的Java信息管理系统适合高校学生完成课程设计或毕业设计时参考也适用于小型田径赛事的信息化管理。系统基于Java与Swing/JFX开发搭配MySQL数据库覆盖运动员管理、赛事安排、报名、成绩录入与排名、信息发布及权限管理等模块提供从建库脚本到界面实现的完整代码。压缩包共81个文件主要包括25个Java源码、48个编译后的class文件、2个JDBC驱动Jar包、1个SQL脚本及1份数据库课设报告PDF整体大小约2.04MB便于导入Eclipse直接运行学习。资源已有2168人学习配套说明文档对系统架构、功能模块和实现过程做了梳理适合想接触Java桌面应用与数据库整合开发的初学者快速上手。1. 田径运动会管理系统把报名、检录、成绩排名装进同一个后台运动会报名截止的那个晚上体育组办公室灯火通明——一沓手写报名表、三四张 Excel 表、手机里源源不断的补报消息负责的老教师对着名单一点点核对谁超了项、谁和谁项目冲突。比赛当天更不敢想终点裁判喊出一个成绩记录员手写记下来再跑到电脑前敲进去名次算完广播台已经开始催获奖名单了。这套 Java 田径运动会管理系统就是要把这一整条链路线上化运动员报名、赛事项目配置、成绩录入、自动排名、破纪录标记、团体总分统计全部收进同一个后台各角色各管一段。适合两类人。一类是学校、单位里真要办运动会的组织者想找一套能部署、能改的系统另一类是学 Java 后端的人想找一个业务完整、结构清晰、看得完也跑得起来的实战项目动手练一练。下面从架构、核心模块、运行到二次开发逐层拆。2. 系统架构与技术选型Spring Boot、MyBatis 与 MySQL 的分层设计2.1 技术选型为什么是 Spring Boot 而非 SSM 手写配置对于这种业务结构清晰的管理系统选型第一标准是「上手快、好部署、周边生态全」。Spring Boot 与 SSMSpring Spring MVC MyBatis相比最大的差别在配置传统 SSM 要写一堆 XML 声明数据源、扫描包、视图解析器而 Spring Boot 的 starter 机制把这些常规配置收敛成依赖项和 application.yml 里的几行参数。运动会这类项目的业务逻辑集中在报名和成绩计算工程复杂度越低越能把精力留到核心功能上。持久层选了 MyBatis。理由很实际成绩排名、团体总分这类统计需求经常涉及多表 join 和条件排序MyBatis 的 XML 可以直接写原生 SQL排查、调优都直观JPA 的实体关系映射写起来快但遇到复杂查询反而要绕。系统没有额外引入分页插件列表查询自己写 Limit 就够了。前端用 Thymeleaf 做服务端模板渲染。运动会的使用场景是局域网内一次短期活动后台页面拆成前后端分离工程反而增加部署成本服务端渲染配合 Bootstrap 就能把管理界面撑起来。登录鉴权用拦截器加 Session 足够用户角色固定为管理员、裁判员两种没必要上 Shiro 这类重型框架。这套系统的标准技术栈组合是JDK 8、Spring Boot 2.x、MyBatis、MySQL 5.7、Thymeleaf、Bootstrap 4。2.2 数据库设计五张核心表如何串起运动会全流程全局看所有业务围绕五张表展开t_user 管登录athlete 表存参赛人员event 表定义项目entry 表连接运动员和项目result 表存每一条比赛结果。核心字段设计如下表名关键字段用途t_userid, username, password, role管理员/裁判员登录athleteid, name, gender, student_no, class_name运动员基础信息eventid, event_name, event_type, round_type, limit_count, current_count, status项目配置、报名限额、报名状态entryid, athlete_id, event_id, create_time报名记录防重靠唯一索引resultid, athlete_id, event_id, score, rank, score_point, record_flag成绩、排名、积分、破纪录标记event 表的 event_type 是整套系统的关键设计之一。它用 RUN 和 FIELD 区分径赛和田赛因为两类项目的排名规则正好相反径赛用时越短名次越靠前田赛跳高、跳远、铅球等成绩数值越大名次越靠前。这个字段直接影响成绩排名模块的排序逻辑如果设计时图省事只存一个成绩数字排名功能就得靠写死分支硬撑。entry 报名表看似简单却最容易出问题。为避免同一名运动员重复报同一个项目表设计时直接建了联合唯一索引 UNIQUE(athlete_id, event_id)在数据库层挡住并发插入造成的重复报名。业务层负责提示性校验——每人限报三个项目、每个项目限额报名——数据库层守住最后一道防线两层配合才可靠。result 成绩表里 score_point 存的是名次对应积分。按田径运动会常见规则取前八名计分第一名到第八名依次 9、7、6、5、4、3、2、1 分。这个积分在成绩录入后由系统自动计算写入统计团体总分时直接 SUM 聚合不用再临时关联名次换算。event 表还放了 current_count 字段记录当前报名人数配合 limit_count 做容量控制。2.3 项目目录结构与一次请求的完整链路工程采用标准 Maven 多模块分层目录包结构按 controller / service / mapper / entity 划分。resources 目录下 mapper 文件夹放 MyBatis 的 XMLsql 文件夹放初始化脚本templates 放 Thymeleaf 页面结构如下src/main/java/com/trackmeet/ ├── controller/ // 对外接口AthleteController, EventController, ResultController ├── service/ // 业务逻辑RegisterService, ResultService, ScoreService ├── mapper/ // MyBatis Mapper 接口 ├── entity/ // 数据库实体Athlete, Event, Entry, Result, User └── config/ // 拦截器、WebMvc 配置 src/main/resources/ ├── mapper/ // MyBatis XMLAthleteMapper.xml, EventMapper.xml... ├── templates/ // Thymeleaf 页面index, event-list, result-board ├── static/ // js/css 静态资源 └── application.yml // 数据源与端口配置一次「查询比赛成绩」的请求是这样走的浏览器访问 /result/listSpring MVC 的 DispatcherServlet 把请求映射到 ResultController 的 list 方法Controller 层只做参数接收和视图转发具体逻辑调用 ResultServiceService 层判断当前用户角色是否有权限再组装查询条件调 ResultMapperMyBatis 根据 ResultMapper.xml 里的 SQL 查出成绩列表返回Controller 把数据塞进 ModelThymeleaf 渲染成 result-board.html 返回浏览器。链路不绕弯每层只做一件事出问题时顺着日志很快能定位。3. 核心模块拆解从报名校验到成绩排名的完整业务闭环3.1 报名模块三重校验把「超报、重复、满员」挡在门外报名是运动会的第一道关也是最容易出争议的地方。报名接口要处理三件事运动员是否真实存在、项目是否处于报名期、是否超员以及项目是否满员。代码按顺序逐层校验任何一步不通过就返回错误不会留下半截数据。PostMapping(/entry/register) public Result register(RequestBody RegisterDTO dto) { Athlete athlete athleteMapper.findById(dto.getAthleteId()); if (athlete null) { return Result.error(运动员不存在); } Event event eventMapper.findById(dto.getEventId()); if (event null || event.getStatus() ! 1) { return Result.error(该项目未在报名期); } int athleteEntryCount entryMapper.countByAthlete(dto.getAthleteId()); if (athleteEntryCount 3) { return Result.error(每名运动员限报3个项目); } int eventEntryCount entryMapper.countByEvent(dto.getEventId()); if (eventEntryCount event.getLimitCount()) { return Result.error(该项目报名人数已满); } // 数据库层有唯一索引兜底防止并发重复 entryMapper.insert(Entry.builder() .athleteId(dto.getAthleteId()) .eventId(dto.getEventId()) .createTime(LocalDateTime.now()) .build()); return Result.success(报名成功); }第一段校验运动员防止前端传了不存在的 ID 直接落库引发关联数据脏掉第二段校验 event 的 status 字段报名状态由管理员在后台控制未开始或已结束时一律拒绝第三段是限报项目数系统按常见规则写死每人限报三项这个值建议提取为常量或数据库配置不同单位规则不一样第四段检查项目容量limit_count 是报名上限。第五段插入前数据库的唯一索引会再兜底一次——如果两个请求同时通过校验执行 insert唯一索引会让其中一个直接抛 DuplicateKeyException实际部署时我在 service 层加 try-catch 把这个异常翻译成「你已报过该项目」返回给前端。这里还有一个并发隐患第四段「先查人数再插入」在极端并发下可能放行第 33 个人。容量校验更稳妥的写法是下沉为一条原子 UPDATE具体方案放到第 5 章避坑部分展开。3.2 成绩录入手动录入与 Excel 批量导入的双通道成绩录入要考虑比赛现场的节奏。径赛分组多一个项目几十条成绩一条条点保存太慢系统同时提供手动表单录入和 Excel 批量导入。手动录入接口是核心单条成绩录入后立即触发排名与积分计算。PostMapping(/result/save) public Result saveResult(RequestBody ResultDTO dto) { Event event eventMapper.findById(dto.getEventId()); if (event null) { return Result.error(项目不存在); } Result result Result.builder() .athleteId(dto.getAthleteId()) .eventId(dto.getEventId()) .score(dto.getScore()) .recordFlag(false) .build(); resultMapper.insert(result); // 录入完成后重算本项目所有成绩的排名与积分 scoreService.calcRankAndScore(dto.getEventId()); return Result.success(成绩已录入); }这里刻意不在单条插入时就算排名因为成绩是逐条录入的最后一条没录完时排名不完整。每次插入后调 calcRankAndScore 重算整个项目数据量在百级规模时性能完全没有压力。ResultDTO 里需要传运动员 ID、项目 ID 和成绩值成绩的格式统一为数值径赛存秒数如 9.85田赛存米数如 5.86显示格式交给前端拼接单位。Excel 批量导入走另一条通道Controller 接收 MultipartFileService 层用 EasyExcel 或 POI 读取逐行校验运动员编号和项目编号解析成 List 后批量插入。导入手动录入共用同一个排名重算逻辑避免两处各写一套算法导致结果不一致。3.3 排名与积分径赛田赛反向排序团体总分一步聚合排名计算不是简单的 ORDER BY score要区分径赛和田赛径赛用时越短越靠前田赛成绩越大越靠前。同一张成绩表两种排序方向并存处理逻辑如下public void calcRankAndScore(Long eventId) { Event event eventMapper.findById(eventId); ListResult results resultMapper.listByEvent(eventId); // 按项目类型选择排序方向 if (RUN.equals(event.getEventType())) { results.sort(Comparator.comparing(Result::getScore)); } else { results.sort(Comparator.comparing(Result::getScore).reversed()); } // 前八名依次计 9,7,6,5,4,3,2,1 分 int[] points {9, 7, 6, 5, 4, 3, 2, 1}; for (int i 0; i results.size(); i) { Result r results.get(i); r.setRank(i 1); r.setScorePoint(i points.length ? points[i] : 0); if (i 0 r.getScore() event.getMeetRecord()) { r.setRecordFlag(true); // 破赛会纪录标记 } resultMapper.updateById(r); } }几个关键点。Comparator.comparing(Result::getScore) 按数值升序径赛用时短排前田赛加 reversed() 反转后成绩大的排前面。这里有个隐藏坑如果 score 以字符串存储排序结果会是「9.85」排在「70.20」后面——字符串比较逐字符比「9」大于「7」所以 9 开头永远排在 7 开头后面。所以表设计时 score 用 DECIMAL(8,2) 而不是 VARCHAR。破纪录判断取第一名成绩与赛会纪录比较小于纪录才置 record_flag。前八名积分写入 score_point第八名之后积分为 0保证团体总分统计不把未获名次的人算进去。团体总分是闭幕式的重头戏。成绩表写入 score_point 后按班级聚合一次 SUM 就能出结果SELECT a.class_name, SUM(r.score_point) AS total_points, COUNT(r.record_flag 1 OR NULL) AS record_count FROM result r JOIN athlete a ON r.athlete_id a.id GROUP BY a.class_name ORDER BY total_points DESC, record_count DESC这个 SQL 用 OR NULL 写法统计破纪录次数避免 COUNT(record_flag 1) 把非破纪录行也算进去。排序先比总分总分相同比破纪录次数符合多数运动会的团体名次判定习惯。大屏幕展示或打印获奖证书时直接调这条 SQL 的结果集即可。4. 跑起来环境准备、数据库初始化与启动验证4.1 环境准备版本匹配和三条命令先确认版本这是新手最容易翻车的地方。项目基于 JDK 8Spring Boot 2.x 对 JDK 版本没有强制要求但不能低于 8Maven 3.6 就行MySQL 建议 5.7 以上。不建议一上来装 JDK 17 搭配 Spring Boot 2.x部分版本组合会出现字节码兼容问题实测不如老老实实装 JDK 8。装好之后先确认环境命令行依次执行 java -version、mvn -v、mysql --version三个命令全部正常输出再继续。特别提醒Windows 上很多同学只配了 PATH 没配 JAVA_HOME导致 java -version 能跑、mvn -v 却报找不到 JAVA_HOME补一个环境变量就好了。Maven 第一次构建会去中央仓库拉大量依赖网络差的话建议在 settings.xml 里配置阿里云 mirror能省下不少等待时间。4.2 数据库初始化与配置修改拿到源码后第一步是建库。工程 resources/sql 目录下提供了初始化脚本 track_meet.sql包含建库、建表、插入基础数据两个测试账号和几个示例项目的完整语句。在 MySQL 里执行mysql -u root -p track_meet.sql脚本里写了 CREATE DATABASE IF NOT EXISTS track_meet 和 USE track_meet所以执行前不用手动建库。执行完用 SHOW TABLES; 确认生成了五张表。接着打开 src/main/resources/application.yml把数据源配置改成你的本机环境server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/track_meet?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true两个必须注意的点。url 里的 characterEncodingutf8 必须保留否则插入中文姓名会变成问号serverTimezoneAsia/Shanghai 是因为新版 MySQL 驱动默认时区与本机不一致不加会在连接时报错。password 改成你自己的数据库密码改完保存。如果 MySQL 密码包含 这类特殊字符注意在 yml 里不需要额外转义但用命令行连接时要用引号包起来。提示url 里的 characterEncodingutf8 不能删这是中文乱码的第一道防线。4.3 启动、登录与功能验收在项目根目录依次执行mvn clean package -DskipTests java -jar target/track-meet-0.0.1-SNAPSHOT.jar看到 Tomcat started on port 8080 的日志说明启动成功浏览器访问 http://localhost:8080。初始化脚本预置了一个管理员账号和一个裁判账号首次登录建议先用管理员进系统创建几个测试运动员、开启一个项目的报名期、录入两条成绩完整走一遍流程。验收盯三个地方报名阶段是否提示「每名运动员限报3个项目」、录入成绩后榜单是否自动按规则排序、团体总分页能否按班级正确聚合。三个点分别对应报名、排名、统计三个核心功能任何一个不对往回追日志就能定位。如果启动时抛 ClassNotFoundException: com.mysql.cj.jdbc.Driver多半是依赖里 mysql-connector-java 版本过老把 pom.xml 里这个依赖升到 8.x 版本即可。也可以用 curl 验证接口存活性curl http://localhost:8080/event/list 返回 200 说明服务正常。5. 避坑指南从初始化到比赛日五个高频坑5.1 中文乱码界面上全是问号现象运动员姓名、班级名称到了页面上全部显示 ???。原因数据库连接串缺少 utf8 参数或者建库时没指定字符集。MySQL 5.7 默认字符集是 latin1存汉字就会丢。解决确认 application.yml 里 url 包含 characterEncodingutf8同时执行 ALTER DATABASE track_meet CHARACTER SET utf8mb4; 建库脚本里补上 DEFAULT CHARSETutf8mb4。这个坑在本地部署时最常出现因为 MySQL 安装方式不同默认配置差异很大。5.2 成绩排序错乱9 秒 85 排到 70 秒后面现象径赛排名中成绩 9.85 秒的运动员排在 70.20 秒的后面。原因建表时 score 字段用了 VARCHAR排序时按字符串逐字符比较「9」的 ASCII 码大于「7」所以 9 开头天然排在 7 开头后面。解决把 score 字段改成 DECIMAL(8,2)实体类里对应属性用 BigDecimal 而不是 String重新导入数据后排序立即恢复正确。这是建表阶段最常见的失误图省事用文本存成绩到实现排序功能时就翻车。5.3 报名超员并发提交挤进了第 33 个人现象一个项目限额 32 人第 33 个人也报名成功了。原因业务层「先查人数再插入」不是原子操作两个请求同时查到 30 人且都通过校验于是都执行插入。解决把容量校验下沉为一条原子 SQL代替先查后插。Spring 里通过 Update 返回的受影响行数判断是否满员// eventMapper 中定义UPDATE event SET current_count current_count 1 // WHERE id #{eventId} AND current_count limit_count int rows eventMapper.incrementCount(eventId); if (rows 0) { return Result.error(该项目报名人数已满); } entryMapper.insert(entry);受影响行数为 0 说明当前计数已到上限插入不会执行。这个方案把校验和更新合并成一个原子操作并发场景下不会超员。5.4 登录拦截器把静态资源也拦了现象登录页能打开但登录后的页面没有样式js、css 请求全部 302 跳到登录页。原因拦截器配置拦截了所有路径包括 static 目录下的资源浏览器请求 /css/app.css 时被当成未登录请求重定向。解决注册拦截器时把静态资源目录放行。常见写法是 excludePathPatterns(/css/, /js/, /img/**, /login)。排查这类问题最快的办法是打开开发者工具 Network 面板看哪个静态资源返回 302路径规律一眼就能看出来。5.5 服务器上时间显示差 8 小时现象录入成绩的时间戳和实际时间差了 8 小时。原因JVM 默认时区与数据库服务器时区不一致。连接串加了 serverTimezoneAsia/Shanghai但 JVM 启动参数没指定时区或者 MySQL 服务器自身的 time_zone 不对。解决启动命令加上 -Duser.timezoneAsia/Shanghai同时确认 MySQL 的 time_zone 参数。两个地方都改掉时间偏移问题基本不会再出现。6. 二次开发方向成绩可视化仪表盘与移动端适配前面把系统跑通了下面说两个实际用得上的改造方向。第一个方向给闭幕式加一块「运动会成绩可视化大屏」。数据已经在了缺的只是展示层。新增一个 summary 接口返回团体总分那条 SQL 的结果前端用 ECharts 画柱状图和饼图比如按班级渲染积分柱状图、按项目类型渲染破纪录数量分布。改动不涉及核心逻辑初学者可以拿这个练手。GetMapping(/summary/chart) ResponseBody public ListMapString, Object summaryChart() { return scoreService.groupByClass(); }第二个方向是移动端适配。裁判在田径场上用手机录成绩比抱着电脑方便得多但现有后台表格在手机上很难用。改造思路是加一组以 m- 开头的 Thymeleaf 模板用 Bootstrap 手机栅格和大按钮重新排版复用同一套 Controller 接口核心是报名列表查看、成绩快速录入、实时排名查看三个页面这样既补足了现场移动使用的短板又不破坏原有桌面端结构。讲到这想起一次真实的翻车经历。之前在一个单位部署这套系统时我只做了桌面端页面就交付了结果比赛当天裁判抱着笔记本在操场边录成绩太阳底下屏幕反光严重录入效率还不如纸笔。从那以后我每接一个管理系统类的项目都会先问一句「现场谁用、用什么设备用」再决定页面适配方案而不是默认桌面端就够了。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑