SpringBoot+Vue体育场馆预约管理系统 附带详细运行指导视频

一、项目演示

项目演示地址: 视频地址

二、项目介绍

项目描述:这是一个基于SpringBoot+Vue框架开发的体育场馆预约管理系统。首先,这是一个前后端分离的项目,代码简洁规范,注释说明详细,易于理解和学习。其次,这项目功能丰富,具有一个体育场馆预约管理系统该有的所有功能。

项目功能:此项目分为两个角色:普通用户管理员普通用户有登录注册、管理个人信息、浏览或租借体育器材、浏览或预约体育场馆信息、管理个人租借体育器材信息、管理个人预约体育场馆信息、浏览公告信息等等功能。管理员有管理所有用户新息、管理所有体育器材信息、管理所有体育场馆信息、管理所有租借体育器材信息、管理所有预约体育场馆信息、管理所有公告信息等等功能。

应用技术:SpringBoot + Vue + MySQL + MyBatis + Redis + ElementUI

运行环境:IntelliJ IDEA2019.3.5 + MySQL5.7(项目压缩包中自带) + Redis5.0.5(项目压缩包中自带) + JDK1.8 + Maven3.6.3(项目压缩包中自带)+ Node14.16.1(项目压缩包中自带)

三、运行截图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、主要代码

1.租借体育器材代码:

	/**
     * 保存租借数据(添加、修改)
     * @param rentalDTO
     * @return
     */
    @Override
    public ResponseDTO<Boolean> saveRental(RentalDTO rentalDTO) {
        // 进行统一表单验证
        CodeMsg validate = ValidateEntityUtil.validate(rentalDTO);
        if(!validate.getCode().equals(CodeMsg.SUCCESS.getCode())){
            return ResponseDTO.errorByMsg(validate);
        }
        Rental rental = CopyUtil.copy(rentalDTO, Rental.class);
        ResponseDTO<Boolean> responseDTO = ResponseDTO.successByMsg(true, "保存成功!");
        if(CommonUtil.isEmpty(rental.getId())){
            // id为空 说明是添加数据
            // 生成8位id
            rental.setId(UuidUtil.getShortUuid());
            rental.setCreateTime(new Date());
            rental.setState(RentalStateEnum.WAIT.getCode());
            String redissonKey = String.format(EQUIPMENT_REDIS_KEY_TEMPLATE, rental.getEquipmentId());
            RLock lock = redissonClient.getLock(redissonKey);
            //1.加锁  阻塞获取锁:获取不到一直循环尝试获取
            lock.lock();
            try {
                //  @Transactional 事务执行完后  再unlock释放锁
                //  为了避免锁在事务提交前释放,我们应该在事务外层使用锁。
                responseDTO = createRental(rental);
            }catch (Exception e){
                logger.error(e.getMessage());
            }finally {
                //解锁
                lock.unlock();
            }
        } else {
            // id不为空 说明是修改数据
            // 修改数据库中数据
            responseDTO = updateRental(rental);

        }
        return responseDTO;
    }

    /**
     * 更新租借信息
     * @param rental
     * @return
     */
    @Transactional
    public ResponseDTO<Boolean> updateRental(Rental rental) {
        if(RentalStateEnum.FAIL.getCode().equals(rental.getState()) || RentalStateEnum.CANCEL.getCode().equals(rental.getState())) {
            myEquipmentMapper.addRentalNum(rental.getNum(), rental.getEquipmentId());
        }
        if(rentalMapper.updateByPrimaryKeySelective(rental) == 0){
            throw new RuntimeException(CodeMsg.RENTAL_EDIT_ERROR.getMsg());
        }
        return ResponseDTO.successByMsg(true, "保存成功!");
    }
    
	 /**
     * 创建租赁信息
     * @param rental
     * @return
     */
    @Transactional
    public ResponseDTO<Boolean> createRental(Rental rental) {
        // 根据体育器材id和租借数量判断体育器材剩余库存
        Equipment equipment = equipmentMapper.selectByPrimaryKey(rental.getEquipmentId());
        if(EquipmentStateEnum.OFF.getCode().equals(equipment.getState())) {
            return ResponseDTO.errorByMsg(CodeMsg.EQUIPMENT_ALREADY_OFF);
        }
        if(equipment.getNum() < rental.getNum()) {
            return ResponseDTO.errorByMsg(CodeMsg.EQUIPMENT_STOCK_ERROR);
        }
        // 数据落地
        if(rentalMapper.insertSelective(rental) == 0) {
            return ResponseDTO.errorByMsg(CodeMsg.RENTAL_ADD_ERROR);
        }
        // 减少体育器材数量
        myEquipmentMapper.decreaseRentalNum(rental.getNum(), rental.getEquipmentId());
        return ResponseDTO.successByMsg(true, "保存成功!");
    }

2.用户登录代码:

	/**
     * 用户登录操作
     * @param userDTO
     * @return
     */
    @Override
    public ResponseDTO<UserDTO> login(UserDTO userDTO) {
        // 进行是否为空判断
        if(CommonUtil.isEmpty(userDTO.getUsername())){
            return ResponseDTO.errorByMsg(CodeMsg.USERNAME_EMPTY);
        }
        if(CommonUtil.isEmpty(userDTO.getPassword())){
            return ResponseDTO.errorByMsg(CodeMsg.PASSWORD_EMPTY);
        }
        if(CommonUtil.isEmpty(userDTO.getCaptcha())){
            return ResponseDTO.errorByMsg(CodeMsg.CAPTCHA_EMPTY);
        }
        if(CommonUtil.isEmpty(userDTO.getCorrectCaptcha())){
            return ResponseDTO.errorByMsg(CodeMsg.CAPTCHA_EXPIRED);
        }
        // 比对验证码是否正确
        String value = stringRedisTemplate.opsForValue().get((userDTO.getCorrectCaptcha()));
        if(CommonUtil.isEmpty(value)){
            return ResponseDTO.errorByMsg(CodeMsg.CAPTCHA_EXPIRED);
        }
        if(!value.toLowerCase().equals(userDTO.getCaptcha().toLowerCase())){
            return ResponseDTO.errorByMsg(CodeMsg.CAPTCHA_ERROR);
        }
        // 对比昵称和密码是否正确
        UserExample userExample = new UserExample();
        // select * from user where username = ? and password = ?
        userExample.createCriteria().andUsernameEqualTo(userDTO.getUsername()).andPasswordEqualTo(userDTO.getPassword());
        List<User> userList = userMapper.selectByExample(userExample);
        if(userList == null || userList.size() != 1){
            return ResponseDTO.errorByMsg(CodeMsg.USERNAME_PASSWORD_ERROR);
        }
        // 生成登录token并存入Redis中
        UserDTO selectedUserDTO = CopyUtil.copy(userList.get(0), UserDTO.class);
        String token = UuidUtil.getShortUuid();
        selectedUserDTO.setToken(token);
        //把token存入redis中 有效期1小时
        stringRedisTemplate.opsForValue().set("USER_" + token, JSON.toJSONString(selectedUserDTO), 3600, TimeUnit.SECONDS);
        return ResponseDTO.successByMsg(selectedUserDTO, "登录成功!");
    }

3.Redis中stream消息队列读取预约数据代码:

    private static final ExecutorService APPOINTMENT_UPDATE_EXECUTOR = Executors.newSingleThreadExecutor();


    @PostConstruct
    private void init() {
        APPOINTMENT_UPDATE_EXECUTOR.submit(new AppointmentHandler());
    }


    private class AppointmentHandler implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    // 1.获取消息队列中的预约信息 XREAD GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.orders >
                    // ReadOffset.lastConsumed() 获取下一个未消费的预约数据
                    List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
                            StreamOffset.create(APPOINTMENT_REDIS_KEY_TEMPLATE, ReadOffset.lastConsumed())
                    );
                    // 2.判断预约信息是否为空
                    if (list == null || list.isEmpty()) {
                        // 如果为null,说明没有消息,继续下一次循环
                        continue;
                    }
                    // 解析数据 获取一条数据  因为上面count(1)指定获取一条
                    MapRecord<String, Object, Object> record = list.get(0);
                    Map<Object, Object> value = record.getValue();
                    String jsonAppointmentString = (String) value.get("appointmentData");
                    Appointment appointment = JSON.parseObject(jsonAppointmentString, Appointment.class);
                    // 3.更新预约数据
                    logger.info("接收到消息队列数据,准备更新...");
                    appointmentMapper.updateByPrimaryKeySelective(appointment);
                    Hall hall = hallMapper.selectByPrimaryKey(appointment.getHallId());
                    if(AppointmentStateEnum.FINISH.getCode().equals(appointment.getState())
                        && HallStateEnum.APPOINT.getCode().equals(hall.getState())) {
                        hall.setState(HallStateEnum.FREE.getCode());
                        hallMapper.updateByPrimaryKeySelective(hall);
                    }
                    logger.info("预约数据更新完成...");
                    // 4.确认消息 XACK
                    stringRedisTemplate.opsForStream().acknowledge(APPOINTMENT_REDIS_KEY_TEMPLATE, "g1", record.getId());
                } catch (Exception e) {
//                    logger.error("处理预约数据异常", e);
                    handlePendingList();
                }
            }
        }

        // 确认异常的预约数据再次处理
        private void handlePendingList() {
            while (true) {
                try {
                    // 1.获取pending-list中的预约信息 XREAD GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.orders 0
                    // ReadOffset.from("0") 从第一个消息开始
                    List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1),
                            StreamOffset.create(APPOINTMENT_REDIS_KEY_TEMPLATE, ReadOffset.from("0"))
                    );
                    // 2.判断预约信息是否为空
                    if (list == null || list.isEmpty()) {
                        // 如果为null,说明没有异常消息,结束循环
                        break;
                    }
                    // 解析数据
                    MapRecord<String, Object, Object> record = list.get(0);
                    Map<Object, Object> value = record.getValue();
                    String jsonAppointmentString = (String) value.get("appointmentData");
                    Appointment appointment = JSON.parseObject(jsonAppointmentString, Appointment.class);
                    // 3.更新预约数据
                    logger.info("接收到消息队列数据,准备更新...");
                    appointmentMapper.updateByPrimaryKeySelective(appointment);
                    Hall hall = hallMapper.selectByPrimaryKey(appointment.getHallId());
                    if(AppointmentStateEnum.FINISH.getCode().equals(appointment.getState())
                            && HallStateEnum.APPOINT.getCode().equals(hall.getState())) {
                        hall.setState(HallStateEnum.FREE.getCode());
                        hallMapper.updateByPrimaryKeySelective(hall);
                    }
                    logger.info("预约数据更新完成...");
                    // 4.确认消息 XACK
                    stringRedisTemplate.opsForStream().acknowledge(APPOINTMENT_REDIS_KEY_TEMPLATE, "g1", record.getId());
                } catch (Exception e) {
//                    logger.error("处理预约数据异常", e);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException interruptedException) {
//                        interruptedException.printStackTrace();
                    }
                }
            }
        }
    }
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
这份资源是一个基于SpringBoot+Vue的沁园健身房预约系统的完整开发源码,包括前端、后端、数据库等部分。该系统主要针对健身房会员提供预约服务,支持会员在线选择课程、场馆预约预约取消等功能。同时该系统还支持教练管理、课程管理、场馆管理等功能,为健身房提供了全方位的预约和管理服务。此外,该系统还提供了会员健身目标设置、健康方案制定等辅助服务,帮助会员更好地制定健身计划和跟踪健身进展。 为了更好地使用本资源,我们提供了详细的部署说明和系统介绍。在部署说明,我们详细介绍了如何将本资源部署到本地或远程服务器上,并配置相关环境参数。在系统介绍,我们对沁园健身房预约系统的各项功能、前后端框架和技术栈进行了详细介绍和解释,以帮助开发者更好地理解系统的设计思路和功能实现。 对于想要深入学习和了解源码的开发者,我们还提供了源码解释。通过逐行分析源码,我们对系统的技术实现、API设计、业务逻辑等进行深入解读和分析,帮助开发者更好地理解源码和在其基础上进行二次开发,并提供更多开发思路和技巧。 总之,本资源适合对SpringBootVue、健身房预约管理系统开发有一定基础的开发者学习和参考。沁园健身房预约系统的设计思路、技术实现和业务逻辑等方面都具有高参考价值,为开发者提供了实践和实现健身房预约管理的宝贵经验和思路,并可推广到其他类型的预约系统,如美容美发预约、餐厅预约等。
以下是一个简单的场地预约和缴费的 C# 代码示例: ```csharp using System; using System.Collections.Generic; namespace SportsBookingSystem { // 场馆类别枚举 enum VenueType { BasketballCourt, BadmintonCourt, SwimmingPool, Gym } // 预约状态枚举 enum BookingStatus { Available, Booked, Paid } // 场地类 class Venue { public VenueType Type { get; set; } public int Capacity { get; set; } public decimal Price { get; set; } public List<Booking> Bookings { get; set; } public Venue(VenueType type, int capacity, decimal price) { Type = type; Capacity = capacity; Price = price; Bookings = new List<Booking>(); } // 预约场地 public Booking Book(DateTime startTime, DateTime endTime, int numOfPeople) { if (numOfPeople > Capacity) // 容量不足 { return null; } foreach (Booking booking in Bookings) { if (startTime >= booking.StartTime && startTime < booking.EndTime || endTime > booking.StartTime && endTime <= booking.EndTime) // 时间冲突 { return null; } } Booking newBooking = new Booking(startTime, endTime, numOfPeople); Bookings.Add(newBooking); return newBooking; } } // 预约类 class Booking { public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public int NumOfPeople { get; set; } public BookingStatus Status { get; set; } public Booking(DateTime startTime, DateTime endTime, int numOfPeople) { StartTime = startTime; EndTime = endTime; NumOfPeople = numOfPeople; Status = BookingStatus.Booked; } // 缴费 public void Pay() { Status = BookingStatus.Paid; } } // 场馆管理类 class VenueManager { private List<Venue> venues; public VenueManager() { venues = new List<Venue>(); venues.Add(new Venue(VenueType.BasketballCourt, 10, 50)); venues.Add(new Venue(VenueType.BadmintonCourt, 8, 30)); venues.Add(new Venue(VenueType.SwimmingPool, 50, 100)); venues.Add(new Venue(VenueType.Gym, 20, 80)); } // 预约场地 public Booking BookVenue(VenueType type, DateTime startTime, DateTime endTime, int numOfPeople) { foreach (Venue venue in venues) { if (venue.Type == type) { return venue.Book(startTime, endTime, numOfPeople); } } return null; } // 缴费 public void PayBooking(Booking booking) { booking.Pay(); } } // 测试程序 class Program { static void Main(string[] args) { VenueManager venueManager = new VenueManager(); // 预约篮球场 Booking basketballBooking = venueManager.BookVenue(VenueType.BasketballCourt, new DateTime(2021, 6, 1, 10, 0, 0), new DateTime(2021, 6, 1, 12, 0, 0), 8); if (basketballBooking != null) { Console.WriteLine("预约成功!场地类型:" + basketballBooking.Type + ",开始时间:" + basketballBooking.StartTime + ",结束时间:" + basketballBooking.EndTime + ",人数:" + basketballBooking.NumOfPeople); venueManager.PayBooking(basketballBooking); Console.WriteLine("缴费成功!"); } else { Console.WriteLine("预约失败!"); } // 预约游泳池 Booking swimmingBooking = venueManager.BookVenue(VenueType.SwimmingPool, new DateTime(2021, 6, 2, 14, 0, 0), new DateTime(2021, 6, 2, 16, 0, 0), 30); if (swimmingBooking != null) { Console.WriteLine("预约成功!场地类型:" + swimmingBooking.Type + ",开始时间:" + swimmingBooking.StartTime + ",结束时间:" + swimmingBooking.EndTime + ",人数:" + swimmingBooking.NumOfPeople); venueManager.PayBooking(swimmingBooking); Console.WriteLine("缴费成功!"); } else { Console.WriteLine("预约失败!"); } Console.ReadLine(); } } } ``` 代码使用了类、枚举和列表等 C# 的基本语法,实现了场地预约和缴费的功能。需要注意的是,代码的时间、人数和价格等参数都是根据实际情况进行设置的,具体实现时需要根据需求进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这里是杨杨吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值