瑞吉外卖优化

本文讲述了在SpringBoot应用中如何通过Redis缓存解决高并发下的性能问题,包括缓存短信验证码、菜品和套餐数据,以及如何配置和使用`@Cacheable`和`@CacheEvict`注解进行缓存控制。

1.缓存问题

  • 用户数量多,系统访问量大频繁访问数据库,系统性能下降,用户体验差

2.导入依赖 和配置

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
spring:  
  redis:
    host: 自己的地址
    password: 自己的密码
    database: 0
    port: 6379

3.key序列化配置类

@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.setHashValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

4.缓存短信验证码

4.1实现思路

1.引入RedisTemplate

2.设置验证码缓存到redis

3.登陆成功删除redis中的验证码

4.2代码 

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;


    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 发送手机短信验证码
     *
     * @param user
     * @return
     */
    @PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user, HttpSession session) {
        //1.获取手机号
        String phone = user.getPhone();
        if (StringUtils.isNotEmpty(phone)) {

            //2.生成四位的验证码
            String code = ValidateCodeUtils.generateValidateCode(4).toString();

            //查看验证码
            log.info("code={}", code);

            //3.调用阿里云短信服务完成发送
            //若要真的发送,执行下面
//            SMSUtils.sendMessage("签名","",phone,code);


            //4.生成的验证码保存到session
//            session.setAttribute(phone, code);

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

            return R.success("发送验证码发送成功");

        }
        return R.success("发送失败");
    }


    /**
     * 移动端登录
     */
    @PostMapping("/login")
    public R<User> login(@RequestBody Map map, HttpSession session) {
        //1.获取手机号
        String phone = map.get("phone").toString();

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

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

        //优化:从redis中获取缓存验证码
        Object codeInSession = redisTemplate.opsForValue().get(phone);

        //4.如果比对成功,判断手机号是否在用户表中
        if (codeInSession != null && codeInSession.equals(code)) {//登陆成功
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone, phone);
            User user = userService.getOne(queryWrapper);
            if (user == null) {//新用户
                //5.如果不在用户表,则自动完成注册
                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);
        }
        return R.error("登陆失败");
    }
}

4.3测试

已经存到redis中

5.缓存菜品数据

5.1实现思路

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

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

注意:在使用缓存过程中,要注意保证数据库中的数据和缓存中的数据一致,如果数据库中的数据发生变化,需要及时清理缓存数据。

5.2代码风暴

   @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish) {
        List<DishDto> dishDtoList = null;
        //动态构造key
        String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();

        //1.先从redis中获取缓存数据

        dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);
        if (dishDtoList != null) {
            //2.如果存在,直接返回,无需查询数据库
            return R.success(dishDtoList);

        }

        //3.如果不存在,查询数据库,再缓存到redis
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
        queryWrapper.eq(Dish::getStatus, 1);
        queryWrapper.orderByDesc(Dish::getSort).orderByAsc(Dish::getUpdateTime);

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

        dishDtoList = list.stream().map((item) -> {
            DishDto dishDto = new DishDto();
            //拷贝
            BeanUtils.copyProperties(item, dishDto);

            Long categoryId = item.getCategoryId();//分类id
            Category category = categoryService.getById(categoryId);
            if (category != null) {
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
            Long dishId = item.getId();
            LambdaQueryWrapper<DishFlavor> queryWrapper1 = new LambdaQueryWrapper<>();
            queryWrapper1.eq(DishFlavor::getDishId, dishId);
            List<DishFlavor> dishFlavors = dishFlavorService.list(queryWrapper1);
            dishDto.setFlavors(dishFlavors);
            return dishDto;
        }).collect(Collectors.toList());

        //查询完数据库,在将数据缓存到redis
        redisTemplate.opsForValue().set(key,dishDtoList,60, TimeUnit.MINUTES);

        return R.success(dishDtoList);
    }

 5.3测试

再次查询时就是从redis缓存中获取了

5.3更新菜品时删除对应的redis缓存 

@PutMapping()
    public R<String> put(@RequestBody DishDto dishDto) {
        dishService.updateWithFlavor(dishDto);

        //清理所有菜品的缓存数据
//        Set keys = redisTemplate.keys("dish_*");
//        redisTemplate.delete(keys);

        //精确清理,清理某个分类下面的缓存
        String key="dish_"+dishDto.getCategoryId()+"_1";
        redisTemplate.delete(key);
        return R.success("修改成功");
    }

5.4添加菜品时删除对应的redis缓存

 @PostMapping()
    public R<String> save(@RequestBody DishDto dishDto) {
        dishService.saveWithFlavor(dishDto);
        //清理所有菜品的缓存数据
//        Set keys = redisTemplate.keys("dish_*");
//        redisTemplate.delete(keys);

        //精确清理,清理某个分类下面的缓存
        String key="dish_"+dishDto.getCategoryId()+"_1";
        redisTemplate.delete(key);

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

6.缓存套餐数据【注解】

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

6.1引入依赖 

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

6.2开启注解缓存

@Slf4j
@SpringBootApplication
@ServletComponentScan
@EnableTransactionManagement
@EnableCaching //开启注解缓存
public class ProductRuijiApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductRuijiApplication.class, args);
        log.info("程序启动成功");
    }
}

6.3查询套餐时加入缓存

    @Cacheable(value = "setmealCache",key = "#setmeal.categoryId+'_'+#setmeal.status")
    @GetMapping("/list")
    public R<List<Setmeal>> list( Setmeal setmeal) {
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(setmeal.getCategoryId() != null, Setmeal::getCategoryId, setmeal.getCategoryId());
        queryWrapper.eq(setmeal.getStatus() != null, Setmeal::getStatus, setmeal.getStatus());
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);
        List<Setmeal> list = setmealService.list(queryWrapper);
        return R.success(list);
    }

6.4删除套餐时删除缓存

   @CacheEvict(value = "setmealCache",allEntries = true)
    @DeleteMapping
    public R<String> delete(@RequestParam List<Long> ids) {
        setmealService.removeWithDish(ids);
        return R.success("删除套餐成功");
    }

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会敲代码的小张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值