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

news/2024/7/7 20:42:43

文章目录

  • 一、项目演示
  • 二、项目介绍
  • 三、运行截图
  • 四、主要代码

一、项目演示

项目演示地址: 视频地址

二、项目介绍

项目描述:这是一个基于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();
                    }
                }
            }
        }
    }

http://lihuaxi.xjx100.cn/news/1529961.html

相关文章

【系统设计系列】 应用层与微服务

系统设计系列初衷 System Design Primer&#xff1a; 英文文档 GitHub - donnemartin/system-design-primer: Learn how to design large-scale systems. Prep for the system design interview. Includes Anki flashcards. 中文版&#xff1a; https://github.com/donnemart…

【数据结构与算法系列3】有序数组的平方 (C++ Python)

给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#xff1a;平方后&#xff0c;数组变为 …

直播 | 丹望医疗王晓林博士“基于微流控的血管化器官/类器官芯片构建及其应用”

类器官模型具有高仿真性&#xff0c;与人体器官有高度相似的组织学特征和功能&#xff0c;尤其在肿瘤模型中能够较好保留肿瘤异质性等优势&#xff0c;在精准医疗及药物筛选等领域具有广泛的应用前景。同时&#xff0c;基于微流控技术的器官芯片能在微流体装置上实现多重微环境…

Maven学习记录

一、Maven是什么 简单来说Maven是一个标准化的java管理和构建工具&#xff0c;它提供了一系列规范&#xff0c;包括项目结构&#xff0c;构建流程&#xff08;编译&#xff0c;测试&#xff0c;打包&#xff0c;发布……&#xff09;&#xff0c;依赖管理等。 标准化就是定下…

【数据库】数据库的一级二级三级封锁协议

背景&#xff1a;秋招做笔试题被问住了&#xff0c;数据库课上没学过这个啊…… 因为笔者也是初学&#xff0c;如果有错误&#xff0c;欢迎发评论批评指正。 数据库的一级、二级、三级封锁协议应该是指适用于当前读的加锁策略。 一级&#xff1a;读不加锁&#xff1b;写加排…

【Linux问题】日期校准

问题 请求阿里云对象存储返回 The difference between the reguest time and the current time is too large. 规定时间和当前时间之间的差异太大。 由于虚拟机出现问题导致服务器时间不准 正常的服务器时间 异常的服务器时间 设置一下时间就好 校准时间 安装 yum insta…

LeetCode刷题笔记【31】:动态规划专题-3(整数拆分、不同的二叉搜索树)

文章目录 前置知识343. 整数拆分题目描述解题思路代码进一步优化 96.不同的二叉搜索树题目描述解题思路代码优化改进 总结 前置知识 参考前文 参考文章&#xff1a; LeetCode刷题笔记【29】&#xff1a;动态规划专题-1&#xff08;斐波那契数、爬楼梯、使用最小花费爬楼梯&…

【美团3.18校招真题2】

大厂笔试真题网址&#xff1a;https://codefun2000.com/ 塔子哥刷题网站博客&#xff1a;https://blog.codefun2000.com/ 最多修改两个字符&#xff0c;生成字典序最小的回文串 提交网址&#xff1a;https://codefun2000.com/p/P1089 由于字符串经过修改一定为回文串&#x…