> 首先我们在这里严重的批评一些，在接口订单的接口中，直接传订单金额，而不是使用下单是已经计算好金额的人，这些接口岂不是使用0.01就能将全部的商品都买下来了？



我们回到订单设计这一个模块，首先我们在确认订单的时候就已经将价格计算完成了，那么我们肯定是想将计算结果给保留下来的，至于计算的过程，我们并不希望这个过程还要进行一遍的计算。



我们返回确认订单的接口，看到这样一行代码：

```java
	@Operation(summary = "结算，生成订单信息" , description = "传入下单所需要的参数进行下单")
    public ServerResponseEntity<ShopCartOrderMergerDto> confirm(@Valid @RequestBody OrderParam orderParam) {
    orderService.putConfirmOrderCache(userId,shopCartOrderMergerDto);
    }
```



这里每经过一次计算，就将整个订单通过`userId`进行了保存，而这个缓存的时间为30分钟，当用户使用

```java
    @PostMapping("/submit")
    @Operation(summary = "提交订单，返回支付流水号" , description = "根据传入的参数判断是否为购物车提交订单，同时对购物车进行删除，用户开始进行支付")
    public ServerResponseEntity<OrderNumbersDto> submitOrders(@Valid @RequestBody SubmitOrderParam submitOrderParam) {
    ShopCartOrderMergerDto mergerOrder = orderService.getConfirmOrderCache(userId);
        if (mergerOrder == null) {
            throw new YamiShopBindException("订单已过期，请重新下单");
        }
        
        // 省略中间一大段。。。
        
        orderService.removeConfirmOrderCache(userId);
    }
```

当无法获取缓存的时候告知用户订单过期，当订单进行提交完毕的时候，将之前的缓存给清除。



我们又回到提交订单中间这几行代码：

```java
List<Order> orders = orderService.submit(userId,mergerOrder);
```

这行代码也就是提交订单的核心代码

```java
eventPublisher.publishEvent(new SubmitOrderEvent(mergerOrder, orderList));
```

其中这里依旧是使用时间的方式，将订单进行提交，看下这个`SubmitOrderEvent`的默认监听事件。

```java
@Component("defaultSubmitOrderListener")
@AllArgsConstructor
public class SubmitOrderListener {
    public void defaultSubmitOrderListener(SubmitOrderEvent event) {
        // ...
    }
}
```



这里有几段值得注意的地方：

- 这里是`UserAddrOrder` 并不是`UserAddr`：

```java
// 把订单地址保存到数据库
UserAddrOrder userAddrOrder = BeanUtil.copyProperties(mergerOrder.getUserAddr(), UserAddrOrder.class);
if (userAddrOrder == null) {
    throw new YamiShopBindException("请填写收货地址");
}
userAddrOrder.setUserId(userId);
userAddrOrder.setCreateTime(now);
userAddrOrderService.save(userAddrOrder);
```

这里是将订单的收货地址进行了保存入库的操作，这里是绝对不能只保存用户的地址id在订单中的，要将地址入库，原因是如果用户在订单中设置了一个地址，如果用户在订单还没配送的时候，将自己的地址改了的话。如果仅采用关联的地址，就会出现问题。



- 为每个店铺生成一个订单

```java
// 每个店铺生成一个订单
for (ShopCartOrderDto shopCartOrderDto : shopCartOrders) {
    
}
```

这里为每个店铺创建一个订单，是为了，以后平台结算给商家时，每个商家的订单单独结算。用户确认收货时，也可以为每家店铺单独确认收货。



- 使用雪花算法生成订单id， 如果对雪花算法感兴趣的，可以去搜索下相关内容：

```java
String orderNumber = String.valueOf(snowflake.nextId());
```

我们不想单多台服务器生成的id冲突，也不想生成uuid这样的很奇怪的字符串id，更不想直接使用数据库主键这种东西时，雪花算法就出现咯。



- 当用户提交订单的时候，购物车里面勾选的商品，理所当然的要清空掉

```java
// 删除购物车的商品信息
if (!basketIds.isEmpty()) {
    basketMapper.deleteShopCartItemsByBasketIds(userId, basketIds);

}
```



- 使用数据库的乐观锁，防止超卖：

```java
if (skuMapper.updateStocks(sku) == 0) {
                skuService.removeSkuCacheBySkuId(key, sku.getProdId());
                throw new YamiShopBindException("商品：[" + sku.getProdName() + "]库存不足");
            }
```

```sql
update tz_sku set stocks = stocks - #{sku.stocks}, version = version + 1,update_time = NOW() where sku_id = #{sku.skuId} and #{sku.stocks} &lt;= stocks
```

超卖一直是一件非常令人头疼的事情，如果对订单直接加悲观锁的话，那么下单的性能将会很差。商城最重要的就是下单啦，要是性能很差，那人家还下个鬼的单哟，所以我们采用数据库的乐观锁进行下单。

所谓乐观锁，就是在 where 条件下加上极限的条件，比如在这里就是更新的库存小于或等于商品的库存，在这种情况下可以对库存更新成功，则更新完成了，否则抛异常（真正的定义肯定不是这样的啦，你可以百度下 “乐观锁更新库存”）。注意这里在抛异常以前，应该将缓存也更新了，不然无法及时更新。



最后我们回到`controller`

```java
return ServerResponseEntity.success(new OrderNumbersDto(orderNumbers.toString()));
```

这里面返回了多个订单项，这里就变成了并单支付咯，在多个店铺一起进行支付的时候需要进行并单支付的操作，一个店铺的时候，又要变成一个订单支付的操作，可是我们只希望有一个统一支付的接口进行调用，所以我们的支付接口要进行一点点的设计咯。





