拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

JavaWeb体育竞赛管理系统:从报名到成绩公示的完整复现指南

JavaWeb体育竞赛管理系统:从报名到成绩公示的完整复现指南

简介:基于JavaWeb的体育竞赛管理系统毕业设计资料包,面向计算机相关专业毕业生及JavaWeb初学者,用于解决体育赛事组织中的报名、成绩与用户管理信息化问题。系统完整覆盖运动员报名与成绩管理、管理员用户与参赛管理、裁判员成绩与申诉处理三大功能模块,基于JDK1.8、MySQL5.7与JSP/Servlet开发,可帮助读者掌握经典JavaWeb分层项目结构。资源共2662个文件、29.37MB,包含JSP动态页面、Java源码与Class编译文件,以及HTML/CSS/JS前端资源、Jar依赖库、SQL数据库脚本和IntelliJ IDEA配置信息,导入后便于直接运行和二次开发。已有903人学习下载,适合作为毕业设计参考、课程设计模板或竞赛管理项目起步代码。资料内还附带PDF/MD说明文档、静态图片素材与工程目录结构,可辅助理解设计思路、部署流程与数据库表关系。

1. 一套JavaWeb体育竞赛管理系统:从报名到成绩公示,值得复现的完整案例

体育竞赛报名还在用Excel表统计、成绩靠人工核算的项目组不在少数,尤其校运会、区级赛事这类规模不大但项目繁杂的比赛,一个报名审核翻来覆去能搞到赛前夜。这套基于JavaWeb的体育竞赛管理系统,解决的正是这条链路上最磨人的三个环节:运动员在线报名与资格校验、管理员对项目和用户的统一管控、裁判员录入成绩后自动排名并公示。它用的是JSP + Servlet + MySQL 5.7这套经典JavaWeb组合,跑在JDK1.8上,用IntelliJ IDEA就能直接部署。适合两类人:一是做毕设需要完整案例的学生,二是想搞清JSP页面、Servlet控制层和DaoImpl数据层如何串成一条真业务线的JavaWeb初学者。下面按我实际拆解和复现的顺序来写。

2. 技术骨架:JDK1.8 + JSP + MySQL 5.7 为什么还是毕设主力

2.1 技术选型:这套组合的取舍逻辑

先说JDK1.8。它已经是很成熟的版本,市面上绝大多数JavaWeb教材、培训机构笔记(包括你搜到的黑马JavaWeb那套资料里的数据库脚本和代码风格)都是基于这个版本写的。用JDK1.8意味着你复制粘贴别人的代码片段时,不用处理var关键字、新日期API带来的语法差异,编译报错概率低很多。MySQL选5.7而不是8.0,理由有两个:一是5.7是当前存量项目里占比最高的版本,老环境的备份导入导出不会出幺蛾子;二是8.0默认的认证插件是caching_sha2_password,老驱动连不上的问题一堆,5.7的mysql_native_password省心。

前端用JSP是这套系统的核心特征。JSP允许在HTML里嵌Java代码,服务端渲染出来的页面直接响应给浏览器,不需要额外的前端构建工具。以这个毕设的规模来说,JSP + Servlet就能把登录、报名、录分、排名公示全部做完,一个Tomcat就能跑起来。有人会问,为什么不直接上Spring Boot?这个问题我在答辩被评委问过。答案是:毕设和真实项目不同,评委想看到你理解请求是怎么到达控制器的、控制器又是怎么调DAO拿数据的,JSP + Servlet把这个过程完全摊开,没有任何黑匣子。Spring Boot里一个@Controller注解就带过去的东西,反而失去了展示价值。

2.2 从压缩包文件反推架构:Class文件名透露的信息

拿到压缩包后我没急着跑,先把里面那一串.class文件理了一遍。文件名虽然重复,但信息量不小:

  • StudentServlet.class:运动员模块的控制器入口,对应报名、查成绩等请求。
  • DirectorServlet.class:裁判员模块控制器,管理成绩录入和申诉处理。
  • DirectorDaoImpl.class:裁判模块的DAO实现类,说明数据访问层被拆成了接口和实现,这是标准三层架构的写法。

对照这个命名规律,系统应该是Controller(Servlet)→ Service → DaoImpl → MySQL 的四层结构。JSP页面在web目录下,负责展示,Servlet负责接收请求和转发,DaoImpl直接操作数据库。值得注意的是压缩包里混进了一个controller.ashx文件,这个是ASP.NET的东西,和JavaWeb没有任何关系,属于打包时混入的杂文件,直接删掉就好,别被它干扰判断。

2.3 IDEA部署配置:Artifacts和Tomcat的关联

IDEA里跑JavaWeb项目,最容易卡住的就是部署配置。核心是把项目打包成war exploded(展开的war包)丢给Tomcat。步骤是:打开File → Project Structure,在Artifacts里新增Web Application: Exploded,选择当前项目;然后在Run Configurations里新建Tomcat Server,把刚才的Artifact加到Deployment列表。

# Tomcat 部署配置的关键项 Deployment → 选择 sports_competition:war exploded Application context → /sports 访问路径 → http://localhost:8080/sports/login.jsp

Application context决定了你访问项目的根路径。这里填/sports是为了避免和Tomcat自带的/冲突,也方便后面拼接资源路径。如果你在JSP里用了绝对路径的CSS或JS引用,context填错了页面会全部裸奔。这个配置是IDEA运行JavaWeb项目的标准动作,几乎所有资料里都会强调,但我见过不少同学Reimport项目后Artifact丢失、或者改了代码点运行还是旧页面,基本都是这一步没配对。

3. 数据库设计:报名审核与自动排名的表结构怎么落

3.1 核心表结构:用户、运动员、赛事、报名、成绩五张表

系统涉及三种角色(管理员、裁判员、运动员),数据库至少要承载这几件事:登录认证、运动员基本资料、赛事定义、报名记录、比赛成绩。我按最常见的做法拆成五张表,下面是建库脚本的关键部分:

CREATE DATABASE IF NOT EXISTS sports_competition DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE sports_competition; CREATE TABLE sys_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(20) NOT NULL COMMENT 'admin/director/athlete', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE athlete ( athlete_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, real_name VARCHAR(50) NOT NULL, gender CHAR(2), phone VARCHAR(20), FOREIGN KEY (user_id) REFERENCES sys_user(user_id) ); CREATE TABLE event ( event_id INT PRIMARY KEY AUTO_INCREMENT, event_name VARCHAR(100) NOT NULL, event_date DATETIME, location VARCHAR(100), score_rule VARCHAR(10) DEFAULT 'desc' COMMENT 'asc:越小越好, desc:越大越好' ); CREATE TABLE registration ( reg_id INT PRIMARY KEY AUTO_INCREMENT, athlete_id INT NOT NULL, event_id INT NOT NULL, status VARCHAR(20) DEFAULT 'pending', audit_remark VARCHAR(255), FOREIGN KEY (athlete_id) REFERENCES athlete(athlete_id), FOREIGN KEY (event_id) REFERENCES event(event_id) ); CREATE TABLE score ( score_id INT PRIMARY KEY AUTO_INCREMENT, registration_id INT NOT NULL, score_value DECIMAL(6,2) NOT NULL, rank_no INT, FOREIGN KEY (registration_id) REFERENCES registration(reg_id) );

建表顺序有讲究:sys_user和event没有外键依赖可以先建,athlete依赖sys_user,registration依赖athlete和event,score依赖registration。按依赖顺序执行才不会报外键错误。表名我用了sys_user而不是user,因为user在某些MySQL版本里是保留字,直接用会报语法错误,这是个容易踩的小坑。

score_rule这个字段值得单独说一下。它不是必选项,但加了之后,裁判员模块录入成绩时就能根据项目类型动态决定排名方向:100米短跑用时越短越好(asc),铅球跳远则是成绩越大越好(desc)。如果没有这个字段,排名逻辑写死在Java代码里,换项目类型就得改代码重新编译,很麻烦。

3.2 成绩排名:MySQL 5.7没有窗口函数的处理方式

这块是我拆这套系统时觉得最有价值的地方。如果你用MySQL 8.0,一个ROW_NUMBER() OVER (ORDER BY score_value DESC)就把名次算完了。但5.7不支持窗口函数,只能用两种替代方案:一是ORDER BY后取结果集,在Java的Service层里循环赋值;二是用用户变量模拟。

-- 方案一:只排序列,名次计算交给Java层 SELECT s.score_id, a.real_name, e.event_name, s.score_value FROM score s JOIN registration r ON s.registration_id = r.reg_id JOIN athlete a ON r.athlete_id = a.athlete_id JOIN event e ON r.event_id = e.event_id WHERE e.event_id = ? AND e.score_rule = 'desc' ORDER BY s.score_value DESC; -- 方案二:MySQL 5.7 用户变量模拟行号 SET @rank_no := 0; SELECT @rank_no := @rank_no + 1 AS rank_no, real_name, score_value FROM (SELECT a.real_name, s.score_value FROM score s JOIN registration r ON s.registration_id = r.reg_id JOIN athlete a ON r.athlete_id = a.athlete_id WHERE r.event_id = ? ORDER BY s.score_value DESC) t;

两个方案的差别在于:方案一拿到的是无序排名列表,需要在Java代码里for循环维护一个计数器,遇到相同成绩还要决定是否并列;方案二直接用MySQL变量生成rank_no,SQL执行完名次就是现成的。我一般推荐方案二,SQL本身就能呈现排名逻辑,答辩时讲起来也更有说服力。注意变量的初始化语句SET @rank_no := 0不能省略,否则每次查询行号从上次的遗留值继续累加,这是血泪经验。

3.3 中文乱码:连接参数和建库字符集

体育竞赛系统里运动员姓名、赛事名称全是中文,乱码是复现率最高的翻车点。问题通常不在JDBC驱动,而在三处没统一:数据库字符集、连接URL参数、JSP页面编码。数据库这边建库时已经指定了utf8mb4,utf8mb4比utf8多支持emoji和生僻字,现在新项目基本都用它。连接URL这处比较容易漏:

String url = "jdbc:mysql://localhost:3306/sports_competition" + "?useUnicode=true&characterEncoding=UTF-8" + "&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai";

useUnicode=true&characterEncoding=UTF-8这两个参数是标配,少了任何一个都不能保证中文正常读写。serverTimezone在MySQL 5.7上不加有时也能跑,但加上可以避免驱动解析时区时的警告和日期偏差。JSP页面这边,每个页面的pageEncoding和contentType都要显式声明UTF-8,不要依赖IDE默认设置。

4. 三大角色链路实现:运动员报名、管理员审核、裁判录分

4.1 运动员模块:报名请求如何走完一整套逻辑

运动员进入系统后,能看到已开放报名的赛事列表,选择赛事后填写参赛信息提交。这个动作在Servlet里要完成三件事:校验登录状态、检查是否重复报名、插入报名记录。下面是RegisterServlet的核心逻辑,注释里标了每一步对应的数据库操作:

@WebServlet("/athlete/register") public class RegisterServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); HttpSession session = req.getSession(false); if (session == null || session.getAttribute("userId") == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } int userId = (Integer) session.getAttribute("userId"); int eventId = Integer.parseInt(req.getParameter("eventId")); // 第一步:查 athlete 表,拿到运动员ID // 第二步:查 registration 表,确认该运动员没有报名同一赛事 // 第三步:插入 registration,status 默认 pending // 第四步:重定向到报名列表页面 } }

这段代码演示的是最基础的Servlet用法:从session取当前用户、从请求参数取目标赛事、按顺序完成查重和插入。注意session里存的是sys_user的user_id,而registration关联的是athlete表的主键,中间需要一个SELECT athlete_id FROM athlete WHERE user_id = ?的转换。这个细节很典型,我见过不少初学的人直接把userId当athleteId用,导致外键约束报错。

运动员模块的"资格自动审核"在系统里并不是一个复杂的人工审批流程,而是程序层面的查重和合法性判断:同一赛事只能报名一次、赛事状态必须是开放中。真正的人工审核落在管理员模块,这样职责拆分符合输入描述里的分工。

4.2 管理员模块:审核报名和管理赛事资源的实现路径

管理员的界面和普通运动员不在同一套导航里,常见做法是登录时根据role字段跳转不同首页。管理员的主要操作是两件事:一是对registration表里pending状态的记录逐条审核,通过或驳回;二是维护event表的赛事信息。

@WebServlet("/admin/audit") public class AuditServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int regId = Integer.parseInt(req.getParameter("regId")); String action = req.getParameter("action"); // approve 或 reject String newStatus = "approve".equals(action) ? "approved" : "rejected"; String remark = req.getParameter("auditRemark"); // 更新 registration 表:set status=?, audit_remark=? where reg_id=? // 如果是 approve,还需要校验该赛事当前报名人数是否已达上限 // 完成后重定向回待审核列表 } }

审核接口虽然代码量不大,但在参数设计上有个容易被忽略的点:executor_id(审核人)这个字段。如果表结构里没有记录"谁在什么时间审核的这条记录",后续出问题没法追溯。我一般会在registration表里再加audit_user_id和audit_time两个字段,算是管理员模块的小升级,毕设答辩时提这个理由很容易加分。

管理员维护赛事这块,和前端的联动主要体现在一个地方:新建赛事时,把score_rule字段一并保存。这个字段就是第3章建表脚本里那个asc/desc,裁判员录分时依赖它来决定排名方向。

4.3 裁判员模块:录分后如何触发自动排名

裁判员模块是整个系统里业务最重的一块。裁判员进入系统后,先看到自己负责的赛事列表(这个对应关系一般在event表里加一个director_id字段),点进某场赛事后,系统列出所有报名状态为approved的运动员,裁判员逐个录入成绩。

public void updateScoreAndRank(int regId, double scoreValue, String rule) { // 第一步:更新 score 表,写入成绩 // UPDATE score SET score_value = ? WHERE registration_id = ? // 第二步:查出该赛事所有成绩,按 rule 排序 // rule=desc 走 ORDER BY score_value DESC // rule=asc 走 ORDER BY score_value ASC // 第三步:遍历结果集,从1开始给 rank_no 赋值 // 如果有并列成绩,常见处理是保留空位,比如 1, 1, 3 // 第四步:批量 UPDATE score SET rank_no = ? WHERE score_id = ? }

这里的排名计算虽然在数据量小的时候用方案一的Java循环就够了,但我建议用第3章方案二的变量法在SQL里直接生成排名。裁判员录分这个动作对实时性要求很高,比赛现场裁判录完一个,成绩公示页立刻要有反应。如果排名在Java层循环算,录分接口的事务边界要包住所有UPDATE,一旦中间有一条失败,成绩写入了但名次没更新,页面就会出现有成绩无名次的尴尬状态。我处理这个问题的习惯是:录分接口和排名计算做成一个事务,要么都成功要么都回滚。

成绩公示页的JSP就简单了,从score表按rank_no排序查询,渲染成一个带序号的表格。运动员端看到的成绩和自己的排名其实读的是同一张score表,不存在两套数据不一致的问题,这是这套系统设计上最干净的地方。

5. 避坑指南:IDEA部署JavaWeb项目的五个翻车点

5.1 高频踩坑记录:现象、原因、解决

翻车点一:Tomcat启动后访问页面报404。现象:IDEA里Tomcat控制台显示启动成功,但浏览器打开localhost:8080出现404页面。 原因:最常见的是Artifact没有部署到Tomcat,或者Application context配置的路径和实际访问路径不一致。很多新手只配置了项目结构,忘了在Run Configuration的Deployment里添加war包。 解决:打开Run → Edit Configurations,切到Deployment页签,点+号添加当前项目的war exploded,然后检查Application context。改完重启Tomcat,不要点Rerun,那样会保留旧部署。

翻车点二:改完代码重新运行,页面还是老样子。现象:JSP改了表格列名,刷新浏览器死活不变;Servlet加了打印日志,控制台看不到。 原因:IDEA默认构建方式是Build on Save,但如果你改的是web.xml、JSP或配置文件,Tomcat不一定会触发热部署。更隐蔽的是Tomcat的conf目录里部署描述符指向的是out目录,而out目录没有被清理。 解决:养成一个固定动作:Build → Rebuild Project,然后重启Tomcat。从那以后我只要改了非Java文件,就强制走一遍Rebuild再加重启,不再把时间浪费在玄学热部署上。

翻车点三:数据库连接报Public Key Retrieval is not allowed。现象:JDBC连接MySQL时抛出这个异常,程序完全起不来。 原因:MySQL连接器版本较新,而MySQL 5.7的认证方式默认使用mysql_native_password,两者握手时驱动需要获取服务器公钥,但URL里没放开限制。这个问题在MySQL 8.0上更常见,5.7偶尔也会触发,取决于驱动版本。 解决:在JDBC URL末尾追加allowPublicKeyRetrieval=true&useSSL=false。这是5.7环境下最省心的两个参数,保证连接过程不做SSL加密校验,也不限制公钥获取。

翻车点四:导入SQL脚本时外键关联失败。现象:source执行建表脚本,创建registration或score表时报外键约束错误。 原因:建表顺序不对。sys_user还没建完就开始建athlete,或者athlete表引擎不是InnoDB。顺带说一句,如果MySQL 5.7的默认引擎被改成了MyISAM,外键约束是虚设的,约束不生效但SQL也不报错,这种问题更可怕。 解决:严格按依赖顺序执行:sys_user和event先建,然后athlete,再registration,最后score。建库脚本里显式加上ENGINE=InnoDB DEFAULT CHARSET=utf8mb4,不依赖服务器默认配置。

翻车点五:Tomcat端口被占用无法启动。现象:启动Tomcat弹窗提示Port 8080 is already in use,或者8005端口被占用导致控制台闪现后退出。 原因:之前的Tomcat进程没有完全杀掉,或者电脑上已经跑了别的服务占用端口。 解决:命令行执行netstat -ano | findstr 8080找到PID,然后taskkill /F /PID 对应PID强杀。如果频繁遇到多开情况,可以在Run Configuration的Tomcat设置里把端口改成8081、8006,改端口比杀进程快得多。

5.2 源码丢了别慌:从class文件反推和兜底手段

如果你拿到的压缩包和这次一样,只有.class没有.java源码,IDEA是没法直接运行的,因为class是编译后的字节码,不是IDEA能编辑的源文件。但class文件并非完全不可读,两个实用手段:一是用反编译工具(JD-GUI、Luyten都能打开class文件查看逻辑),对着逻辑重新手写源码,虽然字段名和方法名会变成无意义的a、b、c,但结构是看得懂的;二是检查项目里是否还有同名不同后缀的资源文件,比如jsp和xml一般不会编译掉,可以从这些文件反推页面流。

我的习惯是拿到任何JavaWeb项目包,先打开Project Structure看模块结构,确认src目录下有没有源码。只有class的话直接反编译成本太高,建议优先找完整版,或者把class当作接口参考,页面和SQL自己重写。这不丢人,毕设项目本来就是以学习和复现为主,不是生产系统。

6. 验证清单与进阶:把一次部署变成可复用的习惯

6.1 功能验证用例

部署完成后,不要直接关机走人,按这组用例逐条过一遍,能覆盖三大角色的主要链路。我复现这套系统时就用这张表做回归验证,每改一次代码就过一遍:

编号操作步骤预期结果
1用运动员账号登录,进入赛事列表能看到状态为open的赛事,已截止的不可报名
2对同一赛事重复提交报名第二次提交被拦截,提示已报名
3管理员进入审核列表,通过一条报名该运动员状态变为approved,出现在裁判员名单中
4裁判员录入该运动员成绩,查看排名排名自动计算,成绩公示页顺序正确
5运动员端查看成绩能看到自己的分数和名次,和裁判端一致
6用裁判员账号访问管理员审核接口被重定向回登录页,角色权限生效

第6条容易忽略,因为很多毕设demo只限制了页面入口,没限制Servlet接口级权限。如果你要做得更严谨,可以在每个Servlet的doGet/doPost开头加一个@WebFilter拦截,校验session里的role是否匹配。

6.2 一个加分技巧:成绩导出为CSV

答辩现场老师大概率会问"成绩数据能不能导出"。不想引入POI依赖的话,一个最简单的方案是把响应ContentType改成CSV格式:

resp.setContentType("text/csv;charset=UTF-8"); resp.setHeader("Content-Disposition", "attachment;filename=scores.csv"); PrintWriter out = resp.getWriter(); out.println("排名,姓名,赛事,成绩"); out.println("1,张三,100米,12.30"); out.flush();

浏览器会直接下载CSV文件,Excel能正常打开。这个技巧不增加任何外部依赖,代码量只有几行,但对"系统可用性"的展现效果很好,数据留存和价值一下就体现出来了。

这套系统我前前后后部署过三台不同的电脑,每次环境细节都有差异:有的MySQL装的是5.7.42,有的是5.7.18;有的IDEA是社区版,新建Artifact的入口位置都不同。从那以后我每次复现JavaWeb项目都强制走一遍固定流程:先建库导脚本,再核对JDBC连接四要素,然后配置Artifact,最后按验证用例跑完整条报名录分链路,全部通过才收工。这套流程救过我很多次,希望帮到你。

本文还有配套的精品资源,点击获取

返回列表