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

news2024/11/28 11:49:09

文章目录

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

一、项目演示

项目演示地址: 视频地址

二、项目介绍

项目描述:这是一个基于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://www.coloradmin.cn/o/975941.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅谈Jmeter性能测试流程

不管是Loadrunner还是jmeter进行性能测试&#xff0c;测试流程基本上都是一样的&#xff0c;限制以Jmeter为例分析测试流程&#xff1a; 一、性能测试需求分析 一般而言&#xff0c;被测对象的性能需求&#xff0c;会在用户需求规格说明说中给出&#xff0c;比如单位时间内的…

MySQL主从复制和读写分离搭建

目录 一、主从复制原理 1、MySql支持从复制类型 2、主从复制的原理⭐⭐⭐ 4、mysql主从复制延迟 异步、同步、半同步复制&#xff1a; 二、主从复制实验 1、mysql 时间同步 1.1 主服务设置被同步的时间 1.2 两台从服务器设置时间同步&#xff08;两台服务器一样配置&am…

MySQL基础1——DDL、DML、DQL及DCL的相关概念和操作

MySQL中DDL、DML、DQL及DCL的相关概念和操作 一. SQL通用语法二. SQL分类1. DDL(Data Definition Language)1.1 查询1.1.1 查询所有数据库1.1.2 查询当前数据库1.1.3 查询当前数据库所有表1.1.4 查询表结构1.1.5 查询指定表的建表语句 1.2 创建1.2.1 创建数据库1.2.2 …

【Web】vue开发环境搭建教程(详细)

系列文章 C#底层库–记录日志帮助类 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/124187709 文章目录 系列文章前言一、安装准备1.1 node.js1.2 国内镜像站1.3 Vue脚手架1.4 element ui1.5 Visual Studio Code 二、安装步骤2.1 下载msi安装包2.2 …

【考研数学】高等数学第五模块 —— 级数(1,常数项级数)

文章目录 引言一、常数项级数1.1 基本概念1.2 基本性质1.3 两个重要级数1.3.1 p 级数1.3.2 几何级数 1.4 正项级数及其敛散性判断1.5 交错级数及其审敛法1.6 级数的绝对收敛与条件收敛 写在最后 引言 来攻坚级数了&#xff0c;其实也不用这么畏难&#xff0c;几年前刚接触时没…

【力扣每日一题】2023.9.5 从两个数字数组里生成最小数字

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们两个数字数组&#xff0c;要我们用这两个数组里的元素组成一个数字&#xff0c;这个数字里需要同时拥有两个数组里的至少一个元…

10个最强大的基于生成式AI的3D建模工具

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 在快速发展的技术世界中&#xff0c;人工智能 (AI) 已经改变了游戏规则&#xff0c;尤其是在 3D 对象生成领域。 AI 驱动的 3D 对象生成器彻底改变了我们创建和可视化 3D 模型的方式&#xff0c;使该过程更加高效、准确且可…

Bigemap如何添加历史影像

工具 Bigemap gis office地图软件 BIGEMAP GIS Office-全能版 Bigemap APP_卫星地图APP_高清卫星地图APP 打开软件&#xff0c;然后点击选择地图这个按钮&#xff0c;列表中有个添加按钮点进去选择添加地图的方式。 ​ 第一种方式&#xff1a;通过地图配置文件批量解析添加地…

ipad触控笔是哪几款?开学季平价电容笔推荐

许多人已经开始用iPad写字和画画了。而且现在的iPad&#xff0c;偏重于实用性&#xff0c;他们认为&#xff0c;要让iPad更加的实用&#xff0c;必须要有一个好的电容笔才行。事实上&#xff0c;如果你仅仅是想要用它来做笔记的话&#xff0c;有许多的平替的电容笔&#xff0c;…

Shell常用的几个正则表达式:[:alnum:], [:alpha:], [:upper:], [:lower:], [:digit:] 认知

一&#xff1a;通配符命令简介&#xff1a; 匹配符合相关条件的符号&#xff0c;匹配文件名查找。 通配符类型&#xff1a; *&#xff1a;匹配任意长度的任意字符 &#xff1f;&#xff1a;匹配任意单个字符 []&#xff1a;匹配指定范围内的任意单个字符 [^]&#xff1a;匹配指…

Odoo|5分钟创建自定义的业务系统唯一序列号

在业务操作中&#xff0c;经常会遇到需要生成全局唯一序列号数据的情况&#xff0c;比如订单号、报价单号等。为了确保数据的唯一性和准确性&#xff0c;通常我们会使用Redis或其他分布式锁机制来实现。然而&#xff0c;很多人可能不知道&#xff0c;odoo框架本身提供了一个原生…

各种工具集锦(持续更新ing...)

诸神缄默不语-个人CSDN博文目录 用LLM&#xff08;大规模预训练语言模型&#xff09;或者说AI的&#xff0c;特见我写的这篇博文&#xff1a;在线LLM应用集锦&#xff08;持续更新ing…&#xff09; 本篇博文不会重复该博文中写过的内容。 文章目录 1. 视频制作1. 视频剪辑软件…

CVE-2023-3450:锐捷 RG-BCR860 命令执行漏洞复现

锐捷 RG-BCR860 命令执行漏洞(CVE-2023-3450)复现 0x01 前言 本次测试仅供学习使用&#xff0c;如若非法他用&#xff0c;与本文作者无关&#xff0c;需自行负责&#xff01;&#xff01;&#xff01; 0x02 漏洞描述 Ruijie Networks RG-BCR860是中国锐捷网络&#xff08;R…

标绘一张图系统

一、概况 智慧武装三维电子沙盘是一种结合了智能技术和虚拟现实技术的沙盘模拟系统。它通过使用三维投影技术和交互式触控技术&#xff0c;将实际战场的地形、建筑物、人员等元素以虚拟的形式呈现在沙盘上。 智慧武装三维电子沙盘可以实时获取各种战场数据&#xff0c;并通过智…

python数据分析基础—取某列字符的前几个字符

文章目录 前言取某列前几个字符方法一&#xff1a;[x[:7] for x in data["calling_nbr"]]方法二&#xff1a;data[calling_nbr].str[:7] 前言 在进行数据分析时&#xff0c;有时候我们需要提取单列的前几个字符串进行分析。本文主要讲述针对这种情况处理方法。 取某…

工业互联网龙头企业研祥智能加入 openKylin

导读近日&#xff0c;研祥智能科技股份有限公司&#xff08;以下简称 “研祥智能”&#xff09;签署 openKylin 社区 CLA&#xff08;Contributor License Agreement 贡献者许可协议&#xff09;&#xff0c;正式加入 openKylin 开源社区。 研祥智能于 1993 年 12 月 31 日成立…

mysql建表考虑那些,怎么建

在使用MySQL进行数据库建表时&#xff0c;需要考虑以下几点&#xff1a; 1 数据库设计 在建表前&#xff0c;需要进行数据库设计&#xff0c;包括确定数据库的名称、表的数量、表之间的关系等&#xff0c;这是建表的前提。 2 表的命名规范 建表时需要注意表名的命名规范&am…

基于SpringBoot的Web开发案例过程讲解-项目准备

基于SpringBoot的Web开发案例过程笔记-项目准备 1&#xff09;环境搭建【1】准备数据库表【2】创建Springboot项目并引入相关依赖【3】配置application.properties文件【4】创建相关的包和类 2) 三层架构工作流程3&#xff09;开发规范-Restful4&#xff09;相关的注解5)项目开…

【数据结构与算法】栈

文章目录 前言一&#xff1a;基本概念1.1 介绍1.2 入栈和出栈示意图1.3 栈的应用场景 二&#xff1a;使用数组模拟栈2.1 思路分析2.2 代码实现2.3 测试 三&#xff1a;使用栈模拟中缀表达式计算器3.1 整体思路3.2 验证32*6-2133.2.1 定义栈3.2.2 返回运算符的优先级3.2.3 判断是…

如何通过Instagram群发消息高效拓展客户?

之前小S有跟大家说过关于独立站&#xff0b;Instagram如何高效引流&#xff0c;发现大家都对Instagram的话题挺关注的。Instagram作为全球最受欢迎的社交媒体之一&#xff0c;对于许多商家和营销人员来说&#xff0c;Instagram是一个不可忽视的营销平台&#xff0c;他们可以通过…