支付过程中的设计模式-状态模式(一)
目的
本文记录在工作中,使用到的一些设计模式。便于后续开发参考。
本文主要介绍支付过程中,使用到的状态模式。
支付流程
一般支付过程,包括
1. 生成待支付订单
2. 开始支付
3. 等待支付结果(一般都是异步通知的方式)
4. 处理结果
5. 支付成功/支付失败
支付状态
因此,在上述支付过程中,支付状态可以分为:
- 初始状态
该状态下,主要完成本地服务器订单创建,并向支付平台发起预支付请求。并由APP(小程序等客户端)发起实际支付操作。
若上述操作成功,则将状态转换成待支付状态。反之,则为支付失败状态。
- 待支付状态
该状态下, 本地服务器并不知道APP客户端支付的实际情况,在等待支付结果的异步通知。
在收到异步通知的时候,将待支付状态转换为支付中状态,表示本地服务器要开始处理支付请求了。
- 支付中状态
该状态下,表示本地服务器正在处理支付请求。 若用户支付成功,则将状态转换为支付成功状态。若用户支付失败,则将状态转换为支付失败状态。
- 支付成功状态
该状态下,表示整个支付过程结束,并支付成功了。
- 支付失败状态
改状态下,表示整个支付过程结束,并支付失败了。
整体状态转换过程如下:
初始状态
|___数据库插入待支付记录,向支付平台发送支付请求
|
待支付状态
|___支付结果通知到达,线程尝试获取处理权限(成功,则进入下一个状态;反之,任然停留在当前状态)
|
支付中状态
|___处理支付结果,根据结果进行状态转移(支付成功,则跳转到PaySucState; 支付失败,则跳转到PayFailState)
|
支付成功/支付失败状态
实现方式
AbstractPayState 状态父类
定义了所有状态的共有模板。
公共方法包括
1. doActive --> 进行状态迁移请求(若成功,则返回true;失败,则返回false)
2. submit --> 在状态迁移请求成功时, 正式提交该请求,实现真正的状态迁移。
3. cancel --> 在状态迁移请求失败时, 取消改请求, 取消本地状态迁移。
(PS: 上述过程中,类似mysql的事务, 先开启事务, 若事务中内容都成功处理,则提交;反之,则取消。)
InitState 初始状态
表示流程开始,但是未发生任何实质交互,包括数据库记录, 微信通信等
UnpaidState 未支付状态
当前状态下,已经在数据库中插入一条待支付记录,且统一下单接口已发送完成(微信交互成功)。
但是,支付异步通知还没有发送到edianban服务器(以下简称e服务器)
PayingState 支付中状态
当前状态下, 支付结果发送到e服务器, 且某一个线程已经获取了当前支付结果的处理权限(该
状态用户多线程处理同一个订单请求时,只有成功变为PayingState状态的线程,拥有处理资格,
其他线程则失去处理资格,直接跳过处理过程)
PaySucState 支付失败状态
当前状态下,该支付结果为支付成功
PayFailState 支付失败状态
当前状态下,该支付结果为支付失败
使用该设计模式的好处
1. 便于管理
我在实现例如微信支付的时候,不需要考虑,一个订单处理完一个阶段,下一步会调用哪个函数。
例如:
简单的支付结果处理的函数里面,在收到支付结果的时候,
1.1 都要先根据订单号查询数据库中是否存在该订单号的订单信息,若没有,则不处理。若有这个订单的信息,则继续判断。
1.2 该订单是否已经被处理。(一般异步通知的接口都会发多次,有幂等性问题)
1.3 同时,需要花费时间,进行加锁(若是分布式系统,则需要增加分布式锁来保证数据的一致性),处理并发性问题。
最终,导致开发者需要花费大量时间在非业务的代码中。
但是,使用状态模式的情况下,
开发者只需要关心在特定阶段,要实现哪些业务逻辑即可,而不需要关系并发,订单状态问题。
例如:
在收到支付结果的时候,
1.1 函数只需要调用状态类的doActive方法,即可调用对应的业务代码。
1.2 若当前订单不存在,则通过状态模式获取当前订单的状态为空。
1.3 若当前订单已经被处理, 则状态会直接迁移到支付成功/支付失败的状态(而不会再次调用处理结果的代码,解决幂等性问题)。
1.4 若同一个订单的多个通知同时到达服务器, 则只有一个线程能够取得处理权限(状态模式中的状态之间的转移,也考虑到了多并发的情况)。
2. 符合开闭原则
2.1 在接入多种支付方式的时候,对原有的支付方式无任何影响。只需要新增拓展新的状态类即可。
这样子可以大大减少开发和测试时间,不需要花费大量时间去做回归性测试(测试新增的支付方式会不会对已经存在的支付方式的功能有影响)。
3. 符合接口单一原则
3.1 状态类主要负责支付过程中状态转移,保证支付流程的完整可靠(功能包括状态转移,并发控制,事务控制等)。
3.2 业务代码类只需要实现业务代码, 不需要关心其他的非业务代码。