赞
踩
项目演示地址: 视频地址
项目描述:这是一个基于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(); } } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。