苍穹外卖-day07
缓存菜品
问题说明:
- 现在遇到一个问题,用户端小程序展示的菜品数据都是通过查询数据库获取的,如果用户端访问量比较大,数据库访问压力随之增大
实现思路:

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

- 用户端小程序在每次访问时,都会执行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缓存中取数据


缓存菜品清理数据
数据一致性问题
- 问题分析
现在有一个新问题:旧的数据已经存储到了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
介绍:

- 快速使用

常用注解

具体实现思路
使用springCache完成套餐管理的redis缓存

代码实现
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();
}
}


功能测试
测试成功!!!
添加购物车
需求分析和业务要求

- 接口设计

- 数据库表设计

代码开发
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>
测试与提交
正常封装,且重复添加只增加数量,由于查看购物车业务还没有开发,前端不会返回结果
测试成功

数据库中数据正确改变

最后提交代码
查看购物车
需求分析和设计
产品原型

- 接口设计

代码开发
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;
}
功能测试
测试成功,
清空购物车
需求分析和设计
- 需求分析
- 接口设计:

代码开发
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);
功能测试

测试成功!!!!!!
删除购物车商品
需求分析和设计

-
接口设计:
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.否则删除此菜品
}
功能测试
略

浙公网安备 33010602011771号