設計一個網路信用購買系統
在我面臨的一次技術面試中,我被要求設計一個電子商務系統,允許用戶從第三方供應商購買網路積分。
自信地,我提出了一個簡單的解決方案:顯示可用的套餐,讓用戶選擇一個,透過外部網關處理付款,並與提供者互動以提供積分。然而,當被問及失敗場景時(例如用戶完成付款後供應商缺貨),我意識到我的設計缺乏有效處理此類問題的彈性。
幾週前,我對閃購系統和庫存預訂模式進行了研究,特別關注庫存預訂策略。限時搶購通常需要應對高需求和有限的庫存,需要複雜的機制來維持系統穩定性和管理客戶期望。我發現的一個概念是臨時庫存預訂,這有助於防止高峰時段超售。
這項研究讓我想起了我的面試經驗。我認識到應用這些庫存預留策略可以解決我最初設計中的缺點。透過在結帳過程中暫時保留庫存,系統可以有效處理提供者庫存耗盡的情況。
在本研究文件中,我旨在分享從我的研究中獲得的見解,並提出一種設計網路信用購買系統的改進方法。透過整合庫存預留策略,我們可以建立一個既強大又用戶友好的平台,能夠處理各種故障場景,同時提供無縫體驗。
1. 關鍵設計考量
在設計網路信用購買系統時,需要考慮幾個關鍵因素,以確保無縫、安全和愉快的使用者體驗。讓我們來分解一下:
1.1 配額管理
- 即時配額驗證:系統應立即檢查網路積分包是否有庫存,這樣使用者就不會意外選擇不可用的選項。
- 臨時配額保留:增加短期保留所選套餐的機制,讓使用者有足夠的時間完成購買,而不會有遺失物品的風險。
- 處理配額衝突:制定策略來管理多個使用者嘗試同時購買相同套餐的情況,確保公平分配。
- 包裹資訊的快取管理:保持快取資料準確且最新,以便使用者始終看到正確的詳細資訊和可用性。
1.2 付款處理
- 安全付款處理:實施強大的安全措施來保護用戶在交易過程中的付款詳細資訊。
- 用於支付保護的託管系統:使用託管服務來持有資金,直到積分交付,確保買家和提供者的安全。
- 支付網關整合:確保系統與可靠的支付網關順暢連接,確保交易無憂。
- 退款機制:建立清晰且使用者友善的流程,以便在付款失敗或取消時發放退款。
1.3 提供商集成
- 系統可用性:與擁有可靠系統的供應商合作,以確保購買過程不會中斷。
- API 可靠性:與提供穩定、記錄完善的 API 的供應商合作,以實現無縫整合。
- 服務啟動驗證:包括檢查以確認購買的積分是否已正確且及時地啟動。
- 錯誤處理和重試:實作協定來快速擷取和解決錯誤,並為任何失敗的進程提供重試機制。
1.4 交易安全
- 資金流向控制:確保資金僅在交易成功完成後才釋放。
- 交易一致性:保持所有交易的記錄準確一致,以防止錯誤。
- 回滾機制:制定計畫在出現問題時恢復交易,保護使用者和系統。
- 審核追蹤:維護詳細的日誌,以幫助有效監控和解決任何問題。
1.5 使用者體驗
- 清晰的錯誤訊息:為使用者提供易於理解且資訊豐富的錯誤訊息,以指導他們解決遇到的任何問題。
- 交易狀態可見度:讓用戶輕鬆即時追蹤購買狀態,提高透明度。
- 快速載入包:最佳化系統,快速載入可用的套件,減少使用者等待時間。
- 即時更新:及時通知用戶其交易或可用套餐的任何變更或更新。
透過考慮這些因素,我們可以設計一個高效、安全、用戶友好的網路信用購買系統,從而提高用戶滿意度和信任度。
2. 系統設計及流程
基於上述基本考慮因素,下一步是將這些原則轉化為穩健且有效的系統設計。透過仔細規劃各個組件之間的交互,我們可以確保系統不僅滿足功能需求,而且在保持可靠性和可擴展性的同時提供無縫的用戶體驗。
在本節中,我們將深入研究系統的架構和流程,展示配額管理、支付處理和服務激活等核心功能是如何緊密結合實現的。目的是強調每種設計選擇如何有助於解決潛在挑戰並提供可靠的電子商務信用購買平台。
讓我們先概述系統流程,透過流程圖視覺化,以說明使用者從開始到結束如何與系統互動。
2.1 流程圖
為了清晰起見,系統流程分為六個階段:
封裝選擇階段
- 首先,使用者存取包選擇頁面,應用程式從快取中取得包資料。此數據包括可用的包及其快取的配額信息,然後顯示給用戶。
- 用戶選擇一個套餐並點擊「購買」。
- 如果該套餐的配額不可用,應用程式會顯示「不可用」訊息並將使用者帶回選擇頁面。否則,系統會暫時為該使用者保留配額。
購買啟動
- 接下來,系統會嘗試為所選套餐保留配額。
- 如果預訂失敗,使用者會看到錯誤訊息並被重新導向回選擇頁面。
- 如果預訂成功,用戶將前往付款頁面。
付款階段
- 在此階段,使用者開始付款流程並被重新導向到第三方支付網關。
- 應用程式等待支付網關的回應(回呼)以確認付款狀態。
付款處理
- 應用程式檢查支付網關的回呼以驗證付款:
- 對於無效回調,系統會記錄問題並停止進一步的步驟。
- 對於有效的回呼:
- 如果付款失敗:系統釋放預留額度並通知使用者。
- 如果付款成功:系統驗證付款,託管資金,並建立新訂單。
服務啟用
- 付款成功後,系統會要求提供者啟動服務。
- 如果啟動失敗:託管資金將退還給客戶,並通知他們失敗。
- 如果啟動成功:系統驗證啟動。
- 如果驗證失敗,客戶將退款。
- 如果驗證成功,託管資金將釋放給提供者,並且客戶會收到通知。
後台程式
- 定期快取更新:包資料快取定期更新。
- 即時配額更新:配額變更透過 WebSocket 連線進行傳達。
此流程可確保為使用者提供流暢、可靠的體驗,同時還可有效管理資源和潛在錯誤。
2.2 時序圖
下面的序列圖有助於說明不同角色和組件之間的交互作用。
為了清晰起見,系統流程分為六個階段:
封裝選擇階段
- 客戶首先造訪套餐選擇頁面。
- 前端從快取中檢索包裹數據,並向客戶顯示所有可用的包裹及其快取的配額資訊。
購買啟動
- 一旦客戶選擇套餐並點擊“購買”,前端就會向後端發送購買請求。
- 後端即時與提供者檢查所選套餐的配額是否仍然可用。
- 如果有配額,後台會暫時預留給提供者15分鐘。
- 後端會向前端發送預訂確認訊息,客戶將被重定向到付款頁面。
付款階段
- 客戶進入付款頁面並提交付款詳細資訊。
- 前端將此資訊傳送到後端,後端初始化與支付網關的支付會話。
- 支付會話準備就緒後,後端會與前端分享會話詳細資訊。
- 前端將客戶重新導向至支付網關以完成付款。
付款處理
- 在支付網關,客戶輸入付款資訊並完成付款流程。
- 支付網關透過回呼通知後端支付狀態:
- 如果付款成功:
- 後端與支付網關驗證支付狀態。
- 付款被託管,後端確認託管。
- 如果付款失敗:
- 後端釋放與提供者的預留配額,並更新付款狀態以反映失敗。
- 如果在預計時間內沒有收到回呼:
- 後端定期輪詢付款閘道以檢查付款狀態。如果付款失敗或逾時,後台會釋放預留額度。
- 如果付款成功:
服務啟用
- 付款成功,後台請求提供者開通服務。
- 提供者回應啟動狀態,後端驗證啟動:
- 如果啟動成功:
- 後端將付款從託管釋放給提供者。
- 成功通知會發送給客戶,讓他們知道服務已準備就緒。
- 如果啟動失敗:
- 後台將託管資金退還給客戶。
- 發送失敗通知以告知客戶該問題。
- 如果啟動成功:
後台程式
- 提供者透過 WebSocket 連線向後端發送有關包裹配額的即時更新。
- 這些更新確保快取始終是最新的,因此客戶在瀏覽時可以看到最準確的軟體包可用性。
三、技術實現
現在我們已經概述了系統的流程和交互,是時候深入研究它們如何在程式碼中組合在一起了。本節逐步分解實現,展示如何將設計轉化為工作部件,處理從管理訂單到與提供者和支付系統互動的所有事務。
// Domain Models @Getter @Setter @Entity public class Package { @Id private String id; private String name; private BigDecimal price; private BigDecimal providerCost; private String description; private boolean active; } @Getter @Setter @Entity public class Order { @Id private String id; private String customerId; private String packageId; private String reservationId; private String paymentId; private String escrowId; private OrderStatus status; private BigDecimal amount; private BigDecimal providerCost; private LocalDateTime createdAt; private LocalDateTime updatedAt; } @Getter @Setter @Entity public class QuotaReservation { @Id private String id; private String packageId; private LocalDateTime expiresAt; private ReservationStatus status; } // Enums public enum OrderStatus { CREATED, RESERVED, PAYMENT_PENDING, PAYMENT_COMPLETED, IN_ESCROW, ACTIVATING, ACTIVATION_FAILED, COMPLETED, REFUNDED } public enum ReservationStatus { ACTIVE, EXPIRED, USED, CANCELLED }
這些類別的作用如下:
套餐:這是我們定義使用者可以購買的網路積分套餐的地方。它會追蹤包 ID、名稱、價格、提供者成本、描述以及包是否處於活動狀態等詳細資訊。
訂單:將此視為用戶購買的記錄。它包括訂單 ID、客戶 ID、所選套餐 ID 等信息,以及預訂 ID、付款 ID、託管 ID、訂單狀態、付款金額、提供者成本和時間戳等相關詳細資訊。
QuotaReservation:處理套餐配額的暫時預訂。它記錄預訂 ID、所綁定的套餐、過期時間及其當前狀態(如有效或過期)。
OrderStatus 枚舉:此枚舉列出了訂單可能經歷的所有可能階段,從 CREATED 和 RESERVED 到 PAYMENT_PENDING、COMPLETED 甚至 REFUNDED。
ReservationStatus 列舉:同樣,此枚舉追蹤配額預留的狀態,無論是活動、過期、已使用或已取消。
這些類別和枚舉共同建構了系統中管理包裹、訂單和配額預留的主幹。這是一種有效處理電子商務功能的簡單而結構化的方法。
// Request/Response DTOs @Getter @Setter public class OrderRequest { private String customerId; private String packageId; private BigDecimal amount; } @Getter @Setter public class PaymentCallback { private String orderId; private String paymentId; private String status; private BigDecimal amount; private LocalDateTime timestamp; } @Getter @Setter public class QuotaResponse { private String packageId; private boolean available; private Integer remainingQuota; private LocalDateTime timestamp; } @Getter @Setter public class ReservationResponse { private String id; private String packageId; private LocalDateTime expiresAt; private ReservationStatus status; } @Getter @Setter public class ActivationResponse { private String orderId; private boolean success; private String activationId; private String errorCode; private String errorMessage; } @Getter @Setter public class VerificationResponse { private String orderId; private String activationId; private boolean success; private String status; private LocalDateTime activatedAt; } @Getter @Setter public class PaymentRequest { private String orderId; private BigDecimal amount; private String currency; private String customerId; private String returnUrl; private String callbackUrl; } @Getter @Setter public class PaymentSession { private String sessionId; private String paymentUrl; private LocalDateTime expiresAt; private String status; } @Getter @Setter public class EscrowResponse { private String id; private String paymentId; private BigDecimal amount; private String status; private LocalDateTime createdAt; }
讓我們來分解一下:
OrderRequest:這一切都是關於建立新訂單所需的資料。它包括客戶 ID、他們想要購買的套餐以及他們將支付的總金額。
PaymentCallback:將此視為來自支付網關的通知。嘗試付款後,它會提供訂單 ID、付款 ID、狀態(成功或失敗)、支付金額以及付款時間等詳細資訊。
QuotaResponse:這是關於檢查可用性的。它告訴我們套餐是否可用、剩餘配額以及資訊上次更新的時間。
ReservationResponse:預訂套餐後,這將為您提供所有詳細資訊:預訂ID、關聯的套餐、預訂何時到期以及其當前狀態(例如有效或已過期) .
ActivationResponse:這告訴我們服務啟動的情況。如果成功或失敗,如果出現問題,它會向我們提供啟動 ID 和錯誤詳細資訊。
VerificationResponse:啟動後,我們驗證一切是否順利。其中包括訂單 ID、啟動 ID、成功狀態以及啟動時間。
-
PaymentRequest:在開始付款流程之前,此 DTO 會收集必要的詳細信息,例如訂單 ID、要支付的金額、貨幣、客戶 ID 和回調 URL。
PaymentSession:這是付款流程開始時創建的內容。它包括會話 ID、付款 URL(使用者去支付的地方)、過期時間以及會話狀態。
EscrowResponse:如果資金被託管,這會告訴我們所有相關信息,例如託管 ID、付款 ID、持有金額、狀態以及創建時間。
所有這些類別都定義了系統不同部分之間通訊的建構塊-無論是發出請求還是回傳回應。他們確保每個人(和所有事物)都在同一頁上。
// Domain Models @Getter @Setter @Entity public class Package { @Id private String id; private String name; private BigDecimal price; private BigDecimal providerCost; private String description; private boolean active; } @Getter @Setter @Entity public class Order { @Id private String id; private String customerId; private String packageId; private String reservationId; private String paymentId; private String escrowId; private OrderStatus status; private BigDecimal amount; private BigDecimal providerCost; private LocalDateTime createdAt; private LocalDateTime updatedAt; } @Getter @Setter @Entity public class QuotaReservation { @Id private String id; private String packageId; private LocalDateTime expiresAt; private ReservationStatus status; } // Enums public enum OrderStatus { CREATED, RESERVED, PAYMENT_PENDING, PAYMENT_COMPLETED, IN_ESCROW, ACTIVATING, ACTIVATION_FAILED, COMPLETED, REFUNDED } public enum ReservationStatus { ACTIVE, EXPIRED, USED, CANCELLED }
快取服務
1. 目的:
該服務負責儲存包資料的本機快取。目標是使系統更快並減少對提供者 API 的不必要呼叫。
2. 主要特點:
- updateCache():此方法透過從提供者取得所有套件數據,每 5 分鐘刷新一次本機快取。它確保快取保持最新。
- getPackage():此方法使用其 ID 從快取中檢索包資訊。
- updatePackageQuota():當配額詳細資料發生變更時,此方法會使用特定套件的新資訊更新快取。
提供者整合
1. 目的:
此服務處理與提供者的 API 的通訊。它管理諸如檢查配額、預訂套餐、啟動服務和驗證這些啟動等任務。
2. 主要特點:
- checkQuota():此方法透過呼叫提供者的 API 來檢查套件是否有足夠的可用配額。
- ReserveQuota():它透過向提供者發送請求來為客戶保留包裹的配額。
- activateService():當需要為訂單啟動服務時,此方法將處理對提供者的請求。
- verifyActivation():啟動後,此方法確認是否一切成功。
- getAllPackages():此方法從提供者檢索所有可用的套件,這對於更新快取或向使用者顯示套件選項非常有用。
3.重試機制:
當出現臨時問題時,該服務使用 RetryTemplate 自動重試對提供者 API 的請求。這確保了系統即使在出現輕微問題時也能保持可靠和彈性。
透過組合這些功能,此程式碼可確保系統有效地管理套件數據,同時保持與提供者的 API 的順暢且可靠的通訊。
// Domain Models @Getter @Setter @Entity public class Package { @Id private String id; private String name; private BigDecimal price; private BigDecimal providerCost; private String description; private boolean active; } @Getter @Setter @Entity public class Order { @Id private String id; private String customerId; private String packageId; private String reservationId; private String paymentId; private String escrowId; private OrderStatus status; private BigDecimal amount; private BigDecimal providerCost; private LocalDateTime createdAt; private LocalDateTime updatedAt; } @Getter @Setter @Entity public class QuotaReservation { @Id private String id; private String packageId; private LocalDateTime expiresAt; private ReservationStatus status; } // Enums public enum OrderStatus { CREATED, RESERVED, PAYMENT_PENDING, PAYMENT_COMPLETED, IN_ESCROW, ACTIVATING, ACTIVATION_FAILED, COMPLETED, REFUNDED } public enum ReservationStatus { ACTIVE, EXPIRED, USED, CANCELLED }
支付網關集成
此類在管理系統如何與支付網關互動以順利、安全地處理金融交易方面發揮關鍵作用。
1.初始化Payment(PaymentRequest請求):
- 將此視為付款流程的開始。它會向支付網關發送包含所有付款詳細資訊的請求。
- 它傳回一個 PaymentSession 對象,其中包含付款 URL 和會話狀態等資訊。
2.holdInEscrow(String paymentId):
- 此方法使用給定的付款 ID 來保護託管帳戶中的付款。
- 它提供了一個 EscrowResponse 對象,其中包含有關託管資金的所有詳細資訊。
3.releaseToProvider(String escrowId):
- 服務成功啟動後,此方法將託管資金釋放給服務提供者。
- 託管 ID 用於識別和釋放正確的資金。
4.refundToCustomer(String escrowId):
- 如果出現問題,例如服務啟動失敗,此方法會將託管資金退還給客戶。
- 它使用託管 ID 來處理退款。
主要特點:
- 該類別使用 WebClient 向支付網關的 REST API 發送 HTTP 請求,確保無縫整合。
- 它處理關鍵操作,例如開始付款、管理託管和處理退款。
- 所有方法都使用同步呼叫(透過 .block())來確保操作在繼續之前完成,從而確保可靠性。
在管理系統中安全且有效率的金融交易時,此類是拼圖的關鍵部分。
// Domain Models @Getter @Setter @Entity public class Package { @Id private String id; private String name; private BigDecimal price; private BigDecimal providerCost; private String description; private boolean active; } @Getter @Setter @Entity public class Order { @Id private String id; private String customerId; private String packageId; private String reservationId; private String paymentId; private String escrowId; private OrderStatus status; private BigDecimal amount; private BigDecimal providerCost; private LocalDateTime createdAt; private LocalDateTime updatedAt; } @Getter @Setter @Entity public class QuotaReservation { @Id private String id; private String packageId; private LocalDateTime expiresAt; private ReservationStatus status; } // Enums public enum OrderStatus { CREATED, RESERVED, PAYMENT_PENDING, PAYMENT_COMPLETED, IN_ESCROW, ACTIVATING, ACTIVATION_FAILED, COMPLETED, REFUNDED } public enum ReservationStatus { ACTIVE, EXPIRED, USED, CANCELLED }
通知 DTO
1. 電子郵件通知:
- 將此視為發送電子郵件通知的藍圖。它包括:
- 收件者的電子郵件 (to)。
- 電子郵件的主題。
- 用於確定格式的範本 ID。
- 用於個人化內容的動態資料(templateData)。
2.簡訊通知:
- 類似電子郵件通知,但針對簡訊量身訂做。它包括:
- 收件者的電話號碼 (phoneNumber)。
- 訊息格式的範本 ID。
- 用於個人化的動態資料(templateData)。
通知服務
此服務處理發送給使用者的所有關於訂單狀態的通知。其工作原理如下:
1.sendSuccessNotification(訂單訂單):
- 此方法處理發送成功通知。它使用:
- buildSuccessEmail 用於建立電子郵件通知。
- buildSuccessSms 用於建立簡訊通知。
- 它還使用 QuotaWebSocketHandler 透過 WebSocket 發送即時更新。
2.sendFailureNotification(訂單順序):
- 這個負責處理失敗通知。它使用:
- buildFailureEmail 用於電子郵件。
- buildFailureSms 用於簡訊。
- 與成功通知一樣,它也會發送 WebSocket 更新。
3. 輔助方法:
- buildSuccessEmail 和 buildFailureEmail:這些方法根據訂單是成功還是失敗建立電子郵件通知。他們使用模板和訂單詳細資訊。
- buildSuccessSms 和 buildFailureSms:它們的功能相同,但用於簡訊通知。
附加功能:
- WebSocket 更新:使用 QuotaWebSocketHandler 保持前端即時更新。
- 錯誤記錄:如果出現問題,它會記錄錯誤以供偵錯。
這項服務可確保用戶始終了解其訂單,無論是透過電子郵件、簡訊或即時更新。
// Domain Models @Getter @Setter @Entity public class Package { @Id private String id; private String name; private BigDecimal price; private BigDecimal providerCost; private String description; private boolean active; } @Getter @Setter @Entity public class Order { @Id private String id; private String customerId; private String packageId; private String reservationId; private String paymentId; private String escrowId; private OrderStatus status; private BigDecimal amount; private BigDecimal providerCost; private LocalDateTime createdAt; private LocalDateTime updatedAt; } @Getter @Setter @Entity public class QuotaReservation { @Id private String id; private String packageId; private LocalDateTime expiresAt; private ReservationStatus status; } // Enums public enum OrderStatus { CREATED, RESERVED, PAYMENT_PENDING, PAYMENT_COMPLETED, IN_ESCROW, ACTIVATING, ACTIVATION_FAILED, COMPLETED, REFUNDED } public enum ReservationStatus { ACTIVE, EXPIRED, USED, CANCELLED }
配額更新類
- 將此類視為配額更新的簡單信使。它攜帶三個關鍵訊息:
- packageId:正在更新的套件的ID。
- availableQuota: 該包還剩多少配額。
- 時間戳:更新的時間。
WebSocket 配置
1.WebSocket配置:
- 這是 WebSocket 通訊成為可能的設定。
- 它註冊一個處理程序 (quotaWebSocketHandler) 來偵聽 /ws/quota 處的 WebSocket 連線。
- 它還可以透過設定 allowedOrigins("*") 來允許來自任何來源的連接。
2.quotaWebSocketHandler():
- 這定義了將管理傳入訊息和連線的 WebSocket 處理程序 bean。
配額WebSocketHandler
這就是所有 WebSocket 魔法發生的地方!它管理伺服器和客戶端之間的即時更新。
1. 領域:
- PackageCacheService:每當配額更新到來時協助更新本機快取。
- ObjectMapper:處理 JSON 有效負載到 Java 物件的轉換,反之亦然。
- 會話:追蹤所有活動的 WebSocket 會話(目前連線的客戶端)。
2、方法:
-
afterConnectionEstablished(WebSocketSession 會話):
- 連線後立即將新的客戶端會話新增至活動清單。
-
afterConnectionClosed(WebSocketSession 會話,CloseStatus 狀態):
- 斷開連線時刪除客戶端會話。
-
handleTextMessage(WebSocketSession會話,TextMessage訊息):
- 處理傳入訊息。
- 將接收到的 JSON 轉換為 QuotaUpdate 物件並更新本機快取。
3. sendOrderUpdate(訂單順序):
- 向所有連接的客戶端發送有關訂單變更的即時更新。
- 將 Order 物件轉換為 JSON 並將其作為訊息傳送到活動的 WebSocket 會話。
- 確保只有開啟的連線才能收到更新。
此守則的主要特點:
- 即時更新:
- 讓客戶立即了解配額變更和訂單更新。
- 執行緒安全管理:
- 使用ConcurrentHashSet處理連接的客戶端,確保多個客戶端處於活動狀態時不會發生衝突。
- 錯誤處理:
- 發送訊息時出現問題時記錄錯誤,以便更輕鬆地進行故障排除。
此設定可確保後端和前端之間的順暢和即時通信,因此使用者始終可以獲得有關配額可用性和訂單狀態的最新資訊。
// Domain Models @Getter @Setter @Entity public class Package { @Id private String id; private String name; private BigDecimal price; private BigDecimal providerCost; private String description; private boolean active; } @Getter @Setter @Entity public class Order { @Id private String id; private String customerId; private String packageId; private String reservationId; private String paymentId; private String escrowId; private OrderStatus status; private BigDecimal amount; private BigDecimal providerCost; private LocalDateTime createdAt; private LocalDateTime updatedAt; } @Getter @Setter @Entity public class QuotaReservation { @Id private String id; private String packageId; private LocalDateTime expiresAt; private ReservationStatus status; } // Enums public enum OrderStatus { CREATED, RESERVED, PAYMENT_PENDING, PAYMENT_COMPLETED, IN_ESCROW, ACTIVATING, ACTIVATION_FAILED, COMPLETED, REFUNDED } public enum ReservationStatus { ACTIVE, EXPIRED, USED, CANCELLED }
以下是這些自訂異常類別的詳細資訊以及如何使用它們來處理系統中的特定錯誤場景:
QuotaNotAvailableException:
- 當使用者嘗試購買套餐,但該套餐的配額已用完時,會觸發此異常。
- 它附帶一條簡單的預設訊息:“軟體包配額不可用”,因此開發者和用戶都可以清楚地了解該問題。
OrderNotFoundException:
- 當系統無法根據提供的 orderId 找到訂單時,此功能就會啟動。
- 它包含詳細的錯誤訊息,例如“未找到訂單:[orderId]”,可以輕鬆準確地找出遺失的訂單。
PaymentVerificationException:
- 如果驗證付款時出現問題(可能金額不匹配,或付款狀態不清楚),則會拋出此異常。
- 它允許您傳遞自訂訊息,增加診斷付款問題的靈活性和上下文。
透過使用這些異常,系統以乾淨且可預測的方式處理錯誤。它們不僅可以提高開發人員的調試效率,還可以確保用戶在出現問題時收到清晰且可操作的回饋。
// Request/Response DTOs @Getter @Setter public class OrderRequest { private String customerId; private String packageId; private BigDecimal amount; } @Getter @Setter public class PaymentCallback { private String orderId; private String paymentId; private String status; private BigDecimal amount; private LocalDateTime timestamp; } @Getter @Setter public class QuotaResponse { private String packageId; private boolean available; private Integer remainingQuota; private LocalDateTime timestamp; } @Getter @Setter public class ReservationResponse { private String id; private String packageId; private LocalDateTime expiresAt; private ReservationStatus status; } @Getter @Setter public class ActivationResponse { private String orderId; private boolean success; private String activationId; private String errorCode; private String errorMessage; } @Getter @Setter public class VerificationResponse { private String orderId; private String activationId; private boolean success; private String status; private LocalDateTime activatedAt; } @Getter @Setter public class PaymentRequest { private String orderId; private BigDecimal amount; private String currency; private String customerId; private String returnUrl; private String callbackUrl; } @Getter @Setter public class PaymentSession { private String sessionId; private String paymentUrl; private LocalDateTime expiresAt; private String status; } @Getter @Setter public class EscrowResponse { private String id; private String paymentId; private BigDecimal amount; private String status; private LocalDateTime createdAt; }
OrderService 類別處理管理訂單時的繁重工作。讓我們來看看它是如何運作的:
主要職責
-
createOrder(OrderRequest 請求):
- 這個方法主要是建立一個新訂單。它檢查包裹是否可用,獲取詳細信息,保留配額,並將訂單保存到資料庫,初始狀態為 RESERVED。
-
processPayment(String orderId, PaymentCallback 回呼):
- 在這裡,付款被處理。系統驗證付款詳細資訊、更新訂單、將付款託管並啟動服務啟動流程。如果出現問題,它會優雅地管理失敗。
-
驗證啟動(訂單順序):
- 此方法會仔細檢查服務啟動是否順利。它最多嘗試 3 次,如果仍然失敗,系統會回退來處理失敗。
-
completeOrder(訂單順序):
- 一旦一切檢查完畢,此方法就會最終確定訂單。它將託管資金釋放給提供者,更新狀態,並通知用戶成功。
-
handleActivationFailure(訂單順序):
- 如果啟動失敗,此方法可確保客戶獲得退款並收到有關問題所在的通知。
-
getOrder(String orderId):
- 這個簡單的方法透過 ID 檢索訂單。如果訂單不存在,則會拋出特定異常。
為什麼它有效
- 由於其事務性質,它確保事務完成或回滾。
- 透過清晰的錯誤處理和重試,它足夠強大,可以處理現實世界中的問題。
- 通知讓使用者隨時了解每一步的情況。
此服務是訂單管理流程的支柱,將所有內容結合在一起以提供無縫的使用者體驗。
// Domain Models @Getter @Setter @Entity public class Package { @Id private String id; private String name; private BigDecimal price; private BigDecimal providerCost; private String description; private boolean active; } @Getter @Setter @Entity public class Order { @Id private String id; private String customerId; private String packageId; private String reservationId; private String paymentId; private String escrowId; private OrderStatus status; private BigDecimal amount; private BigDecimal providerCost; private LocalDateTime createdAt; private LocalDateTime updatedAt; } @Getter @Setter @Entity public class QuotaReservation { @Id private String id; private String packageId; private LocalDateTime expiresAt; private ReservationStatus status; } // Enums public enum OrderStatus { CREATED, RESERVED, PAYMENT_PENDING, PAYMENT_COMPLETED, IN_ESCROW, ACTIVATING, ACTIVATION_FAILED, COMPLETED, REFUNDED } public enum ReservationStatus { ACTIVE, EXPIRED, USED, CANCELLED }
OrderController 類別負責管理系統中訂單的 REST API 端點。 Think 是發出請求的客戶端和執行繁重任務的後端服務之間的橋樑。
關鍵端點
-
POST /api/orders (createOrder):
- 此端點處理建立新訂單。
- 發生的事情是這樣的:
- 它接收來自客戶端的 OrderRequest。
- 呼叫 OrderService.createOrder 處理請求並建立訂單。
- 發回:
- 如果一切順利,新建立的訂單將收到 200 OK 回應。
- 如果包配額不可用,則會出現 409 衝突。
- 任何意外問題都會出現 500 內部伺服器錯誤。
-
POST /api/orders/callback (handlePaymentCallback):
- 此處理由支付網關發送的付款更新。
- 流程如下:
- 它收到一個包含所有付款詳細資訊的 PaymentCallback。
- 呼叫 OrderService.processPayment 處理付款並更新訂單狀態。
- 可能的反應是:
- 200 OK 如果付款成功處理。
- 如果提供的訂單 ID 不存在,則傳回 404 Not Found。
- 422 如果付款驗證不匹配,則無法處理實體。
- 500 內部伺服器錯誤,出現任何意外狀況。
-
GET /api/orders/{orderId} (getOrder):
- 此端點透過 ID 取得特定訂單的詳細資訊。
- 其工作原理如下:
- 它呼叫 OrderService.getOrder 來擷取訂單。
- 回傳:
- 如果找到的話,會給出 200 OK 回應以及訂單詳細資訊。
- 如果訂單 ID 與任何記錄都不匹配,則會出現 404 Not Found。
特徵
- 關注點分離:OrderController 將所有業務邏輯委託給 OrderService,保持事物乾淨且集中。
- 驗證:使用@Valid註解驗證請求有效負載,以確保傳入的資料符合預期。
- 錯誤處理:
- 針對常見問題(例如配額不可用或訂單缺失)提供具體且有用的回應。
- 記錄任何問題以使偵錯更容易。
- 日誌記錄:追蹤傳入請求、錯誤和訂單詳細資訊等關鍵事件,以提高可見度。
此控制器確保客戶端和後端無縫通信,使訂單管理盡可能順利。
結論
本研究文件為設計電子商務信用銷售系統、解決配額管理、支付處理和服務啟動等重要挑戰奠定了基礎。雖然此設計涵蓋了基礎知識,但總有改進的空間!
以下是一些改進此設計的想法:
- 使用事件驅動架構讓系統更靈活可擴充。
- 新增基於訊息佇列的處理以順利處理大量交易。
- 探索進階快取策略以加快速度並減少對外部 API 的依賴。
- 考慮分散式系統模式以更輕鬆地擴展和更好的可靠性。
- 實作斷路器以優雅地處理第三方服務問題。
- 設定監控和警報以儘早發現問題並快速修復。
- 加強安全措施以保護使用者及其資料。
非常感謝您的閱讀!我希望本文檔有用,並為探索類似挑戰的任何人提供清晰的思路。當然,這種設計並不完美——總有改進的空間。如果您有任何想法或建議,我很想聽聽。
資源:
- 深入探討簡潔架構和 SOLID 原則
- 使用微服務架構設計電子商務應用程式
- 為閃購設計可擴展的後端
- 維基百科上的託管
以上是設計一個網路信用購買系統的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

公司安全軟件導致部分應用無法正常運行的排查與解決方法許多公司為了保障內部網絡安全,會部署安全軟件。 ...

將姓名轉換為數字以實現排序的解決方案在許多應用場景中,用戶可能需要在群組中進行排序,尤其是在一個用...

系統對接中的字段映射處理在進行系統對接時,常常會遇到一個棘手的問題:如何將A系統的接口字段有效地映�...

在使用MyBatis-Plus或其他ORM框架進行數據庫操作時,經常需要根據實體類的屬性名構造查詢條件。如果每次都手動...

在使用IntelliJIDEAUltimate版本啟動Spring...

Java對象與數組的轉換:深入探討強制類型轉換的風險與正確方法很多Java初學者會遇到將一個對象轉換成數組的�...

電商平台SKU和SPU表設計詳解本文將探討電商平台中SKU和SPU的數據庫設計問題,特別是如何處理用戶自定義銷售屬...

Redis緩存方案如何實現產品排行榜列表的需求?在開發過程中,我們常常需要處理排行榜的需求,例如展示一個�...
