Spring Boot羽毛球场地管理系统:从需求拆分到并发防重与答辩全流程 📅 发布时间:2026/9/7 17:26:54 👁 浏览次数: 选课系统里挂出“基于 Spring Boot 的羽毛球场地管理系统”这个题目时大多数人的第一反应是这不就是增删改查吗用户注册、场地列表、预约下单、管理员审核听起来没有任何门槛。但等你真正动手第一版代码写完才发现光是“同一场地同一时段不能重复预约”这一条就能牵扯出状态设计、事务边界、并发处理、时间时区一堆问题。这篇文章我按自己复盘项目、指导新人时反复验证过的顺序从拿到题目到答辩结束把一套羽毛球场地管理系统的完整链路拆开讲清楚需求、设计、数据库、核心代码、部署、论文、答辩一条线走完。1. 拿到题目以后先别急着写代码先想清楚这套系统到底考什么我见过很多拿到类似题目的同学第一版代码通常长得很像一张用户表、一张场地表、一张订单表然后就是增删改查。这种完成度交上去平时分可能够但答辩时老师只要追一句“两个用户同时提交同一时段的预约怎么办”场面就会很尴尬。原因不是代码写得少而是设计阶段漏掉了一条链路。1.1 从“羽毛球场地管理系统”里拆出三个真实需求从题目本身看它至少包含三个层次的需求。第一个层次是信息管理。场地、用户、订单这些基础数据的增删改查这是所有管理系统的地基也是大多数人理解中的“毕设工作量”。第二个层次是业务规则。谁能预约、什么时段能约、同一时段能不能重复、预约之后状态怎么变化、用户取消后管理员怎么看这些才是真正的业务逻辑。场地预约类系统的核心不在 CRUD而在“约得上、不冲突、状态清”。第三个层次是交付保障。项目能不能在另一台电脑上跑起来数据库脚本是否完整论文里的流程图和实际代码能不能对应答辩时能不能把关键决策讲清楚。这个层次最容易被忽略但它往往决定了答辩表现。1.2 功能做到什么程度才算“完整”功能不是越多越好。一个常见的错误是一开始就想着做支付、优惠券、积分、消息推送结果主流程还没跑通先把项目复杂度抬上去了。我更建议按这个标准来判断核心预约链路完整能走通“选场地→选时段→提交预约→状态变化→取消/完成”管理员能兜底能维护场地、查看订单、取消异常预约演示数据合理打开系统不会让人一眼看出是空壳文档和代码对得上论文里的图和实际系统能互相解释。在这个基础上优惠券、会员卡、消息提醒都只是锦上添花不建议一开始就做。等你把主流程和部署全部跑完还有时间和精力再考虑加扩展功能这才是一个可持续的顺序。2. 系统设计先把流程图画出来再谈模块和表很多同学的习惯是一拿到题目就建表这和盖房子不打地基是一个道理。表结构其实是流程设计的结果不是凭空想出来的。先画流程再拆模块最后再建表、写接口整个过程会顺很多。2.1 角色权限与一条核心预约链路这套系统里至少要区分两类角色普通用户和管理员。普通用户负责约场地、取消预约、看自己的订单管理员负责维护场地和时段、查看所有订单、取消异常预约。权限实现上可以用 Spring Security也可以用一个简单的拦截器判断登录状态和角色。毕设场景下如果项目规模不大拦截器其实更直观也更容易在论文里讲清楚如果老师点名要求安全框架再引入 Spring Security 也不迟。核心预约流程可以这样描述用户登录进入场地列表选择一个场地查看指定日期下有哪些可用时段选择一个时段提交预约系统校验该场地、该日期、该时段是否已被占用校验通过后创建订单订单进入“待支付”或“已确认”状态用户按约定时间到场使用管理员可查看订单并在异常情况下取消订单。这条链路画出来之后你的用户用例图、活动图、时序图都有了基础素材。代码只是这张流程图的实现论文也只是这张流程图的文字化。2.2 预约冲突是这类系统的第一个技术门槛预约冲突是这套系统最容易翻车的地方。规范表达是同一场地、同一日期、同一时段只能存在一个有效预约。这里的关键词是“有效”。订单状态不同是否占用场地时段的结果完全不同。比如“待支付”和“已确认”状态算占用“已完成”“已取消”“已过期”不算占用。有些同学查重时直接数“这个场地有没有订单”结果用户取消之后那个时段再也没法约了因为数据库里还留着一条历史订单。这个问题在答辩时非常容易被问到也算是一个经典细节。先设计好订单状态再去写查重逻辑顺序不能反。注意不要只把预约日期当成字符串处理。数据库、JDBC、JVM 三层的时区如果不一致最容易出现“明明选的晚上 8 点库里却是中午 12 点”这种基础错误。3. 数据库设计场地预约系统的命门在约束不在表多很多毕设的数据库设计问题不是表不够而是约束不清晰。这个场景里业务核心是“防冲突、防脏数据”所以数据库设计的重点要放在时间、状态和唯一性上。3.1 最少需要这几张表每张表负责什么一套最小可用的羽毛球场地管理系统至少需要四张核心表表名职责核心字段t_user用户信息id, username, password, phone, rolet_court场地信息id, name, location, statust_time_slot可预约时段id, start_time, end_timet_booking预约订单id, booking_no, user_id, court_id, slot_id, book_date, status, create_time建表时建议统一使用 utf8mb4 字符集避免中文和特殊字符出问题。下面是一个简化示例CREATE TABLE t_user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(64) NOT NULL, password varchar(128) NOT NULL, phone varchar(20) DEFAULT NULL, role tinyint NOT NULL DEFAULT 1, create_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_court ( id bigint NOT NULL AUTO_INCREMENT, name varchar(64) NOT NULL, location varchar(128) DEFAULT NULL, status tinyint NOT NULL DEFAULT 1, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_time_slot ( id bigint NOT NULL AUTO_INCREMENT, start_time time NOT NULL, end_time time NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_booking ( id bigint NOT NULL AUTO_INCREMENT, booking_no varchar(32) NOT NULL, user_id bigint NOT NULL, court_id bigint NOT NULL, slot_id bigint NOT NULL, book_date date NOT NULL, status tinyint NOT NULL, create_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_court_date (court_id, book_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单表里的booking_no是业务订单号建议生成唯一编号不要直接用自增 id 当订单号展示给用户这样后续沟通和排查都更清晰。3.2 时段建模和订单状态为什么容易差 8 小时时段建模有两种常见方式。第一种是把时段做成固定表比如t_time_slot里存 08:00-09:00、09:00-10:00 这样的固定切片订单表只关联slot_id。这种方式很符合羽毛球馆“按小时预订”的习惯前端下拉选择也简单查询冲突时条件很直观。第二种是订单里直接存start_time和end_time允许用户自定义任意时长。这种方式更灵活但查冲突时要处理交叉判断逻辑更复杂。对毕业设计来说我更推荐第一种固定时段表。原因很简单业务更贴切、查询更简单、论文里也更容易解释清楚。订单状态是所有业务规则的抓手。建议至少包含这几个状态状态含义是否占用时段待支付已提交但未支付是已确认支付成功或管理员确认是已完成用户到场使用完毕否已取消用户或管理员取消否已过期超时未使用否这里有一个容易被忽视的细节用户取消和管理员取消可以共用一个“已取消”状态但在订单表里建议额外加一个cancel_type或cancel_reason避免以后查日志时说不清是谁、在什么环节取消了订单。时区问题也要在一开始就处理好。在 Spring Boot 的配置里常见的写法是spring: datasource: url: jdbc:mysql://localhost:3306/badminton_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jackson: time-zone: GMT8如果三层时区不一致时间就会偏移。排查时优先检查这三处MySQL 全局时区、JDBC 连接串里的serverTimezone、Spring Boot 的 Jackson 时区配置。3.3 防重复预约的两种兜底方案先查后插是最直观的防重方式但它在并发情况下有风险。如果两个用户同时查到“该时段空闲”然后同时插入就可能产生两条有效订单。比较稳妥的做法有两条路。第一条是悲观锁在查重时把对应的记录锁住SELECT id FROM t_booking WHERE court_id ? AND book_date ? AND slot_id ? AND status IN (0, 1) FOR UPDATE;这样当一个事务在读这条时间段记录时另一个事务会被阻塞住必须等前一个提交或回滚后才能继续。锁的粒度只控制在这一段业务涉及的订单记录上不会锁整张表。第二条是数据库唯一索引兜底。但因为“已取消”状态不应该阻碍重新预约所以不能简单地对(court_id, book_date, slot_id)建唯一索引。有一种思路是用 MySQL 生成列只在有效状态时把字段置为 1其余置为 NULL因为 MySQL 唯一索引允许多个 NULL所以可以通过这种方式实现条件唯一约束。不过这里有个判断毕业设计不建议为了炫技硬上生成列语法。代码层判断加事务在演示场景下已经能解释清楚如果你想把并发这块作为论文里的亮点再去了解唯一索引和悲观锁的区别反而更扎实。4. Spring Boot 核心实现把预约流程写成稳定闭环技术栈的选型原则是选熟的不选新的。一套自己用过、能排查问题的组合远比一套听起来高级但不熟悉的组合更稳。4.1 技术栈选择怎么选比选什么更重要比较推荐的保守组合是Spring Boot 2.7.x JDK 8 MyBatis-Plus MySQL。原因是这个组合的资料最全遇到问题最容易搜到答案而且大部分参考项目都基于这个体系。如果选 Spring Boot 3.x意味着 JDK 至少要用 17部分依赖命名空间从javax改成了jakarta前端构建工具链也可能跟着变学习成本会高一些。不是说新版不好而是毕业设计的时间窗口有限稳定压倒一切。代码结构建议分成几层Controller接收参数、校验基础格式、封装响应Service写业务规则比如查重、状态流转、订单号生成Mapper数据访问不拼大量业务逻辑Entity / DTO / VO分离数据库实体、接收参数、返回视图避免前端字段直接暴露到数据库层。这种分层一开始看起来繁琐但写论文、改需求、排 bug 的时候会省很多时间。4.2 核心预约接口的一段常见写法预约下单是这个系统的核心接口。下面是一段简化示例结构实际项目里需要把类名、枚举、返回结果替换成自己的实现Service public class BookingService { private final CourtMapper courtMapper; private final TimeSlotMapper timeSlotMapper; private final BookingMapper bookingMapper; public BookingResult createBooking(BookingCreateRequest request) { Court court courtMapper.selectById(request.getCourtId()); if (court null || court.getStatus() ! CourtStatus.OPEN) { throw new BusinessException(场地不存在或未开放); } TimeSlot slot timeSlotMapper.selectById(request.getSlotId()); if (slot null) { throw new BusinessException(预约时段不存在); } Long exist bookingMapper.countValidBooking( request.getCourtId(), request.getBookDate(), request.getSlotId()); if (exist ! null exist 0) { throw new BusinessException(该场地该时段已被预约); } Booking booking new Booking(); booking.setBookingNo(generateBookingNo()); booking.setUserId(request.getUserId()); booking.setCourtId(request.getCourtId()); booking.setSlotId(request.getSlotId()); booking.setBookDate(request.getBookDate()); booking.setStatus(BookingStatus.WAIT_PAY); bookingMapper.insert(booking); return BookingResult.of(booking); } }这段代码的核心要点是顺序先判断场地是否存在并开放再判断时段是否存在再查重最后插入订单。顺序不能反越早过滤无效输入越少制造脏数据。4.3 事务、状态与异常三个翻车点第一个翻车点是事务失效。Transactional要加在 Service 方法上且要保证调用链经过 Spring 代理。如果在一个类内部用this调用另一个方法事务注解可能不生效。第二个翻车点是事务范围过大。不要把整个 Controller 方法都加上事务尤其是涉及日志、第三方通知之类的操作。事务的范围越小锁持有的时间越短系统越不容易出现性能瓶颈。第三个翻车点是异常被吞掉。如果你的代码里把异常 catch 住之后没有重新抛出事务默认不会回滚。这块在写代码时要特别注意不能为了“让前端不报错”而把异常吃掉。订单状态流转建议提前画出来待支付 → 已确认 → 已完成待支付/已确认 → 已取消已确认 → 已完成。状态不能乱跳比如已取消的订单不应该能变成已完成。这个规则可以在 Service 层写死也可以引入更完整的状态机框架但当前场景用简单的 if 判断或枚举校验就足够了。事务只加在“查重插入”这一组业务操作上。如果在 Controller 入口方法无脑加 Transactional日志、通知、第三方调用都会被卷进事务里锁时间会明显拉长。5. 安装部署别让“能跑”变成薛定谔的能跑“我本地能跑”和“项目已经完成”是两件事。真正能交付的项目应该在换一台电脑、换一个数据库实例后按照文档一步步还能跑起来。这也是很多参考源码最容易露馅的地方。5.1 拿到一套参考项目先检查四样东西不管你是从网上找参考还是接手别人的代码建议先检查四样东西README 是否存在里面的步骤是否能照着走通SQL 脚本是否完整是否包含建库、建表、初始数据配置文件是否写死了本机路径、账号和端口前端构建产物或源码是否齐全后端接口和前端页面是否能对应。这四样检查清楚项目才具备“能部署”的基础。否则代码再漂亮换台电脑跑不起来在整个流程里价值都要打折扣。5.2 配置外部化与打包运行Spring Boot 的配置尽量拆开。比如本地用application-dev.yml演示或部署时用application-prod.yml通过启动参数切换mvn clean package -DskipTests java -jar target/badminton-system-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod数据库密码、文件路径这类敏感或环境相关的信息不要硬编码在 Java 代码里。用配置文件和环境变量覆盖既安全又方便切换环境。5.3 一套部署排查顺序部署阶段最容易出的问题往往不是代码逻辑而是环境和配置。建议按下面的顺序排查现象优先检查补充思路启动即失败端口占用、数据库连接、JDK 版本、依赖是否下载完整看启动日志前几行不要只看最后报错接口 404Controller 扫描路径、路由、打包是否包含页面确认后端是否真的启动成功登录失败数据库初始账号、密码加密方式、角色字段检查前端请求参数和后端日志预约失败日期格式、时区、订单状态条件、事务看数据库里订单实际状态时间差 8 小时JDBC 连接串、JVM 时区、MySQL 全局时区统一成 Asia/Shanghai前端白屏后端是否启动、跨域配置、前端构建产物是否更新浏览器 F12 看 Network 请求这套顺序的本质是先看现象再看输入再看环境再看参数最后才怀疑工具边界和代码逻辑。不要一上来就改代码很多时候问题出在配置或环境。5.4 演示环境准备的几个细节答辩演示不是现场写代码而是把“准备过的状态”展示出来。提前准备这几样东西能避免大半尴尬准备两个演示账号普通用户和管理员账号密码写进演示说明准备一份看起来真实的数据比如 4 个场地、未来两天可约时段、一两条已完成订单关掉其他占用端口的程序确保启动一次成功如果现场可能断网确保项目离线能跑前端不要依赖外网 CDN准备一个“重置演示数据”的 SQL防止演示时把数据搞乱。部署这件事本质上是在验证你对整套系统的掌控程度。能顺利跑起来的项目答辩时的底气都不一样。6. 论文、答辩与后续毕设结束工程能力才开始代码写完、部署通过只完成了项目的一部分。论文和答辩是把你的设计过程用语言和文字再表达一遍。很多同学在这两步翻车不是因为项目做得不好而是因为材料对不上。6.1 论文不是代码的说明书而是设计决策的记录论文最忌讳写成“点击这个按钮系统跳转到那个页面”的操作手册。更值得写的是为什么选 Spring Boot为什么订单要设计这些状态为什么时段要用独立表为什么用这种方式防重复预约。论文素材建议在写代码之前就开始积累核心流程图和 ER 图画出来之后再写技术选型和需求分析最后补系统实现和测试。这样论文里的每张图、每张表都能和实际代码对上而不是代码写完之后再反推文档。开题报告、任务书和正文之间也要互相呼应。开题里写了什么目标任务书里的阶段划分要能承接正文里要有对应章节体现。老师一眼就能看出材料是临时拼的还是完整走下来的。6.2 答辩前先想清楚的四个问题答辩时间通常不长但高频问题非常集中。建议提前准备这四个核心流程是什么用户和管理员的交互链路分别怎么走同一时段重复预约怎么处理你的事务和锁是怎么加的为什么选 Spring Boot而不是 SSM 或者更早的框架订单状态有哪些为什么这样设计用户取消后时段能不能重新被约这四个问题能答清楚说明这套系统是真的理解了。答不清楚代码即使全对也容易被认为不是自己做的。演示时的顺序也很重要。比较稳妥的演示路线是先用普通用户预约一个场地再到管理员端查看订单然后取消订单再回到用户端重新预约同一个时段证明“取消后时段可以重新被约”。这条演示路线本身就是对系统核心规则的展示。6.3 从毕设到工程化三个不要着急但值得做的延伸毕设结束之后如果想继续往工程方向走有三个方向可以参考一是加缓存。用 Redis 缓存场地时段库存进一步降低数据库压力。这个改动不大但能很好地展示你对并发和性能的理解。二是加更复杂的流程。如果后续要支持请假审批、退款审批等多角色流程可以引入 Flowable 这类工作流引擎。但要注意当前预约场景的状态机已经够用硬塞工作流引擎只会增加复杂度。三是把部署链路完善起来。前后端分离部署到云服务器、用 Docker 打包、加一套简单的 CI/CD这些能力在真实项目里非常常用。如果只是场地预约业务状态机已经够用不建议为了简历好看硬塞 Flowable。流程一旦复杂到多条分支、多角色审批时工作流引擎才真正值钱。回到最开始的问题Spring Boot 羽毛球场地管理系统到底难不难我的看法是纯增删改查不难难的是把一个业务问题拆成数据模型、接口逻辑、部署流程、文档表达并把它完整闭环地交付出去。如果你能顺着这条链路走一遍收获的绝不是一个交差的源码而是一套以后遇到业务系统都不慌的底层思路。如果时间有限我的建议是严格按这个顺序落地需求拆分 → 核心流程图 → 数据库 ER 图 → 核心预约接口 → 管理端补全 → 本地打包部署 → 整理 README → 写论文素材 → 准备演示数据和答辩问题。这个顺序能最大程度避免“代码写完了但文档对不上”的尴尬。祝你顺利。