> 首先我们在这里严重的批评一些,在接口订单的接口中,直接传订单金额,而不是使用下单是已经计算好金额的人,这些接口岂不是使用0.01就能将全部的商品都买下来了? 我们回到订单设计这一个模块,首先我们在确认订单的时候就已经将价格计算完成了,那么我们肯定是想将计算结果给保留下来的,至于计算的过程,我们并不希望这个过程还要进行一遍的计算。 我们返回确认订单的接口,看到这样一行代码: ```java @ApiOperation(value = "结算,生成订单信息", notes = "传入下单所需要的参数进行下单") public ResponseEntity<ShopCartOrderMergerDto> confirm(@Valid @RequestBody OrderParam orderParam) { orderService.putConfirmOrderCache(userId,shopCartOrderMergerDto); } ``` 这里每经过一次计算,就将整个订单通过`userId`进行了保存,而这个缓存的时间为30分钟,当用户使用 ```java @PostMapping("/submit") @ApiOperation(value = "提交订单,返回支付流水号", notes = "根据传入的参数判断是否为购物车提交订单,同时对购物车进行删除,用户开始进行支付") public ResponseEntity<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 = mapperFacade.map(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 ResponseEntity.ok(new OrderNumbersDto(orderNumbers.toString())); ``` 这里面返回了多个订单项,这里就变成了并单支付咯,在多个店铺一起进行支付的时候需要进行并单支付的操作,一个店铺的时候,又要变成一个订单支付的操作,可是我们只希望有一个统一支付的接口进行调用,所以我们的支付接口要进行一点点的设计咯。