OrderWebController提交订单
/*** 下单功能,提交订单。 需要去创建订单、验令牌、验价格、锁库存* @param vo 订单提交的数据* @param model* @param redirectAttributes* @return 下单成功来到支付选择页,下单失败回到订单确认页重新确认订单信息*/@PostMapping("/submitOrder")public String submitOrder(OrderSubmitVo vo, Model model, RedirectAttributes redirectAttributes) {try {SubmitOrderResponseVo responseVo = orderService.submitOrder(vo);if (responseVo.getCode() == 0) {model.addAttribute("submitOrderResp", responseVo); // 返回页面数据// 下单成功来到支付选择页return "pay";} else {String msg = "下单失败";switch (responseVo.getCode()) {case 1:msg += "订单信息过期,请刷新重新提交";break;case 2:msg += "订单商品价格发生变化,请确认后再次提交";break;case 3:msg += "商品库存不足";}redirectAttributes.addFlashAttribute("msg", msg); // 返回页面数据// 下单失败回到订单确认页重新确认订单信息return "redirect:http://order.gulimall.com/toTrade";}}catch (Exception e){if(e instanceof NoStockException){String msg = ((NoStockException) e).getMessage();redirectAttributes.addFlashAttribute("msg",msg);}return "redirect:http://order.gulimall.com/toTrade";}}
OrderServiceImpl
@Service("orderService")public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {// 将页面传递过来的数据在同一个线程内进行共享private ThreadLocal<OrderSubmitVo> submitVoThreadLocal = new ThreadLocal<>();@AutowiredOrderItemService orderItemService;@AutowiredCartFeginService cartFeginService;@AutowiredMemberFeginService memberFeginService;@AutowiredProductFeignService productFeignService;@AutowiredWmsFeignService wmsFeignService;@AutowiredStringRedisTemplate redisTemplate;@AutowiredRabbitTemplate rabbitTemplate;@AutowiredThreadPoolExecutor executor;@AutowiredPaymentInfoService paymentInfoService;/*** 给订单确认页返回需要用的数据* @return*/@Overridepublic OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {// 登录拦截器,获取当前登录用户信息MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();OrderConfirmVo orderConfirmVo = new OrderConfirmVo();// 获取主线程中的requestAttributesRequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();// 异步处理CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {// 每个子线程共享之前的请求数据RequestContextHolder.setRequestAttributes(requestAttributes);// 1、远程查询所有的收获地址列表List<MemberAddressVo> address = memberFeginService.getAddress(memberRespVo.getId());orderConfirmVo.setAddress(address);}, executor);// 异步处理CompletableFuture<Void> getCartItemsFuture = CompletableFuture.runAsync(() -> {// 每个子线程共享之前的请求数据RequestContextHolder.setRequestAttributes(requestAttributes);// 2、远程查询购物车所有选中的购物项,要注意获取最新的商品价格,而不是先前存到redis中的商品价格// 利用fegin的RequestInterceptor拦截器功能,远程调用其他服务时,其他服务也能感知当前登录的用户List<OrderItemVo> cartItems = cartFeginService.getCurrentUserCartItems();orderConfirmVo.setItems(cartItems);}, executor).thenRunAsync(() -> {// 获取购物项中的商品skuIdList<OrderItemVo> items = orderConfirmVo.getItems();List<Long> collect = items.stream().map(OrderItemVo::getSkuId).collect(Collectors.toList());// 远程调用库存服务,获取商品的库存信息R skuHasStock = wmsFeignService.getSkuHasStock(collect);List<SkuStockVo> stockVos = skuHasStock.getData(new TypeReference<List<SkuStockVo>>() { });if(stockVos != null){Map<Long, Boolean> map = stockVos.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));// 设置购物车中商品的库存信息orderConfirmVo.setStocks(map);}},executor);// 3、查询用户积分信息Integer integration = memberRespVo.getIntegration();orderConfirmVo.setIntegration(integration);// 4、其他数据,如订单总价,商品总价自动计算// TODO 5、防重令牌String token = UUID.randomUUID().toString().replace("-", "");// 放重令牌保存至服务器redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 30 , TimeUnit.MINUTES);// 放重令牌发送给页面orderConfirmVo.setToken(token);CompletableFuture.allOf(getAddressFuture,getCartItemsFuture).get();return orderConfirmVo;}/*** 下单操作。创建订单、验令牌、验价格、锁库存* @param vo* @return*/@Transactional@Overridepublic SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {submitVoThreadLocal.set(vo);SubmitOrderResponseVo responseVo = new SubmitOrderResponseVo();MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();responseVo.setCode(0);// 1、验证令牌。令牌的对比和删除必须保证原子性String orderToken = vo.getOrderToken();// 如果调用get方法与传入的val相同,调用del方法,不相同返回0(0失败,1成功)String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";// 使用Lua脚本,原子验证令牌和删除令牌Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken);// String redisToken = redisTemplate.opsForValue().get(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());if (result == 0) {// 令牌验证失败responseVo.setCode(1);return responseVo;} else {// 令牌验证成功// 2、创建订单,订单项等信息OrderCreateTo order = createOrder();// 3、验价:根据订单计算的价格与界面提交的价格进行对比(可能因后台修改商品价格出现不一致的情况)BigDecimal payAmount = order.getOrder().getPayAmount();// 界面提交的价格BigDecimal payPrice = vo.getPayPrice();// 金额对比:只要差额在0.01就说明对比成功if (Math.abs(payAmount.subtract(payPrice).doubleValue()) < 0.01) {// 4、保存订单数据至数据库saveOrder(order);// 5、锁定库存、只要有异常就回滚订单数据// 需要订单号,所有订单项(skuId、skuName、数量)WareSkuLockVo lockVo = new WareSkuLockVo();lockVo.setOrderSn(order.getOrder().getOrderSn());// List<OrderItemEntity>转化为List<OrderItemVo>List<OrderItemVo> locks = order.getItems().stream().map(item -> {OrderItemVo itemVo = new OrderItemVo();itemVo.setSkuId(item.getSkuId());itemVo.setCount(item.getSkuQuantity());itemVo.setTitle(item.getSkuName());return itemVo;}).collect(Collectors.toList());lockVo.setLocks(locks);// 5.1 远程锁库存R r = wmsFeignService.orderLockStock(lockVo);if (r.getCode() == 0) {// 锁定库存成功(rebbitMQ消息通知)responseVo.setOrder(order.getOrder());// 6、远程扣减积分// int i = 10 / 0; // 模拟异常,此时订单回滚,库存不回滚,需要借助RabbitMQ使库存回滚// 7、订单创建成功,将每个订单信息发送给MQ// 通过路由键order.create.order发送给延迟队列order.create.order。// 30分钟后延迟队列将消息通过交换机order-event-exchange、路由键order.release.order// 发送给order.release.order.queue、order.release.coupon.queue等队列// 此时需要查询订单的最新状态,判断当前订单是正常关单还是异常关单,若是异常关单,需要回滚相关数据rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", order.getOrder());return responseVo;} else {// 锁定库存失败String msg = (String) r.get("msg");throw new NoStockException(1L);// responseVo.setCode(3);// return responseVo;}} else {// 金额对比失败responseVo.setCode(2);return responseVo;}}}// 保存订单数据private void saveOrder(OrderCreateTo order) {// 保存订单数据:oms_orderOrderEntity orderEntity = order.getOrder();orderEntity.setModifyTime(new Date());this.save(orderEntity);// 批量保存订单项数据:oms_order_itemList<OrderItemEntity> orderItems = order.getItems();orderItemService.saveBatch(orderItems);}// 创建订单private OrderCreateTo createOrder() {OrderCreateTo orderCreateTo = new OrderCreateTo();// 1、构建订单String orderSn = IdWorker.getTimeId();OrderEntity orderEntity = buildOrder(orderSn);// 2、获取所有的订单项List<OrderItemEntity> itemEntities = buildOrderItems(orderSn);// 3、验价computePrice(orderEntity, itemEntities);orderCreateTo.setItems(itemEntities);orderCreateTo.setOrder(orderEntity);return orderCreateTo;}/*** 验价:根据所有订单项的价格计算出订单的价格* @param orderEntity 订单内的价格* @param itemEntities 所有订单项价格*/private void computePrice(OrderEntity orderEntity, List<OrderItemEntity> itemEntities) {// 订单总额,计算各种优惠的总额,计算积分和成长值总额BigDecimal total = new BigDecimal("0.0");BigDecimal couponAmount = new BigDecimal("0.0");BigDecimal promotionAmount = new BigDecimal("0.0");BigDecimal integrationAmount = new BigDecimal("0.0");Integer integration = 0;Integer growth = 0;// 遍历所有订单项的价格。计算出订单的相关金额for (OrderItemEntity entity : itemEntities) {couponAmount = couponAmount.add(entity.getCouponAmount());promotionAmount = promotionAmount.add(entity.getPromotionAmount());integrationAmount = integrationAmount.add(entity.getIntegrationAmount());growth += entity.getGiftGrowth();integration += entity.getGiftIntegration();total = total.add(entity.getRealAmount());}// 订单的总额orderEntity.setTotalAmount(total);// 应付总额 = 订单的总额 + 运费orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));// 各种优惠的总额orderEntity.setPromotionAmount(promotionAmount);orderEntity.setCouponAmount(couponAmount);orderEntity.setIntegrationAmount(integrationAmount);// 积分和成长值总额orderEntity.setGrowth(growth);orderEntity.setIntegration(integration);}// 构建订单private OrderEntity buildOrder(String orderSn) {MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();OrderEntity orderEntity = new OrderEntity();orderEntity.setOrderSn(orderSn);orderEntity.setMemberId(memberRespVo.getId()); // 设置会员id// 获取收货地址信息// 想要获取ThreadLocal内的数据,必须先往里面set数据OrderSubmitVo orderSubmitVo = submitVoThreadLocal.get(); // 页面传递过来的数据R fare = wmsFeignService.getFare(orderSubmitVo.getAddrId());FareVo fareData = fare.getData(new TypeReference<FareVo>(){});// 设置运费信息orderEntity.setFreightAmount(fareData.getFare());// 设置收货地址信息、收货地址详情信息、收货人名字、收货人手机号、收货人邮编、省市区信息orderEntity.setReceiverCity(fareData.getAddress().getCity());orderEntity.setReceiverDetailAddress(fareData.getAddress().getDetailAddress());orderEntity.setReceiverName(fareData.getAddress().getName());orderEntity.setBillReceiverPhone(fareData.getAddress().getPhone());orderEntity.setReceiverPostCode(fareData.getAddress().getPostCode());orderEntity.setReceiverProvince(fareData.getAddress().getProvince());orderEntity.setReceiverRegion(fareData.getAddress().getRegion());// 设置订单状态orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());orderEntity.setAutoConfirmDay(7);orderEntity.setDeleteStatus(0); // 删除状态,0未删除return orderEntity;}// 构建所有订单项信息private List<OrderItemEntity> buildOrderItems(String orderSn) {// 最后确定每个购物项的价格List<OrderItemVo> cartItems = cartFeginService.getCurrentUserCartItems();if(cartItems != null && cartItems.size() > 0){List<OrderItemEntity> orderItemEntities = cartItems.stream().map(cartItem -> {// 通过购物车的商品信息返回订单项的详细信息OrderItemEntity orderItemEntity = buildOrderItem(cartItem);orderItemEntity.setOrderSn(orderSn);return orderItemEntity;}).collect(Collectors.toList());return orderItemEntities;}return null;}// 构建指定的某一个订单项private OrderItemEntity buildOrderItem(OrderItemVo cartItem) {OrderItemEntity itemEntity = new OrderItemEntity();// 1、订单信息:订单号// 2、商品的spu信息Long skuId = cartItem.getSkuId();// 远程调用商品服务、根据skuId查询spu信息R r = productFeignService.getSpuInfoBySkuId(skuId);SpuInfoVo data = r.getData(new TypeReference<SpuInfoVo>() { });itemEntity.setSpuId(data.getId());itemEntity.setSpuBrand(data.getBrandId().toString());itemEntity.setSpuName(data.getSpuName());itemEntity.setCategoryId(data.getCatalogId());// 3、商品的sku信息itemEntity.setSkuId(skuId);itemEntity.setSkuName(cartItem.getTitle());itemEntity.setSkuPic(cartItem.getImage());itemEntity.setSkuPrice(cartItem.getPrice());// 将集合按照指定的分隔符转换为字符串数组String skuAttr = StringUtils.collectionToDelimitedString(cartItem.getSkuAttr(), ";");itemEntity.setSkuAttrsVals(skuAttr);itemEntity.setSkuQuantity(cartItem.getCount());// 4、优惠信息(不做)itemEntity.setPromotionAmount(new BigDecimal("0"));itemEntity.setCouponAmount(new BigDecimal("0"));itemEntity.setIntegrationAmount(new BigDecimal("0"));// 5、积分信息。成长积分和赠送积分itemEntity.setGiftGrowth(cartItem.getPrice().intValue() * cartItem.getCount());itemEntity.setGiftIntegration(cartItem.getPrice().intValue() * cartItem.getCount());// 6.订单项价格信息。总额 - 优惠金额// 当前订单项的实际金额BigDecimal orgin = itemEntity.getSkuPrice().multiply(new BigDecimal(itemEntity.getSkuQuantity().toString()));// 当前订单项优惠后的金额BigDecimal subtract = orgin.subtract(itemEntity.getPromotionAmount()).subtract(itemEntity.getCouponAmount()).subtract(itemEntity.getIntegrationAmount());itemEntity.setRealAmount(subtract);return itemEntity;}/*** 关闭订单(30分钟未支付)* @param entity*/@Overridepublic void closeOrder(OrderEntity entity) {// 查询订单的最新状态(30分钟内容订单状态可能有变化,所以要取最新状态)OrderEntity orderEntity = this.getById(entity.getId());if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()) { // 订单状态为待付款// 关闭订单,将订单状态修改为已取消OrderEntity update = new OrderEntity();update.setId(entity.getId());update.setStatus(OrderStatusEnum.CANCLED.getCode());this.updateById(update);OrderTo orderTo = new OrderTo();BeanUtils.copyProperties(orderEntity, orderTo);try {// 每一条消息进行日志记录(数据库保存每一条消息的详细信息)// 定期扫描数据库将失败的消息(待付款的订单数据)再发送到MQrabbitTemplate.convertAndSend("order-event-exchange", "order.release.other", orderTo);} catch (Exception e) {// 将没法送成功的消息进行重试发送}}}/*** 根据订单号获取订单实体类* @param orderSn* @return*/@Overridepublic OrderEntity getOrderByOrderSn(String orderSn) {OrderEntity entity = this.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));return entity;}}
WareSkuController锁定库存
/*** 为当前订单锁定库存*/@PostMapping("/lock/order")public R orderLockStock(@RequestBody WareSkuLockVo vo) {try{Boolean stock = wareSkuService.orderLockStock(vo);return R.ok();}catch (NoStockException e){return R.error(BizCodeEnume.NO_STOCK_EXCEPTION.getCode(),BizCodeEnume.NO_STOCK_EXCEPTION.getMsg());}}
WareSkuServiceImpl锁定库存
@RabbitListener(queues = "stock.release.stock.queue")@Service("wareSkuService")public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {@Autowiredprivate WareSkuDao wareSkuDao;@Autowiredprivate ProductFeignService productFeignService;@AutowiredWareOrderTaskService orderService;@AutowiredWareOrderTaskDetailService orderDetailService;@AutowiredWareOrderTaskService wareOrderTaskService;@AutowiredOrderFeignService orderFeignService;@AutowiredRabbitTemplate rabbitTemplate;/** 为某个订单锁定库存* @Transactional(rollbackFor = NoStockException.class):执行要回滚NoStockException异常。* 可以不用加。因为默认只要是运行时异常都会回滚** 库存解锁的场景:* 1、下订单成功,订单过期没有支付被系统自动取消、被用户手动取消。都要解锁库存* 2、下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚* 之前锁定的库存就要自动解锁*/@Transactional(rollbackFor = NoStockException.class)@Overridepublic Boolean orderLockStock(WareSkuLockVo vo) {// 保存库存工作单详情,方便追溯。WareOrderTaskEntity taskEntity = new WareOrderTaskEntity();taskEntity.setOrderSn(vo.getOrderSn()); //为哪个订单号锁的库存orderService.save(taskEntity);// 1、获取每个商品在每个仓库的库存详情:SkuWareHasStockList<OrderItemVo> locks = vo.getLocks();List<SkuWareHasStock> collect = locks.stream().map(item -> {SkuWareHasStock stock = new SkuWareHasStock();Long skuId = item.getSkuId();stock.setSkuId(skuId);stock.setNum(item.getCount());// 查询当前商品在哪些仓库有库存List<Long> wareIds = wareSkuDao.listWareIdHasSkuStock(skuId);stock.setWareId(wareIds);return stock;}).collect(Collectors.toList());// 2、锁定库存for (SkuWareHasStock hasStock : collect) {Boolean skuStocked = false;Long skuId = hasStock.getSkuId();List<Long> wareIds = hasStock.getWareId();Integer num = hasStock.getNum();// 2.1 若仓库为空。即没有任何仓库有这个商品的库存,直接抛异常if (wareIds == null || wareIds.size() == 0) {throw new NoStockException(skuId);}// 2.2 遍历仓库,扣库存(每个仓库依次扣库存)// 如果每个商品都锁定成功,将当前商品锁定了几件的工作单记录发给MQ// 锁定失败,前面保存的工作单信息就回滚了。发送出去的消息,即使要解锁记录,由于去数据库查不到id,所以就不用解锁for (Long wareId : wareIds) {// 2.3 锁定库存。stock_locked加num// 返回1表示成功(库存表1行受影响),返回0表示失败(0行受影响)Long count = wareSkuDao.lockSkuStock(skuId, wareId, num);if (count == 1) {// 库存锁定成功skuStocked = true;// 将数据保存到数据库wms_ware_order_task_detail表WareOrderTaskDetailEntity detailEntity = new WareOrderTaskDetailEntity(null, skuId, "", hasStock.getNum(), taskEntity.getId(), wareId, 1);orderDetailService.save(detailEntity);StockLockedTo stockLockedTo = new StockLockedTo();stockLockedTo.setId(taskEntity.getId());StockDetailTo detailTo = new StockDetailTo();BeanUtils.copyProperties(detailEntity, detailTo);// 防止回滚之后找不到数据,所以保存完整库存单stockLockedTo.setDetail(detailTo);rabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked", stockLockedTo);break;} else {// 当前仓库库存不足,尝试锁下一个仓库}}// 当前商品所有仓库都无货(没锁住库存)if (skuStocked == false) {throw new NoStockException(skuId);}}// 所有商品的库存都是锁定成功才会返回truereturn true;}//内部类。商品在每个仓库拥有的库存数据@Dataclass SkuWareHasStock {private Long skuId; // 当前商品的IDprivate Integer num; //private List<Long> wareId; // 有库存的仓库ID}/*** 1、库存自动解锁* 下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚,之前锁定的库存就要自动解锁* 订单失败:锁库存失败** 只要解锁库存的消息失败,一定要告诉服务解锁失败,需要手动ACK,回复消息** 解锁库存* 查询数据库关于这个订单的锁定库存信息wms_ware_order_task、wms_ware_order_task_detail* 若有数据,即表示库存锁定成功。此时根据订单情况判断是否需要解锁库存* 1、订单不存在,表示订单数据自身已回滚,此时必须解锁库存* 2、订单存在,根据订单状态确认是否需要解锁库存* 1)订单状态为已取消,此时需要解锁库存* 2)订单状态未取消,此时不能解锁库存* 若没有数据,表示库存锁定失败,库存已回滚,这种情况就无需解锁库存*/@Overridepublic void unlockStock(StockLockedTo stockLockedTo){System.out.println("收到解锁库存的消息......");StockDetailTo detail = stockLockedTo.getDetail();Long detailId = detail.getId();// 解锁库存// 1.查询关于这个订单的锁定库存信息WareOrderTaskDetailEntity orderTaskDetailEntity = orderDetailService.getById(detailId);if (orderTaskDetailEntity != null) {// 有锁定库存信息,即库存锁定成功,根据订单情况解锁Long id = stockLockedTo.getId(); //库存工作单wms_ware_order_task表的IdWareOrderTaskEntity taskEntity = wareOrderTaskService.getById(id);String orderSn = taskEntity.getOrderSn();// 远程调用订单服务,根据订单号获取订单实体R r = orderFeignService.getOrderStatus(orderSn);if (r.getCode() == 0) {OrderVo data = r.getData(new TypeReference<OrderVo>() { });if (data == null || data.getStatus() == 4) {// 订单不存在(订单数据已经回滚) 或者 有订单但订单状态是已取消,才可以解锁库存// 只有状态是1(已锁定),才能解锁if (orderTaskDetailEntity.getLockStatus() == 1) {unLockStock(detail.getSkuId(), detail.getWareId(), detail.getSkuNum(), detailId);// 手动确认RabbitMQ中order.release.order.queue队列的的消息(即消费这条消息)// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}}} else {// 其它状态(包含订单成功)不解锁// 拒绝消息以后重放到队列里面,让其他服务继续消费解锁(防止因自身原因误删RabbitMQ中的消息)// channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);throw new RuntimeException("远程服务失败....."); // 需要重新解锁。监听器中已实现}} else {// 若不存在锁定库存信息,即库存锁定失败,库存回滚,这种情况无需解锁// 手动确认RabbitMQ中order.release.order.queue队列的的消息(即消费这条消息)// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}}/*** 防止因为订单服务故障,导致订单状态未改变,从而无法解锁库存*/@Transactional@Overridepublic void unlockStock(OrderTo orderTo) {String orderSn = orderTo.getOrderSn();//查询最新库存状态WareOrderTaskEntity task = wareOrderTaskService.getOrderTaskByOrderSn(orderSn);Long id = task.getId();List<WareOrderTaskDetailEntity> list = orderDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>().eq("task_id", id).eq("lock_status", 1));for (WareOrderTaskDetailEntity entity : list) {unLockStock(entity.getSkuId(), entity.getWareId(), entity.getSkuNum(), entity.getId());}}public void unLockStock(Long skuId, Long wareId, Integer num, Long taskDetailId) {// 库存解锁,将锁定库存数stock_locked减去num。即恢复原库存wareSkuDao.unlockStock(skuId, wareId, num);// 更新库存工作单状态WareOrderTaskDetailEntity entity = new WareOrderTaskDetailEntity();entity.setId(taskDetailId);entity.setLockStatus(2); // 2-已解锁orderDetailService.updateById(entity);}}
一、释放订单
1、流程
2、订单服务的MQ配置类MyMQConfig
@Configurationpublic class MyMQConfig {// @RabbitListener(queues = "order.release.order.queue")// public void listener(OrderEntity orderEntity, Channel channel, Message message) throws IOException {//// System.out.println("收到过期的订单信息,准备关闭订单:" + orderEntity);// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);// }@Beanpublic MessageConverter messageConverter() {return new Jackson2JsonMessageConverter();}/*** 注解@Bean注解:可以在容器中自动创建Binding、Queue、Exchange(RabbitMQ没有的情况)* RabbitMQ只要存在,@Bean声明的属性发生变化,重启服务后也不会覆盖先前的数据,只有删除队列才行* 所有的消息默认会先抵达延迟队列order.delay.queue,延迟一段时间后,才会抵达order.release.order.queue队列。然后才被消费掉* @return*/// 创建延迟队列(过了存活时间就会变成死信,然后将信息抛给orderReleaseOrderQueue队列,RabbitMQ监听orderReleaseOrderQueue队列,处理数据)@Beanpublic Queue orderDelayQueue() {Map<String, Object> arguments = new HashMap<>();// 指定死信路由arguments.put("x-dead-letter-exchange", "order-event-exchange");// 指定死信路由键arguments.put("x-dead-letter-routing-key", "order.release.order");// 消息过期时间(毫秒)arguments.put("x-message-ttl", 60000);Queue orderDelayQueue = new Queue("order.delay.queue", true, false, false, arguments);return orderDelayQueue;}// 创建普通队列@Beanpublic Queue orderReleaseOrderQueue() {return new Queue("order.release.order.queue", true, false, false);}// 创建topic类型交换机@Beanpublic Exchange orderEventExchange() {return new TopicExchange("order-event-exchange", true, false);}// 创建交换机与队列order.delay.queue的绑定关系。Binding(目的地,目的地类型,交换机,路由键,参数)@Beanpublic Binding orderCreateBingding() {return new Binding("order.delay.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.create.order", null);}// 创建交换机与队列order.release.order.queue的绑定关系@Beanpublic Binding orderReleaseBingding() {return new Binding("order.release.order.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.release.order", null);}@Beanpublic Binding orderReleaseOtherBingding() {return new Binding("stock.release.stock.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.release.other.#", null);}}
3、订单服务的监听器OrderCloseListener
// 监听order.release.order.queue队列中的消息(30分钟后(订单30分钟未支付场景)才会到达这个队列)@Component@RabbitListener(queues = "order.release.order.queue")public class OrderCloseListener {@AutowiredOrderService orderService;@RabbitHandlerpublic void listener(OrderEntity entity, Channel channel, Message msg) throws IOException {try {System.out.println("收到过期的订单信息,准备关闭订单:" + entity.getOrderSn());orderService.closeOrder(entity);channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);} catch (Exception e) {System.out.println("订单关闭异常,库存解锁异常:" + e.getMessage());// 拒绝消息,让其重新回到消息队列channel.basicReject(msg.getMessageProperties().getDeliveryTag(), true);}}}
4、订单创建成功后发送消息
通过路由键order.create.order发送给延迟队列order.create.order。30分钟后延迟队列将消息通过交换机order-event-exchange、路由键order.release.order发送给order.release.order.queue、order.release.coupon.queue等队列
5、关闭订单时消费消息
查询订单的最新状态,判断当前订单是正常关单还是异常关单,若是异常关单,需要回滚相关数据
二、库存自动解锁
1、流程
2、库存服务的MQ配置类MyRabbitConfig
@Configurationpublic class MyRabbitConfig {// 使用JSON序列化机制,进行消息转换@Beanpublic MessageConverter messageConverter() {return new Jackson2JsonMessageConverter();}// 创建topic类型交换机。路由键采用模糊匹配。TopicExchange(交换机名称,是否持久化,是否自动删除)@Beanpublic Exchange stockEventExchange() {return new TopicExchange("stock-event-exchange", true, false);}// 创建普通队列。Queue(队列名称,是否持久化,是否排他(只允许单个连接它,若支持多个连接,则谁抢到消息算谁的),是否自动删除)@Beanpublic Queue stockReleaseStockQueue() {return new Queue("stock.release.stock.queue", true, false, false);}// 创建延迟队列。库存锁定成功后,消息先发给延迟队列,等待消息过期后,再发给普通队列@Beanpublic Queue stockDelayQueue() {Map<String, Object> arguments = new HashMap<>();// 设置死信路由,表示消息过期后交给哪个交换机arguments.put("x-dead-letter-exchange", "stock-event-exchange");// 设置死信路由键,表示消息过期后交给哪个路由键arguments.put("x-dead-letter-routing-key", "stock.release");// 设置消息过期时间arguments.put("x-message-ttl", 60000);return new Queue("stock.delay.queue", true, false, false, arguments);}// 创建交换机与普通队列的绑定关系@Beanpublic Binding stockReleaseBinding() {return new Binding("stock.release.stock.queue", Binding.DestinationType.QUEUE, "stock-event-exchange", "stock.release.#", null);}// 创建交换机与延迟队列的绑定关系@Beanpublic Binding stockLockedBinding() {return new Binding("stock.delay.queue", Binding.DestinationType.QUEUE, "stock-event-exchange", "stock.locked", null);}// 第一次监听消息时,idea会连接RabbitMQ,此时才会创建RabbitMQ中没有的队列、交换机和绑定关系// 如果没有监听消息操作,RabbitMQ中就不会创建队列、交换机和绑定关系// 如果需要修改rabbitMQ中已存在的队列交换机,需要先删除,然后再次创建// @RabbitListener(queues = "stock.release.stock.queue") // 需要注释掉,否则此时有两个在监听stock.release.stock.queue队列,导致消息消费异常// public void listener(WareInfoEntity entity, Channel channel, Message msg) throws IOException, IOException {// System.out.println("收到过期的订单信息:准备关闭订单" + entity.getId());// channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);// }}
3、库存服务的监听器StockReleaseListener
@Service@RabbitListener(queues = "stock.release.stock.queue")public class StockReleaseListener {@AutowiredWareSkuService wareSkuService;/*** 监听解锁库存功能*/@RabbitHandlerpublic void handleStockLockedRelease(StockLockedTo stockLockedTo, Message message, Channel channel) throws IOException {System.out.println("收到接收解锁库存的信息......");try {wareSkuService.unlockStock(stockLockedTo);// 手动确认RabbitMQ中order.release.order.queue队列的的消息(即消费这条消息)channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);}catch (Exception e){System.out.println("rabbitMQ错误:"+e.getMessage());// 只要有任何异常,回退消息。拒绝消息以后重放到队列里面,让其他服务继续消费解锁(防止因自身原因误删RabbitMQ中的消息)channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}}
4、锁定库存后发送消息
5、解锁库存时消费消息
/*** 1、库存自动解锁* 下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚,之前锁定的库存就要自动解锁* 订单失败:锁库存失败** 只要解锁库存的消息失败,一定要告诉服务解锁失败,需要手动ACK,回复消息** 解锁库存* 查询数据库关于这个订单的锁定库存信息wms_ware_order_task、wms_ware_order_task_detail* 若有数据,即表示库存锁定成功。此时根据订单情况判断是否需要解锁库存* 1、订单不存在,表示订单数据自身已回滚,此时必须解锁库存* 2、订单存在,根据订单状态确认是否需要解锁库存* 1)订单状态为已取消,此时需要解锁库存* 2)订单状态未取消,此时不能解锁库存* 若没有数据,表示库存锁定失败,库存已回滚,这种情况就无需解锁库存*/@Overridepublic void unlockStock(StockLockedTo stockLockedTo){System.out.println("收到解锁库存的消息......");StockDetailTo detail = stockLockedTo.getDetail();Long detailId = detail.getId();// 解锁库存// 1.查询关于这个订单的锁定库存信息WareOrderTaskDetailEntity orderTaskDetailEntity = orderDetailService.getById(detailId);if (orderTaskDetailEntity != null) {// 有锁定库存信息,即库存锁定成功,根据订单情况解锁Long id = stockLockedTo.getId(); //库存工作单wms_ware_order_task表的IdWareOrderTaskEntity taskEntity = wareOrderTaskService.getById(id);String orderSn = taskEntity.getOrderSn();// 远程调用订单服务,根据订单号获取订单实体R r = orderFeignService.getOrderStatus(orderSn);if (r.getCode() == 0) {OrderVo data = r.getData(new TypeReference<OrderVo>() { });if (data == null || data.getStatus() == 4) {// 订单不存在(订单数据已经回滚) 或者 有订单但订单状态是已取消,才可以解锁库存// 只有状态是1(已锁定),才能解锁if (orderTaskDetailEntity.getLockStatus() == 1) {unLockStock(detail.getSkuId(), detail.getWareId(), detail.getSkuNum(), detailId);// 手动确认RabbitMQ中order.release.order.queue队列的的消息(即消费这条消息)// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}}} else {// 其它状态(包含订单成功)不解锁// 拒绝消息以后重放到队列里面,让其他服务继续消费解锁(防止因自身原因误删RabbitMQ中的消息)// channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);throw new RuntimeException("远程服务失败....."); // 需要重新解锁。监听器中已实现}} else {// 若不存在锁定库存信息,即库存锁定失败,库存回滚,这种情况无需解锁// 手动确认RabbitMQ中order.release.order.queue队列的的消息(即消费这条消息)// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}}
三、订单关闭后主动解锁库存
1、流程
2、订单服务的MQ配置类MyMQConfig
创建订单交换机与库存释放队列stock.release.stock.queue的绑定关系
3、库存服务的MQ监听器StockReleaseListener
监听库存服务的stock.release.stock.queue队列
4、关闭订单后发送消息
OrderServiceImpl订单关闭后(30分钟未支付),将消息通过订单交换机order-event-exchange、路由键order.release.other发送给库存队列stock.release.stock.queue监听库存队列,收到消息后判断该订单的状态,若为已取消,则主动释放库存
5、解锁库存时消费消息
监听库存队列stock.release.stock.queue,收到消息后解锁库存,WareSkuServiceImpl判断该订单的状态,若为已取消,则主动释放库存
下单流程
1、验证令牌。令牌的对比和删除必须保证原子性
2、创建订单,订单项等信息
3、验价:根据订单计算的价格与界面提交的价格进行对比(可能因后台修改商品价格出现不一致的情况)
4、保存订单数据(OrderEntity和OrderItemEntity)至数据库
5、远程锁定库存、只要有异常就回滚订单数据
6、远程扣减积分
7、订单创建成功,将每个订单信息发送给MQ
订单信息(OrderEntity)发送给MQ
1、通过路由键order.create.order发送给延迟队列order.create.order。
2、30分钟后延迟队列将消息通过交换机order-event-exchange、路由键order.release.order发送给order.release.order.queue、order.release.coupon.queue等队列
3、OrderCloseListener监听order.release.order.queue队列数据,调用关闭订单方法orderService.closeOrder(entity)
4、closeOrder首先查询订单的最新状态,若订单状态仍为待付款,主动关闭订单(即回滚数据),即将订单状态更新为已取消;同时将这条订单消息再次发送给MQ,30分钟后再次判断订单状态(正常情况此时应该为已取消,目的是防止关单程序异常导致关单失败)
解锁库存
库存锁定成功后,将锁定库存信息(StockLockedTo:含库存工作单Id和工作的详情表所有信息)发送给MQ。
1、通过路由键stock.locked发送给延迟队列stock.delay.queue
2、2分钟后延迟队列将消息通过交换机stock-event-exchange、路由键stock.release发送给stock.release.stock.queue队列
3、StockReleaseListener监听stock.release.stock.queue队列数据,调用解锁库存方法wareSkuService.unlockStock(stockLockedTo)
4、解锁库存
1)查询关于这个订单的锁定库存信息WareOrderTaskDetailEntity,若有锁定库存信息,即库存锁定成功,根据订单情况解锁
2)远程调用订单服务,根据订单号获取订单实体
3)只有订单不存在(订单数据已经回滚) 或者 有订单但订单状态是已取消,同时锁定库存详情的锁定状态为已锁定,才可以解锁库存。其他状态不需要解锁库存
4)库存解锁,将WareSkuEntity表的锁定库存数stock_locked减去num。即恢复原库存
5)更新库存工作单WareOrderTaskDetailEntity的锁定状态为已解锁
MQ消息记录表



