基于Spring boot的外卖项目瑞吉外卖Day10使用Redis缓存技术

2023-04-12 09:41:25 浏览数 (2)

环境搭建

一、maven坐标

在项目的pom.xml文件中导入spring data redis的maven坐标

代码语言:javascript复制
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
二、配置文件

在项目的application.yml中加入redis相关配置:

代码语言:javascript复制
spring
 Fedis:
  host:172.17.2.94
  port:6379
  password: root@123456
  database: 0
三、配置类

在项目中加入配置类RedisConfig

代码语言:javascript复制
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
	@Bean
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory 		connectionFactory) {
		RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
		//默认的Key序列化器为:JdkSerializationRedisSerializer
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setConnectionFactory(connectionFactory);
		return redisTemplate;
	}
}

Spring Cache

一、Spring Cache 介绍

​ Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。 Spring Cache提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不同的缓存技术。CacheManager是Spring提供的各种缓存技术抽象接口。针对不同的缓存技术需要实现不同的CacheManager:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JnqNcAsy-1681044555781)(C:Users86138AppDataRoamingTyporatypora-user-imagesimage-20230321210236763.png)]

二、Spring Cache常见注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lsUqm1dc-1681044555782)(C:Users86138AppDataRoamingTyporatypora-user-imagesimage-20230321210437455.png)]

三、Spring Cache 使用方式

在Spring Boot项目中使用Spring Cache的操作步骤(使用redis缓存技术)

  1. 导入maven坐标
  2. 配置application.yml
  3. 在启动类上加入@EnableCaching注解,开启缓存注解功能
  4. 在Controller的方法上加入@Cacheable、@CacheEvict等注解,进行缓存操作

缓存短信验证码

前面已经实现了移动端手机验证码登录,随机生成的验证码我们是保存在HttpSession中的。现在需要改造为将验证码缓存在Redis中 1、在服务端UserController中注入RedisTemplate对象,用于操作Redis

代码语言:javascript复制
    @Autowired
    private RedisTemplate template;

2、在服务端UserController的sendMsg方法中,将随机生成的验证码缓存到Redis中,并设置有效期为5分钟

代码语言:javascript复制
/**
     * 发送手机短信验证码
     * @param user
     * @param session
     * @return
     */
           @PostMapping("/sendMsg")
        public R<String> sendMsg(@RequestBody User user, HttpSession session){
            String phone=user.getPhone();
            if(StringUtils.isNotEmpty(phone)) {
                String code = ValidateCodeUtils.generateValidateCode(4).toString();
                log.info("code={}", code);
                //调用阿里云提供的短信服务API完成发送短信
                SMSUtils.sendMessage("阿里云短信测试",
                          "SMS_154950909",phone,code);

                //需要将生成的验证码保存到Session
                //session.setAttribute(phone, code);
                //将生成的验证码保存到Redis中,并且设置有效期为5分钟
                redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);

                return R.success("手机验证码短信发送成功");
            }

        return R.error("短信发送失败");
    }

3、在服务端UserController的login方法中,从Redis中获取缓存的验证码,如果登录成功则删除Redis中的验证码

代码语言:javascript复制
 @PostMapping("/login")
    public R<User> login(@RequestBody Map map, HttpSession session) {
        log.info(map.toString());
        //获取手机号
        String phone = map.get("phone").toString();

        //获取验证码
        String code = map.get("code").toString();

        //从Session中获取保存的验证码
        //Object codeInSession = session.getAttribute(phone);

        //从Redis中获取保存的验证码
        Object codeInSession =redisTemplate.opsForValue().get(phone);

        //进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
        if (codeInSession != null && codeInSession.equals(code)) {
            //如果能够比对成功,说明登录成功

            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone, phone);

            User user = userService.getOne(queryWrapper);
            if (user == null) {
                //判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
                user = new User();
                user.setPhone(phone);
                user.setStatus(1);
                userService.save(user);
            }
            session.setAttribute("user", user.getId());
            
            //如果用户登录成功,删除redis中缓存数据
            redisTemplate.delete(phone);
            
            return R.success(user);
        }

缓存菜品数据

前面实现了移动端菜品查看功能,对应的服务端方法为DishController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。现在需要对此方法进行缓存优化,提高系统的性能。

改造DishController的list方法,先从Redis中获取菜品数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询到的菜品数据放入Redis。

代码语言:javascript复制
/**
     * 根据条件查询对应的菜品数据
     * @param dish
     * @return
     */
    @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish) {
        log.info("dish:{}", dish);

        List<DishDto> dishDtos = null;

        String key="dish_" dish.getCategoryId() "_" dish.getStatus();

        //从redis中获取缓存数据
        dishDtos =(List<DishDto>) redisTemplate.opsForValue().get(key);

        //如果不为空,直接返回
        if(dishDtos != null){
            return R.success(dishDtos);
        }

        //条件构造器
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isNotEmpty(dish.getName()), Dish::getName, dish.getName());
        queryWrapper.eq(null != dish.getCategoryId(), Dish::getCategoryId, dish.getCategoryId());
        //添加条件,查询状态为1(起售状态)的菜品
        queryWrapper.eq(Dish::getStatus,1);
        queryWrapper.orderByDesc(Dish::getUpdateTime);

        List<Dish> dishs = dishService.list(queryWrapper);

        dishDtos = dishs.stream().map(item -> {
            DishDto dishDto = new DishDto();
            BeanUtils.copyProperties(item, dishDto);
            Category category = categoryService.getById(item.getCategoryId());
            if (category != null) {
                dishDto.setCategoryName(category.getName());
            }
            LambdaQueryWrapper<DishFlavor> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(DishFlavor::getDishId, item.getId());

            dishDto.setFlavors(dishFlavorService.list(wrapper));
            return dishDto;
        }).collect(Collectors.toList());
        
        //如果不存在,需要查询数据库,将数据缓存到redis中
        redisTemplate.opsForValue().set(key,dishDtos,60, TimeUnit.MINUTES);

        return R.success(dishDtos);
    }

改造DishController的save和update方法,加入清理缓存的逻辑

代码语言:javascript复制
/**
     * 新增菜品
     * @param dishDto
     * @return
     */
    @PostMapping
    public R<String> save(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());
        dishService.saveWithFlavor(dishDto);
        //清理所有菜品的缓存数据
//        Set keys = redisTemplate.keys("dish_*");
//        redisTemplate.delete(keys);

        //清理某个分类下面的菜品缓存数据
        String key="dish_" dishDto.getCategoryId() "_1";
        redisTemplate.delete(key);
        return R.success("新增菜品成功");
    }


    /**
     * 新增菜品,并清理redis缓存
     * @param dishDto
     * @return
     */
    @PutMapping
    public R<String> update(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());
        dishService.updateWithFlavor(dishDto);
        //清理所有菜品的缓存数据
//        Set keys = redisTemplate.keys("dish_*");
//        redisTemplate.delete(keys);

        //清理某个分类下面的菜品缓存数据
        String key="dish_" dishDto.getCategoryId() "_1";
        redisTemplate.delete(key);

        return R.success("新增菜品成功");
    }

缓存套餐数据

前面已经实现了移动端套餐查看功能,对应的服务端方法为SetmealController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。现在需要对此方法进行缓存优化,提高系统的性能。

  1. 导入Spring Cache和Redis相关maven坐标
  2. 在application.yml中配置缓存数据的过期时间
  3. 在启动类上加入@EnableCaching注解,开启缓存注解功能
  4. 在SetmealController的list方法上加入@Cacheable注解
  5. 在SetmealController的save和delete方法上加入CacheEvict注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lKBbyRHY-1681044555783)(C:Users86138AppDataRoamingTyporatypora-user-imagesimage-20230322005212058.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-stQkBRSS-1681044555784)(C:Users86138AppDataRoamingTyporatypora-user-imagesimage-20230322005226476.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDUuLh1C-1681044555784)(C:Users86138AppDataRoamingTyporatypora-user-imagesimage-20230322005234804.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pIoUVjPS-1681044555785)(C:Users86138AppDataRoamingTyporatypora-user-imagesimage-20230322005248369.png)]

0 人点赞