天天看点

瑞吉外卖之 redis优化缓存

前言

👏作者简介:我是笑霸final,一名热爱技术的在校学生。

📝个人主页:​​个人主页1​​​ || 笑霸final的主页2

📕系列专栏::本文写在java专栏

📧如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀

🔥如果感觉博主的文章还不错的话,👍点赞👍 + 👀关注👀 + 🤏收藏🤏

🐉获取代码 访问gitee:​​gitee链接​​

文章目录

  • ​​一、环境搭建​​
  • ​​二、缓存验证码​​
  • ​​三、缓存菜品数据​​
  • ​​四、用springCache 优化 套餐数据​​
  • ​​springCache的使用步骤​​
  • ​​缓存套餐数据​​

一、环境搭建

  • 加入maven坐标
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>      
  • 配置文件
spring:
  redis:
    host: **************** #ip地址
    port: 6379 #端口号
    password: ***********  #密码
    database: 0
    jedis: #连接池
      pool:
        max-active: 20  #最大连接数,负值表示没有限制,默认8
        max-wait: -1    #最大阻塞等待时间,负值表示没限制,默认-1
        max-idle: 4     #最大空闲连接,默认8
        min-idle: 0     #最小空闲连接,默认0      
  • 配置类(不是必须的)

    用来设置序列化的

package com.xbfinal.reggie.config;

import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @autor 笑霸fianl~
 * 欢迎访问GitHub:https://github.com/XBfinal
 * 欢迎访问Gitee:https://gitee.com/XBfianl
 * 欢迎访问CSDN:https://blog.csdn.net/weixin_52062043
 */
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

        //设置键的序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置值的序列化方式
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }

}      

二、缓存验证码

思路:
  • 在服务端​

    ​UserController​

    ​ 中注入 RedisTemplate对象,用来操作redis
@Autowired
    private RedisTemplate redisTemplate;      
  • 在服务端​

    ​UserController​

    ​ 的sendMsg方法中 讲随机生成的验证码缓存到redis中,并设置有效期5分钟。
//将生成的验证码缓存到redis中,并设置有效期5分钟
                redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);      
  • 在服务端​

    ​UserController​

    ​的login方法中,从redis获取验证码。登陆成功就删除redis中的验证码
//从redis中获取缓存的验证码
        Object codeInsession = redisTemplate.opsForValue().get(phone);      
//如果登陆成功就删除验证码
            redisTemplate.delete(phone);      
  • 代码
@RestController
@RequestMapping("/user")
@Slf4j
public class Usercontroller {

    @Autowired
    private UserSerice userSerice;

    @Autowired
    private JavaMailSender mailSender;

    @Autowired
    private RedisTemplate redisTemplate;

    @Value("${spring.mail.username}")
    private String MyFrom;


    /**
     * 发送验证码
     * @param user
     * @return
     */
    @PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user,
                             HttpSession session){
        log.info("R<String> sendMsg()进来了");
        //获取手机号
        final String phone = user.getPhone();//就不修改实体类了把邮件写进 Phone

        //判断手机号不为空
        if(StringUtils.isNotEmpty(phone)) {
            //生成4位验证码
            final String code =
                    ValidateCodeUtils.generateValidateCode(4).toString();
            System.out.println("==========");
            System.out.println(code);//验证码
            System.out.println("==========");
            //***************************************************/
            //创建简单邮件消息
            SimpleMailMessage message = new SimpleMailMessage();
            message.setFrom(MyFrom);//用自己的邮件发
            //谁要接收
            message.setTo(phone);
            //邮件标题
            message.setSubject("验证码");
            //邮件内容
            message.setText("【笑霸final】你的验证码为:"+code);//
            try {
                mailSender.send(message);
                //需要保存一下验证码,后面用来验证
                //session.setAttribute(phone, code);

                //将生成的验证码缓存到redis中,并设置有效期5分钟
                redisTemplate.opsForValue().set(phone,code,1, TimeUnit.MINUTES);

                return R.success("发送成功");
            } catch (MailException e) {
                e.printStackTrace();
                return R.error("短信发送失败");
            }
        }
        return R.error("短信发送失败");
    }

    /**
     * 登陆
     * @param
     * @param session
     * @return
     */
    @PostMapping("login")
    public R<User> login(@RequestBody Map map,
                           HttpSession session){

        //获取邮箱和验证码
        final String phone = map.get("phone").toString();
        final String code = map.get("code").toString();
        //获取session的验证码并比对
        //final Object codeInsession = session.getAttribute(phone);

        //从redis中获取缓存的验证码
        Object codeInsession = redisTemplate.opsForValue().get(phone);

        if(codeInsession!=null && codeInsession.equals(code)){
            //登陆成功
            //查数据库,没有就存入数据库
            LambdaQueryWrapper<User> queryWrapper
                    =new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone,phone);
             User user = userSerice.getOne(queryWrapper);
            if(user == null){
                //新用户 入库
                 user = new User();
                 user.setPhone(phone);
                 user.setStatus(1);
                userSerice.save(user);
            }else if(user.getStatus()==0){
                //账号被禁用
                return R.error("账号被禁用");
            }
            //成功存入session
            session.setAttribute("user",user.getId());

            //如果登陆成功就删除验证码
            redisTemplate.delete(phone);

            return R.success(user);
        }
        return R.error("验证码和手机不匹配");
    }
}      

三、缓存菜品数据

思路
  • 当然 还是要中注入​

    ​RedisTemplate​

    ​对象
  • 改造​

    ​dishController​

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

    ​dishController​

    ​的save和update方法,加入清理缓存逻辑(保证数据一致)
@GetMapping("/list")
    public R<List<DishDto>> listR(Dish dish){
        List<DishDto> dtoList=null;
        //先构造key
        String key="dish"+dish.getCategoryId()+"_"+dish.getStatus();

        //先从redis获取缓存数据,如果存在直接返回
        dtoList= (List<DishDto>)redisTemplate.opsForValue().get(key);
        if(dtoList!=null){
                //说明存在
            log.info("在redis中查询的数据");
            return R.success(dtoList);

        }


        //查询条件对象
        LambdaQueryWrapper<Dish> lambdaQueryWrapper
                = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
        //添加起售状态的条件
        lambdaQueryWrapper.eq(Dish::getStatus,1);
        //添加排序
        lambdaQueryWrapper.orderByAsc(Dish::getSort).orderByAsc(Dish::getUpdateTime);

        final List<Dish> list = dishService.list(lambdaQueryWrapper);
        dtoList  = list.stream().map((item) -> {
            DishDto dishDto = new DishDto();

            BeanUtils.copyProperties(item,dishDto);

            Long categoryId = item.getCategoryId();//分类id
            //根据id查询分类对象
            Category category = categoryService.getById(categoryId);

            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
            final Long id = item.getId();//当前菜品id
            LambdaQueryWrapper<DishFlavor>lambdaQueryWrapper1
                    =new LambdaQueryWrapper<>();
            lambdaQueryWrapper1.eq(DishFlavor::getDishId,id);
            final List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper1);
            dishDto.setFlavors(dishFlavorList);

            return dishDto;
        }).collect(Collectors.toList());

        //redis不存在,则查询数据库并加入缓存,设置1小时的缓存时间
        redisTemplate.opsForValue().set(key,dtoList,1, TimeUnit.HOURS);

        return R.success(dtoList);
    }      
注意 :使用缓存的过程中要保证数据库和redis中的数据一致。当数据库发生变化时,需要及时清理缓存数据,在​

​save和update方法​

​添加如下代码
  • 清理所有菜品的缓存
//清理所有菜品的缓存
       final Set keys = redisTemplate.keys("dish_*");
       redisTemplate.delete(keys);      
  • 精确清理
//精确清理
        String key="dish"+dishDto.getCategoryId()+"_"+dishDto.getStatus();
        redisTemplate.delete(key);      

四、用springCache 优化 套餐数据

springCache的使用步骤

  • 第一步:导入springCache的maven坐标
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-cache</artifactId>
   <version>2.7.0</version>
</dependency>      
  • 第二步:配置yml文件
spring:
  cache:
    redis:
      time-to-live: 1800000 #设置缓存的有效期
    type:      
  • 第三步:在启动类上加入​

    ​@EnableCaching​

    ​注解,开启缓存功能
  • 第四步:在controller的方法上加入相对应的注解进行缓存操作

常用注解

@CacheEvict :应用到​​

​移除数据​

​​的方法上,如删除方法,调用方法时会从缓存中移除相应的数据

@Cacheable:应用到​​

​读取数据​

​​的方法上,即可缓存的方法,如查找方法,先从缓存中读取,如果没有再调用相应方法获取数据,然后把数据添加到缓存中

@CachePut :应用到​​

​写数据​

​的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存

Cacheable的参数如下(其他的注解也大同小异)

  • ​value、cacheNames​

    ​:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
  • ​key​

    ​:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = “#p0”):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档
  • ​condition​

    ​:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = “#p0”, condition = “#p0.length() < 3”),表示只有当第一个参数的长度小于3的时候才会被缓存。
  • ​unless​

    ​:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
  • keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的

缓存套餐数据

public class R<T> implements Serializable      
  • 在​

    ​SetmealController​

    ​​的list方法上加入​

    ​@Cacheable​

  • 在​

    ​SetmealController​

    ​​的save和delete、saveUpdate、stop方法上加入​

    ​@CacheEvict​

@CacheEvict(value = "setmealCache" ,allEntries = true)//allEntries = true清理setmealCache下所有的缓存
  
  @Cacheable(value = "setmealCache",key = "#setmeal.categoryId")