> 首先我們在這里嚴重的批評一些,在接口訂單的接口中,直接傳訂單金額,而不是使用下單是已經計算好金額的人,這些接口豈不是使用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} <= stocks
```
超賣一直是一件非常令人頭疼的事情,如果對訂單直接加悲觀鎖的話,那么下單的性能將會很差。商城最重要的就是下單啦,要是性能很差,那人家還下個鬼的單喲,所以我們采用數據庫的樂觀鎖進行下單。
所謂樂觀鎖,就是在 where 條件下加上極限的條件,比如在這里就是更新的庫存小于或等于商品的庫存,在這種情況下可以對庫存更新成功,則更新完成了,否則拋異常(真正的定義肯定不是這樣的啦,你可以百度下 “樂觀鎖更新庫存”)。注意這里在拋異常以前,應該將緩存也更新了,不然無法及時更新。
最后我們回到`controller`
```java
return ResponseEntity.ok(new OrderNumbersDto(orderNumbers.toString()));
```
這里面返回了多個訂單項,這里就變成了并單支付咯,在多個店鋪一起進行支付的時候需要進行并單支付的操作,一個店鋪的時候,又要變成一個訂單支付的操作,可是我們只希望有一個統一支付的接口進行調用,所以我們的支付接口要進行一點點的設計咯。
- 開發環境準備
- 基本開發手冊
- 項目目錄結構
- 權限管理
- 通用分頁表格
- Swagger文檔
- undertow容器
- 對xss攻擊的防御
- 分布式鎖
- 統一的系統日志
- 統一驗證
- 統一異常處理
- 文件上傳下載
- 一對多、多對多分頁
- 認證與授權
- 從授權開始看源碼
- 自己寫個授權的方法-開源版
- 商城表設計
- 商品信息
- 商品分組
- 購物車
- 訂單
- 地區管理
- 運費模板
- 接口設計
- 必讀
- 購物車的設計
- 訂單設計-確認訂單
- 訂單設計-提交訂單
- 訂單設計-支付
- 生產環境
- nginx安裝與跨域配置
- 安裝mysql
- 安裝redis
- 傳統方式部署項目
- docker
- 使用docker部署商城
- centos jdk安裝
- docker centos 安裝
- Docker Compose 安裝與卸載
- docker 鏡像的基本操作
- docker 容器的基本操作
- 通過yum安裝maven
- 常見問題