学习目标
1、能够描述超卖问题
2、理解redission的AtomicLong如何初始化库存
3、实现菜品库存的优化
4、能够描述购物车操作流程【添加购物车、移除购物车】
5、理解购物车操作中订单锁的意义,及对菜品库存使用redission的AtomicLong操作
6、能够描述下单操作流程【订单加锁,合并购物车订单项到可核算订单项】
第一章 点餐平台-点餐
1、库存超卖设置
1.1、什么是超卖
当前菜品库存只有1个,当有两个线程过来后,都执行成功了,生成了两个订单,这就是超卖,如图所示:
方案一:乐观锁机制
使用mybatis plus 里面的@version注解:
@Version
private 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 {
@Autowired
IDishService dishService;
@Autowired
IDishFlavorService dishFlavorService;
@Autowired
RedissonClient redissonClient;
@DubboReference(version = "${dubbo.application.version}",check = false)
AffixFace affixFace;
@Override
public 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));
}
//处理口味1
List<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
@Transactional
public 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
@Transactional
public 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
@Transactional
public 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);
}
}
@Override
public 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 初始化菜品库存
*/
@Component
public class InitDish {
@Autowired
IDishService dishService;
@Autowired
RedissonClient redissonClient;
@PostConstruct
public 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
@Transactional
public 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
@Transactional
public 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【☆☆☆】