苍穹外卖--04--Redis 缓存菜品信息、购物车、地址簿、用户下单
Redis 缓存菜品信息
用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。

通过 Redis 来缓存菜品数据,减少数据库查询操作。

缓存逻辑分析:
- 每个分类下的菜品保存一份缓存数据
- 数据库中菜品数据有变更时清理缓存数据
具体编码
修改用户端接口 DishController 的 list 方法(该方法负责根据分类 id,查询该分类下的所有菜品),加入缓存处理逻辑:
@Autowired
private RedisTemplate redisTemplate;
/**
* 根据分类 id 查询菜品
*
* @param categoryId
* @return
*/
@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.size() > 0){
//如果存在,直接返回,无须查询数据库
return Result.success(list);
}
////////////////////////////////////////////////////////
Dish dish = new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
//如果不存在,查询数据库,将查询到的数据放入 redis 中
list = dishService.listWithFlavor(dish);
////////////////////////////////////////////////////////
redisTemplate.opsForValue().set(key, list);
return Result.success(list);
}
为了保证数据库和Redis中的数据保持一致,修改管理端接口 DishController 的相关方法,加入清理缓存逻辑。
所有对增删改的方法都需要改造:
- 新增菜品
- 修改菜品
- 批量删除菜品
- 起售、停售菜品
在管理端 DishController 中修改代码:
新增菜品功能–增加删除缓存
/**
* 新增菜品
*
* @param dishDTO
* @return
*/
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO) {
log.info("新增菜品:{}", dishDTO);
dishService.saveWithFlavor(dishDTO);
//清理缓存数据
String key = "dish_" + dishDTO.getCategoryId();
cleanCache(key);
return Result.success();
}
2). 菜品批量删除功能–增加删除缓存
/**
* 菜品批量删除
*
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids) {
log.info("菜品批量删除:{}", ids);
dishService.deleteBatch(ids);
//将所有的菜品缓存数据清理掉,所有以dish_开头的key
cleanCache("dish_*");
return Result.success();
}
其它不一一列举
Spring Cache
Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:
- EHCache
- Caffeine
- Redis(当前所用)

CachePut
@PostMapping
@CachePut(value = "userCache", key = "#user.id")
public User save(@RequestBody User user){
userMapper.insert(user);
return user;
}
数据内容为:
user.id = 1;
user.name = "Alice";
那么,Spring 层面认为的缓存:
缓存名称(value):userCache
这是逻辑上的缓存分区名称,Spring 会用它区分不同的缓存组。
缓存键(key):1(由 #user.idSpEL 表达式计算得出)
缓存值(value):User对象({id=1, name="Alice"})
到 Redis 里真正存的内容是:
Redis Key: "userCache::1"
Redis Value: "{\"id\":1,\"name\":\"Alice\"}" (序列化后的JSON)
用 Spring Cache 缓存套餐
代码开发
实现步骤:
1). 导入 Spring Cache 和 Redis 相关 maven 坐标
2). 在启动类上加入 @EnableCaching 注解,开启缓存注解功能
3). 在用户端接口 SetmealController 的 list 方法上加入 @Cacheable 注解
4). 在管理端接口 SetmealController 的 save、delete、update、startOrStop 等方法上加入 CacheEvict 注解
3). 在用户端接口 SetmealController 的 list 方法上加入 @Cacheable 注解
/**
* 条件查询
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类 id 查询套餐")
@Cacheable(cacheNames = "setmealCache",key = "#categoryId") //key: setmealCache::100
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);
}
用户将商品/套餐加入购物车
需求分析
- 用户可以将菜品或者套餐添加到购物车。
- 对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;
这个功能简单来说,就是接收前端传来的信息,并存在表里即可,无需额外操作。
表设计:
用户的购物车数据,也是需要保存在数据库中的,购物车对应的数据表为 shopping_cart 表,具体表结构如下:

代码实现
先用文字描述代码逻辑
- 只能存到自己的购物车中,因此要先拿到用户 ID
- 判断当前商品是否在购物车中
- 如果已经存在,就更新数量,数量加1
- 如果不存在,插入数据,数量就是1
- 判断当前添加到购物车的是菜品还是套餐
- 添加到购物车的是菜品
- 添加到购物车的是套餐
ShoppingCart shoppingCart = new ShoppingCart();
BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);
//只能查询自己的购物车数据
shoppingCart.setUserId(BaseContext.getCurrentId());
//判断当前商品是否在购物车中
List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);
if (shoppingCartList != null && shoppingCartList.size() == 1) {
//如果已经存在,就更新数量,数量加 1
shoppingCart = shoppingCartList.get(0);
shoppingCart.setNumber(shoppingCart.getNumber() + 1);
shoppingCartMapper.updateNumberById(shoppingCart);
} else {
//如果不存在,插入数据,数量就是 1
//判断当前添加到购物车的是菜品还是套餐
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 {
//添加到购物车的是套餐
Setmeal setmeal = setmealMapper.getById(shoppingCartDTO.getSetmealId());
shoppingCart.setName(setmeal.getName());
shoppingCart.setImage(setmeal.getImage());
shoppingCart.setAmount(setmeal.getPrice());
}
shoppingCart.setNumber(1);
shoppingCart.setCreateTime(LocalDateTime.now());
shoppingCartMapper.insert(shoppingCart);
}
}
}
对于以上内容,shoppingCartMapper.list查询功能由于传入参数不定,是动态查询,单独写在 XML 文件中。
package com.sky.mapper;
...
@Mapper
public interface ShoppingCartMapper {
/**
* 条件查询
*
* @param shoppingCart
* @return
*/
List<ShoppingCart> list(ShoppingCart shoppingCart);
/**
* 更新商品数量
*
* @param shoppingCart
*/
@Update("update shopping_cart set number = #{number} where id = #{id}")
void updateNumberById(ShoppingCart shoppingCart);
/**
* 插入购物车数据
*
* @param shoppingCart
*/
@Insert("insert into shopping_cart (name, user_id, dish_id, setmeal_id, dish_flavor, number, amount, image, create_time) " +
" values (#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})")
void insert(ShoppingCart shoppingCart);
}
//本质:根据传入的购物车对象中的非空字段,动态生成WHERE条件查询购物车记录。
//动态添加条件(按需拼接):
//如果传入 userId→ 按用户 ID 过滤
//如果传入 dishId→ 按菜品 ID 过滤
//如果传入 setmealId→ 按套餐 ID 过滤
//如果传入 dishFlavor→ 按口味过滤
<?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" parameterType="ShoppingCart" resultType="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>
order by create_time desc
</select>
</mapper>
查看购物车
在添加购物车功能中已经写好了动态查询购物车的 SQL,直接用就好
/**
* 查看购物车
* @return
*/
public List<ShoppingCart> () {
return shoppingCartMapper.list(ShoppingCart.
builder().
userId(BaseContext.getCurrentId()).
build());
}
地址薄
设计地址簿的表

这里面有一个字段 is_default,我们在设置默认地址时,只需要更新这个字段就可以了。
业务功能和接口分析
对于地址簿管理,我们需要实现以下几个功能:
- 查询地址列表
- 新增地址
- 修改地址
- 删除地址
- 设置默认地址
- 查询默认地址
先粗粒度设计接口,共包含7个接口。
- 新增地址
- 查询登录用户所有地址
- 查询默认地址
- 根据id修改地址
- 根据id删除地址
- 根据id查询地址
- 设置默认地址
业务功能和接口不一定是一一对应的:
修改地址功能,就需要同时调用查询地址接口和修改地址接口
更多推荐




所有评论(0)