
简介本资源是一套完整的体育馆使用预约平台毕业设计项目面向计算机专业本科生及Java全栈初学者旨在解决传统场馆预约流程不规范、人工管理效率低、数据容错性差等实际问题。系统基于Spring Boot后端框架与Vue前端框架构建采用MySQL持久化数据涵盖场地管理、用户预约、论坛互动、公告发布、订单处理等核心模块具备工程可部署性与业务完整性。压缩包共750个文件含101个Java后端逻辑文件、58个Vue组件页面、156个JS交互脚本、49个CSS样式资源及32个HTML模板辅以SQL建表脚本、YML配置、BAT一键部署脚本如install.bat、run.bat等实用工具整体大小为22.42MB。目前已有76人学习下载读者可直接导入IDE运行调试快速掌握前后端分离开发流程、权限控制实现、预约状态机设计及MySQL事务处理等关键技术点。1. 项目缘起为什么我们需要一个体育馆预约平台在高校或者大型社区里体育馆资源紧张是个老生常谈的问题。我经历过无数次这样的场景下午三点想约个羽毛球场打开管理方的Excel表格或者微信群发现晚上七点前的场地全被标红了或者更糟到了体育馆才发现自己以为预约成功的场次因为管理员忘了更新表格已经被另一拨人占用了。这种混乱、低效、全靠人工和“人缘”的预约方式不仅浪费了大家的时间也造成了公共资源的闲置与冲突。这就是我动手开发这个“体育馆使用预约平台”最直接的动力。我想用技术手段把“谁先到谁得”或者“谁认识管理员谁优先”这种不透明的规则变成一个公平、公开、可追溯的线上流程。这个平台的核心目标很简单让用户能像在电商平台下单一样清晰、便捷地查看场地空闲时段并完成预约让管理员能从一个统一的、数据化的后台管理所有资源、订单和用户彻底告别Excel和微信群接龙的原始时代。整个项目采用了当前企业级Web应用开发中最主流、也最经得起考验的技术栈组合Spring Boot作为后端API服务的基石Vue.js作为构建用户界面的前端框架MySQL作为持久化数据的数据库。这套组合拳的优势在于前后端分离的架构让开发和部署可以并行且独立Spring Boot的“约定大于配置”理念极大地简化了后端服务的搭建而Vue的响应式和组件化特性则让前端交互体验变得流畅且易于维护。接下来我会带你从零开始拆解这个平台的每一个核心模块分享我在设计、开发和部署过程中趟过的坑和积累的经验。2. 后端基石Spring Boot服务的设计与核心实现后端是整个平台的大脑和规则执行者它负责处理所有业务逻辑、数据校验以及与数据库的交互。选择Spring Boot是因为它提供了一套完整的、开箱即用的解决方案能让我们快速搭建一个稳健、可扩展的RESTful API服务。2.1 项目结构与依赖配置我采用的是经典的MVC分层架构但结合Spring Boot的特性做了一些调整。项目根目录下主要的包结构如下src/main/java/com/gymbooking/ ├── GymBookingApplication.java // 主启动类 ├── config/ // 配置类如跨域、Swagger、安全等 ├── controller/ // 控制器层接收HTTP请求 ├── service/ // 业务逻辑层接口 ├── service/impl/ // 业务逻辑层实现 ├── dao/ 或 mapper/ // 数据访问层MyBatis的Mapper接口 ├── entity/ 或 model/ // 实体类与数据库表对应 ├── dto/ // 数据传输对象用于前后端交互 └── utils/ // 工具类如日期处理、JWT工具等在pom.xml中除了Spring Boot Web、MySQL Driver、MyBatis等基础依赖有几个关键依赖值得特别说明Spring Boot Starter Validation用于对Controller层接收的参数进行注解式校验比如NotNull、Size、Email等。这能确保进入业务逻辑的数据是基本合规的将无效请求拦截在最外层。MyBatis Plus这是一个强大的MyBatis增强工具。我强烈推荐使用它而不是原生MyBatis。它提供了通用的BaseMapper让基础的CRUD操作无需编写XML同时其强大的QueryWrapper可以让你用Java Lambda表达式优雅地构建复杂查询条件极大提升了开发效率。Hutool一个国人开发的Java工具库集成了文件处理、加密解密、日期转换等众多实用功能。比如用它来生成订单号、处理日期区间重叠判断代码会简洁很多。Spring Security JWT对于预约平台用户认证和授权是必须的。我采用Spring Security整合JWTJSON Web Token的方案。用户登录成功后后端生成一个加密的Token返回给前端前端在后续请求的Header中携带此Token。后端通过一个自定义的过滤器Filter来校验Token的有效性。这样实现了无状态的认证服务端压力小也适合前后端分离的架构。注意在引入JWT时务必妥善保管生成Token时使用的密钥Secret并且为Token设置合理的过期时间如2小时。对于刷新Token的逻辑我通常采用“双Token”机制一个短期的Access Token用于接口访问一个长期的Refresh Token用于获取新的Access Token这需要在业务逻辑层仔细设计。2.2 核心业务逻辑与数据库设计数据库设计是项目的根基设计得好后续开发事半功倍。核心表大概有这几张用户表 (user)存储用户基本信息。字段包括id、用户名、密码加密存储、手机号、邮箱、角色普通用户、管理员、注册时间等。场馆/场地表 (venue)这是资源表。可以设计两级结构比如先有“体育馆”作为场馆其下再有“羽毛球场地1”、“篮球场A”等具体场地。字段包括id、名称、所属场馆ID、类型羽毛球、篮球等、状态可用、维修中、简介、图片等。预约订单表 (booking_order)这是核心的业务表。字段包括id、订单号唯一可用时间戳随机数生成、用户ID、场地ID、预约开始时间、预约结束时间、订单状态待支付、已预约、进行中、已完成、已取消、创建时间、总金额等。这里最复杂的业务逻辑集中在“预约”这个动作上它不是一个简单的插入操作必须解决“资源冲突”问题。即在同一时间段内同一个场地只能有一个有效的预约订单。我的解决方案是在Service层实现一个原子性的校验和创建过程。伪代码如下Service public class BookingServiceImpl implements BookingService { Autowired private BookingOrderMapper orderMapper; Transactional(rollbackFor Exception.class) // 开启事务 public BookingResult createOrder(BookingRequest request) { // 1. 参数基础校验时间是否合法、场地是否存在等 validateRequest(request); // 2. 核心冲突检查查询该场地在请求时间段内是否有其他“已预约”或“进行中”的订单 LambdaQueryWrapperBookingOrder wrapper new LambdaQueryWrapper(); wrapper.eq(BookingOrder::getVenueId, request.getVenueId()) .eq(BookingOrder::getStatus, BookingStatus.BOOKED) // 已预约状态 .or().eq(BookingOrder::getStatus, BookingStatus.IN_PROGRESS) // 或进行中状态 .and(w - w .lt(BookingOrder::getStartTime, request.getEndTime()) // 新订单结束时间 旧订单开始时间 .gt(BookingOrder::getEndTime, request.getStartTime()) // 新订单开始时间 旧订单结束时间 ); Long conflictCount orderMapper.selectCount(wrapper); if (conflictCount 0) { throw new BusinessException(该时间段内场地已被预约请选择其他时间); } // 3. 生成订单实体设置状态为“待支付”或“已预约”取决于是否需在线支付 BookingOrder newOrder buildOrder(request); // 4. 插入订单 orderMapper.insert(newOrder); // 5. 可能触发的其他逻辑发送短信通知、更新场地缓存等 sendNotification(newOrder); return BookingResult.success(newOrder); } }踩坑心得时间冲突的SQL条件判断是新手极易出错的地方。上面代码中的lt(小于) 和gt(大于) 组合判断的是“时间区间有交集”这是最标准的做法。千万不要写成BETWEEN start AND end那会漏掉很多交叉情况。另外务必在数据库层对venue_id、start_time、end_time和status字段建立复合索引能极大提升冲突检查查询的性能尤其是在高并发预约场景下。2.3 接口安全与性能考量除了基本的JWT认证接口安全还需要注意以下几点防重复提交用户可能连续点击“提交预约”按钮。前端可以做按钮禁用但后端更可靠。我采用“幂等性”设计为每个预约请求生成一个唯一的“请求ID”如UUID存入Redis并设置较短过期时间。在处理请求前先检查该ID是否存在存在则视为重复请求直接返回之前的结果。数据权限校验在查询“我的订单”或取消订单时必须在Service层校验当前登录用户ID是否与订单的用户ID匹配防止越权操作。PreAuthorize注解可以帮我们优雅地实现方法级别的权限控制。敏感信息脱敏返回用户列表或订单详情时手机号、邮箱等敏感信息需要部分隐藏如“138****1234”。这可以在DTO对象返回前通过Hutool的DesensitizedUtil工具类处理。性能方面对于场馆列表、场地状态某天某个场地的占用时间段这类查询频繁、变化相对不频繁的数据一定要引入缓存。我使用Redis作为缓存中间件。例如将“场地ID日期”作为Key将该场地当天的已被预约时间段列表作为Value存入Redis并设置过期时间为第二天凌晨。当用户查询某天场地状态时先查缓存没有则查数据库并回填缓存。当有新的预约成功或取消时需要删除或更新对应的缓存数据保证数据一致性。3. 前端交互用Vue 3构建动态且友好的用户界面前端的目标是提供一个清晰、响应迅速的操作界面。我选择了Vue 3的Composition API配合script setup语法以及Pinia状态管理这套组合让代码组织更清晰逻辑复用更方便。3.1 项目初始化与核心组件设计使用Vite初始化项目能获得更快的启动和热更新速度。核心的页面组件包括登录/注册页表单验证使用VeeValidate或Element Plus自带的规则提交时显示加载状态。场馆场地浏览页这是门户。左侧可以是场馆/场地类型的筛选器右侧主区域以卡片或列表形式展示场地。每个场地卡片需要清晰展示名称、图片、类型、当前状态如“可预约”、“已满”。这里的关键是状态需要根据后端接口返回的实时数据或缓存数据动态计算显示。场地详情与预约页点击具体场地进入。页面顶部展示场地详情核心部分是一个可视化时间选择器。我推荐使用基于JavaScript的日历库如dayjs配合UI框架的日期时间选择器如Element Plus的DatePicker自己封装一个。这个组件需要默认显示未来几天如一周作为可选项。用户选择日期后向后端请求该场地在该日期已被预约的时间段。在UI上将已预约的时间段标记为不可选如灰色禁用将空闲时间段标记为可选。用户选择开始和结束时间后能实时计算并显示费用。个人中心页包含“我的预约”订单列表提供查看详情、取消预约在允许取消的时间段内等功能。3.2 状态管理与API交互使用Pinia来管理全局状态比如用户登录信息(userStore)和购物车/临时预约信息(bookingStore)。对于API请求我使用axios进行封装主要做了以下几件事请求/响应拦截器在请求拦截器中自动从userStore或localStorage获取Token并添加到请求头Authorization中。在响应拦截器中统一处理HTTP状态码如401跳转登录页和业务逻辑错误码如“场地冲突”进行友好的消息提示。API模块化将不同功能的接口按模块划分到不同的JS文件中如auth.api.js、venue.api.js、order.api.js便于维护。加载状态管理为每个可能耗时的按钮操作如提交预约绑定一个加载状态防止用户重复点击并提升体验。一个典型的预约页面交互逻辑如下Vue 3 Composition API风格script setup import { ref, computed, onMounted } from vue; import { useRoute } from vue-router; import { getVenueDetail, getBookedSlots, createOrder } from /api/venue.api; import { useUserStore } from /stores/user; import { ElMessage } from element-plus; const route useRoute(); const userStore useUserStore(); const venueId route.params.id; const venueDetail ref({}); const selectedDate ref(); const bookedSlots ref([]); // 格式如 [{start: 09:00, end: 10:00}, ...] const timeRange ref([, ]); // 用户选择的时间范围 const loading ref(false); // 获取场地详情 onMounted(async () { venueDetail.value await getVenueDetail(venueId); }); // 监听日期变化获取该日期的已预约时段 watch(selectedDate, async (newDate) { if (newDate) { bookedSlots.value await getBookedSlots(venueId, newDate); } }); // 提交预约 const handleSubmit async () { if (!userStore.isLoggedIn) { ElMessage.warning(请先登录); return; } if (!timeRange.value[0] || !timeRange.value[1]) { ElMessage.warning(请选择预约时间); return; } loading.value true; try { const orderData { venueId: venueId, date: selectedDate.value, startTime: timeRange.value[0], endTime: timeRange.value[1], }; const result await createOrder(orderData); ElMessage.success(预约成功订单号 result.orderNo); // 跳转到订单详情页或我的订单列表 } catch (error) { ElMessage.error(error.message || 预约失败); } finally { loading.value false; } }; /script3.3 用户体验优化细节路由守卫使用Vue Router的全局前置守卫beforeEach对需要登录的页面如预约页、个人中心进行拦截未登录则重定向到登录页。页面缓存对于场馆列表页可以使用keep-alive组件进行缓存用户从详情页返回时无需重新加载列表体验更流畅。错误边界对于可能出错的组件如依赖API数据的组件可以使用Vue 3的onErrorCaptured生命周期钩子或类似机制捕获并展示友好的错误界面而不是让整个页面白屏。移动端适配使用响应式CSS框架如Element Plus本身是响应式的或媒体查询确保在手机和平板上也有良好的浏览和操作体验。4. 前后端协同与数据流转前后端分离开发约定好API接口规范是关键。我采用RESTful风格并使用Swagger后端集成springfox或knife4j自动生成API文档前端开发时可以直接参考。4.1 统一的响应体格式前后端约定一个固定的JSON响应格式便于前端统一处理。通常包含code业务状态码、message提示信息、data业务数据和timestamp时间戳。// 后端统一返回对象 Data public class RT implements Serializable { private Integer code; private String message; private T data; private Long timestamp System.currentTimeMillis(); // 成功静态方法 public static T RT success(T data) { RT r new R(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } // 失败静态方法 public static T RT error(Integer code, String message) { RT r new R(); r.setCode(code); r.setMessage(message); return r; } }4.2 跨域问题处理在开发阶段前端运行在localhost:5173Vite默认端口后端在localhost:8080浏览器会因同源策略阻止请求。在后端可以通过配置一个全局的CORS过滤器来解决Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); // 生产环境应替换为具体前端域名 config.setAllowCredentials(true); config.addAllowedMethod(*); config.addAllowedHeader(*); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }重要提示在生产环境部署时addAllowedOriginPattern(*)这种允许所有源的配置是极不安全的必须替换为确切的前端访问域名例如config.addAllowedOrigin(https://booking.yourdomain.com)。4.3 文件上传与访问如果平台需要上传场地图片或用户头像这部分需要单独处理。我通常的做法是后端提供/api/upload接口接收MultipartFile文件。文件不直接存入数据库而是保存到服务器的某个目录如/static/upload/或对象存储服务如阿里云OSS、腾讯云COS。数据库中只保存文件的访问路径URL。后端需要配置静态资源映射使得存储的图片能被外部访问。例如将/static/upload/**路径映射到服务器的实际文件目录。前端使用input typefile或对应UI组件上传获取到后端返回的URL后再随其他表单数据一并提交。5. 部署上线从开发环境到生产环境的跨越项目开发完成最终要部署到服务器上对外提供服务。我以最经典的Linux服务器 Nginx Jar包部署方式为例。5.1 后端服务打包与运行打包在Spring Boot项目根目录下使用Maven命令mvn clean package -DskipTests进行打包。完成后会在target目录下生成一个可执行的JAR文件如gym-booking-0.0.1-SNAPSHOT.jar。这个JAR包内嵌了Tomcat服务器因此可以直接运行。配置文件分离切勿将包含数据库密码、Redis密码、JWT密钥等敏感信息的application.yml或application.properties文件打包进JAR正确的做法是在JAR包同目录下创建一个config文件夹将生产环境的配置文件如application-prod.yml放在里面。Spring Boot会自动加载外部配置文件优先级高于JAR包内部。这样也方便不同环境测试、生产切换配置。服务器运行将JAR包和外部配置文件上传到服务器。使用nohup命令在后台运行服务并将日志输出到文件nohup java -jar -Dspring.profiles.activeprod gym-booking-0.0.1-SNAPSHOT.jar app.log 21 这里-Dspring.profiles.activeprod指定使用prod环境的配置。更专业的做法是使用systemd来管理服务可以设置开机自启、自动重启等。5.2 前端项目构建与Nginx配置构建在前端项目根目录下运行npm run buildVite项目是npm run build。这个命令会编译、压缩所有代码生成一个dist目录里面是纯粹的HTML、CSS、JS文件。部署将dist目录下的所有文件上传到服务器的某个目录例如/var/www/gym-booking-frontend/。Nginx配置Nginx有两个主要作用一是作为Web服务器托管前端静态文件二是作为反向代理将API请求转发给后端Spring Boot服务。server { listen 80; server_name your-domain.com; # 你的域名 # 前端静态文件 location / { root /var/www/gym-booking-frontend; index index.html; try_files $uri $uri/ /index.html; # 支持Vue Router的history模式 } # 反向代理后端API location /api/ { proxy_pass http://localhost:8080/; # 转发到后端服务 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 代理上传的静态文件如果文件存在服务器本地 location /upload/ { alias /path/to/your/upload/directory/; } }配置完成后重启Nginxsudo systemctl restart nginx。5.3 数据库初始化与数据迁移在生产服务器上安装MySQL创建对应的数据库和用户。将开发环境的数据库结构表结构导出为SQL文件在生产环境执行。注意不要直接导出全部数据尤其是测试数据只导出表结构即可。初始的管理员账号等基础数据可以通过编写一个数据库初始化脚本或使用Flyway/Liquibase这样的数据库版本管理工具来插入。5.4 域名、HTTPS与监控域名解析在域名服务商处将你的域名如booking.yourdomain.com解析到服务器的公网IP。HTTPS使用Let‘s Encrypt的Certbot工具为你的域名申请免费的SSL证书并在Nginx中配置将HTTP请求重定向到HTTPS。这是现代网站的标配能提升安全性。基础监控至少需要监控服务器的CPU、内存、磁盘使用率。可以使用htop、df等命令手动查看或使用更专业的监控系统如PrometheusGrafana。同时要定期查看Spring Boot应用日志app.log和Nginx的访问/错误日志以便及时发现和排查问题。走到这一步一个完整的、可运行的体育馆预约平台就已经部署上线了。从需求分析、技术选型、编码实现到部署运维每一个环节都充满了选择和挑战。这个项目麻雀虽小五脏俱全涵盖了用户认证、资源管理、事务处理、前后端交互、缓存、部署等Web开发的常见核心问题是一个非常棒的练手和学习的项目。希望我的这些拆解和经验能帮你少走一些弯路。本文还有配套的精品资源点击获取