## 订单接口-2

上节说到确认订单，这节讨论**提交订单**的接口设计。

调用接口为：`/a/order/submit`

```java
	@PostMapping("/submit")
    @Operation(summary = "提交订单，返回支付流水号" , description = "根据传入的参数判断是否为购物车提交订单，同时对购物车进行删除，用户开始进行支付")
    public ServerResponseEntity<List<Long>> submitOrders() {
        Long userId = AuthUserContext.get().getUserId();
        //确认订单时将shopCartOrderMerger放入缓存中，提交时使用userId将其从缓存中取出
        ShopCartOrderMergerVO mergerOrder = cacheManagerUtil.getCache(OrderCacheNames.ORDER_CONFIRM_KEY,String.valueOf(userId));
        // 看看订单有没有过期
        if (mergerOrder == null) {
            return ServerResponseEntity.fail(ResponseEnum.ORDER_EXPIRED);
        }
        // 与确认订单相同，使用RedisUtil.cad来检测原子性，判断是否重复提交
        boolean cad = RedisUtil.cad(OrderCacheNames.ORDER_CONFIRM_UUID_KEY + CacheNames.UNION + userId, String.valueOf(userId));
        if (!cad) {
            return ServerResponseEntity.fail(ResponseEnum.REPEAT_ORDER);
        }
        List<Long> orderIds = orderService.submit(userId,mergerOrder);
        return ServerResponseEntity.success(orderIds);
    }
```

提交订单时，将订单信息存入缓存，用户在提交订单的时候将会判断缓存内订单过期没有过期且非重复提交后，调用`orderService.submit(userId,mergerOrder)`方法来提交订单。

```java
@Override
@Transactional(rollbackFor = Exception.class)
public List<Long> submit(Long userId, ShopCartOrderMergerVO mergerOrder) {
    List<Order> orders = saveOrder(userId, mergerOrder);
    // 省略部分见下文
}
```

首先将订单存入数据库，调用`saveOrder`方法：

```java
public List<Order> saveOrder(Long userId, ShopCartOrderMergerVO mergerOrder) {
        OrderAddr orderAddr = BeanUtil.map(mergerOrder.getUserAddr(), OrderAddr.class);
        // 地址信息
        if (Objects.isNull(orderAddr)) {
            // 请填写收货地址
            throw new mall4cloudException("请填写收货地址");
        }
        // 保存收货地址
        orderAddrService.save(orderAddr);
        // 订单商品参数
        List<ShopCartOrderVO> shopCartOrders = mergerOrder.getShopCartOrders();
        List<Order> orders = new ArrayList<>();
        List<OrderItem> orderItems = new ArrayList<>();
        List<Long> shopCartItemIds = new ArrayList<>();
        if(CollectionUtil.isNotEmpty(shopCartOrders)) {
            // 每个店铺生成一个订单
            for (ShopCartOrderVO shopCartOrderDto : shopCartOrders) {
                Order order = getOrder(userId, mergerOrder.getDvyType(), shopCartOrderDto);
                for (ShopCartItemVO shopCartItemVO : shopCartOrderDto.getShopCartItemVO()) {
                    OrderItem orderItem = getOrderItem(order, shopCartItemVO);
                    orderItems.add(orderItem);
                    shopCartItemIds.add(shopCartItemVO.getCartItemId());
                }
                order.setOrderItems(orderItems);
                order.setOrderAddrId(orderAddr.getOrderAddrId());
                orders.add(order);
            }
        }
        orderMapper.saveBatch(orders);
        orderItemService.saveBatch(orderItems);
        // 清空购物车
        shopCartFeignClient.deleteItem(shopCartItemIds);
        return orders;
    }
```

每个店铺生成订单的时候，使用了`getOrder`方法，写入订单信息，将其状态设置为"**未付款**"。

```java
private Order getOrder(Long userId, Integer dvyType, ShopCartOrderVO shopCartOrderDto) {
        ServerResponseEntity<Long> segmentIdResponse = segmentFeignClient.getSegmentId(Order.DISTRIBUTED_ID_KEY);
        if (!segmentIdResponse.isSuccess()) {
            throw new mall4cloudException("获取订单id失败");
        }
        // 订单信息
        Order order = new Order();
        order.setOrderId(segmentIdResponse.getData());
        order.setShopId(shopCartOrderDto.getShopId());
        order.setShopName(shopCartOrderDto.getShopName());

        // 用户id
        order.setUserId(userId);
        // 商品总额
        order.setTotal(shopCartOrderDto.getTotal());
        order.setStatus(OrderStatus.UNPAY.value());
        order.setIsPayed(0);
        order.setDeleteStatus(0);
        order.setAllCount(shopCartOrderDto.getTotalCount());
        order.setDeliveryType(DeliveryType.NOT_DELIVERY.value());
        return order;
    }
```

完成后回到`orderService.submit(userId,mergerOrder)`方法

```java
List<SkuStockLockDTO> skuStockLocks = new ArrayList<>();
for (Order order : orders) {
    orderIds.add(order.getOrderId());
    List<OrderItem> orderItems = order.getOrderItems();
    for (OrderItem orderItem : orderItems) {
        skuStockLocks.add(new SkuStockLockDTO(orderItem.getSpuId(), orderItem.getSkuId(), orderItem.getOrderId(), 	orderItem.getCount()));
    }
}
// 锁定库存
ServerResponseEntity<Void> lockStockResponse = skuStockLockFeignClient.lock(skuStockLocks);
// 锁定不成，抛异常，让回滚订单
if (!lockStockResponse.isSuccess()) {
    throw new mall4cloudException(lockStockResponse.getMsg());
}
```

提交订单的时候会把该订单的库存锁上`skuStockLockFeignClient.lock(skuStockLocks)`，多加一个库存锁定表，先减完spu和sku的库存后，再将锁库存的信息存入表中`sku_stock_lock`，倘若此时订单取消或者超过30分钟未支付，则通过`StockUnlockConsumer`监听来解锁库存。

```java
@Override
@Transactional(rollbackFor = Exception.class)
public ServerResponseEntity<Void> lock(List<SkuStockLockDTO> skuStockLocksParam) {

    List<SkuStockLock> skuStockLocks = new ArrayList<>();
    for (SkuStockLockDTO skuStockLockDTO : skuStockLocksParam) {
        //略...
        // 减sku库存
        int skuStockUpdateIsSuccess = skuStockMapper.reduceStockByOrder(skuStockLockDTO.getSkuId(), skuStockLockDTO.getCount());
        if (skuStockUpdateIsSuccess < 1) {
            throw new mall4cloudException(ResponseEnum.NOT_STOCK, skuStockLockDTO.getSkuId());
        }
        // 减商品库存
        int spuStockUpdateIsSuccess = spuExtensionMapper.reduceStockByOrder(skuStockLockDTO.getSpuId(), skuStockLockDTO.getCount());
        if (spuStockUpdateIsSuccess < 1) {
            throw new mall4cloudException(ResponseEnum.NOT_STOCK, skuStockLockDTO.getSkuId());
        }
    }
    // 保存库存锁定信息
    skuStockLockMapper.saveBatch(skuStockLocks);
    List<Long> orderIds = skuStockLocksParam.stream().map(SkuStockLockDTO::getOrderId).collect(Collectors.toList());
    // 一个小时后解锁库存
    SendStatus sendStatus = stockMqTemplate.syncSend(RocketMqConstant.STOCK_UNLOCK_TOPIC, new GenericMessage<>(orderIds), RocketMqConstant.TIMEOUT, RocketMqConstant.CANCEL_ORDER_DELAY_LEVEL + 1).getSendStatus();
    if (!Objects.equals(sendStatus,SendStatus.SEND_OK)) {
        // 消息发不出去就抛异常，发的出去无所谓
        throw new mall4cloudException(ResponseEnum.EXCEPTION);
    }
    return ServerResponseEntity.success();
}
```

`stockMqTemplate.syncSend(...).getSendStatus();`此处发送库存解锁的消息，使用的`RocketMqConstant.CANCEL_ORDER_DELAY_LEVEL`的参数

参考`RocketMqConstant`，16+1即第十七个，为一小时。

```java
public class RocketMqConstant {

    // 延迟消息 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h (1-18)

    /**
     * 取消订单时间，实际上30分钟
     */
    public static final int CANCEL_ORDER_DELAY_LEVEL = 16;
}
```

锁定库存之后，发送消息，假如30分钟后尚未支付，则取消订单

```java
SendStatus sendStatus = orderCancelTemplate.syncSend(RocketMqConstant.ORDER_CANCEL_TOPIC, new GenericMessage<>(orderIds), RocketMqConstant.TIMEOUT, RocketMqConstant.CANCEL_ORDER_DELAY_LEVEL).getSendStatus();
if (!Objects.equals(sendStatus,SendStatus.SEND_OK)) {
    // 消息发不出去就抛异常，发的出去无所谓
    throw new mall4cloudException(ResponseEnum.EXCEPTION);
}
```

最后返回的是订单ID列表，方便下一步进行支付

```java
return ServerResponseEntity.success(orderIds);
```

订单支付的接口设计请看下节：**订单接口-3**
