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<>();
@Autowired
OrderItemService orderItemService;
@Autowired
CartFeginService cartFeginService;
@Autowired
MemberFeginService memberFeginService;
@Autowired
ProductFeignService productFeignService;
@Autowired
WmsFeignService wmsFeignService;
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
RabbitTemplate rabbitTemplate;
@Autowired
ThreadPoolExecutor executor;
@Autowired
PaymentInfoService paymentInfoService;
/**
* 给订单确认页返回需要用的数据
* @return
*/
@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
// 登录拦截器,获取当前登录用户信息
MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
// 获取主线程中的requestAttributes
RequestAttributes 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(() -> {
// 获取购物项中的商品skuId
List<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
@Override
public 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_order
OrderEntity orderEntity = order.getOrder();
orderEntity.setModifyTime(new Date());
this.save(orderEntity);
// 批量保存订单项数据:oms_order_item
List<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
*/
@Override
public 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 {
// 每一条消息进行日志记录(数据库保存每一条消息的详细信息)
// 定期扫描数据库将失败的消息(待付款的订单数据)再发送到MQ
rabbitTemplate.convertAndSend("order-event-exchange", "order.release.other", orderTo);
} catch (Exception e) {
// 将没法送成功的消息进行重试发送
}
}
}
/**
* 根据订单号获取订单实体类
* @param orderSn
* @return
*/
@Override
public 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 {
@Autowired
private WareSkuDao wareSkuDao;
@Autowired
private ProductFeignService productFeignService;
@Autowired
WareOrderTaskService orderService;
@Autowired
WareOrderTaskDetailService orderDetailService;
@Autowired
WareOrderTaskService wareOrderTaskService;
@Autowired
OrderFeignService orderFeignService;
@Autowired
RabbitTemplate rabbitTemplate;
/** 为某个订单锁定库存
* @Transactional(rollbackFor = NoStockException.class):执行要回滚NoStockException异常。
* 可以不用加。因为默认只要是运行时异常都会回滚
*
* 库存解锁的场景:
* 1、下订单成功,订单过期没有支付被系统自动取消、被用户手动取消。都要解锁库存
* 2、下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚
* 之前锁定的库存就要自动解锁
*/
@Transactional(rollbackFor = NoStockException.class)
@Override
public Boolean orderLockStock(WareSkuLockVo vo) {
// 保存库存工作单详情,方便追溯。
WareOrderTaskEntity taskEntity = new WareOrderTaskEntity();
taskEntity.setOrderSn(vo.getOrderSn()); //为哪个订单号锁的库存
orderService.save(taskEntity);
// 1、获取每个商品在每个仓库的库存详情:SkuWareHasStock
List<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);
}
}
// 所有商品的库存都是锁定成功才会返回true
return true;
}
//内部类。商品在每个仓库拥有的库存数据
@Data
class SkuWareHasStock {
private Long skuId; // 当前商品的ID
private Integer num; //
private List<Long> wareId; // 有库存的仓库ID
}
/**
* 1、库存自动解锁
* 下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚,之前锁定的库存就要自动解锁
* 订单失败:锁库存失败
*
* 只要解锁库存的消息失败,一定要告诉服务解锁失败,需要手动ACK,回复消息
*
* 解锁库存
* 查询数据库关于这个订单的锁定库存信息wms_ware_order_task、wms_ware_order_task_detail
* 若有数据,即表示库存锁定成功。此时根据订单情况判断是否需要解锁库存
* 1、订单不存在,表示订单数据自身已回滚,此时必须解锁库存
* 2、订单存在,根据订单状态确认是否需要解锁库存
* 1)订单状态为已取消,此时需要解锁库存
* 2)订单状态未取消,此时不能解锁库存
* 若没有数据,表示库存锁定失败,库存已回滚,这种情况就无需解锁库存
*/
@Override
public 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表的Id
WareOrderTaskEntity 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
@Override
public 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
@Configuration
public 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);
// }
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 注解@Bean注解:可以在容器中自动创建Binding、Queue、Exchange(RabbitMQ没有的情况)
* RabbitMQ只要存在,@Bean声明的属性发生变化,重启服务后也不会覆盖先前的数据,只有删除队列才行
* 所有的消息默认会先抵达延迟队列order.delay.queue,延迟一段时间后,才会抵达order.release.order.queue队列。然后才被消费掉
* @return
*/
// 创建延迟队列(过了存活时间就会变成死信,然后将信息抛给orderReleaseOrderQueue队列,RabbitMQ监听orderReleaseOrderQueue队列,处理数据)
@Bean
public 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;
}
// 创建普通队列
@Bean
public Queue orderReleaseOrderQueue() {
return new Queue("order.release.order.queue", true, false, false);
}
// 创建topic类型交换机
@Bean
public Exchange orderEventExchange() {
return new TopicExchange("order-event-exchange", true, false);
}
// 创建交换机与队列order.delay.queue的绑定关系。Binding(目的地,目的地类型,交换机,路由键,参数)
@Bean
public Binding orderCreateBingding() {
return new Binding("order.delay.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.create.order", null);
}
// 创建交换机与队列order.release.order.queue的绑定关系
@Bean
public Binding orderReleaseBingding() {
return new Binding("order.release.order.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.release.order", null);
}
@Bean
public 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 {
@Autowired
OrderService orderService;
@RabbitHandler
public 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
@Configuration
public class MyRabbitConfig {
// 使用JSON序列化机制,进行消息转换
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
// 创建topic类型交换机。路由键采用模糊匹配。TopicExchange(交换机名称,是否持久化,是否自动删除)
@Bean
public Exchange stockEventExchange() {
return new TopicExchange("stock-event-exchange", true, false);
}
// 创建普通队列。Queue(队列名称,是否持久化,是否排他(只允许单个连接它,若支持多个连接,则谁抢到消息算谁的),是否自动删除)
@Bean
public Queue stockReleaseStockQueue() {
return new Queue("stock.release.stock.queue", true, false, false);
}
// 创建延迟队列。库存锁定成功后,消息先发给延迟队列,等待消息过期后,再发给普通队列
@Bean
public 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);
}
// 创建交换机与普通队列的绑定关系
@Bean
public Binding stockReleaseBinding() {
return new Binding("stock.release.stock.queue", Binding.DestinationType.QUEUE, "stock-event-exchange", "stock.release.#", null);
}
// 创建交换机与延迟队列的绑定关系
@Bean
public 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 {
@Autowired
WareSkuService wareSkuService;
/**
* 监听解锁库存功能
*/
@RabbitHandler
public 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)订单状态未取消,此时不能解锁库存
* 若没有数据,表示库存锁定失败,库存已回滚,这种情况就无需解锁库存
*/
@Override
public 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表的Id
WareOrderTaskEntity 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的锁定状态为已解锁