苍穹外卖 项目记录 day07 商品缓存-购物车模块开发
文章目录
- 前言
- 清理缓存数据
- Spring Cache 和常用注解
- 缓存套餐
- 添加购物车
- 查看购物车
- 清空购物车
前言
将商品信息放进redis缓存 Spring Cache技术
系统查询性能 用户端访问量过大 数据库访问压力随之增大 系统响应慢
使用Redis 缓存菜品数据 减少数据库查询 基于内存保存数据
前端 发请求 查询 后端服务 查询缓存是否存在 (存在缓存 读取缓存 不存在缓存 查询数据库 查询到数据 载入缓存)
key-value 键值对匹配
缓存逻辑分析 根据分类缓存数据 每个分类下菜品保存一份缓存数据
key dish_id value string List对象序列化成字符串 存储
菜品数据变更时 清理缓存数据 (延迟双删)
每次切换查询数据 都会发请求 查询数据库
public class DishController {
@Autowired
private DishService dishService;
@Autowired
// 已创建RedisConfiguration 直接注入即可
private RedisTemplate redisTemplate;
/**
* 根据分类id查询菜品
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
//查询Redis 中是否存在菜品数据 操作redis 注入对象
//构造redis的 key
String key = "dish_" + categoryId;
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
if(list != null && list.size() > 0){
// 如果存在 直接返回 不查询数据库
return Result.success(list);
}
//如果不存在 查询数据库 将查询到的数据存入Redis中
Dish dish = new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
list = dishService.listWithFlavor(dish);
redisTemplate.opsForValue().set(key, list);
return Result.success(list);
}
}
清理缓存数据
保证数据一致性 缓存和数据库
修改 删除时 状态变化 新增时 清理缓存
改造对应方法 (DishController) 修改 admin 下 DishController 方法
清理缓存数据
package com.sky.controller.admin;
/**
* @author Admin
* @title: DishController
* @projectName minjiang-takeaway
* @description: DishController
* @date 2024/1/20 18:17
*/
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Set;
/**
* 菜品管理
*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品管理相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private RedisTemplate redisTemplate;
/**
* 新增菜品
*/
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO){
log.info("新增菜品:{}",dishDTO.toString());
dishService.saveWithFlavor(dishDTO);
//清理缓存数据
String key = "dish_" + dishDTO.getCategoryId();
redisTemplate.delete(key);
return Result.success();
}
/**
* 菜品分页查询
*
* @param dishPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
log.info("菜品分页查询:{}", dishPageQueryDTO);
//调用分页查询接口
PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
return Result.success(pageResult);
}
/**
* 菜品批量删除
*/
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids){
log.info("菜品批量删除:{}",ids);
dishService.deleteBatch(ids);
//将所有菜品缓存数据清理掉 所有以dish_开头的key
cleanCache("dish_*");
return Result.success();
}
/**
* 根据id查询菜品 先由id进行菜品数据回显 才能进行菜品修改
*
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result<DishVO> getById(@PathVariable Long id) {
log.info("根据id查询菜品:{}", id);
DishVO dishVO = dishService.getByIdWithFlavor(id);
return Result.success(dishVO);
}
/**
* 修改菜品
*
* @param dishDTO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {
log.info("修改菜品:{}", dishDTO);
dishService.updateWithFlavor(dishDTO);
//将所有菜品缓存数据清理掉 所有以dish_开头的key
cleanCache("dish_*");
return Result.success();
}
/**
* 实现菜品起售停售
*/
@PostMapping("/status/{status}")
@ApiOperation("菜品起售停售")
public Result<String> startOrStop(@PathVariable Integer status,Long id){
dishService.startOrStop(status,id);
cleanCache("dish_*");
return Result.success();
}
/**
*
* 根据分类id查询菜品列表
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品列表")
public Result<List<Dish>> list(Long categoryId){
List<Dish> list = dishService.list(categoryId);
return Result.success(list);
}
/**
* 抽取统一清理缓存数据方法
*/
private void cleanCache(String pattern){
//将所有菜品缓存数据清理掉 所有以dish_开头的key
Set keys = redisTemplate.keys(pattern);
redisTemplate.delete(keys);
}
}
Spring Cache 和常用注解
Spring Cache spring缓存框架 简单加一个注解 实现缓存功能
Spring Cache 提供一层抽象 底层可切换不同缓存实现 EHCache Caffeine Redis
导入pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Spring Cache 常用注解
@EnableCaching 开启缓存注解 加载启动类上
@Cacheable 方法执行前 查询缓存是否有数据 有数据返回缓存数据 无数据 调用方法 将方法换回值 放到缓存中
@CachePut 将方法返回值放到缓存中
@CacheEvict 将一条或多条数据从缓存中删除
@PostMapping
//@CachePut(cacheNames = "userCache",key = "#user.id")
//@CachePut(cacheNames = "userCache",key = "#p0.id")
//@CachePut(cacheNames = "userCache",key = "#root.args[0].id")
@CachePut(cacheNames = "userCache",key = "#result.id")
//存到Redis的key值 userCache::user.id
public User save(@RequestBody User user){
userMapper.insert(user);
//插入数据时 保存数据到redis
return user;
}
@GetMapping
@Cacheable(cacheNames = "userCache",key = "#id")
//获取user前 先从redis里找
public User getById(Long id){
User user = userMapper.getById(id);
return user;
}
@DeleteMapping
@CacheEvict(cacheNames = "userCache",key="#id")
public void deleteById(Long id){
userMapper.deleteById(id);
}
@DeleteMapping("/delAll")
@CacheEvict(cacheNames = "userCache",allEntries = true)
public void deleteAll(){
userMapper.deleteAll();
}
缓存套餐
导入 Spring Cache 和 Redis 相关pom.xml坐标
启动类上 加入 @EnableCaching注解 开启缓存功能
用户端接口 SetmealController list方法加上@Cacheable注解
管理端接口 SetmealController save delete update startOrStop 加上CacheEvict注解
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching
public class SkyApplication {
public static void main(String[] args) {
SpringApplication.run(SkyApplication.class, args);
log.info("server started");
}
}
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
@Cacheable(cacheNames = "setmealCache", key = "#categoryId") // key: setmealCache::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);
}
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐管理相关接口")
@Slf4j
public class SetmealController {
@Autowired
private SetmealService setmealService;
@PostMapping
@ApiOperation("新增套餐")
@CacheEvict(cacheNames = "setmealCache",key="#setmealDto.categoryId")
public Result save(@RequestBody SetmealDTO setmealDto){
setmealService.saveWithDish(setmealDto);
return Result.success("新增套餐成功");
}
/**
* 分页查询
* @param setmealPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("套餐分页查询")
public Result<PageResult> page(SetmealPageQueryDTO 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){
//删除套餐表
setmealService.deleteBatch(ids);
return Result.success();
}
/**
* 根据id查询套餐 实现修改回显
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询套餐")
public Result<SetmealVO> getById(@PathVariable Long id){
SetmealVO setmealVO = setmealService.getByIdWithDish(id);
return Result.success(setmealVO);
}
@PutMapping
@ApiOperation("修改套餐")
@CacheEvict(cacheNames = "setmealCache",allEntries = true)
public Result update(@RequestBody SetmealDTO setmealDTO){
setmealService.update(setmealDTO);
return Result.success("修改成功");
}
@PostMapping("/status/{status}")
@ApiOperation("套餐起售停售")
@CacheEvict(cacheNames = "setmealCache",allEntries = true)
public Result<String> startOrStop(@PathVariable Integer status,Long id){
setmealService.startOrStop(status,id);
return Result.success();
}
}
添加购物车
增删改查
POST请求 接口设计
添加购物车 /user/shoppingCart/add
数据库设计 shopping_cart (冗余字段 逻辑外键) 设计 冗余字段 提高查询速度
不同用户 购物车区分开
创建ShoppingCartController ShoppingCartMapper.java SshoppingCartMapper.xml
ShoppingCartService ShoppingCartServiceImpl
@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "C端购物车接口")
public class ShoppingCartController {
@Autowired
private ShoppingCartService shoppingCartService;
@PostMapping("/add")
@ApiOperation("添加购物车")
public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){
log.info("添加购物车:{}",shoppingCartDTO);
//业务操作一般封装到service中
shoppingCartService.addShoppingCart(shoppingCartDTO);
return Result.success("添加购物车成功");
}
}
package com.sky.mapper;
import com.sky.entity.ShoppingCart;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;
import java.util.List;
/**
* @author Admin
* @title: ShoppingCartMapper
* @projectName minjiang-takeaway
* @description: ShoppingCartMapper
* @date 2024/6/23 3:24
*/
@Mapper
public interface ShoppingCartMapper {
//动态条件 查询购物车数据
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,user_id,dish_id,dish_favor,number,amount,image,create_time)" +
" values(#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number}#{amount},#{image},#{createTime})")
void insert(ShoppingCart shoppingCart);
}
<?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>
public interface ShoppingCartService {
/**
* 添加购物车
* @param shoppingCartDTO
*/
void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}
@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private DishMapper dishMapper;
@Autowired
private SetmealMapper setmealMapper;
@Override
public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
/**
* 添加购物车逻辑
*/
//判断商品是否已存在与购物车
//查询是否已存在 套餐id + userId select * from shopping_cart where setmeal_id = ? and user_id = ?
// select * from shopping_cart where dish_id = ? and user_id = ? and dish_flavor = ?
//动态sql
ShoppingCart shoppingCart = new ShoppingCart();
//属性拷贝
BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
Long userId = BaseContext.getCurrentId();
//添加userId属性
shoppingCart.setUserId(userId);
List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
//如果存在,则更新数量
if(list != null && list.size() > 0){
ShoppingCart cart = list.get(0);
cart.setNumber(cart.getNumber()+1); // update shopping_cart set number = ? where id = ?
shoppingCartMapper.updateNumberById(cart);
}
else{
//如果不存在,则添加到购物车
//前端上送id
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);
}
}
}
查看购物车
Get /user/shoppingCart/list
/**
* 查询购物车数据
*/
@GetMapping("/list")
@ApiOperation("查询购物车数据")
public Result<List<ShoppingCart>> list(){
List<ShoppingCart> list = shoppingCartService.showShoppingCart();
return Result.success(list);
}
/**
* 查看购物车
* @return
*/
@Override
public List<ShoppingCart> showShoppingCart() {
//获取当前微信用户id
Long userId = BaseContext.getCurrentId();
ShoppingCart shoppingCart = ShoppingCart.builder()
.userId(userId)
.build();
List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
return list;
}
清空购物车
user/shoppingCart/clean
删除购物车某一项商品
/**
* 删除购物车商品
*/
@PostMapping("/sub")
@ApiOperation("删除购物车商品")
public Result deleteById(@RequestBody ShoppingCartDTO shoppingCartDTO){
shoppingCartService.deleteShoppingCart(shoppingCartDTO);
return Result.success("删除成功");
}
/**
* 删除购物车商品
* @param shoppingCartDTO
*/
void deleteShoppingCart(ShoppingCartDTO shoppingCartDTO);
@Override
public void deleteShoppingCart(ShoppingCartDTO shoppingCartDTO) {
ShoppingCart shoppingCart = new ShoppingCart();
BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
//设置查询条件 查询当前用户的购物车数据
shoppingCart.setUserId(BaseContext.getCurrentId());
//查询购物车列表数据
List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
if(list != null && list.size()>0){
shoppingCart = list.get(0);
Integer number = shoppingCart.getNumber();
if(number == 1){
//当前商品在购物车中份数为1 直接删除
shoppingCartMapper.deleteById(shoppingCart.getId());
}else{
shoppingCart.setNumber(shoppingCart.getNumber() -1);
shoppingCartMapper.updateNumberById(shoppingCart);
}
}
}