|
|
> 首先我们在这里严重的批评一些,在接口订单的接口中,直接传订单金额,而不是使用下单是已经计算好金额的人,这些接口岂不是使用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} <= stocks
|
|
|
```
|
|
|
|
|
|
超卖一直是一件非常令人头疼的事情,如果对订单直接加悲观锁的话,那么下单的性能将会很差。商城最重要的就是下单啦,要是性能很差,那人家还下个鬼的单哟,所以我们采用数据库的乐观锁进行下单。
|
|
|
|
|
|
所谓乐观锁,就是在 where 条件下加上极限的条件,比如在这里就是更新的库存小于或等于商品的库存,在这种情况下可以对库存更新成功,则更新完成了,否则抛异常(真正的定义肯定不是这样的啦,你可以百度下 “乐观锁更新库存”)。注意这里在抛异常以前,应该将缓存也更新了,不然无法及时更新。
|
|
|
|
|
|
|
|
|
|
|
|
最后我们回到`controller`
|
|
|
|
|
|
```java
|
|
|
return ServerResponseEntity.success(new OrderNumbersDto(orderNumbers.toString()));
|
|
|
```
|
|
|
|
|
|
这里面返回了多个订单项,这里就变成了并单支付咯,在多个店铺一起进行支付的时候需要进行并单支付的操作,一个店铺的时候,又要变成一个订单支付的操作,可是我们只希望有一个统一支付的接口进行调用,所以我们的支付接口要进行一点点的设计咯。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|