当前位置:   article > 正文

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, "保存成功!");
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

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, "登录成功!");
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/676011
推荐阅读
相关标签
  

闽ICP备14008679号