- 查询商品信息,查询收货地址
- 查询库存信息
- 计算价格
1. 验证令牌防止重复提交
- 获取当前用户登录的信息
- 通过lua脚本验证令牌和删除令牌
2. 验证价格
- 从数据库查询最新的商品价格
- 和订单的金额对比,如果不一致则返回错误信息
3… 验证库存并锁库存
- 先判断仓库是否有足够的库存
- 保存库存工作单详情信息(哪个商品需要修改多少库存),用于追溯(解锁库存等)
- 锁定库存,并将工作单信息发送给MQ
- 如果锁定失败,工作单信息都回滚。已经发送出去的消息,即使要解锁库存,由于在数据库查不到工作单,所以就不用解锁
4. 生成订单
- 生成订单信息
- 发送订单信息到MQ
5. 删购物车中对应的记录(消息队列)
6. 用户支付
- 成功支付,更新订单信息为已支付
- 规定时间内未支付,关闭订单
如果用户下单后一直不支付,库存处于锁定状态,陷入店家商品卖不出,买家无法购买的情况。所以,需要定时关单。
常用解决方案:
-
利用定时任务轮询数据库
缺点:消耗系统内存,增加了数据库的压力,存在较大的时间误差 -
rabbitmq的延时队列和死信队列结合(推荐)
1. 在订单创建成功后,发送消息到延时队列。
- 配置延时队列,延时时间就是订单从创建到支付过程,允许的最大等待时间。
- 延时时间一到,消息被转入死信队列**
2. 订单系统监听死信队列,获取到死信消息后,执行关单解库存 *** 作
- 先查询一下数据库,判断此订单状态是否已支付
- 已支付订单不用任何 *** 作,把消息消费掉就行
- 未支付订单更新订单状态为已取消
解锁库存分为以下两种情况:
- 如果验库存并锁库存成功,还没来得及执行下单 *** 作,服务器就宕机了。需要定时解锁库存
- 订单关闭,需要解锁库存
1. 查询数据库中是否有该库存工作单
- 有:证明库存锁定成功了
- 没有:说明锁库存的时候回滚了,不用执行任何 *** 作
2. 有库存工作单,再查询是否有订单
- 没有订单,需要解锁库存
- 有订单,再判断订单状态,如果已支付,则不能解锁库存,如果已取消,则解锁库存
3. 解锁库存
- 解锁库存
- 更新库存工作单的状态为已解锁
@Override public Order/confirm/iVo /confirm/iOrder() throws ExecutionException, InterruptedException { //构建Order/confirm/iVo Order/confirm/iVo confirmVo = new Order/confirm/iVo(); //获取当前用户登录的信息 MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get(); RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); //开启第一个异步任务 CompletableFuture下订单代码addressFuture = CompletableFuture.runAsync(() -> { //每一个线程都来共享之前的请求数据 RequestContextHolder.setRequestAttributes(requestAttributes); //1、远程查询所有的收获地址列表 List address = memberFeignService.getAddress(memberResponseVo.getId()); /confirm/iVo.setMemberAddressVos(address); }, threadPoolExecutor); //开启第二个异步任务 CompletableFuture cartInfoFuture = CompletableFuture.runAsync(() -> { //每一个线程都来共享之前的请求数据 RequestContextHolder.setRequestAttributes(requestAttributes); //2、远程查询购物车所有选中的购物项 List currentCartItems = cartFeignService.getCurrentCartItems(); /confirm/iVo.setItems(currentCartItems); //feign在远程调用之前要构造请求,调用很多的拦截器 }, threadPoolExecutor).thenRunAsync(() -> { List items = /confirm/iVo.getItems(); //获取全部商品的id List skuIds = items.stream() .map((itemVo -> itemVo.getSkuId())) .collect(Collectors.toList()); //远程查询商品库存信息 R skuHasStock = wmsFeignService.getSkuHasStock(skuIds); List skuStockVos = skuHasStock.getData("data", new TypeReference >() {}); if (skuStockVos != null && skuStockVos.size() > 0) { //将skuStockVos集合转换为map Map
skuHasStockMap = skuStockVos.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock)); /confirm/iVo.setStocks(skuHasStockMap); } },threadPoolExecutor); //3、查询用户积分 Integer integration = memberResponseVo.getIntegration(); /confirm/iVo.setIntegration(integration); //4、价格数据自动计算 //防重令牌(防止表单重复提交) //为用户设置一个token,三十分钟过期时间(存在redis) String token = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set(USER_ORDER_TOKEN_PREFIX+memberResponseVo.getId(),token,30, TimeUnit.MINUTES); /confirm/iVo.setOrderToken(token); CompletableFuture.allOf(addressFuture,cartInfoFuture).get(); return /confirm/iVo; }
@Transactional(rollbackFor = Exception.class) // @GlobalTransactional(rollbackFor = Exception.class) @Override public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) { /confirm/iVoThreadLocal.set(vo); SubmitOrderResponseVo responseVo = new SubmitOrderResponseVo(); //去创建、下订单、验令牌、验价格、锁定库存... //获取当前用户登录的信息 MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get(); responseVo.setCode(0); //1、验证令牌是否合法【令牌的对比和删除必须保证原子性】 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; String orderToken = vo.getOrderToken(); //通过lure脚本原子验证令牌和删除令牌 Long result = redisTemplate.execute(new DefaultRedisscript验证库存代码(script, Long.class), Arrays.asList(USER_ORDER_TOKEN_PREFIX + memberResponseVo.getId()), orderToken); if (result == 0L) { //令牌验证失败 responseVo.setCode(1); return responseVo; } else { //令牌验证成功 //1、创建订单、订单项等信息 OrderCreateTo order = createOrder(); //2、验证价格 BigDecimal payAmount = order.getOrder().getPayAmount(); BigDecimal payPrice = vo.getPayPrice(); if (Math.abs(payAmount.subtract(payPrice).doublevalue()) < 0.01) { //金额对比 //TODO 3、保存订单 saveOrder(order); //4、库存锁定,只要有异常,回滚订单数据 //订单号、所有订单项信息(skuId,skuNum,skuName) WareSkuLockVo lockVo = new WareSkuLockVo(); lockVo.setOrderSn(order.getOrder().getOrderSn()); //获取出要锁定的商品数据信息 List orderItemVos = order.getOrderItems().stream().map((item) -> { OrderItemVo orderItemVo = new OrderItemVo(); orderItemVo.setSkuId(item.getSkuId()); orderItemVo.setCount(item.getSkuQuantity()); orderItemVo.setTitle(item.getSkuName()); return orderItemVo; }).collect(Collectors.toList()); lockVo.setLocks(orderItemVos); //TODO 调用远程锁定库存的方法 //出现的问题:扣减库存成功了,但是由于网络原因超时,出现异常,导致订单事务回滚,库存事务不回滚(解决方案:seata) //为了保证高并发,不推荐使用seata,因为是加锁,并行化,提升不了效率,可以发消息给库存服务 R r = wmsFeignService.orderLockStock(lockVo); if (r.getCode() == 0) { //锁定成功 responseVo.setOrder(order.getOrder()); // int i = 10/0; //TODO 订单创建成功,发送消息给MQ rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",order.getOrder()); //删除购物车里的数据 // redisTemplate.delete(CART_PREFIX+memberResponseVo.getId()); return responseVo; } else { //锁定失败 String msg = (String) r.get("msg"); throw new NoStockException(msg); //responseVo.setCode(3); //return responseVo; } } else { responseVo.setCode(2); return responseVo; } } }
@Transactional(rollbackFor = Exception.class) @Override public boolean orderLockStock(WareSkuLockVo vo) { WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity(); wareOrderTaskEntity.setOrderSn(vo.getOrderSn()); wareOrderTaskEntity.setCreateTime(new Date()); wareOrderTaskService.save(wareOrderTaskEntity); //1、按照下单的收货地址,找到一个就近仓库,锁定库存 //2、找到每个商品在哪个仓库都有库存 List监听订单死信队列代码locks = vo.getLocks(); List collect = locks.stream().map((item) -> { SkuWareHasStock stock = new SkuWareHasStock(); Long skuId = item.getSkuId(); stock.setSkuId(skuId); stock.setNum(item.getCount()); //查询这个商品在哪个仓库有库存 List wareIdList = wareSkuDao.listWareIdHasSkuStock(skuId); stock.setWareId(wareIdList); return stock; }).collect(Collectors.toList()); //2、锁定库存 for (SkuWareHasStock hasStock : collect) { boolean skuStocked = false; Long skuId = hasStock.getSkuId(); List wareIds = hasStock.getWareId(); if (org.springframework.util.StringUtils.isEmpty(wareIds)) { //没有任何仓库有这个商品的库存 throw new NoStockException(skuId); } //1、如果每一个商品都锁定成功,将当前商品锁定了几件的工作单记录发给MQ //2、锁定失败。前面保存的工作单信息都回滚了。发送出去的消息,即使要解锁库存,由于在数据库查不到指定的id,所有就不用解锁 for (Long wareId : wareIds) { //锁定成功就返回1,失败就返回0 Long count = wareSkuDao.lockSkuStock(skuId,wareId,hasStock.getNum()); if (count == 1) { skuStocked = true; WareOrderTaskDetailEntity taskDetailEntity = WareOrderTaskDetailEntity.builder() .skuId(skuId) .skuName("") .skuNum(hasStock.getNum()) .taskId(wareOrderTaskEntity.getId()) .wareId(wareId) .lockStatus(1) .build(); wareOrderTaskDetailService.save(taskDetailEntity); //TODO 告诉MQ库存锁定成功 StockLockedTo lockedTo = new StockLockedTo(); lockedTo.setId(wareOrderTaskEntity.getId()); StockDetailTo detailTo = new StockDetailTo(); BeanUtils.copyProperties(taskDetailEntity,detailTo); lockedTo.setDetailTo(detailTo); rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo); break; } else { //当前仓库锁失败,重试下一个仓库 } } if (skuStocked == false) { //当前商品所有仓库都没有锁住 throw new NoStockException(skuId); } } //3、肯定全部都是锁定成功的 return true; }
@RabbitListener(queues = "order.release.order.queue") @Service public class OrderCloseListener { @Autowired private OrderService orderService; @RabbitHandler public void listener(OrderEntity orderEntity, Channel channel, Message message) throws IOException { System.out.println("收到过期的订单信息,准备关闭订单" + orderEntity.getOrderSn()); try { orderService.closeOrder(orderEntity); channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (Exception e) { channel.basicReject(message.getMessageProperties().getDeliveryTag(),true); } } }
在WareSkuMapper对应的xml中添加映射:
update wms_ware_sku set stock_locked = stock_locked + #{count} where id = #{id} update wms_ware_sku set stock_locked = stock_locked - #{count} where id = #{id}
在gmall-wms-interface中的GmallWmsApi中添加接口方法
@PostMapping("wms/waresku/check/lock") public ResponseVo关订单代码> checkAndLock(@RequestBody List
lockVOS);
@Override public void closeOrder(OrderEntity orderEntity) { //关闭订单之前先查询一下数据库,判断此订单状态是否已支付 OrderEntity orderInfo = this.getOne(new QueryWrapper监听库存死信队列代码(). eq("order_sn",orderEntity.getOrderSn())); if (orderInfo.getStatus().equals(OrderStatusEnum.CREATE_NEW.getCode())) { //代付款状态进行关单 OrderEntity orderUpdate = new OrderEntity(); orderUpdate.setId(orderInfo.getId()); orderUpdate.setStatus(OrderStatusEnum.CANCLED.getCode()); this.updateById(orderUpdate); // 发送消息给MQ OrderTo orderTo = new OrderTo(); BeanUtils.copyProperties(orderInfo, orderTo); try { //TODO 确保每个消息发送成功,给每个消息做好日志记录,(给数据库保存每一个详细信息)保存每个消息的详细信息 rabbitTemplate.convertAndSend("order-event-exchange", "order.release.other", orderTo); } catch (Exception e) { //TODO 定期扫描数据库,重新发送失败的消息 } } }
@RabbitHandler public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException { log.info("******收到解锁库存的信息******"); try { //解锁库存 wareSkuService.unlockStock(to); // 手动删除消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (Exception e) { // 解锁失败 将消息重新放回队列,让别人消费 channel.basicReject(message.getMessageProperties().getDeliveryTag(),true); } } @RabbitHandler public void handleOrderCloseRelease(OrderTo orderTo, Message message, Channel channel) throws IOException { log.info("******收到订单关闭,准备解锁库存的信息******"); try { wareSkuService.unlockStock(orderTo); // 手动删除消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (Exception e) { // 解锁失败 将消息重新放回队列,让别人消费 channel.basicReject(message.getMessageProperties().getDeliveryTag(),true); } }解库存代码
@Override public void unlockStock(StockLockedTo to) { //库存工作单的id StockDetailTo detail = to.getDetailTo(); Long detailId = detail.getId(); WareOrderTaskDetailEntity taskDetailInfo = wareOrderTaskDetailService.getById(detailId); if (taskDetailInfo != null) { //查出wms_ware_order_task工作单的信息 Long id = to.getId(); WareOrderTaskEntity orderTaskInfo = wareOrderTaskService.getById(id); //获取订单号查询订单状态 String orderSn = orderTaskInfo.getOrderSn(); //远程查询订单信息 R orderData = orderFeignService.getOrderStatus(orderSn); if (orderData.getCode() == 0) { //订单数据返回成功 OrderVo orderInfo = orderData.getData("data", new TypeReference() {}); //判断订单状态是否已取消或者支付或者订单不存在 if (orderInfo == null || orderInfo.getStatus() == 4) { //订单已被取消,才能解锁库存 if (taskDetailInfo.getLockStatus() == 1) { //当前库存工作单详情状态1,已锁定,但是未解锁才可以解锁 unLockStock(detail.getSkuId(),detail.getWareId(),detail.getSkuNum(),detailId); } } } else { //消息拒绝以后重新放在队列里面,让别人继续消费解锁 //远程调用服务失败 throw new RuntimeException("远程调用服务失败"); } } else { //无需解锁 } } public void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId) { //库存解锁 wareSkuDao.unLockStock(skuId,wareId,num); //更新工作单的状态 WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity(); taskDetailEntity.setId(taskDetailId); //变为已解锁 taskDetailEntity.setLockStatus(2); wareOrderTaskDetailService.updateById(taskDetailEntity); } @Transactional(rollbackFor = Exception.class) @Override public void unlockStock(OrderTo orderTo) { String orderSn = orderTo.getOrderSn(); //查一下最新的库存解锁状态,防止重复解锁库存 WareOrderTaskEntity orderTaskEntity = wareOrderTaskService.getOrderTaskByOrderSn(orderSn); //按照工作单的id找到所有 没有解锁的库存,进行解锁 Long id = orderTaskEntity.getId(); List list = wareOrderTaskDetailService.list(new QueryWrapper () .eq("task_id", id).eq("lock_status", 1)); for (WareOrderTaskDetailEntity taskDetailEntity : list) { unLockStock(taskDetailEntity.getSkuId(), taskDetailEntity.getWareId(), taskDetailEntity.getSkuNum(), taskDetailEntity.getId()); } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)