设计一个互联网信用购买系统
在我面临的一次技术面试中,我被要求设计一个电子商务系统,允许用户从第三方提供商处购买互联网积分。
自信地,我提出了一个简单的解决方案:显示可用的套餐,让用户选择一个,通过外部网关处理付款,并与提供商交互以提供积分。然而,当被问及失败场景时(例如用户完成付款后供应商缺货),我意识到我的设计缺乏有效处理此类问题的弹性。
几周前,我对闪购系统和库存预订模式进行了研究,特别关注库存预订策略。限时抢购通常需要应对高需求和有限的库存,需要复杂的机制来维持系统稳定性和管理客户期望。我发现的一个概念是临时库存预订,这有助于防止高峰时段超售。
这项研究让我想起了我的面试经历。我认识到应用这些库存预留策略可以解决我最初设计中的缺点。通过在结帐过程中临时保留库存,系统可以有效处理提供商库存耗尽的情况。
在本研究文档中,我旨在分享从我的研究中获得的见解,并提出一种设计互联网信用购买系统的改进方法。通过整合库存预留策略,我们可以构建一个既强大又用户友好的平台,能够处理各种故障场景,同时提供无缝体验。
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系统的接口字段有效地映�...

在使用IntelliJIDEAUltimate版本启动Spring...

在使用MyBatis-Plus或其他ORM框架进行数据库操作时,经常需要根据实体类的属性名构造查询条件。如果每次都手动...

Java对象与数组的转换:深入探讨强制类型转换的风险与正确方法很多Java初学者会遇到将一个对象转换成数组的�...

电商平台SKU和SPU表设计详解本文将探讨电商平台中SKU和SPU的数据库设计问题,特别是如何处理用户自定义销售属...

Redis缓存方案如何实现产品排行榜列表的需求?在开发过程中,我们常常需要处理排行榜的需求,例如展示一个�...
