学习目标
1、能够描述超卖问题
2、理解redission的AtomicLong如何初始化库存
3、实现菜品库存的优化
4、能够描述购物车操作流程【添加购物车、移除购物车】
5、理解购物车操作中订单锁的意义,及对菜品库存使用redission的AtomicLong操作
6、能够描述下单操作流程【订单加锁,合并购物车订单项到可核算订单项】
第一章 点餐平台-点餐
1、库存超卖设置
1.1、什么是超卖
当前菜品库存只有1个,当有两个线程过来后,都执行成功了,生成了两个订单,这就是超卖,如图所示:

方案一:乐观锁机制
使用mybatis plus 里面的@version注解:
@Versionprivate Integer version;
- 取出记录时,获取当前 version select version from tab_dish===>thead-A:1,thead-B:2
- 更新时,带上这个 version
update tab_dish d set d.dish_num =d.dish_num-1 ,version = 3 where version = 1 and d.dish_num>=0 ,
update tab_dish d set d.dish_num =d.dish_num-1 ,version = 3 where version = 2 and d.dish_num>=0 , - 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
方案二:比较替换
- 先查询当前库存数量
thead-A:select d.dish_num from tab_dish where id =’10101’===》old_dish_num=1
thead-B:select d.dish_num from tab_dish where id =’10101’===》old_dish_num=1
- 执行对应的修改
thead-A:update tab_dish d set d.dish_num =d.dish_num-1 where d.dish_num = #{old_dish_num} and d.dish_num-1>=0 and id =’10101’
thead-B:update tab_dish d set d.dish_num =d.dish_num-1 where d.dish_num = #{old_dish_num} and d.dish_num-1>=0 and id =’10101’
方案三:加lock锁处理
使用redisson分布式锁给当前业务值加锁,保证同一时间只有一个线程执行当前的方法
———-加锁———————
thead-A:select d.dish_num from tab_dish where id =’10101’===》1
thead-A:判断当前库存1>0
thead-A:update tab_dish d set d.dish_num =d.dish_num-1 where id =’10101’
———-解锁———————
方案四:加lock锁,使用atomicLong的方式处理库存
使用reis的单线程特点解决库存的超卖问题
1.2、Redis初始化库存
解决超卖问题关键就是库存的控制,前面我们在讲解redisson的时候,讲解过AtomicLong【原子整长型】,它有如下方法:
`//获得原子性整长形RAtomicLong atomicLongOper=redissonClient.getAtomicLong("AtomicLongOper");//添加库存:在餐掌柜项目中:1、项目启动时mysql->redis 2、对菜品进行增删修时改动redis中的库存atomicLongOper.set(dishNum);//先递减1,然后返回元素:购物车添加菜品flag=atomicLongOper.decrementAndGet();log.info("先递减1,然后返回元素:{}",flag);//先递增1,然后返回元素:购物车取消菜品flag=atomicLongOper.incrementAndGet();log.info("先递增1,然后返回元素:{}",flag);
AtomicLong操作是原子性的,我们可以使用它来初始化、减库存、增库存等操作,我们先看下Redis初始化流程

1.3、优化-菜品功能
找到DishFaceImpl类做如下优化:
package com.itheima.restkeeper.face;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import com.itheima.restkeeper.AffixFace;import com.itheima.restkeeper.DishFace;import com.itheima.restkeeper.constant.AppletCacheConstant;import com.itheima.restkeeper.enums.DishEnum;import com.itheima.restkeeper.exception.ProjectException;import com.itheima.restkeeper.pojo.Dish;import com.itheima.restkeeper.pojo.DishFlavor;import com.itheima.restkeeper.req.AffixVo;import com.itheima.restkeeper.req.DishVo;import com.itheima.restkeeper.service.IDishFlavorService;import com.itheima.restkeeper.service.IDishService;import com.itheima.restkeeper.utils.BeanConv;import com.itheima.restkeeper.utils.EmptyUtil;import com.itheima.restkeeper.utils.ExceptionsUtil;import lombok.extern.slf4j.Slf4j;import org.apache.dubbo.config.annotation.DubboReference;import org.apache.dubbo.config.annotation.DubboService;import org.apache.dubbo.config.annotation.Method;import org.redisson.api.RAtomicLong;import org.redisson.api.RLock;import org.redisson.api.RedissonClient;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.transaction.annotation.Transactional;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;import java.util.stream.Collectors;/*** @ClassName DishFaceImpl.java* @Description 菜品接口实现*/@Slf4j@DubboService(version = "${dubbo.application.version}",timeout = 5000,methods ={@Method(name = "findDishVoPage",retries = 2),@Method(name = "createDish",retries = 0),@Method(name = "updateDish",retries = 0),@Method(name = "deleteDish",retries = 0)})public class DishFaceImpl implements DishFace {@AutowiredIDishService dishService;@AutowiredIDishFlavorService dishFlavorService;@AutowiredRedissonClient redissonClient;@DubboReference(version = "${dubbo.application.version}",check = false)AffixFace affixFace;@Overridepublic Page<DishVo> findDishVoPage(DishVo dishVo,int pageNum,int pageSize)throws ProjectException {try {//查询菜品分页Page<Dish> page = dishService.findDishVoPage(dishVo, pageNum, pageSize);Page<DishVo> pageVo = new Page<>();BeanConv.toBean(page,pageVo);//结果集转换List<Dish> dishList = page.getRecords();List<DishVo> dishVoList = BeanConv.toBeanList(dishList,DishVo.class);if (!EmptyUtil.isNullOrEmpty(dishVoList)){dishVoList.forEach(n->{//处理附件List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(n.getId());if (!EmptyUtil.isNullOrEmpty(affixVoList)){n.setAffixVo(affixVoList.get(0));}//处理口味1List<DishFlavor> dishFlavors = dishFlavorService.findDishFlavorByDishId(n.getId());List<String> dishFavorList = new ArrayList<>();for (DishFlavor dishFlavor : dishFlavors) {dishFavorList.add(String.valueOf(dishFlavor.getDataKey()));}String[] dishFlavorDataKey = new String[dishFavorList.size()];dishFavorList.toArray(dishFlavorDataKey);n.setHasDishFlavor(dishFlavorDataKey);});}pageVo.setRecords(dishVoList);//返回结果return pageVo;} catch (Exception e) {log.error("查询菜品列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));throw new ProjectException(DishEnum.PAGE_FAIL);}}@Override@Transactionalpublic DishVo createDish(DishVo dishVo) throws ProjectException{try {//创建菜品DishVo dishVoResult = BeanConv.toBean(dishService.createDish(dishVo), DishVo.class);dishVoResult.setHasDishFlavor(dishVo.getHasDishFlavor());//绑定附件if (!EmptyUtil.isNullOrEmpty(dishVoResult)){affixFace.bindBusinessId(AffixVo.builder().businessId(dishVoResult.getId()).id(dishVo.getAffixVo().getId()).build());}dishVoResult.setAffixVo(AffixVo.builder().pathUrl(dishVo.getAffixVo().getPathUrl()).businessId(dishVoResult.getId()).id(dishVo.getAffixVo().getId()).build());//构建初始化库存String key = AppletCacheConstant.REPERTORY_DISH+dishVoResult.getId();RAtomicLong atomicLong = redissonClient.getAtomicLong(key);atomicLong.set(dishVoResult.getDishNumber());return dishVoResult;} catch (Exception e) {log.error("保存菜品异常:{}", ExceptionsUtil.getStackTraceAsString(e));throw new ProjectException(DishEnum.CREATE_FAIL);}}@Override@Transactionalpublic Boolean updateDish(DishVo dishVo) throws ProjectException {Boolean flag = false;//修改菜品flag = dishService.updateDish(dishVo);//处理菜品图片if (flag){List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(dishVo.getId());List<Long> affixIds = affixVoList.stream().map(AffixVo::getId).collect(Collectors.toList());if (!affixIds.contains(dishVo.getAffixVo().getId())){//删除图片flag = affixFace.deleteAffixVoByBusinessId(dishVo.getId());//绑定新图片affixFace.bindBusinessId(AffixVo.builder().businessId(dishVo.getId()).id(dishVo.getAffixVo().getId()).build());}}//构建redis库存String key = AppletCacheConstant.REPERTORY_DISH+dishVo.getId();RAtomicLong atomicLong = redissonClient.getAtomicLong(key);atomicLong.set(dishVo.getDishNumber());return flag;}@Override@Transactionalpublic Boolean deleteDish(String[] checkedIds)throws ProjectException {try {Boolean flag = dishService.deleteDish(checkedIds);for (String checkedId : checkedIds) {//删除图片affixFace.deleteAffixVoByBusinessId(Long.valueOf(checkedId));//删除菜品库存String key = AppletCacheConstant.REPERTORY_DISH+checkedId;RAtomicLong atomicLong = redissonClient.getAtomicLong(key);atomicLong.delete();}return flag;} catch (Exception e) {log.error("删除菜品异常:{}", ExceptionsUtil.getStackTraceAsString(e));throw new ProjectException(DishEnum.DELETE_FAIL);}}@Overridepublic DishVo findDishByDishId(Long dishId)throws ProjectException {try {//按菜品ID查找菜品Dish dish = dishService.getById(dishId);if (!EmptyUtil.isNullOrEmpty(dish)){return BeanConv.toBean(dish,DishVo.class);}return null;} catch (Exception e) {log.error("查找菜品所有菜品异常:{}", ExceptionsUtil.getStackTraceAsString(e));throw new ProjectException(DishEnum.SELECT_DISH_FAIL);}}}
项目启动时InitDish类自动初始化所有起售且有效状态的菜品库存
package com.itheima.restkeeper.init;import com.itheima.restkeeper.constant.AppletCacheConstant;import com.itheima.restkeeper.pojo.Dish;import com.itheima.restkeeper.service.IDishService;import org.redisson.api.RAtomicLong;import org.redisson.api.RedissonClient;import org.redisson.client.RedisClient;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import java.util.List;/*** @ClassName InitDish.java* @Description 初始化菜品库存*/@Componentpublic class InitDish {@AutowiredIDishService dishService;@AutowiredRedissonClient redissonClient;@PostConstructpublic void initDishNumber(){//查询所有有效的且起售状态的菜品List<Dish> dishList = dishService.findDishVos();for (Dish dish : dishList) {//构建初始化库存String key = AppletCacheConstant.REPERTORY_DISH+dish.getId();RAtomicLong atomicLong = redissonClient.getAtomicLong(key);atomicLong.set(dish.getDishNumber());}}}
2、购物车操作
2.1、功能区拆解
开桌之后用户可以在H5首页选购菜品,及菜品的数量,如图所示:

下图为整个购物车操作的流程图,我们首先进行下流程分析:

2.2、数据结构
购物车:在Redis中临时存储选购的菜品、选购的数量
其中数据结构就是OrderItemVo的json字符串,其结构如下:

2.3、功能开发

AppletController
传入菜品ID、订单编号、菜品口味、操作动作请求opertionShoppingCart方法@PostMapping("opertion-shopping-cart/{dishId}/{orderNo}/{dishFlavor}/{opertionType}")@ApiOperation(value = "操作购物车",notes = "每次点击添加或减少1,则添加或减少购物车订单项1,同时增减库存1")@ApiImplicitParams({@ApiImplicitParam(paramType = "path",name = "dishId",value = "菜品ID",dataType = "Long"),@ApiImplicitParam(paramType = "path",name = "orderNo",value = "订单编号",dataType = "Long"),@ApiImplicitParam(paramType = "path",name = "dishFlavor",value = "菜品口味",dataType = "String"),@ApiImplicitParam(paramType = "path",name = "opertionType",value = "操作动作",dataType = "String")})public ResponseWrap<OrderVo> opertionShoppingCart(@PathVariable("dishId") Long dishId,@PathVariable("orderNo") Long orderNo,@PathVariable("dishFlavor") String dishFlavor,@PathVariable("opertionType") String opertionType) {OrderVo orderVoResult = appletFace.opertionShoppingCart(dishId,orderNo,dishFlavor,opertionType);return ResponseWrapBuild.build(BrandEnum.SUCCEED,orderVoResult);}
AppletFace
/**** @description 每次点击添加或减少,则添加或减少购物车订单项,同时增减库存* 【1】加入购物车逻辑:* 首先进行库存判定,如果库存够,则减库存,新增购物车订单项,如果库存不够则给出相对应提示信息,且回滚库存* 【2】减少购物车逻辑:* 首先从购物车订单项中移除菜品信息,回滚库存* @param dishId 菜品ID* @param orderNo 订单Id* @param dishFlavor 口味* @param opertionType 操作方式:移除购物车:REMOVE,加入购物车:ADD* @return*/OrderVo opertionShoppingCart(Long dishId,Long orderNo,String dishFlavor,String opertionType) throws ProjectException;
AppletFaceImpl
购物车功能-总述
购物车功能-添加1、获得订单加锁
2、是否获得到锁
3、操作购物车订单项
3.1、添加到购物车订单项3.2、移除购物车订单项
4、查询当前订单信息,并且处理订单项
@Override@Transactionalpublic OrderVo opertionShoppingCart(Long dishId,Long orderNo,String dishFlavor,String opertionType) throws ProjectException {//1、获得订单加锁:此处多余:因为此业务中使用atomicLong本身就是原子性的,所以不需要加锁String keyOrder = AppletCacheConstant.ADD_TO_ORDERITEM_LOCK + orderNo;RLock lockOrder = redissonClient.getLock(keyOrder);OrderVo orderVoResult = null;try {//2、因为此业务中使用atomicLong本身就是原子性的,所以不需要加锁if (lockOrder.tryLock(AppletCacheConstant.REDIS_WAIT_TIME,AppletCacheConstant.REDIS_LEASETIME,TimeUnit.SECONDS)) {String keyDish = AppletCacheConstant.REPERTORY_DISH + dishId;RAtomicLong atomicLong = redissonClient.getAtomicLong(keyDish);//3.1、添加到购物车订单项if (opertionType.equals(SuperConstant.OPERTION_TYPE_ADD)) {this.addToShoppingCart(dishId, orderNo,dishFlavor, atomicLong);}//3.2、移除购物车订单项if (opertionType.equals(SuperConstant.OPERTION_TYPE_REMOVE)) {this.removeToShoppingCart(dishId, orderNo, atomicLong);}//4、查询当前订单信息,并且处理订单项orderVoResult = orderService.findOrderByOrderNo(orderNo);return handlerOrderVo(orderVoResult);}} catch (InterruptedException e) {log.error("===编辑dishId:{},orderNo:{}进入购物车加锁失败:{}",dishId, orderNo, ExceptionsUtil.getStackTraceAsString(e));throw new ProjectException(OpenTableEnum.TRY_LOCK_FAIL);} finally {lockOrder.unlock();}return orderVoResult;}

6
/*** @param dishId 菜品ID* @param orderNo 订单编号* @param atomicLong 原子计数器* @return* @description 添加购物车订单项*/private void addToShoppingCart(Long dishId,Long orderNo,String dishFlavor,RAtomicLong atomicLong) {//1、如果库存够,redis减库存if (atomicLong.decrementAndGet() >= 0) {//2、mysql菜品表库存Boolean flag = dishService.updateDishNumber(-1L, dishId);if (!flag) {//减菜品库存失败,归还redis菜品库存atomicLong.incrementAndGet();throw new ProjectException(ShoppingCartEnum.UPDATE_DISHNUMBER_FAIL);}//3、查询redis缓存的购物车订单项String key = AppletCacheConstant.ORDERITEMVO_STATISTICS + orderNo;RMapCache<Long, OrderItemVo> orderItemVoRMap = redissonClient.getMapCache(key);OrderItemVo orderItemHandler = orderItemVoRMap.get(dishId);//4.1、如果以往购物车订单项中无则新增if (EmptyUtil.isNullOrEmpty(orderItemHandler)) {Dish dish = dishService.getById(dishId);List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(dish.getId());OrderVo orderVo = orderService.findOrderByOrderNo(orderNo);OrderItemVo orderItemVo = OrderItemVo.builder().productOrderNo(orderNo).categoryId(dish.getCategoryId()).dishId(dishId).dishName(dish.getDishName()).dishFlavor(dishFlavor).dishNum(1L).price(dish.getPrice()).reducePrice(dish.getReducePrice()).build();orderItemVo.setAffixVo(affixVoList.get(0));//沿用订单中的分库键orderItemVo.setShardingId(orderVo.getShardingId());orderItemVoRMap.put(dishId, orderItemVo);//4.2、如果以往购物车订单项中有此菜品,则进行购物车订单项数量递增} else {orderItemHandler.setDishNum(orderItemHandler.getDishNum() + 1);orderItemVoRMap.put(dishId, orderItemHandler);}} else {//5、redis库存不足,虽然可以不处理,但建议还是做归还库存atomicLong.incrementAndGet();throw new ProjectException(ShoppingCartEnum.UNDERSTOCK);}}
购物车功能-移除

1、菜品库存增加
2、查询redis缓存的购物车订单项
3、购物车订单项存在
3.1、购物车订单项的数量大于1,修改当前数量3.2购物车订单项的数量等于1,则删除此订单项4、redis菜品库存增加
/*** @param dishId 菜品ID* @param orderNo 订单编号* @param atomicLong 原子计数器* @return* @description 移除购物车订单项*/private void removeToShoppingCart(Long dishId, Long orderNo, RAtomicLong atomicLong) {boolean flag = true;//1、菜品库存增加flag = dishService.updateDishNumber(1L, dishId);if (!flag) {throw new ProjectException(ShoppingCartEnum.UPDATE_DISHNUMBER_FAIL);}//2、查询redis缓存的购物车订单项String key = AppletCacheConstant.ORDERITEMVO_STATISTICS + orderNo;RMapCache<Long, OrderItemVo> orderItemVoRMap = redissonClient.getMapCache(key);OrderItemVo orderItemHandler = orderItemVoRMap.get(dishId);//3、购物车订单项存在if (!EmptyUtil.isNullOrEmpty(orderItemHandler)) {//3.1、购物车订单项的数量大于1,修改当前数量if (orderItemHandler.getDishNum().intValue() > 1) {orderItemHandler.setDishNum(orderItemHandler.getDishNum() - 1);orderItemVoRMap.put(dishId, orderItemHandler);} else {//3.2购物车订单项的数量等于1,则删除此订单项orderItemVoRMap.remove(dishId, orderItemHandler);}//4、redis菜品库存增加atomicLong.incrementAndGet();}else {throw new ProjectException(ShoppingCartEnum.UPDATE_DISHNUMBER_FAIL);}}
第二章 点餐平台-下订单
1、功能区拆解

用户在选择好菜品及数量后,点击去结算按钮,则会把购物车的订单项菜品同步到可核算订单项中,操作如下:

2、功能开发
- AppletController
传入当前要进行下单的订单号orderNo
@PostMapping("placeOrder/{orderNo}")@ApiOperation(value = "下单操作",notes = "添加购物车订单项到可结算订单项")@ApiImplicitParam(paramType = "path",name = "orderNo",value = "订单编号",dataType = "Long")public ResponseWrap<OrderVo> placeOrder(@PathVariable("orderNo") Long orderNo) {OrderVo orderVoResult = appletFace.placeOrder(orderNo);return ResponseWrapBuild.build(BrandEnum.SUCCEED,orderVoResult);}
- AppletFace
/**** @description 下单操作:添加购物车订单项到可结算订单项* @param orderNo 订单编号* @return*/OrderVo placeOrder(Long orderNo) throws ProjectException;
- AppletFaceImpl

1、锁定订单
2、查询可以核算订单项
3、查询购物车订单项
4、购物车订单项不为空才合并
5、求交集:购物车订单项与核算订单项合并
6、求差集:保存购物车订单项到可核算订单项
7、计算更新订单金额
9、清理redis购物车订单项目
9、再次查询订单
@Override@Transactionalpublic OrderVo placeOrder(Long orderNo) throws ProjectException {try {//1、锁定订单String key = AppletCacheConstant.ADD_TO_ORDERITEM_LOCK + orderNo;RLock lock = redissonClient.getLock(key);OrderVo orderVoResult = null;try {if (lock.tryLock(AppletCacheConstant.REDIS_WAIT_TIME,AppletCacheConstant.REDIS_LEASETIME,TimeUnit.SECONDS)) {Boolean flag = true;orderVoResult = orderService.findOrderByOrderNo(orderNo);//2、查询可以核算订单项List<OrderItem> orderItemList = orderItemService.findOrderItemByOrderNo(orderVoResult.getOrderNo());List<OrderItemVo> orderItemVoStatisticsList = BeanConv.toBeanList(orderItemList, OrderItemVo.class);if (EmptyUtil.isNullOrEmpty(orderItemVoStatisticsList)) {orderItemVoStatisticsList = new ArrayList<>();}//3、查询购物车订单项key = AppletCacheConstant.ORDERITEMVO_STATISTICS + orderNo;RMapCache<Long, OrderItemVo> orderItemVoRMap = redissonClient.getMapCache(key);List<OrderItemVo> orderItemVoTemporaryList = (List<OrderItemVo>)orderItemVoRMap.readAllValues();//4、购物车订单项不为空才合并if (!EmptyUtil.isNullOrEmpty(orderItemVoTemporaryList)) {//5、求交集:购物车订单项与核算订单项合并flag = this.intersection(flag,orderItemVoStatisticsList,orderItemVoTemporaryList);if (!flag) {throw new ProjectException(OrderItemEnum.UPDATE_ORDERITEM_FAIL);}//6、求差集:保存购物车订单项到可核算订单项flag = this.difference(flag,orderItemVoStatisticsList,orderItemVoTemporaryList);if (!flag) {throw new ProjectException(OrderItemEnum.UPDATE_ORDERITEM_FAIL);}//7、计算更新订单信息flag = this.calculateOrderAmount(orderNo,orderVoResult);if (!flag) {throw new ProjectException(OrderItemEnum.SAVE_ORDER_FAIL);}//8、清理redis购物车订单项目orderItemVoTemporaryList.forEach(n->{orderItemVoRMap.remove(n.getDishId());});}}} catch (InterruptedException e) {log.error("合并订单出错:{}",ExceptionsUtil.getStackTraceAsString(e));throw new ProjectException(OrderItemEnum.LOCK_ORDER_FAIL);} finally {lock.unlock();}//9、再次查询订单return handlerOrderVo(orderVoResult);}catch (Exception e){log.error("下单操作异常:{}", ExceptionsUtil.getStackTraceAsString(e));throw new ProjectException(OrderEnum.PLACE_ORDER_FAIL);}}
/**** @description 求交集:购物车订单项与核算订单项合并* @param flag 是否操作成功* @param orderItemVoStatisticsList 可核算订单项* @param orderItemVoTemporaryList 购物车订单项* @return 是否操作成功*/private Boolean intersection(boolean flag,List<OrderItemVo> orderItemVoStatisticsList,List<OrderItemVo> orderItemVoTemporaryList){//1、求交集List<OrderItemVo> listIntersection = new ArrayList<>();listIntersection.addAll(orderItemVoTemporaryList);listIntersection.retainAll(orderItemVoStatisticsList);if (!EmptyUtil.isNullOrEmpty(listIntersection)) {//2、获得核算订单项中需要增加的订单项List<OrderItemVo> orderItemVoHandler = orderItemVoStatisticsList.stream().filter(n ->listIntersection.contains(n)).collect(Collectors.toList());List<OrderItem> orderItemHandler = BeanConv.toBeanList(orderItemVoHandler, OrderItem.class);//3、循环累加当前核算订单的菜品数量List<OrderItem> orderItems = new ArrayList<>();orderItemHandler.forEach(n -> {listIntersection.forEach(k -> {if (n.getDishId().longValue() == k.getDishId().longValue()) {OrderItem orderItem = OrderItem.builder().id(n.getId()).dishNum(n.getDishNum() + k.getDishNum()).build();orderItems.add(orderItem);}});});//4、批量修改可结算订单项目flag = orderItemService.updateBatchById(orderItems);}return flag;}
/**** @description 求差集:保存购物车订单项到可核算订单项* @param flag 是否操作成功* @param orderItemVoStatisticsList 可核算订单项* @param orderItemVoTemporaryList 购物车订单项* @return 是否操作成功*/private Boolean difference(boolean flag,List<OrderItemVo> orderItemVoStatisticsList,List<OrderItemVo> orderItemVoTemporaryList){//1、求差集List<OrderItemVo> listDifference = new ArrayList<>();listDifference.addAll(orderItemVoTemporaryList);listDifference.removeAll(orderItemVoStatisticsList);//2、处理订单项if (!EmptyUtil.isNullOrEmpty(listDifference)) {List<OrderItem> orderItems = BeanConv.toBeanList(listDifference, OrderItem.class);flag = orderItemService.saveBatch(orderItems);}return flag;}
/**** @description 计算更新订单信息* @param orderNo 订单编号* @param orderVoResult 计算结果* @return* @return: java.lang.Boolean*/private Boolean calculateOrderAmount(Long orderNo,OrderVo orderVoResult){//1、计算订单金额List<OrderItem> orderItemListResult = orderItemService.findOrderItemByOrderNo(orderNo);BigDecimal sumPrice = orderItemListResult.stream().map(n -> {BigDecimal price = n.getPrice();BigDecimal reducePrice = n.getReducePrice();Long dishNum = n.getDishNum();//如果有优惠价格以优惠价格计算if (EmptyUtil.isNullOrEmpty(reducePrice)) {return price.multiply(new BigDecimal(dishNum));} else {return reducePrice.multiply(new BigDecimal(dishNum));}}).reduce(BigDecimal.ZERO, BigDecimal::add);//2、更新订单金额信息orderVoResult.setPayableAmountSum(sumPrice);OrderVo orderVoHandler = OrderVo.builder().id(orderVoResult.getId()).payableAmountSum(sumPrice).build();return orderService.updateById(BeanConv.toBean(orderVoHandler, Order.class));}
课堂讨论
1、什么是库存超卖?餐掌柜中是如何解决超卖问题的?
2、除了使用atomicLong来处理库存,你还有什么样的解决方案?
3、简述添加购物车的流程?
4、多人同时点餐,你是怎么做到让大家看到的信息都是同步的?
5、点餐后不下单,你如何同步归还库存,有哪些方案?
6、简述下订单的流程【如何合并购物车订单项到可核算订单项】?
7、为什么你们的项目在操作购物车的时候就扣库存呢?有没有更好的方案?
课后任务
1、完成点餐、下订单开发,同步到git【☆☆☆☆☆】
2、完成当天课堂讨论,同步到git【☆☆☆☆☆】
3、完成2-5道sql练习【☆☆☆☆】
4、梳理项目二-点餐、下订单业务,同步到git【☆☆☆】
