Java体育场馆预约系统设计与实现

一、项目介绍

体育场馆预约系统是一个基于Java开发的Web应用程序,旨在为用户提供便捷的场馆预约服务。系统主要面向学校、体育中心等场所,实现场馆资源的高效管理和使用。

二、系统功能模块

1. 用户管理模块

  • 用户注册与登录
  • 个人信息管理
  • 角色权限控制(管理员/普通用户)

2. 场馆管理模块

  • 场馆信息维护
  • 场地状态管理
  • 场馆类型分类

3. 预约管理模块

  • 在线预约
  • 预约记录查询
  • 预约取消功能
  • 预约时段冲突检测

4. 公告管理模块

  • 系统公告发布
  • 场馆使用规则
  • 预约须知

三、技术架构

后端技术栈:

  • Spring Boot
  • Spring Security
  • MyBatis Plus
  • MySQL
  • Redis

前端技术栈:

  • Vue.js
  • Element UI
  • Axios
  • Vue Router

四、数据库设计

主要数据表

-- 用户表
CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50),
    password VARCHAR(100),
    role VARCHAR(20),
    create_time DATETIME
);

-- 场馆表
CREATE TABLE venue (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    type VARCHAR(50),
    status INT,
    description TEXT
);

-- 预约记录表
CREATE TABLE booking (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,
    venue_id INT,
    booking_date DATE,
    time_slot VARCHAR(50),
    status INT
);

五、核心功能实现

1. 预约功能实现

@Service
public class BookingService {
    @Autowired
    private BookingMapper bookingMapper;
    
    public Result bookVenue(BookingDTO bookingDTO) {
        // 检查时段是否可用
        if (!checkTimeSlotAvailable(bookingDTO)) {
            return Result.error("该时段已被预约");
        }
        
        // 创建预约记录
        Booking booking = new Booking();
        booking.setUserId(bookingDTO.getUserId());
        booking.setVenueId(bookingDTO.getVenueId());
        booking.setBookingDate(bookingDTO.getBookingDate());
        booking.setTimeSlot(bookingDTO.getTimeSlot());
        booking.setStatus(BookingStatus.CONFIRMED.getCode());
        
        bookingMapper.insert(booking);
        return Result.success("预约成功");
    }
}

2. 场馆查询功能

@RestController
@RequestMapping("/venue")
public class VenueController {
    @Autowired
    private VenueService venueService;
    
    @GetMapping("/list")
    public Result getVenueList(VenueQueryDTO queryDTO) {
        Page<Venue> page = venueService.queryVenueList(queryDTO);
        return Result.success(page);
    }
}

六、预约管理模块详细设计

1、数据库设计

-- 预约记录表
CREATE TABLE booking (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL COMMENT '用户ID',
    venue_id BIGINT NOT NULL COMMENT '场馆ID',
    booking_date DATE NOT NULL COMMENT '预约日期',
    time_slot VARCHAR(20) NOT NULL COMMENT '预约时段',
    status TINYINT NOT NULL COMMENT '状态:0-待确认,1-已确认,2-已取消,3-已完成',
    create_time DATETIME NOT NULL COMMENT '创建时间',
    update_time DATETIME NOT NULL COMMENT '更新时间',
    remark VARCHAR(200) COMMENT '备注',
    UNIQUE KEY uk_venue_date_slot (venue_id, booking_date, time_slot)
);

2、实体类设计

@Data
public class Booking {
    private Long id;
    private Long userId;
    private Long venueId;
    private LocalDate bookingDate;
    private String timeSlot;
    private Integer status;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    private String remark;
}

@Data
public class BookingDTO {
    @NotNull(message = "场馆ID不能为空")
    private Long venueId;
    
    @NotNull(message = "预约日期不能为空")
    @Future(message = "预约日期必须是将来日期")
    private LocalDate bookingDate;
    
    @NotEmpty(message = "预约时段不能为空")
    private String timeSlot;
    
    private String remark;
}

3、Service层实现

@Service
@Slf4j
public class BookingService {
    @Autowired
    private BookingMapper bookingMapper;
    
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 创建预约
     */
    @Transactional
    public Result createBooking(BookingDTO dto, Long userId) {
        // 1. 检查预约时间是否合法
        if (!isValidBookingTime(dto.getBookingDate(), dto.getTimeSlot())) {
            return Result.error("预约时间不在允许范围内");
        }

        // 2. 检查时段冲突(使用Redis实现分布式锁)
        String lockKey = String.format("booking:lock:%d:%s:%s", 
            dto.getVenueId(), dto.getBookingDate(), dto.getTimeSlot());
        
        boolean locked = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        
        if (!locked) {
            return Result.error("该时段正在被其他用户预约,请稍后重试");
        }

        try {
            // 3. 检查时段是否已被预约
            if (checkTimeSlotConflict(dto)) {
                return Result.error("该时段已被预约");
            }

            // 4. 创建预约记录
            Booking booking = new Booking();
            BeanUtils.copyProperties(dto, booking);
            booking.setUserId(userId);
            booking.setStatus(BookingStatus.PENDING.getCode());
            booking.setCreateTime(LocalDateTime.now());
            booking.setUpdateTime(LocalDateTime.now());

            bookingMapper.insert(booking);
            return Result.success("预约成功");
        } finally {
            redisTemplate.delete(lockKey);
        }
    }

    /**
     * 取消预约
     */
    @Transactional
    public Result cancelBooking(Long bookingId, Long userId) {
        Booking booking = bookingMapper.selectById(bookingId);
        
        if (booking == null) {
            return Result.error("预约记录不存在");
        }
        
        if (!booking.getUserId().equals(userId)) {
            return Result.error("无权操作此预约");
        }
        
        if (!BookingStatus.canCancel(booking.getStatus())) {
            return Result.error("当前状态不允许取消");
        }

        booking.setStatus(BookingStatus.CANCELLED.getCode());
        booking.setUpdateTime(LocalDateTime.now());
        bookingMapper.updateById(booking);
        
        return Result.success("取消成功");
    }

    /**
     * 查询预约记录
     */
    public PageResult<BookingVO> queryBookings(BookingQueryDTO queryDTO) {
        Page<Booking> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
        
        LambdaQueryWrapper<Booking> wrapper = new LambdaQueryWrapper<Booking>()
            .eq(queryDTO.getUserId() != null, Booking::getUserId, queryDTO.getUserId())
            .eq(queryDTO.getVenueId() != null, Booking::getVenueId, queryDTO.getVenueId())
            .ge(queryDTO.getStartDate() != null, Booking::getBookingDate, queryDTO.getStartDate())
            .le(queryDTO.getEndDate() != null, Booking::getBookingDate, queryDTO.getEndDate())
            .eq(queryDTO.getStatus() != null, Booking::getStatus, queryDTO.getStatus())
            .orderByDesc(Booking::getCreateTime);

        Page<Booking> resultPage = bookingMapper.selectPage(page, wrapper);
        
        // 转换为VO
        List<BookingVO> bookingVOs = resultPage.getRecords().stream()
            .map(this::convertToVO)
            .collect(Collectors.toList());

        return new PageResult<>(bookingVOs, resultPage.getTotal());
    }

    /**
     * 检查时段冲突
     */
    private boolean checkTimeSlotConflict(BookingDTO dto) {
        LambdaQueryWrapper<Booking> wrapper = new LambdaQueryWrapper<Booking>()
            .eq(Booking::getVenueId, dto.getVenueId())
            .eq(Booking::getBookingDate, dto.getBookingDate())
            .eq(Booking::getTimeSlot, dto.getTimeSlot())
            .ne(Booking::getStatus, BookingStatus.CANCELLED.getCode());
            
        return bookingMapper.selectCount(wrapper) > 0;
    }

    /**
     * 验证预约时间是否合法
     */
    private boolean isValidBookingTime(LocalDate bookingDate, String timeSlot) {
        // 1. 检查是否是将来日期
        if (bookingDate.isBefore(LocalDate.now())) {
            return false;
        }

        // 2. 检查是否在可预约天数范围内(如:最多预约30天后的场地)
        if (bookingDate.isAfter(LocalDate.now().plusDays(30))) {
            return false;
        }

        // 3. 检查时段格式是否正确(如:10:00-11:00)
        if (!timeSlot.matches("\\d{2}:\\d{2}-\\d{2}:\\d{2}")) {
            return false;
        }

        // 4. 检查是否在营业时间内
        String[] times = timeSlot.split("-");
        LocalTime startTime = LocalTime.parse(times[0]);
        LocalTime endTime = LocalTime.parse(times[1]);
        
        return startTime.isAfter(LocalTime.of(8, 0)) && 
               endTime.isBefore(LocalTime.of(22, 0));
    }
}

4、Controller层实现

@RestController
@RequestMapping("/api/bookings")
@Validated
public class BookingController {
    @Autowired
    private BookingService bookingService;

    /**
     * 创建预约
     */
    @PostMapping
    public Result createBooking(@RequestBody @Valid BookingDTO dto) {
        Long userId = SecurityUtils.getCurrentUserId();
        return bookingService.createBooking(dto, userId);
    }

    /**
     * 取消预约
     */
    @PostMapping("/{id}/cancel")
    public Result cancelBooking(@PathVariable("id") Long bookingId) {
        Long userId = SecurityUtils.getCurrentUserId();
        return bookingService.cancelBooking(bookingId, userId);
    }

    /**
     * 查询预约记录
     */
    @GetMapping
    public Result<PageResult<BookingVO>> queryBookings(BookingQueryDTO queryDTO) {
        Long userId = SecurityUtils.getCurrentUserId();
        // 普通用户只能查询自己的预约记录
        if (!SecurityUtils.isAdmin()) {
            queryDTO.setUserId(userId);
        }
        return Result.success(bookingService.queryBookings(queryDTO));
    }
}

5、前端实现(Vue)

<template>
  <div class="booking-container">
    <!-- 预约表单 -->
    <el-form :model="bookingForm" :rules="rules" ref="bookingForm">
      <el-form-item label="场馆" prop="venueId">
        <el-select v-model="bookingForm.venueId">
          <el-option
            v-for="venue in venues"
            :key="venue.id"
            :label="venue.name"
            :value="venue.id"
          />
        </el-select>
      </el-form-item>
      
      <el-form-item label="日期" prop="bookingDate">
        <el-date-picker
          v-model="bookingForm.bookingDate"
          type="date"
          :picker-options="dateOptions"
        />
      </el-form-item>
      
      <el-form-item label="时段" prop="timeSlot">
        <el-select v-model="bookingForm.timeSlot">
          <el-option
            v-for="slot in timeSlots"
            :key="slot.value"
            :label="slot.label"
            :value="slot.value"
          />
        </el-select>
      </el-form-item>
      
      <el-form-item>
        <el-button type="primary" @click="submitBooking">
          提交预约
        </el-button>
      </el-form-item>
    </el-form>

    <!-- 预约记录列表 -->
    <el-table :data="bookings" border>
      <el-table-column prop="venueName" label="场馆" />
      <el-table-column prop="bookingDate" label="日期" />
      <el-table-column prop="timeSlot" label="时段" />
      <el-table-column prop="status" label="状态">
        <template slot-scope="scope">
          <el-tag :type="getStatusType(scope.row.status)">
            {{ getStatusText(scope.row.status) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作">
        <template slot-scope="scope">
          <el-button
            v-if="canCancel(scope.row)"
            type="danger"
            size="small"
            @click="cancelBooking(scope.row.id)"
          >
            取消预约
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    
    <el-pagination
      :current-page.sync="query.pageNum"
      :page-size="query.pageSize"
      :total="total"
      @current-change="handlePageChange"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      bookingForm: {
        venueId: '',
        bookingDate: '',
        timeSlot: ''
      },
      rules: {
        venueId: [{ required: true, message: '请选择场馆' }],
        bookingDate: [{ required: true, message: '请选择日期' }],
        timeSlot: [{ required: true, message: '请选择时段' }]
      },
      venues: [],
      bookings: [],
      query: {
        pageNum: 1,
        pageSize: 10
      },
      total: 0,
      dateOptions: {
        disabledDate(time) {
          return time.getTime() < Date.now() - 8.64e7;
        }
      }
    };
  },
  created() {
    this.loadVenues();
    this.loadBookings();
  },
  methods: {
    async submitBooking() {
      try {
        await this.$refs.bookingForm.validate();
        const res = await this.$api.post('/bookings', this.bookingForm);
        this.$message.success('预约成功');
        this.loadBookings();
      } catch (error) {
        this.$message.error(error.message);
      }
    },
    
    async cancelBooking(id) {
      try {
        await this.$confirm('确认取消该预约?');
        await this.$api.post(`/bookings/${id}/cancel`);
        this.$message.success('取消成功');
        this.loadBookings();
      } catch (error) {
        if (error !== 'cancel') {
          this.$message.error(error.message);
        }
      }
    },
    
    async loadBookings() {
      const res = await this.$api.get('/bookings', { params: this.query });
      this.bookings = res.data.records;
      this.total = res.data.total;
    },
    
    handlePageChange() {
      this.loadBookings();
    },
    
    canCancel(booking) {
      return booking.status === 0 || booking.status === 1;
    }
  }
};
</script>

6、关键点说明

  1. 并发控制

    • 使用Redis分布式锁防止并发预约
    • 数据库唯一索引确保数据一致性
  2. 预约规则

    • 预约时间范围限制
    • 营业时间验证
    • 状态流转控制
  3. 性能优化

    • 分页查询
    • 索引优化
    • 缓存使用
  4. 安全控制

    • 身份验证
    • 权限校验
    • 数据隔离
  5. 用户体验

    • 友好的错误提示
    • 实时状态更新
    • 便捷的操作界面

通过以上设计和实现,可以为用户提供一个功能完善、体验良好的预约管理模块。

七、系统特点

  1. 实时性

    • 采用Redis缓存热点数据
    • 实时更新场馆预约状态
  2. 可扩展性

    • 模块化设计
    • 支持新增场馆类型
    • 灵活的预约规则配置
  3. 用户友好

    • 直观的预约界面
    • 便捷的查询功能
    • 完善的提示信息

八、系统优化

  1. 性能优化

    • 数据库索引优化
    • 接口响应时间优化
    • 并发预约处理
  2. 安全性优化

    • Spring Security安全框架
    • 接口访问权限控制
    • 数据加密传输

九、总结与展望

本系统实现了体育场馆预约的基本功能,为用户提供了便捷的预约服务。未来可以考虑添加以下功能:

  1. 在线支付功能
  2. 场馆评价系统
  3. 微信小程序端
  4. 智能推荐功能
  5. 数据统计分析

通过不断优化和完善,使系统更好地服务于用户,提高场馆资源利用率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/920671.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【阅读记录-章节3】Build a Large Language Model (From Scratch)

目录 3 Coding attention mechanisms3.1 The problem with modeling long sequences背景&#xff1a;注意力机制的动机 3.2 Capturing data dependencies with attention mechanismsRNN的局限性与改进Transformer架构的革命 3.3 Attending to different parts of the input wit…

Kubernetes配置管理ConfigMap、Secret

Your burden will become a gift, and your suffering will light your way. 应用部署的一个最佳实践是将应用所需的配置信息与程序分离,这样可以使应用程序被更好地复用,通过不同的配置也能实现更灵活的功能。将应用打包为容器镜像后,可以通过环境变量或者外挂文件的方式在…

141. Sprite标签(Canvas作为贴图)

上节课案例创建标签的方式&#xff0c;是把一张图片作为Sprite精灵模型的颜色贴图,本节给大家演示把Canvas画布作为Sprite精灵模型的颜色贴图&#xff0c;实现一个标签。 注意&#xff1a;本节课主要是技术方案讲解&#xff0c;默认你有Canvas基础&#xff0c;如果没有Canvas基…

「OpenCV交叉编译」ubuntu to arm64

Ubuntu x86_64 交叉编译OpenCV 为 arm64OpenCV4.5.5、cmake version 3.16.3交叉编译器 gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu 可在arm或linaro官网下载所需版本&#xff0c;本文的交叉编译器可点击链接跳转下载 Downloads | GNU-A Downloads – Arm Developer L…

鸿蒙网络编程系列48-仓颉版UDP回声服务器示例

1. UDP回声服务器简介 回声服务器指的是这样一种服务器&#xff0c;它接受客户端的连接&#xff0c;并且把收到的数据原样返回给客户端&#xff0c;本系列的第2篇文章《鸿蒙网络编程系列2-UDP回声服务器的实现》中基于ArkTS语言在API 9的环境下实现了UDP回声服务器&#xff0c…

【WPF】Prism学习(七)

Prism Dependency Injection 1.注册类型&#xff08;Registering Types&#xff09; 1.1. Prism中的服务生命周期&#xff1a; Transient&#xff08;瞬态&#xff09;&#xff1a;每次请求服务或类型时&#xff0c;都会获得一个新的实例。Singleton&#xff08;单例&#xf…

springboot基于Hadoop的NBA球员大数据分析与可视化(1)(6)

摘 要 科学技术日新月异&#xff0c;人们的生活都发生了翻天覆地的变化&#xff0c;NBA球员大数据分析与可视化系统当然也不例外。过去的信息管理都使用传统的方式实行&#xff0c;既花费了时间&#xff0c;又浪费了精力。在信息如此发达的今天&#xff0c;可以通过网络这个媒…

Q3净利增长超预期,文心大模型调用量大增,百度未来如何分析?

首先&#xff0c;从百度发布的2024年第三季度财务报告来看&#xff0c;其净利润同比增长17%&#xff0c;超出了市场预期&#xff0c;显示出百度整体财务表现的强劲。这一增长不仅体现在总营收和百度核心营收上&#xff0c;更具体地反映在归属百度核心的净利润上&#xff0c;这标…

Vscode/Code-server无网环境安装通义灵码

Date: 2024-11-18 参考材料&#xff1a;https://help.aliyun.com/zh/lingma/user-guide/individual-edition-login-tongyi-lingma?spma2c4g.11186623.0.i0 1. 首先在vscode/code-server插件市场中安装通义插件&#xff0c;这步就不细说了。如果服务器没网&#xff0c;会问你要…

开源TTS语音克隆神器GPT-SoVITS_V2版本地整合包部署与远程使用生成音频

文章目录 前言1.GPT-SoVITS V2下载2.本地运行GPT-SoVITS V23.简单使用演示4.安装内网穿透工具4.1 创建远程连接公网地址 5. 固定远程访问公网地址 前言 本文主要介绍如何在Windows系统电脑使用整合包一键部署开源TTS语音克隆神器GPT-SoVITS&#xff0c;并结合cpolar内网穿透工…

实战 | C#中使用YoloV8和OpenCvSharp实现目标检测 (步骤 + 源码)

导 读 本文主要介绍在C#中使用YoloV8实现目标检测,并给详细步骤和代码。 详细步骤 【1】环境和依赖项。 需先安装VS2022最新版,.NetFramework8.0,然后新建项目,nuget安装 YoloSharp,YoloSharp介绍: https://github.com/dme-compunet/YoloSharp 最新版6.0.1,本文…

IDE配置tomcat

1.导航到 Tomcat 安装目录 E:\apache-tomcat-9.0.95-windows-x64\apache-tomcat-9.0.95 2.启动 Tomcat 服务&#xff1a;bin\startup.bat

python读取Oracle库并生成API返回Json格式

一、安装必要的库 首先&#xff0c;确保已经安装了以下库&#xff1a; 有网模式 pip install flask pip install gevent pi install cx_Oracle离线模式&#xff1a; 下载地址&#xff1a;https://pypi.org/simple/flask/ # a. Flask Werkzeug-1.0.1-py2.py3-none-any.whl J…

MAC借助终端上传jar包到云服务器

前提&#xff1a;保证工程本地已打包完成&#xff1a;图中路径即为项目的target目录下已准备好的jar包 第一步&#xff1a;打开终端&#xff08;先不要连接自己的服务器&#xff09;&#xff0c;输入下面的上传命令&#xff1a; scp /path/to/local/app.jar username192.168.1…

Python数据分析NumPy和pandas(四十、Python 中的建模库statsmodels 和 scikit-learn)

主要学习两个流行的建模工具包&#xff0c;statsmodels 和 scikit-learn。 一、pandas 与模型代码之间的接口 模型开发的常见工作流程是使用 pandas 进行数据加载和清理&#xff0c;然后再切换到建模库来构建模型本身。模型开发过程的一个重要部分在机器学习中称为特征工程&a…

实操案例|TinyVue树表+动态行合并

本文由孟智强同学原创。 背景 团队某个小项目切换 UI 框架&#xff0c;要将 Element 换成 TinyVue。期间遇到一个树表形式的业务表格&#xff0c;支持多级下钻&#xff0c;且第一列有合并行。当初用 Element 实现这个表格时费了一些周折&#xff0c;料想 TinyVue 上场应该也不…

Mesh路由组网

Mesh无线网格网络&#xff0c;多跳&#xff08;multi-hop&#xff09;网络&#xff0c;为解决全屋覆盖信号&#xff0c;一般用于家庭网络和小型企业 原理 网关路由器&#xff08;主路由&#xff0c;连接光猫&#xff09;&#xff0c;Mesh路由器&#xff08;子路由&#xff0c;…

基于Windows系统用C++做一个点名工具

目录 一、前言 二、主要技术点 三、准备工作 四、主界面 1.绘制背景图 2、实现读取花名册功能 3.实现遍历花名册功能 4.实现储存功能 4.1创建数据库 4.2存储数据到数据库表 4.3读取数据库表数据 一、前言 人总是喜欢回忆过去&#xff0c;突然回忆起…

11.9K Star!强大的 Web 爬虫工具 FireCrawl:为 AI 训练与数据提取提供全面支持

在这个信息爆炸的时代&#xff0c;数据就是力量。尤其是对于开发者来说&#xff0c;获取并利用好数据&#xff0c;就意味着拥有更多的主动权和竞争力。 无论是用来训练大语言模型&#xff0c;还是用于增强检索生成&#xff08;RAG&#xff09;&#xff0c;数据都扮演着至关重要…

云原生之k8s服务管理

文章目录 服务管理Service服务原理ClusterIP服务 对外发布应用服务类型NodePort服务Ingress安装配置Ingress规则 Dashboard概述 认证和授权ServiceAccount用户概述创建ServiceAccount 权限管理角色与授权 服务管理 Service 服务原理 容器化带来的问题 自动调度&#xff1a;…