ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java田径运动会管理系统实战:从表结构到并发报名与成绩排名

Java田径运动会管理系统实战:从表结构到并发报名与成绩排名 简介面向Java课程设计、毕业设计及运动会信息化管理学习者这套完整项目实现了运动员管理、赛事安排、在线报名、成绩录入与排名展示、信息发布和权限控制等功能。系统基于Java与MySQL开发数据存储与管理由MySQL完成代码注释清晰配套SQL初始化脚本、readme说明和数据库课设报告报告详细描述了设计思路、系统架构、功能模块与实现过程便于复盘整个开发脉络。资源包共81个文件压缩包大小2.04MB其中包含25个java源文件、48个class文件、2个jar依赖包含MySQL驱动、1个sql建表脚本和1个pdf课程设计文档用Eclipse可直接导入运行目录结构清晰二次开发也比较方便。已有2168人学习浏览无论是作为课设参考还是项目实战素材都能帮助巩固Java面向对象编程、数据库操作及软件工程规范是一份实用且完整的课程设计参考资料。1. java田径运动会管理系统在管什么先从一次运动会组织事故说起嘴上随便说一个 java田径运动会管理系统很多人第一反应是“不就是报报名、录成绩吗”。但真经历过一次运动会组织事故的人不会这么想。某高校去年春季运动会秩序册是手工排的三个项目共用一块场地还排在同一时间检录处吵成一团更麻烦的是成绩统计裁判手填的纸单传到录入组再往 Excel 里敲等敲完已经晚上八点女子跳远和男子铅球还搞混了两条记录。事后复盘大家发现真正缺的不是 Excel是一个能把编排冲突、成绩排名规则、数据校验都提前兜住的系统。这也是 java田径运动会管理系统 最核心的价值先用技术把“比赛项目能不能一个人报多个、时间会不会撞、田赛径赛分别怎么排名、成绩修改有没有留痕”这些规则落地再谈管理后台和数据导出。我见过不少同学把这个项目做成纯 CRUD 的增删改查 Demo最后交上去只能演示不敢真用。这篇笔记就围绕真实可落地来写适合两类人一类是准备拿 Java 做课设或毕设想选个不烂大街又不太难的题目另一类是单位或学校确实要办运动会需要一个轻量系统的人的。后面所有表结构和代码都是按“能直接跑起来、能经得住一场 500 人规模的比赛”这个标准来写的。会用 Spring Boot MyBatis MySQL前端不做重渲染页面后台管理用模板或静态页都行。2. 表结构与领域模型把比赛项目、报名和成绩拆成可落地的实体运动会系统的难点不在页面在数据模型。项目、报名、成绩这三张主表设计对了后面编排和排名才有地基。很多人喜欢把所有东西塞进一张大表运动会项目要分田赛、径赛、团体、男女、预决赛一张表根本装不下最后只能用字符串硬凑查询和统计全是坑。2.1 比赛项目表为什么用状态字段而不是直接删除项目表是最容易设计得过简单的表。很多 Demo 里只有 id、项目名称、比赛时间三个字段这在实际运动会里完全不够用。我一般会给项目表加上项目类型、性别分组、人数上限、轮次和状态这样前端可以按条件筛选编排算法也能直接读字段而不是靠猜。CREATE TABLE sports_event ( id BIGINT AUTO_INCREMENT PRIMARY KEY, event_code VARCHAR(20) NOT NULL COMMENT 项目编号如A001, event_name VARCHAR(50) NOT NULL COMMENT 项目名称如男子100米, event_type TINYINT NOT NULL COMMENT 1-径赛 2-田赛 3-团体, gender TINYINT NOT NULL COMMENT 1-男子 2-女子 3-混合, max_participants INT NOT NULL DEFAULT 8 COMMENT 个人项目人数上限团体项目为每队人数, rounds TINYINT NOT NULL DEFAULT 1 COMMENT 1-预决赛合并 2-预赛决赛, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-报名中 2-比赛中 3-已结束 4-停用, sort_order INT DEFAULT 0 COMMENT 秩序册排序权重越小越靠前, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );这里最关键的不是 id 和名称而是 event_type、rounds 和 status 三个字段。event_type 决定了后面成绩排名走哪条规则径赛比时间数值越小越好田赛比距离或高度数值越大越好。rounds 决定了这个项目需要录几轮成绩预赛决赛就是两轮而不是一组成绩打天下。status 字段则是一个被很多人忽略的设计删除项目不要用 DELETE而是把 status 置为 4因为历史成绩还要引用这个项目真删了会导致成绩表变成孤儿数据。这个习惯在运动会系统里尤其重要比完赛后还要出成绩册数据要能追溯。参数上需要注意的是 max_participants径赛一般取 8 人一道一人田赛可以设 12 人预赛后取前 8 进决赛。如果做团体项目这个值指的是每队的人数上限报名时要和队伍数量区分开别混用。2.2 报名表并发报名时怎么用条件更新挡住超员项目表定好以后报名表是第一个容易出现并发问题的表。运动会报名是典型的短时高并发场景通知一发出几百个学生同时登录报名如果代码写成“先检查人数再插入”那并发请求打过来时大概率超员。这个问题后面避坑章节会展开讲但表结构这里就要先留好伏笔。CREATE TABLE enrollment ( id BIGINT AUTO_INCREMENT PRIMARY KEY, athlete_id BIGINT NOT NULL COMMENT 运动员ID, event_id BIGINT NOT NULL COMMENT 比赛项目ID, group_code VARCHAR(20) DEFAULT NULL COMMENT 预赛分组编号由编排算法生成, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-已报名 2-已退赛, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_athlete_event (athlete_id, event_id), KEY idx_event_status (event_id, status) );报名表唯一索引 uk_athlete_event 是硬约束同一个运动员同一个项目只能报一次这个必须由数据库兜底不能只靠 Java 代码判断。索引 idx_event_status 是给管理端“查某个项目报名了多少人”用的没有这个索引随着报名数据增长后台查询会越来越慢。运动员信息建议单独建 athlete 表包括姓名、性别、学号、院系、手机号不要塞进 user 表。原因有两个一是运动员可能没有登录账号是报名负责人代录的二是 user 表将来要接权限职责分离更清晰。字段上注意学号要加唯一索引成绩册打印时要按学号排序。2.3 成绩表复合主键与唯一索引的设计细节成绩表是整个系统里最容易返工的表。见过有人把“一次比赛成绩”设计成一行包含第一次成绩、第二次成绩、决赛成绩这是把 Excel 思维带进了数据库。田径比赛每个项目可能有预赛、决赛田赛还有多次试跳、试投正确做法是每条成绩单独一行用轮次和 attempt_no 区分。CREATE TABLE score_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, enrollment_id BIGINT NOT NULL COMMENT 报名记录ID, event_id BIGINT NOT NULL COMMENT 项目ID, athlete_id BIGINT NOT NULL COMMENT 运动员ID, round_no TINYINT NOT NULL DEFAULT 1 COMMENT 轮次1-预赛/第一轮 2-决赛/第二轮, attempt_no TINYINT NOT NULL DEFAULT 1 COMMENT 田赛第几次试跳/试投径赛固定为1, raw_result VARCHAR(20) NOT NULL COMMENT 原始成绩字符串如11.23秒 / 6.58米, score_value DECIMAL(10,3) NOT NULL COMMENT 可比较数值径赛越小越好田赛越大越好, rank_no INT DEFAULT NULL COMMENT 本组名次由排名算法回填, is_valid TINYINT NOT NULL DEFAULT 1 COMMENT 1-有效 0-犯规, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_enrollment_round_attempt (enrollment_id, round_no, attempt_no) );这里有两个字段容易被忽视。第一个是 score_value它把成绩统一转成可比较的数值径赛直接存秒数田赛存米数这样排名时不需要解析字符串。很多系统直接用 raw_result 排序最后排出来“10.2秒”比“9.8秒”大看着没问题但一旦出现“11.1”和“9.90”这种字符串排序就会翻车。第二是 uk_enrollment_round_attempt 唯一索引它保证同一个人、同一轮、同一次尝试不会重复录入。裁判重复提交时数据库会直接报 Duplicate 错误而不是生成两条互相矛盾的成绩。3. 编排与成绩处理从赛程冲突检测到田赛径赛排名规则表结构准备好之后接下来是系统的核心业务逻辑批量导入报名、编排赛程、统计成绩。这一章不写页面只写服务层和算法因为这几个环节最容易出逻辑错误。3.1 批量导入报名的检验顺序先查项目再插数据运动会报名经常由院系负责人统一提交 Excel。批量导入最容易踩的坑是一次性把几千行插进去遇到一条错误数据整个事务回滚好的全没了又得重传。我更推荐逐行校验、逐行插入把每行的错误原因记录下来返回给用户。public ImportResult importEnrollments(MultipartFile file) { ListEnrollmentError errors new ArrayList(); int successCount 0; ListEnrollmentExcelRow rows ExcelReader.read(file); for (int i 0; i rows.size(); i) { EnrollmentExcelRow row rows.get(i); try { SportsEvent event eventMapper.selectByCode(row.getEventCode()); if (event null) { errors.add(new EnrollmentError(i 1, 项目编号不存在 row.getEventCode())); continue; } if (event.getStatus() ! 1) { errors.add(new EnrollmentError(i 1, 项目当前不在报名期 event.getEventName())); continue; } int count enrollmentMapper.countByEventId(event.getId()); if (count event.getMaxParticipants()) { errors.add(new EnrollmentError(i 1, 报名人数已满 event.getEventName())); continue; } enrollmentMapper.insert(buildEnrollment(row, event)); successCount; } catch (DuplicateKeyException e) { errors.add(new EnrollmentError(i 1, 该运动员已报名此项目)); } } return new ImportResult(successCount, errors); }这段代码的关键是校验顺序。先查项目是否存在再查状态再查人数最后才插入。如果把人数校验放在项目校验前面项目编号写错了也会去 count 一次浪费查询不说错误提示还不准确。逐行 try-catch 的作用是让单条错误不影响整批数据前面成功的行照样入库返回结果里带上 Excel 行号用户改错时能直接定位。批量导入的 Excel 模板列顺序建议固定为学号、姓名、性别、项目编号、备注。性别不要用“男/女”用“1/2”更不容易出编码问题。项目编号由系统生成和项目名称一一对应这样即使项目名称改了也不影响历史导入数据。3.2 赛程冲突检测时间区段重叠判断的边界处理编排赛程是运动会系统里最体现“管理”价值的功能也是很多人会直接跳过的功能。做过真实运动会的人都深有体会一个人报了三四个项目如果两个项目的时间段重叠检录时就得二选一赛后又来找裁判长理论。常见做法是编排时先列出所有运动员的报名项目然后依次给每个项目分配时间段分配前检测该运动员是否已有冲突。public boolean hasTimeConflict(LocalDateTime startTime, LocalDateTime endTime, ListScheduleItem existingSchedules) { for (ScheduleItem item : existingSchedules) { LocalDateTime existingStart item.getStartTime(); LocalDateTime existingEnd item.getEndTime(); if (startTime.isBefore(existingEnd) endTime.isAfter(existingStart)) { return true; } } return false; }这个重叠判断的写法要注意两个边界一是两个时间段首尾相接的情况比如 9:00-9:30 和 9:30-10:00按上面的逻辑startTime.isBefore(existingEnd) 在 9:30 时返回 false因为不早于所以不认为冲突这符合运动会实际——前一个项目结束后运动员可以赶去下一个检录只要留出从场地到检录处的移动时间即可。二是完全相等的时间段isBefore 和 isAfter 同时为 true能正确识别冲突。编排算法我通常会按场地分开处理。径赛项目按时间线排田赛项目单独排因为不同场地可以并行比赛。如果两个径赛项目重叠系统会在编排阶段就给出提示不会等到比赛当天才发现。参数上相邻径赛项目之间的时间间隔至少要留 10 分钟用于检录和疏散同一运动员的两个项目如果只有 10 分钟间隔系统应该给出黄色警告而不是直接拦截因为有些短距离项目确实能跑完。3.3 成绩排名田赛取最优、径赛取最短的规则实现成绩排名看起来简单实际上是最容易写错规则的地方。径赛是预赛和决赛分开排名决赛成绩直接决定名次田赛要把每个运动员所有试跳、试投的最优成绩取出来再按最优成绩排名。如果代码里只写一个 sort那田径规则就全乱了。public ListScoreRankVO rankEvent(Long eventId) { SportsEvent event eventMapper.selectById(eventId); ListScoreRecord records scoreMapper.selectValidByEventId(eventId); MapLong, ListScoreRecord groupedByAthlete records.stream() .collect(Collectors.groupingBy(ScoreRecord::getAthleteId)); ListScoreRankVO ranks new ArrayList(); groupedByAthlete.forEach((athleteId, recordList) - { if (event.getEventType() 2) { // 田赛取最优成绩数值越大越好 recordList.sort(Comparator.comparing(ScoreRecord::getScoreValue).reversed()); } else { // 径赛取最短时间数值越小越好 recordList.sort(Comparator.comparing(ScoreRecord::getScoreValue)); } ScoreRecord best recordList.get(0); ranks.add(new ScoreRankVO(athleteId, best.getScoreValue(), best.getRawResult())); }); if (event.getEventType() 2) { ranks.sort(Comparator.comparing(ScoreRankVO::getScoreValue).reversed()); } else { ranks.sort(Comparator.comparing(ScoreRankVO::getScoreValue)); } ranks.forEach(r - r.setRank(ranks.indexOf(r) 1)); return ranks; }这段代码的关键是 score_value 的可比较性。田赛里“6.58米”存成 6.580径赛里“11.23秒”存成 11.230排序时直接用数值比较完全不需要解析字符串。注意字段类型选了 DECIMAL(10,3)是因为田径成绩要精确到百分之一秒或厘米float 和 double 存在精度问题稍微多一点数据就会出现 11.229999 这种诡异结果。团体项目的排名规则不一样它不直接比较个人成绩而是要把个人名次换算成分数比如第一名 9 分、第二名 7 分再按总分排团队名次。这个逻辑不适合塞在 score_record 表里我一般单独建积分规则表把“名次-分数”的映射存进去方便不同单位按照自己的规则调整而不是写死在代码里。4. 权限与审计三类角色如何共用一套成绩修改流程运动会系统不需要很复杂的权限体系但也不能做成谁都能改成绩。常见做法是分管理员、裁判、普通用户三类角色。管理员管项目和用户裁判只能录成绩和改自己负责的项目普通用户只能看秩序册和成绩。这里最大的坑是权限控制不到位导致裁判能改其他裁判负责的项目成绩出问题后还查不到是谁改的。4.1 三张权限表为什么角色权限要拆开而不是写死字段很多入门项目会在 sys_user 表里加一个 role_type 字段1 是管理员2 是裁判3 是普通用户简单直接。这个做法在用户量只有几个人的系统里没问题但一旦出现“裁判长”这种半个管理员角色就得改表结构。我宁可一开始就多建两张表把用户和角色拆开后面加角色不用动表结构。CREATE TABLE sys_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(100) NOT NULL, real_name VARCHAR(50) NOT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-启用 0-禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE sys_role ( id BIGINT AUTO_INCREMENT PRIMARY KEY, role_code VARCHAR(30) NOT NULL UNIQUE COMMENT ADMIN/JUDGE/ATHLETE, role_name VARCHAR(50) NOT NULL ); CREATE TABLE sys_user_role ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id) );这里没有做细粒度的 permission 表因为运动会的操作就那么几类角色足够覆盖。但是 user 和 role 拆分后将来如果要加权限只需要再建 role_permission 表不影响现有表结构。用户表里 status 字段是给“该用户被禁用”留后门的比如裁判离职或学生毕业不用删账号把状态改成 0 就行历史操作记录还能追溯。一个小提示如果运动会规模特别小比如只有三五个人管理三张表确实有点重可以在 sys_user 里直接加 role_type。但既然要做成可复用的系统拆开更稳妥。我见过的翻车案例里至少有一半是因为图省事把权限写死后来需求一变就得重构。4.2 URL级权限用 Spring Security 控制接口访问范围表设计好后接口权限控制建议直接交给 Spring Security而不是自己在每个 Controller 里判断当前用户角色。Spring Security 的过滤器链配置能统一拦截方法级别还能用注解精确控制。下面这段配置适合当前场景。Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(csrf - csrf.disable()) .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/login).permitAll() .requestMatchers(/api/admin/**).hasRole(ADMIN) .requestMatchers(/api/judge/**).hasAnyRole(ADMIN, JUDGE) .requestMatchers(/api/public/**).permitAll() .anyRequest().authenticated() ) .sessionManagement(session - session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .exceptionHandling(ex - ex .authenticationEntryPoint((req, resp, e) - { resp.setContentType(application/json;charsetUTF-8); resp.getWriter().write({\code\:401,\msg\:\未登录或登录已过期\}); })); return http.build(); } }需要注意 hasRole 里面的角色名会自动加上“ROLE_”前缀如果你的角色编码是 ADMIN那这里直接写 hasRole(ADMIN) 就行。关于裁判只能改自己负责的项目这种数据权限URL 级别控制不了需要在 Service 层加判断。常见做法是给裁判分配项目权限表或者把裁判和项目的关联关系放在内存缓存里修改成绩时先校验当前裁判是否有该项目的操作权限。还有一个细节是登录方式。运动会系统通常是内部使用做成无状态 JWT 会更方便前端拿到 token 后每次请求带上。无状态会话配置如果省略Spring Security 默认启用 session前后端不分离时能用分离了就经常出现 403。上面加了 STATELESS整体逻辑更清晰。4.3 成绩修改审计把操作记录和成绩快照一起存下来成绩修改必须有留痕这个是血泪经验换来的。比赛结束后一旦有人质疑成绩你得能查出来“谁在什么时间把 11.23 改成了 11.43”。只记“某某修改了成绩”是不够的要把旧值和新值都存下来最好连修改原因一起存。CREATE TABLE score_audit_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, score_record_id BIGINT NOT NULL COMMENT 成绩记录ID, operator_id BIGINT NOT NULL COMMENT 操作人用户ID, old_value VARCHAR(20) NOT NULL COMMENT 修改前原始成绩, new_value VARCHAR(20) NOT NULL COMMENT 修改后原始成绩, old_score_value DECIMAL(10,3) NOT NULL COMMENT 修改前可比较数值, new_score_value DECIMAL(10,3) NOT NULL COMMENT 修改后可比较数值, reason VARCHAR(200) DEFAULT NULL COMMENT 修改原因, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );审计日志这块常见的落地方式有两种。一种是在 Service 方法里动手写成绩更新前先查旧值再插入一条日志放在同一个事务里简单可靠。另一种是用 AOP 切面拦截 update 方法自动记录旧值和新值代码侵入小但是调试起来比较绕。我更推荐前者因为成绩修改频率不高多写一行代码不算负担事务保证也不会丢日志。要注意的是日志表里的 old_value 和 new_value 一定要存字符串形式的原始成绩因为最终打印成绩册和成绩公告时展示的是“11.23秒”而不是数值只存数值会让你回溯时还得重新格式化。5. 部署与避坑运动会系统常见的 5 个踩坑现场这一章从把项目跑起来到真正上线挑 5 个我见过最多、也最容易让新手崩溃的问题。每条按现象、原因、解决写可以直接对照着排查。5.1 翻车现场Maven 依赖冲突Spring Boot 一启动就报 ClassNotFound现象项目在本地 IDE 里跑得好好的用mvn spring-boot:run启动时突然报NoClassDefFoundError或者提示BeanCreationException: Error creating bean with name xxx。很多人第一反应是去清 IDE 缓存折腾半天发现没用。原因最常见的是 POI 和 Spring Boot 自带依赖的版本冲突。导入 Excel 用的 POI 依赖poi-ooxml会传递引入commons-compress、xmlbeans等库和 Spring Boot 的 starter-web 里某个传递依赖版本不匹配。Maven 默认采用“就近原则”选版本但冲突时不会报错只在运行时类加载阶段才炸出来。解决先跑一下mvn dependency:tree -Dincludesorg.apache.poi看 POI 到底依赖了哪些库、版本是多少再和 Spring Boot 管理的版本对比。最简单的处理是用exclusion排除冲突库让 Spring Boot 的版本生效。比如排除poi-ooxml自带的xmlbeans改成显式声明一个和 Spring Boot 兼容的版本。这个问题的通用排查思路是看清谁引起的 Caused bymaven dependency:tree 是最靠谱的工具别靠猜。5.2 踩坑中文文件名导致上传的 Excel 读不出来现象本地 Windows 开发环境下上传“运动会报名表.xlsx”一点问题没有部署到 Linux 服务器之后同一个文件上传就报FileNotFoundException或者java.nio.file.InvalidPathException。原因Windows 本地文件系统用 GBK 编码Linux 用 UTF-8而 MultipartFile 拿到原始文件名后如果没有做统一编码处理直接拼到路径上就会乱码。更隐蔽的是有些前端上传组件会对文件名做 URL 编码后端拿到的名字和被编码过的名字对不上导致文件找不着。解决不管原始文件名叫什么保存到服务器时统一用 UUID 重命名把后缀保留下来。原始文件名存到数据库字段里用于回显文件在磁盘上的名字和用户看到的名称彻底分开这就从根上避开了中文编码问题。代码上就是String newFileName UUID.randomUUID().toString() . suffix;那一行的事但很多人就是在这行上省事才掉的坑。5.3 踩坑并发报名时人数超限MyBatis 先查再插的经典翻车现象报名截止前最后 10 分钟大量学生同时提交报名后台查出某个项目报名人数只有 7 人但实际插入成功了好几条最终人数超出 8 人上限。很多人以为是数据写重复了其实不是。原因count(*)查询和insert之间不是原子操作。两个请求同时查到 count7都判断没满然后各自插入最终就变成了 9 人。数据库自带唯一索引只能拦住“同一个人重复报名”拦不住“不同人同时超员”。解决有两个靠谱方案。一是给 enrollment 表加一个selected_count字段放在 sports_event 表里更新时用UPDATE sports_event SET selected_count selected_count 1 WHERE id ? AND selected_count max_participants受影响行数为 0 说明满员事务回滚。二是用SELECT ... FOR UPDATE锁住项目行让并发请求串行化。运动会系统并发量不算大条件更新方案更简单不需要锁事务的复杂处理。5.4 踩坑导出成绩册时没参赛的人被静默过滤现象导出运动会成绩册时发现人数少了一截尤其是报了名但没来参赛的运动员整个人的记录从成绩册里消失了。看起来像是数据被误删。原因查询成绩时很多人习惯直接查 score_record 表条件是is_valid 1。未参赛的人根本没有成绩记录自然查不出来。但从管理角度讲成绩册应该列出所有报名运动员未参赛的显示“缺赛”或“无成绩”而不是直接不出现。解决导出成绩册时以enrollment表为主表LEFT JOIN score_record这样即使没有成绩也能保留下这行记录。代码上注意 join 条件要带上轮次和项目 ID避免一个运动员多条成绩记录时把数据拆散成多行。5.5 踩坑上传文件超过 1MB 被拒Excel 模板还没读就返回 500现象上传一个 2MB 的 Excel 文件接口直接报MaxUploadSizeExceededException前端提示 500。模板里只要放一张 Logo 图片就必中招。原因Spring Boot 的 multipart 默认最大文件大小是 1MB超过就抛异常。项目里配了spring.servlet.multipart.enabled但忘记调大小。解决在 application.yml 里把限制放开。spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB这里 max-file-size 是单个文件大小max-request-size 是一次请求所有文件的总大小。如果前端一次只传一个文件两个都设置成 10MB 就行。要注意的是这两个参数只对 Servlet 标准的 MultipartFile 生效如果用了第三方组件可能需要另外配置落地前先用一个 5MB 的测试文件验证一下。6. 验证与进阶用法把系统从“能交差”做到“真有用”系统写完不等于能用尤其运动会系统这种“平时不用、用一次就必须不能掉链子”的场景。我的习惯是上线前按下面这张清单过一遍缺哪补哪。验证项检查方法通过标准并发报名用 JMeter 或 ab 模拟 50 个线程同时报名同一项目人数不超过上限无 500 错误编排冲突造一个运动员报两个时间重叠的项目系统给出冲突提示不生成秩序册成绩排名分别录入径赛和田赛数据检查排序结果径赛最小值为第一名田赛最大值为第一名裁判越权用普通裁判账号尝试修改非负责项目成绩接口返回无权限审计留痕修改一条成绩查询 score_audit_log新旧值、操作人、时间完整最后说一个我自己的实战教训。当初给某单位做类似系统时我自认为功能都测过了结果比赛当天上午报名并发一冲才发现数据库连接池默认才 10 个连接大量请求排队等连接页面转圈卡死。后来在配置里把连接池调成 20并加了 HikariCP 的 maximum-pool-size 参数才稳住。这件事之后我养成了习惯凡是这种“短时高并发、之后长时间闲置”的系统一定要在交付前做一次并发冒烟测试不要等现场翻车。进阶用法上如果想把系统做得更贴近真实赛事可以把历届运动会成绩单独建一张 history 表比赛结束后把本届成绩快照存进去下次办赛时可以拉出来做校记录对比。这不需要改现有表结构只要加一个定时任务或赛后归档按钮纯粹是数据利用率的问题。还有秩序册生成可以预留一个模板接口把编排好的项目、时间、分组信息灌进 Word 或 PDF 模板省去手动排版。这两件事都不难但能把系统从“管理工具”变成“赛事数据资产”。希望这篇 java田径运动会管理系统 的实战笔记能帮到你下次办赛别再熬夜手排秩序册。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进