当前位置: 首页 > news >正文

苍穹外卖资源点整理+个人错误解析-Day07-缓存商品、购物车

缓存商品

缓存菜品

问题说明

实现思路

就是使用redis来缓存菜品数据,减少数据库查询操作。

逻辑分析:

小程序显示菜品的逻辑其实是根据分类来展示。所以要缓存菜品的话每个分类下的菜品都是一份缓存数据。

设计的redis结构,左边的123……为分类id,右边的string装的是该菜品分类下的所有菜品。

而使用string的原因一方面是方便使用id进行查询,传入的是json形式

菜品分类的每一种菜都序列化然后转为redis的字符串

代码

缓存菜品

根据代码分析,每当点击分类的时候,请求去了user/dish/list

就是传入了user包的dishcontroller

第一步是创建key,即redis的库名:

代码:

String key = "dsh"+categoryId;

创建完成后来查询这里面是否存在数据,而redis的string中获取指定key的值,其redis代码是这样写的:get key。所以代码:

//使用List<DishVO>作为类型原因是放进去什么类型就是什么类型
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);

查出有的话直接输出,没有的话则需要从数据库进行添加,直接输出:

//如果存在直接返回无需查询数据库
if (list!=null&&list.size()>0){return Result.success(list);
}

那么如果没有的话如何添加呢:

//在用户端,根据id查询菜品不仅仅是要查询出菜品,还得将口味查询出
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);

完整代码:

public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate RedisTemplate redisTemplate;@GetMapping("/list")public Result<List<DishVO>> list(Long categoryId){//在查询前构造一下redis中的key:dish_分类idString key = "dsh"+categoryId;//查询redia中是否有菜品数据List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);//如果存在直接返回无需查询数据库if (list!=null&&list.size()>0){return Result.success(list);}//在用户端,根据id查询菜品不仅仅是要查询出菜品,还得将口味查询出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的数据如果不改变的话,会出现就像以下的错误:

小程序价格显示88,但在数据库其实早就修改为66.所以需要清除缓存数据。

而在修改,删除,起停售,新增都需要清除缓存数据。

而进行这些操作,都是在admin/dish的dishconrtoller这个类中进行。 

清除缓存,其实就是在进行增删改操作时,会将原来存在redis的数据删除,等操作完成后再进行加载。

新增清除缓存

需要注意的是,并不是每一份都要删除,哪一份受影响哪一份删除。比如再某一个菜品分类下添加了菜品,只需要将这个菜品分类的数据删除再重新加载即可。

@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO){//根据文档中返回数据的数据结构的data为非必选,泛型可以不加
log.info("新增菜品:{}",dishDTO);
dishService.savewithFiavor(dishDTO);
//清理缓存数据String key = "dish"+dishDTO.getCategoryId();redisTemplate.delete(key);return Result.success();
}
批量删除清除缓存

批量删除稍微复杂一点,省略去删除一个的情况,删除多个可能删除的是同一个分类下的,也可能是不同的分类下的。

正常来说可能需要通过数据库进行查询,但这个太繁琐。直接将所有分类全部进行删除重加载。

/*
* 菜品批量删除*/@DeleteMapping@ApiOperation("菜品批量删除")public Result delete(@RequestParam List<Long> ids){
log.info("菜品批量删除:{}",ids);
dishService.deleteBatch(ids);
//将所有菜品缓存数据清除Set keys = redisTemplate.keys("dish_*");redisTemplate.delete(keys);return Result.success();}
修改清除缓存

如果修改菜品修改的是分类,那么同时会影响到两个分类,简单解决的话直接全部删除即可:

@PutMapping
@ApiOperation("修改菜品")public Result update(DishDTO dishDTO){log.info("修改菜品:{}",dishDTO);dishService.updateWithFlavor(dishDTO);//将所有菜品缓存数据清除Set keys = redisTemplate.keys("dish_*");redisTemplate.delete(keys);return Result.success();}
起售停售清除缓存
//完成起售停售
@PostMapping("/status/{status}")
@ApiOperation("起售停售菜品")
public Result startorstop(@PathVariable Integer status,Long id){dishService.startOrStop(status,id);//可以将所有清理,也可以精确清理:将菜品id对应的菜品数据查出,获取分类id,构造分类id动态keySet keys = redisTemplate.keys("dish_*");redisTemplate.delete(keys);return Result.success();
综合

以上每个方法都添加一个清除缓存太过麻烦,可以将清除缓存抽取成一个方法,在各个方法直接调用即可,如图就是抽取出的方法。由于目前只在这个类调用,所以就在这个类写成静态方法。

如何调用:

像添加这种精确修改的话:

缓存套餐

spring Cache

介绍

使用该框架之前先导入maven坐标:

springcache是如何知道我们使用的是哪个缓存实现,对于这个问题我们并不需要做任何配置。只需要在pom文件导入过redis的一个java客户端就可以。像如果想用EHcach作为缓存实现,那么只需要导入EHcachr即可。

常用注解:

入门案例

在启动类添加开启缓存注解功能:

考虑提供的三个注解加在哪里:

一般都是加在controller层中:

新增
比如下图就是新增用户的方法。即将传进来的用户数据插入user表中。

理想的效果是数据插入user表中的同时,还需要把这个数据保存到redis,所以使用@CachePut:

此外,最终我们是要将数据存到redis中的,而redis中是key-value这样形式的数据。如果成功将user数据存储到redis,那么对应的key值是什么:

有这么一套规则:如果使用springCache来缓存数据,那么key的生成是与cacheNames有关系的,具体关系是:userCache::xxxx,这个xxxx是设置的key=""

但是这样的话每一个key都相同,所以需要动态计算key:

有一种语法:spEL。如图所示,需要注意的是#user.id这个user是与形参保持一致的。

也可以不用写id,换成result的话:

result是当前这个方法的返回值,而返回值里面是包含id的,这个叫做对象导航

还有一个方法:

p0是指取第一个参数,除了p0,还可以写a0。

还可以:

意义与p0,a0一样,都是取第一个参数。

@OPtions这个注解的作用

@Options 是MyBatis提供的注解,用于配置映射语句的选项参数。
支持主键回写功能:通过 useGeneratedKeys = true 和 keyProperty = "id" 配置,实现插入数据后自动获取数据库生成的主键值并设置到对象属性中
增强SQL执行控制。

useGeneratedKeys = true:指示 MyBatis 使用数据库生成的主键值。通常用于插入操作后获取自动生成的主键。
keyProperty = "id":指定将生成的主键值赋给 User 对象的 id 属性。

生成的redis如图所示:

其实key是可以做成像这样的树形结构的。而层次就是通过“:”来进行划分。

这是整体的key,然后划分为上图所示。

至于为什么会有Empty这个,则是因为有两个:而中间什么都没有导致的。

set a:b:c:d itcast

根据id进行查询

如图,在getById加上一个断点。但是打开接口文档发现还是成功返回数据。

而正常情况下是不会返回数据的,因为会停在打断点的地方,所以为什么会有数据:

因为这个springCache底层是基于代理技术。一旦加入了注解,就会为我们当前的controller来创建一个代理对象。

在请求这个方法之前,其实先进入的是那个代理对象,在代理对象中查询redis,查询到就直接返回,而这个getbyid方法其实根本没进行调用。

如果将redis中数据删除,再进行文档,还是会显示出,但其实原理略有区别。在代理对象查询redis,发现并没有数据,所以通过反射调用到真正调用方法。

根据主键删除数据

只有在这个方法完全执行完之后@CacheEvict才会执行。

删除全部数据

这是全部清除。

思路

开发

启动类:

位于controller/user下的SetmealController中的根据分类id查询套餐方法,添加缓存:

而清理缓存则位于controller/admin的SetmealController中:

新增:

批量删除:

相对于新增来说,新增是可以进行精确查询的,因为每次只新增一个,而批量删除无法进行精确,一方面是因为批量删除的ids可以是一个可以是一群,另一方面是由于@CacheEvict注解写成key的话是这样的:key:setmealcache::100。所以清空所有数据。

修改、起售停售都是清空所有数据,注解和删除一样。

购物车

添加

分析

产品原型:

作用大概就是用户暂时存放商品的地方。

存放的是菜品和套餐。

对于套餐来说直接添加即可,但是菜品的话略微复杂。

没有口味的菜品直接添加,设置了口味的菜品没有添加按钮,取而代之的是一个选择规格按钮。需要在窗口选择口味再加入购物车。

如果还想加入相同的就需要加1.

接口设计:

有一个思维误区就是关于菜品id和套餐id的,这里是添加功能,一次只能添加一个菜品或一个套餐,不可能一次添加会同时添加菜品和套餐的。

至于购物车里面同时存在,这是可以的。

数据库设计:

代码

controller层

从前端传进来的参数一般使用DTO类进行接收:

public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO)并不需要写出泛型,以内返回值只有code是必须的,其他都是非必须:

@RestController
@RequestMapping("user/shoppingCart")
@Api("c端购物车相关接口")
@Slf4j
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;
/*添加购物车
* */@PostMapping("/add")@ApiOperation("添加购物车 ")public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){log.info("添加购物车,商品信息为:{}",shoppingCartDTO);shoppingCartService.addShoppingCart(shoppingCartDTO);return Result.success();}
}

service接口:

public interface ShoppingCartService {void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}

实现类ShoppignCartServiceImpl

逻辑:

得先注意,假如我在购物车加了两份同样的菜品,此时购物车会是两份数据吗?

并不是,而是一个数据(但是一个数据的原因是这个数据的属性有一个number代表商品数量)。

而执行这个操作在数据库中就是update操作。

商品不在购物车就需要进行insert操作。

所以就需要先进行判断,判断商品是否存在,然后再进行插入操作。

并且每个用户都有自己的购物车,体现就体现在user_id这个字段。查询购物车商品时就需要将userid作为条件进行查询。

判断当前加入购物车的商品是否存在

判断套餐的话比较简单,sql代码:

select * from shopping_cart where user_id=? and setmeal_id=xx

但是判断菜品的话就不能只判断一个菜品,同一个菜品还可能有不同的口味:

select * from shopping_cart where user_id=? and dish_id =xx and dish_flavor

关于菜品,口味也是一个查询字段

可以使用动态sql来写sql,而不是用两个。

代码部分:

先创建一个shoppingcart对象,然后赋值。

//判断当前加入购物车的商品是否已经存在
//是根据用户id和菜品/套餐id进行查询ShoppingCart shoppingCart = new ShoppingCart();BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);

但是这样是不够的,dto中没有userid,需要使用拦截器赋值:

//没有用户id,需要给shoppingcart
Long currentId = BaseContext.getCurrentId();
shoppingCart.setUserId(currentId);
List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);

存在数量+1

//判断存在
if (list !=null &&list.size()>0){ShoppingCart shoppingCart1 = list.get(0);//为什么使用get(0),是因为list只会查到一条数据
//存在数量加1
shoppingCart1.setNumber(shoppingCart1.getNumber()+1);
shoppingCartMapper.updateNumberById(shoppingCart1);}

不存在插入数据

在购物车这个实体类中,有名字,价格,图片路径这几个前端并没有返回需要后端进行查询。

先判断传进来的是菜品还是套餐,只有知道了是什么才能去查对应的表。

如何知道是菜品还是套餐,只需要尝试get菜品id和套餐id,哪个不是空就传入哪个。

if (dishId!=null){//本次添加的是菜品Dish dish =dishMapper.getById(dishId);shoppingCart.setName(dish.getName());shoppingCart.setImage(dish.getImage());shoppingCart.setAmount(dish.getPrice());
}else {//本次添加的是套餐 Long setmealId1 = shoppingCartDTO.getSetmealId();Setmeal setmeal =setmealMapper.getById(setmealId1);shoppingCart.setName(setmeal.getName());shoppingCart.setImage(setmeal.getImage());shoppingCart.setAmount(setmeal.getPrice());}
shoppingCart.setCreateTime(LocalDateTime.now());shoppingCart.setNumber(1);shoppingCartMapper.insert(shoppingCart);
}}

mapper

判断当前加入购物车的商品是否存在——即查询

为什么要使用list进行封装:此处发现,如果是判断是否存在只会存入一条数据,但是使用list是为了复用。这个方法可能会成为购物车的通用方法。

泛型使用shoppingcart,因为是购物车对象。

ShoppingCartMapper {//动态条件查询List<ShoppingCart> list(ShoppingCart shoppingCart);

使用动态sql:

<!--查询是否有加入内容--><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>

存在数量+1

@Update("update  shopping_cart set number=#{number} where id = #{id}")
void updateNumberById(ShoppingCart shoppingCart);

完整实现层,mapper层代码:

@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private DishMapper dishMapper;
@Autowired
private SetmealMapper setmealMapper;//添加购物车//逻辑:注意的是数量,同一份菜品的话,不是两条数据而是一个数据其中的number+1//所以先判断是否存在,再执行操作@Overridepublic void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {//判断当前加入购物车的商品是否已经存在//是根据用户id和菜品/套餐id进行查询ShoppingCart shoppingCart = new ShoppingCart();BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);//没有用户id,需要给shoppingcartLong currentId = BaseContext.getCurrentId();shoppingCart.setUserId(currentId);List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);//判断存在if (list !=null &&list.size()>0){ShoppingCart shoppingCart1 = list.get(0);//存在数量加1
shoppingCart1.setNumber(shoppingCart1.getNumber()+1);
shoppingCartMapper.updateNumberById(shoppingCart1);}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 setmealId1 = shoppingCartDTO.getSetmealId();Setmeal setmeal =setmealMapper.getById(setmealId1);shoppingCart.setName(setmeal.getName());shoppingCart.setImage(setmeal.getImage());shoppingCart.setAmount(setmeal.getPrice());}  shoppingCart.setCreateTime(LocalDateTime.now());shoppingCart.setNumber(1);shoppingCartMapper.insert(shoppingCart);}}
}

mapper:

@Mapper
public interface ShoppingCartMapper {//动态条件查询List<ShoppingCart> list(ShoppingCart shoppingCart);@Update("update  shopping_cart set number=#{number} where id = #{id}")void updateNumberById(ShoppingCart shoppingCart);
//添加@Insert("insert into shopping_cart (name,image,dish_id,setmeal_id,dish_flavor,number,amount,create_time)" +" values (#{name},#{image},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{createTime})")void insert(ShoppingCart shoppingCart);
}

mapper.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>

查看

分析

产品原型:

查询类的操作,但并不需要提供任何参数给接口,userid甚至都不是必须的。因为在每一次业务操作时,都会在请求头带个token。后端通过解析可以得到id。

接口设计:

代码

controller层:

//查询购物车中的数据
@GetMapping("list")
@ApiOperation("查看购物车")
public Result<List<ShoppingCart>> list(){List<ShoppingCart> list=shoppingCartService.shoppingCart();return Result.success(list);

service层:

//查看购物车List<ShoppingCart> shoppingCart();

serviceimpl层:

查看某个用户购物车只需要提供用户id,可以从拦截器拿到:

Long currentId = BaseContext.getCurrentId();

但是list方法需要传入shoppingcart对象,需要构建出来:

hoppingCart shoppingCart = ShoppingCart.builder().userId(currentId).build();

完整代码:

@Override
public List<ShoppingCart> shoppingCart() {//查询某个用户的购物车,只需要传入用户idLong currentId = BaseContext.getCurrentId();//但是list传入的对象得是shoppingcart类型对象ShoppingCart shoppingCart = ShoppingCart.builder().userId(currentId).build();List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);return list;
}

清空

分析

产品原型:

点击清空删除所有商品,就是删除操作。

不需要提交参数,和查看购物车相似,从后端可以获取userid,返回结果主要返回code告诉前端成功失败。

接口文档:

代码

controller:

//清空购物车
@DeleteMapping("/clean")
@ApiOperation("清空购物车")
public Result clean(){shoppingCartService.cleanShoppingCart();return Result.success();
}

serviceimpl:

@Override
public void cleanShoppingCart() {//查询某个用户的购物车,只需要传入用户idLong currentId = BaseContext.getCurrentId();shoppingCartMapper.deleteByUserId(currentId);
}

mapper:

//清空购物车@Delete("delete  from shopping_cart where user_id = #{userId}")void deleteByUserId(Long currentId);

http://www.dtcms.com/a/604982.html

相关文章:

  • 自己做网站还是开通阿里巴巴诚信通安徽科技学院
  • 【033】Dubbo3从0到1系列之dubbo协议支持的序列化方式
  • 开源 Objective-C IOS 应用开发(五)iOS操作(action)和输出口(Outlet)
  • openEuler系统部署Node.js开发环境指南
  • 杭州网站建设市场青岛餐饮加盟网站建设
  • 手动清除Ubuntu系统中的内存缓存的步骤
  • 花店微信小程序怎么做,创建一个小程序需要多少钱
  • c# 异步编程详细说明及实践
  • 系统架构设计师论文分享-论设计模式的应用
  • 漫谈我与C++
  • HarmonyOS 6.0 服务卡片实战:把「轻食刻」装进桌面,让轻断食一眼可控
  • 建设网站用什么技术网站的基本类型
  • 罗湖附近公司做网站建设哪家效益快阜阳微网站建设多少钱
  • C++-Qt-音视频-基础问题01
  • [Linux]学习笔记系列 -- [kernel]notifier
  • Blender学习笔记(0) -- 思维导图框架
  • 云手机 服务器网络安全
  • 服务器BMC开发视角:解析CPU管理的两大核心接口PECI与APML
  • Linux 服务器安装 dstat 监控插件
  • 与实验室服务器互相ping
  • C++ 二叉搜索树的模拟实现(key结构的非递归和key_value结构的非递归的实现)
  • dw制作简单网站如何推广新品
  • SUSE Linux Enterprise Server 15 SP4安装步骤
  • 红帽企业 Linux 9 启动过程详解:从按下电源到登录提示符
  • 合肥建设厅网站建设一个一般网站需要多少钱
  • 麻省理工学院未来研发更高温超导体打开了新路径
  • Android studio修改app 桌面logo和名称
  • 【MCU控制 初级手札】2.1 电学基础知识 【电学基础】
  • C#1113变量类型
  • RabbitMq消费消息遇到的坑