苍穹外卖-day07

缓存菜品

问题说明:

  • 现在遇到一个问题,用户端小程序展示的菜品数据都是通过查询数据库获取的,如果用户端访问量比较大,数据库访问压力随之增大

实现思路:

image-20250331203859655

缓存逻辑分析:

image-20250331204849692

修改前:用户每一次访问菜单列表,都会执行sql语句造成资源浪费

image-20250331232333807

  • 用户端小程序在每次访问时,都会执行sql语句查询查询从磁盘中获取,一遍,如果是多个用户同时查看,那么就会造成资源浪费,这时候可以将这种查询的资源使用redis存储在内存中

代码实现

  • 从redis中获取数据,
  • 如果redis中有数据则直接返回
  • 如果redis中没有数据,那么再去查询数据,然后存储redis

user/DishController.java

 @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<DishVO>> list(Long categoryId) {
        //从redis中获取菜品列表
        //根据菜品id生成redis的key
        String key =  "dish_"+categoryId;

        //从redis中获取菜品列表
        List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);

        //如果redis中有数据,那么直接返回
        if (list != null && list.size() > 0){
            log.info("从redis中获取菜品列表");
            return Result.success(list);
        }

        //如果没有数据,就从mysql中查数据,然后插入redis
        Dish dish = new Dish();
        dish.setCategoryId(categoryId);
        dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品

        list = dishService.listWithFlavor(dish);

        //将数据插入redis中
        redisTemplate.opsForValue().set(key,list);

        return Result.success(list);
    }

功能测试& 提交

测试成功,现在用户重复查看列表时会直接从redis缓存中取数据

image-20250331232413845

image-20250331232547929

缓存菜品清理数据

数据一致性问题

  • 问题分析

现在有一个新问题:旧的数据已经存储到了redis中,用户反复访问时,只会从内存中获取固定的数据,而不会刷新,所以现在要在更改数据时(新增,修改,删除)时 删除redis缓存,将修改后的数据重新添加到redis

:上述操作只会在管理端出现,因此需要转到admin的controller进行修改

⚠️:可不可以使用切面加注解方式解决,同公共字段填充 ,没有必须,只需要在后端dishController里相关修改删除新增方法中清除缓存即可

代码实现

admin/dishController.java

public class DishController {

    @Autowired
    private DishService dishService;

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 新增菜品
     *
     * @param dishDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品:{}",dishDTO);
        dishService.saveWithFlavor(dishDTO);
        //清理缓存
        CleanCache("dish_"+dishDTO.getCategoryId());

        return Result.success();
    }


    /**
     * 菜品批量删除
     *
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("菜品批量删除")
//    RequestParam注解自动讲前端传的字符串,转换为集合
    public Result delete(@RequestParam List<Long> ids) {
        log.info("菜品批量删除:{}", ids);
        dishService.deleteBatch(ids);

        //清理缓存
        // TODO: lulufish: 有必要删除的时候清理缓存吗?毕竟删除的前提是禁用这个菜品,既然禁用了,那么就不会再被用户查询到了,等修改的时候再一并清理缓存不就好了吗?
        //为了确保缓存一致性  还是都删一下
        CleanCache("dish_*");
        return Result.success();
    }


    /**
     * 根据id修改菜品
     *
     * @param dishDTO
     * @return
     */
    @PutMapping
    @ApiOperation("根据id修改菜品")
    public Result update(@RequestBody DishDTO dishDTO) {
        log.info("修改菜品:{}", dishDTO);
        dishService.updateWithFlavor(dishDTO);

        //为了确保缓存一致性  还是都删一下
        CleanCache("dish_*");
        return Result.success();
    }


    /**
     * 启用或禁用菜品
     * @param status
     * @param id
     * @return
     */
    @PostMapping("/status/{status}")
    @ApiOperation("启用或禁用菜品")
    public Result status(@PathVariable Integer status,Long id){
        dishService.startOrStop(status,id);

        //为了确保缓存一致性  还是都删一下
        CleanCache("dish_*");
        return Result.success();
    }

    //抽取一个清理redis缓存的共用方法
    private void CleanCache(String pattern){
        Set keys = redisTemplate.keys(pattern);
        redisTemplate.delete(keys);
    }

}

这里套餐查询时,也需要添加缓存,只有没有添加,现在补上

user/SetmealController.java

 /**
     * 条件查询
     *
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询套餐")
    public Result<List<Setmeal>> list(Long categoryId) {
        
        Setmeal setmeal = new Setmeal();
        setmeal.setCategoryId(categoryId);
        setmeal.setStatus(StatusConstant.ENABLE);
        
        //套餐管理,先从redis中取套餐数据
        String key = "dish_"+categoryId;
        List<Setmeal> list  =  (List<Setmeal>) redisTemplate.opsForValue().get(key);
        //如果有直接返回
        if (list !=null && list.size() > 0){
            log.info("从redis中获取套餐数据");
            return Result.success(list);
        }

        //没有在从数据库中查询然后添加到redis中
        list = setmealService.list(setmeal);
        redisTemplate.opsForValue().set(key,list);
        return Result.success(list);
        
    }

Spring Cache实现缓存套餐

使用spring cache

介绍:

image-20250401132949015

  • 快速使用

image-20250401133047231

常用注解

image-20250401133225396

具体实现思路

使用springCache完成套餐管理的redis缓存

image-20250401185438615

代码实现

1.导入依赖

此项目不用导入,

<!-- 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.3</version>
</dependency>
  • user\SetmealController.java 用户端套餐管理
@RestController("userSetmealController")
@RequestMapping("/user/setmeal")
@Api(tags = "C端-套餐浏览接口")
@Slf4j
public class SetmealController {

    @Autowired
    private SetmealService setmealService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据分类id查询套餐
     *
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询套餐")
    @Cacheable(cacheNames = "setmealCache",key = "#categoryId")
    public Result<List<Setmeal>> list(Long categoryId) {
        log.info("使用springCache查询套餐");
        Setmeal setmeal = new Setmeal();
        setmeal.setCategoryId(categoryId);
        setmeal.setStatus(StatusConstant.ENABLE);

        //没有在从数据库中查询然后添加到redis中
        List<Setmeal>list = setmealService.list(setmeal);
        return Result.success(list);

    }

    /**
     * 根据套餐id查询包含的菜品列表
     *
     * @param id
     * @return
     */
    @GetMapping("/dish/{id}")
    @ApiOperation("根据套餐id查询包含的菜品列表")
    @Cacheable(cacheNames = "setmealCache",key = "#id")
    public Result<List<DishItemVO>> dishList(@PathVariable("id") Long id) {
        //如果已经在缓存中查询了,就不会执行此方法,包括log日志
        log.info("使用springCache 在redis中查询菜品列表");
        List<DishItemVO> list = setmealService.getDishItemById(id);
        System.out.println(list);
        return Result.success(list);
    }
}
  • admin\SetmealController.java管理端套餐管理
import java.util.List;


@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐管理接口")
@Slf4j
public class SetmealController {

    @Autowired
    private SetmealService setmealService;


    /**
     * 新增套餐及其对应菜品关系数据
     *
     * @param setmealDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增套餐")
    //@CachePut 更新缓存的值,但不会清除旧数据。故使用@CacheEvict
    @CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")
    public Result save(@RequestBody SetmealDTO setmealDTO){
        log.info("新增套餐:{}",setmealDTO);
        setmealService.saveWithDish(setmealDTO);
        return Result.success();
    }


    /**
     * 套餐分页查询
     *
     * @param setmealPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("套餐分页查询")
    //每个用户只查一次不需要 缓存到redis
    public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO){

        log.info("套餐分页查询信息:{}",setmealPageQueryDTO);
        PageResult pageResult =  setmealService.pageQuery(setmealPageQueryDTO);
        return Result.success(pageResult);

    }


    /**
     * 批量删除套餐
     *
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("批量删除套餐")
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result delete(@RequestParam List<Long> ids){
        log.info("删除套餐:{}", ids);
        setmealService.deleteBatch(ids);
        return Result.success();
    }



    /**
     * 修改套餐
     *
     * @param setmealDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改套餐")
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result update(@RequestBody SetmealDTO setmealDTO){
        log.info("修改套餐:{}",setmealDTO);
        setmealService.update(setmealDTO);
        return Result.success();
    }


    /**
     * 套餐启用 & 禁用
     *
     * @param status
     * @param id
     * @return
     */
    @PostMapping("/status/{status}")
    @ApiOperation("套餐启用禁用")
    @CachePut(cacheNames = "setmealCache",key = "#id")
    public Result status(@PathVariable Integer status,Long id){
        log.info("修改套餐为{}状态",status == StatusConstant.ENABLE ? "启售" : "停售");
        setmealService.startOrStop(status,id);
        return Result.success();
    }

}

image-20250401225634026image-20250401225642664

功能测试

测试成功!!!

添加购物车

需求分析和业务要求

image-20250402131346749

image-20250402131354108

  • 接口设计

image-20250402131505507

  • 数据库表设计
image-20250402131558653

image-20250402131625788

代码开发

ShoppingCartController

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "C端购物车相关接口")
public class ShoppingCartController {

    @Autowired
    private ShoppingCartService shoppingCartService;


    /**
     * 添加购物车
     *
     * @param shoppingCartDTO
     * @return
     */
    @PostMapping("/add")
    @ApiOperation("添加购物车")
    public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){
        log.info("添加购物车:{}",shoppingCartDTO);
        shoppingCartService.addShoppingCart(shoppingCartDTO);

        return Result.success();
    }
}

serive

public interface ShoppingCartService {

    /**
     * 添加购物车
     *
     * @param shoppingCartDTO
     */
    void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}

//impl

@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private SetmealMapper setmealMapper;

    /**
     * 添加购物车
     *
     * @param shoppingCartDTO
     */
    @Override
    public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
        //获取购物车列表
        ShoppingCart shoppingCart = new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
        shoppingCart.setUserId(BaseContext.getCurrentId());

        //判断购物车中是否存在该商品
        List<ShoppingCart> shoppingCartList  = shoppingCartMapper.list(shoppingCart);
        if (shoppingCartList!=null && shoppingCartList.size()>0){
            //那么购物车中有商品 , 跟新数量加1 即可
            ShoppingCart cart = shoppingCartList.get(0);
            cart.setNumber(cart.getNumber()+1);
            shoppingCartMapper.updateNumberById(cart);
        }else{
            //否则购物车中不存在菜品或套餐:
            //判断添加的是菜品还是套餐
            Long dishId = shoppingCartDTO.getDishId();
            if (dishId != null){
                //那么就是有菜品.否则就是有套餐
                //那么就根据菜品id查询菜品信息,添加菜品
                Dish dish = dishMapper.getById(dishId);
                shoppingCart.setName(dish.getName());
                shoppingCart.setImage(dish.getImage());
                shoppingCart.setAmount(dish.getPrice());
            }else {
                //否则添加套餐
                Long setmealId = shoppingCartDTO.getSetmealId();
                Setmeal setmeal = setmealMapper.getById(setmealId);
                shoppingCart.setName(setmeal.getName());
                shoppingCart.setImage(setmeal.getImage());
                shoppingCart.setAmount(setmeal.getPrice());
            }
            //跟新菜品/套餐 数据
            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCartMapper.insert(shoppingCart);


        }

    }
}

ShoppingCartMapper.java

@Mapper
public interface ShoppingCartMapper {

    /**
     * 根据用户id菜品id套餐id口味分类,查询 购物车
     * @param shoppingCart
     * @return
     */
    List<ShoppingCart> list(ShoppingCart shoppingCart);


    /**
     * 根据id修改购物车数量
     * @param cart
     */
    @Update("update shopping_cart set number=#{number} where id=#{id}")
    void updateNumberById(ShoppingCart cart);


    /**
     * 新增套餐
     *
     */
    @Insert("insert into shopping_cart (id, name, image, user_id, dish_id, setmeal_id, dish_flavor, number, amount, create_time) " +
            "values (#{id},#{name},#{image},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{createTime})")
    void insert(ShoppingCart shoppingCart);

}


//xml
    <select id="list" resultType="com.sky.entity.ShoppingCart">
        select * from shopping_cart
        <where>
            <if test="userId != null">
                and user_id = #{userId}
            </if>
            <if test="dishId != null">
                and dish_id = #{dishId}
            </if>
            <if test="setmealId != null">
                and setmeal_id = #{setmealId}
            </if>
            <if test="dishFlavor != null">
                and dish_flavor = #{dishFlavor}
            </if>
        </where>
    </select>

测试与提交

正常封装,且重复添加只增加数量,由于查看购物车业务还没有开发,前端不会返回结果

测试成功

image-20250402140320665

数据库中数据正确改变

image-20250402140546529

最后提交代码

查看购物车

需求分析和设计

产品原型

image-20250402165214824

  • 接口设计

image-20250402165228250

代码开发

controller

    /**
     * 查看购物车
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("查询购物车")
    public Result<List<ShoppingCart>> list() {
        log.info("查询购物车。。。。。。");
        List<ShoppingCart> list = shoppingCartService.showShoppingCart();
        return Result.success(list);
    }

service

    /**
     *查看购物车
     * @return
     */
    List<ShoppingCart> showShoppingCart();


//impl
  /**
     * 查看购物车
     * @return
     */
    @Override
    public List<ShoppingCart> showShoppingCart() {
        //获取当前用户id
        Long userId = BaseContext.getCurrentId();
        ShoppingCart shoppingCart = ShoppingCart.builder()
                .userId(userId)
                .build();
        //使用当前用户id查询当前用户的购物车
        List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
        return list;
    }

功能测试

测试成功,

image-20250402165434097

清空购物车

需求分析和设计

  • 需求分析
image-20250402165559490
  • 接口设计:

image-20250402165657341

代码开发

controller

    /**
     * 清空购物车
     * @return
     */
    @DeleteMapping("/clean")
    @ApiOperation("清空当前用户购物车")
    public Result clean(){
        log.info("清空购物车");
        shoppingCartService.cleanShoppingCart();
        return Result.success();
    }

service

    /**
     * 清空购物车
     *
     */
    void cleanShoppingCart();

//impl
    /**
     *
     * 清空购物车
     */
    @Override
    public void cleanShoppingCart() {
        //只清空当前用户的购物车
        //获取当前用户id
        Long userId = BaseContext.getCurrentId();
        shoppingCartMapper.deleteByUserId(userId);

    }

mapper

    /**
     * 根据当前用户id清空购物车
     * @param userId
     */
    @Delete("delete from shopping_cart where user_id = #{userId}")
    void deleteByUserId(Long userId);

功能测试

image-20250402170540234

测试成功!!!!!!

删除购物车商品

需求分析和设计

image-20250402170913888

  • 接口设计:

    user/shoppingCart/sub POST

,有哪些菜品在根据用户传过来的菜品或套餐id修改指定套餐

代码开发

controller

    /**
     * 清空购物车单个菜品
     * @param shoppingCartDTO
     * @return
     */
    @PostMapping("/sub")
    @ApiOperation("删除购物车单个菜品")
    public Result sub(@RequestBody ShoppingCartDTO shoppingCartDTO) {
        log.info("清空当前用户车单个物品:{}",shoppingCartDTO.toString());
        shoppingCartService.subShoppingCart(shoppingCartDTO);
        return Result.success();
    }

service

    /**
     * 删除购物车中一个菜品
     */
    void subShoppingCart(ShoppingCartDTO shoppingCartDTO);


//impl
 /**
     * 删除购物车中某个菜品
     * @param shoppingCartDTO
     */
    @Override
    public void subShoppingCart(ShoppingCartDTO shoppingCartDTO) {
        //1.获取当前用户的购物车中所有菜品,根据用户当前id查询
        Long userId = BaseContext.getCurrentId();
        ShoppingCart shoppingCart = new ShoppingCart();
        shoppingCart.setUserId(userId);
        List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);

        //2.遍历每一个菜品数量,如果大于1 那么数量-1
        if (list == null || list.size() == 0){
            throw new BaseException("购物车中不存在该商品");
        }
        ShoppingCart cart = list.get(0);
        Integer number = cart.getNumber();
        if (number > 1){
            //数量大于1,跟新数量-1
            cart.setNumber(number - 1);
            shoppingCartMapper.updateNumberById(cart);
        }else {
            //数量等于1,删除该商品
            shoppingCartMapper.deleteById(cart);
        }
        //3.否则删除此菜品
    }

功能测试

posted @ 2025-04-06 23:30  han390  阅读(190)  评论(0)    收藏  举报