苍穹外卖学习笔记——第七天

缓存商品、购物车

缓存菜品

问题说明

  • 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大,从而导致系统响应慢、用户体验差。

实现思路

  • 通过Redis来缓存菜品数据,减少数据库查询操作,具体流程如下:
  • 缓存逻辑分析:
    • 每个分类下的菜品保存一份缓存数据,其中缓存数据的key为“dish_分类id”,value为对应菜品数组序列化后的string。
    • 数据库中菜品数据有变更时清理缓存数据。

代码开发

  • 修改用户端接口 DishController 的 list 方法,加入缓存处理逻辑:
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {

    //构造redis中的key,规则:dish_分类id
    String key = "dish_" + categoryId;

    //查询redis中是否存在菜品数据
    List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
    if (list != null && !list.isEmpty()) {
        //如果存在,直接返回,无需查询数据库
        return Result.success(list);
    }

    //如果不存在,查询数据库
    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);
}
  • 修改管理端接口 DishController 的相关方法,加入清理缓存的逻辑,需要改造以下方法:新增菜品、修改菜品、批量删除菜品和起售、停售菜品。

    • 抽取清理缓存的方法:
    private void cleanCache(String pattern) {
        Set<String> keys = redisTemplate.keys(pattern);
        redisTemplate.delete(keys);
    }
    
    • 调用清理缓存的方法,保证数据一致性:
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品:{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);
    
        //清理缓存数据
        String key = "dish_" + dishDTO.getCategoryId();
        cleanCache(key);
        return Result.success();
    }
    
    //其他方法内添加以下代码
    //将所有的菜品缓存数据清理掉,即所有以dish_开头的key
    cleanCache("dish_*");
    

功能测试

  • 可以通过如下方式进行测试:查看控制台sql、前后端联调、查看Redis中的缓存数据。

缓存套餐

Spring Cache

  • Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。

  • Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:EHCache、Caffeine和Redis。

  • maven坐标为

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

常用注解

常用注解 说明
@EnableCaching 开启缓存注解功能,通常加在启动类上
@Cacheable 在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中
@CachePut 将方法的返回值放到缓存中
@CacheEvict 将一条或多条数据从缓存中删除

用法:

  • CachePut(cacheNames = "cachename", key = "#variable.property"),使用Spring Cache缓存数据时,key的生成规则为:cachename::{variable.property的值},其中key按照spEL(spring Expression Language)的规则来写,其中一种写法是目标变量.目标属性,这个"."叫做对象导航。
  • CacheEvict(cacheNames = "cachename", allEntries = true),使用allEntries = true即可删除cachename下的所有键值对。

实现思路

  • 导入Spring Cache和Redis相关maven坐标。
  • 在启动类上加入@EnableCaching注解,开启缓存注解功能。
  • 在用户端接口SetmealController的 list 方法上加入@Cacheable注解。
  • 在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解。

代码开发

  • 在用户端接口SetmealController的 list 方法上加入@Cacheable注解:
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
@Cacheable(cacheNames = "setmealCache", key = "#categoryId")
public Result<List<Setmeal>> list(Long categoryId) {
    Setmeal setmeal = new Setmeal();
    setmeal.setCategoryId(categoryId);
    setmeal.setStatus(StatusConstant.ENABLE);

    List<Setmeal> list = setmealService.list(setmeal);
    return Result.success(list);
}
  • 在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解:
@CacheEvict(cacheNames = "setmealCache", key = "#setmealDTO.categoryId") //save上

@CacheEvict(cacheNames = "setmealCache", allEntries = true) //其他方法上

功能测试

  • 通过前后端联调方式来进行测试,同时观察redis中缓存的套餐数据。

添加购物车

需求分析和设计

产品原型

接口设计

数据库设计

shopping_cart购物车表
字段名 数据类型 说明 备注
id bigint 主键 自增
name varchar(32) 商品名称 冗余字段
image varchar(255) 商品图片路径 冗余字段
user_id bigint 用户id 逻辑外键
dish_id bigint 菜品id 逻辑外键
setmeal_id bigint 套餐id 逻辑外键
dish_flavor varchar(50) 菜品口味
number int 商品数量
amount decimal(10,2) 商品单价 冗余字段
create_time datetime 创建时间

代码开发

  • 根据添加购物车接口的参数设计DTO:
@Data
public class ShoppingCartDTO implements Serializable {

    private Long dishId;
    private Long setmealId;
    private String dishFlavor;

}
  • 根据添加购物车接口创建ShoppingCartController:
@RestController("userShopController")
@RequestMapping("/user/shop")
@Api(tags = "C端-店铺相关接口")
@Slf4j
public class ShopController {

    public static final String KEY = "SHOP_STATUS";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 查询店铺营业状态
     *
     * @return
     */
    @GetMapping("/status")
    @ApiOperation("查询店铺营业状态")
    public Result<Integer> getStatus() {
        Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
        log.info("查询店铺营业状态为:{}", status == 1 ? "营业中" : "打烊中");
        return Result.success(status);
    }
}
  • 创建ShoppingCartService接口:
public interface ShoppingCartService {

    /**
     * 添加购物车
     *
     * @param shoppingCartDTO
     */
    void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}
  • 创建ShoppingCartServiceImpl实现类,并实现add方法:
@Service
@Slf4j
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);
        Long userId = BaseContext.getCurrentId();
        shoppingCart.setUserId(userId);

        List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);

        //如果已经存在了,只需要将数量加一
        if (list != null && !list.isEmpty()) {
            ShoppingCart cart = list.get(0);
            cart.setNumber(cart.getNumber() + 1);
            shoppingCartMapper.updateNumberById(cart);
        } else {
            //如果不存在,需要插入一条购物车数据

            //判断本次添加到购物车的是菜品还是套餐
            Long dishId = shoppingCartDTO.getDishId();
            if (dishId != null) {
                //本次添加到购物车的是菜品
                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接口:
@Mapper
public interface ShoppingCartMapper {

    /**
     * 动态条件查询
     *
     * @param shoppingCart
     * @return
     */
    List<ShoppingCart> list(ShoppingCart shoppingCart);

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

    /**
     * 插入购物车数据
     *
     * @param shoppingCart
     */
    @Insert("insert into shopping_cart (name, image, user_id, dish_id, setmeal_id, dish_flavor, number, amount, create_time) " +
            "VALUES (#{name}, #{image}, #{userId}, #{dishId}, #{setmealId}, #{dishFlavor}, #{number}, #{amount}, #{createTime})")
    void insert(ShoppingCart shoppingCart);
}
  • 创建ShoppingCartMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.ShoppingCartMapper">

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

</mapper>

功能测试

  • 可以通过如下方式进行测试:查看控制台sql、Swagger接口文档测试、前后端联调。

查看购物车

需求分析和设计

产品原型

接口设计

代码开发

  • 在ShoppingCartController中创建查看购物车的方法:
@GetMapping("/list")
@ApiOperation("查看购物车")
public Result<List<ShoppingCart>> list() {
    List<ShoppingCart> list = shoppingCartService.showShoppingCart();
    return Result.success(list);
}
  • 在ShoppingCartService接口中声明查看购物车的方法:
List<ShoppingCart> showShoppingCart();
  • 在ShoppingCartServiceImpl中实现查看购物车的方法:
@Override
public List<ShoppingCart> showShoppingCart() {
    ShoppingCart shoppingCart = new ShoppingCart().builder()
            .userId(BaseContext.getCurrentId())
            .build();
    List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
    return list;
}

功能测试

可以通过接口文档进行测试,最后完成前后端联调测试即可。

清空购物车

需求分析和设计

产品原型

接口设计

代码开发

  • 在ShoppingCartController中创建清空购物车的方法:
@DeleteMapping("/clean")
@ApiOperation("清空购物车")
public Result clean() {
    shoppingCartService.cleanShoppingCart();
    return Result.success();
}
  • 在ShoppingCartService接口中声明清空购物车的方法:
void cleanShoppingCart();
  • 在ShoppingCartServiceImpl中实现清空购物车的方法:
@Override
public void cleanShoppingCart() {
    Long userId = BaseContext.getCurrentId();
    shoppingCartMapper.deleteByUserId(userId);
}
  • 在ShoppingCartMapper接口中创建根据用户id清空购物车的方法:
@Delete("delete from shopping_cart where user_id = #{userId}")
void deleteByUserId(Long userId);

功能测试

通过Swagger接口文档进行测试,通过后再前后端联调测试即可。

删除购物车中一个商品

需求分析和设计

产品原型

接口设计

接口设计

代码开发

  • 在ShoppingCartController中创建删除购物车中一个商品的方法:
@PostMapping("/sub")
@ApiOperation("删除购物车中一个商品")
public Result sub(@RequestBody ShoppingCartDTO shoppingCartDTO) {
    log.info("删除购物车中一个商品:{}", shoppingCartDTO);
    shoppingCartService.subShoppingCart(shoppingCartDTO);
    return Result.success();
}
  • 在ShoppingCartService接口中声明删除购物车中一个商品的方法:
void subShoppingCart(ShoppingCartDTO shoppingCartDTO);
  • 在ShoppingCartServiceImpl中实现删除购物车中一个商品的方法:
@Override
public void subShoppingCart(ShoppingCartDTO shoppingCartDTO) {
    ShoppingCart shoppingCart = new ShoppingCart();
    BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);
    //设置查询条件,查询当前登录用户的这一条购物车数据
    Long userId = BaseContext.getCurrentId();
    shoppingCart.setUserId(userId);

    List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);

    if (list != null && !list.isEmpty()) {
        shoppingCart = list.get(0);

        Integer number = shoppingCart.getNumber();
        if (number == 1) {
            //当前商品在购物车中的分数为1,直接删除当前记录
            shoppingCartMapper.deleteById(shoppingCart.getId());
        } else {
            //当前商品在购物车中的分数不为1,修改份数即可
            shoppingCart.setNumber(number - 1);
            shoppingCartMapper.updateNumberById(shoppingCart);
        }
    }
}
  • 在ShoppingCartMapper接口中创建根据id删除购物车中一个商品的方法:
@Delete("delete from shopping_cart where id = #{id}")
void deleteById(Long id);

功能测试

通过Swagger接口文档进行测试,通过后再前后端联调测试即可。

posted @   zgg1h  阅读(337)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示