基于 Spring Boot + Vue3 的体育场馆在线预约系统设计与实现

发布时间:2026/10/30 12:23:41
基于 Spring Boot + Vue3 的体育场馆在线预约系统设计与实现 温馨提示本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系名片一、项目背景与意义随着全民健身意识的提升羽毛球、篮球、网球、游泳等体育场馆的使用需求快速增长。传统场馆预约多依赖电话登记或现场排队存在信息不透明、时段冲突、人工对账繁琐等问题。基于 Spring Boot Vue3 构建一套体育场馆在线预约系统可以让用户随时查看场馆空闲时段、在线下单支付、扫码入场同时帮助场馆管理者实现场地排期、订单统计和运营分析具有较高的实用价值。本文将从需求分析、技术选型、数据库设计、核心功能实现到部署上线完整介绍该系统的设计与实现过程适合作为 Java 全栈方向的课程设计或毕业设计参考。二、系统需求分析2.1 角色划分普通用户注册登录、浏览场馆、查看场地时段、在线预约、支付订单、取消预约、查看历史订单、评价场馆。场馆管理员维护场馆与场地信息、设置开放时段与价格、审核预约、核销入场、查看经营数据。系统管理员用户管理、角色权限管理、系统参数配置、日志审计。2.2 核心业务流程用户选择场馆与日期后系统按场地维度展示可预约时段用户选定时段提交预约系统通过数据库唯一约束与分布式锁防止同一时段被重复占用预约成功后生成订单并进入支付流程支付回调完成后订单状态变为「已支付」用户到场后由管理员核销订单状态流转为「已完成」。2.3 非功能性需求并发预约场景下保证时段不超卖。接口响应时间控制在 300ms 以内列表查询支持分页。前后端分离接口统一使用 JSON 交互并具备完善的鉴权机制。三、技术选型层次技术栈说明后端框架Spring Boot 3.x快速构建 RESTful 服务内置 Tomcat持久层MyBatis-Plus简化 CRUD支持分页与条件构造器数据库MySQL 8.0存储用户、场馆、场地、订单等数据缓存Redis缓存时段库存、分布式锁、登录令牌鉴权Spring Security JWT无状态认证与接口权限控制前端框架Vue3 Vite组合式 API构建速度快UI 组件库Element Plus后台管理界面快速搭建状态管理Pinia管理用户信息与全局状态网络请求Axios统一封装请求拦截与错误处理四、系统架构设计系统采用前后端分离架构整体分为表现层、接口层、业务层、数据层四部分。前端 Vue3 应用通过 Axios 调用后端 RESTful 接口后端按模块拆分为用户模块、场馆模块、预约模块、订单模块与统计模块。flowchart TD A[Vue3 前端应用] --|HTTP/JSON| B[Spring Boot 接口层] B -- C[Spring Security JWT 鉴权] C -- D[业务服务层] D -- E[MyBatis-Plus 持久层] E -- F[(MySQL)] D -- G[(Redis 缓存与分布式锁)] D -- H[支付回调接口]五、数据库设计核心表包括用户表、场馆表、场地表、时段表、预约订单表与支付记录表。以下给出关键表结构。CREATE TABLE venue ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT 场馆名称, address VARCHAR(255) NOT NULL COMMENT 场馆地址, open_time TIME NOT NULL COMMENT 开放开始时间, close_time TIME NOT NULL COMMENT 开放结束时间, status TINYINT DEFAULT 1 COMMENT 1营业 0停业, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE court ( id BIGINT PRIMARY KEY AUTO_INCREMENT, venue_id BIGINT NOT NULL COMMENT 所属场馆, name VARCHAR(64) NOT NULL COMMENT 场地名称, type VARCHAR(32) NOT NULL COMMENT 场地类型, price DECIMAL(10,2) NOT NULL COMMENT 每小时价格, status TINYINT DEFAULT 1 ); CREATE TABLE booking_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单号, user_id BIGINT NOT NULL, court_id BIGINT NOT NULL, booking_date DATE NOT NULL, start_time TIME NOT NULL, end_time TIME NOT NULL, amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付 2已完成 3已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_court_slot (court_id, booking_date, start_time) );其中uk_court_slot唯一索引是防止同一场地同一时段被重复预约的最后一道防线配合 Redis 分布式锁可在高并发下保证数据一致性。六、后端核心功能实现6.1 项目结构sports-booking ├── sports-common 公共模块统一响应、异常、工具类 ├── sports-security 鉴权模块JWT、Security 配置 ├── sports-admin 管理端接口 ├── sports-api 用户端接口 └── sports-application 启动模块6.2 统一响应与全局异常处理Data public class ResultT { private int code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } public static T ResultT fail(int code, String message) { ResultT r new Result(); r.setCode(code); r.setMessage(message); return r; } }RestControllerAdvice public class GlobalExceptionHandler { ExceptionHandler(BusinessException.class) public ResultVoid handleBusiness(BusinessException e) { return Result.fail(e.getCode(), e.getMessage()); } ExceptionHandler(Exception.class) public ResultVoid handleException(Exception e) { return Result.fail(500, 系统繁忙请稍后重试); } }6.3 预约核心逻辑预约接口需要完成时段校验、加锁、库存判断、订单落库四个步骤。下面使用 Redis 的SETNX实现简易分布式锁。Service RequiredArgsConstructor public class BookingService { private final StringRedisTemplate redisTemplate; private final BookingOrderMapper orderMapper; private final CourtMapper courtMapper; public String book(Long userId, BookingDTO dto) { String lockKey lock:court: dto.getCourtId() : dto.getBookingDate() : dto.getStartTime(); Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, Duration.ofSeconds(10)); if (Boolean.FALSE.equals(locked)) { throw new BusinessException(400, 该时段正在被预约请稍后重试); } try { Court court courtMapper.selectById(dto.getCourtId()); if (court null || court.getStatus() 0) { throw new BusinessException(400, 场地不可用); } Long count orderMapper.selectCount(new LambdaQueryWrapperBookingOrder() .eq(BookingOrder::getCourtId, dto.getCourtId()) .eq(BookingOrder::getBookingDate, dto.getBookingDate()) .eq(BookingOrder::getStartTime, dto.getStartTime()) .in(BookingOrder::getStatus, 0, 1, 2)); if (count 0) { throw new BusinessException(400, 该时段已被预约); } BookingOrder order new BookingOrder(); order.setOrderNo(IdUtil.getSnowflakeNextIdStr()); order.setUserId(userId); order.setCourtId(dto.getCourtId()); order.setBookingDate(dto.getBookingDate()); order.setStartTime(dto.getStartTime()); order.setEndTime(dto.getEndTime()); order.setAmount(court.getPrice()); order.setStatus(0); orderMapper.insert(order); return order.getOrderNo(); } finally { redisTemplate.delete(lockKey); } } }6.4 JWT 鉴权Component public class JwtUtil { private static final String SECRET sports-booking-secret-key; private static final long EXPIRE 7 * 24 * 3600 * 1000L; public String generate(Long userId, String username) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(username, username) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public Claims parse(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }七、前端核心功能实现7.1 请求封装import axios from axios import { ElMessage } from element-plus import { useUserStore } from /stores/user const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Bearer ${userStore.token} } return config }) request.interceptors.response.use( res { const { code, message, data } res.data if (code 200) return data ElMessage.error(message) return Promise.reject(new Error(message)) }, err { ElMessage.error(err.message || 网络异常) return Promise.reject(err) } ) export default request7.2 场地时段选择组件template div classslot-grid div v-forslot in slots :keyslot.startTime :class[slot, { disabled: slot.booked, active: selected slot.startTime }] clickchoose(slot) {{ slot.startTime }} - {{ slot.endTime }} /div /div /template script setup import { ref, onMounted } from vue import request from /utils/request const props defineProps({ courtId: Number, date: String }) const emit defineEmits([select]) const slots ref([]) const selected ref() const loadSlots async () { slots.value await request.get(/court/slots, { params: { courtId: props.courtId, date: props.date } }) } const choose slot { if (slot.booked) return selected.value slot.startTime emit(select, slot) } onMounted(loadSlots) /script style scoped .slot-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; } .slot { padding: 10px; border: 1px solid #dcdfe6; border-radius: 6px; text-align: center; cursor: pointer; } .slot.disabled { background: #f5f7fa; color: #c0c4cc; cursor: not-allowed; } .slot.active { border-color: #409eff; color: #409eff; } /style八、系统部署与运行准备 MySQL 8.0 与 Redis 环境执行初始化 SQL 脚本。修改后端application.yml中的数据库与 Redis 连接配置。执行mvn clean package打包后端使用java -jar启动。前端执行npm install与npm run build将产物部署到 Nginx。Nginx 配置/api反向代理到后端服务解决跨域问题。九、总结与展望本文基于 Spring Boot 3 Vue3 实现了一套体育场馆在线预约系统覆盖了用户预约、订单支付、场地管理与数据统计等核心功能并通过 Redis 分布式锁与数据库唯一索引解决了并发预约的时段冲突问题。后续可继续扩展的方向包括接入微信小程序端、引入消息队列处理支付回调与超时取消订单、增加场馆地图与导航、基于历史数据做热门时段推荐等。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询