接上一篇內(nèi)容, 來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Payment Proxy + 有限狀態(tài)機(jī)的DEMO。建議先查看上一篇內(nèi)容再來看這篇文章坏逢。
狀態(tài)機(jī)設(shè)計(jì)
State定義
public enum PaymentOrderStates {
CREATED(0,"訂單已創(chuàng)建"),
WAIT_PAYMENT(1,"訂單待支付"),
PAYMENT_SUCCESS(2,"支付成功"),
PAYMENT_CANCELED(3,"支付取消"),
PAYMENT_FAILED(4,"支付失敗"),
WAIT_REFUND(5,"等待退款"),
REFUND_SUCCESS(6,"退款成功"),
REFUND_FAILED(7,"退款失敗"),
FINISHED(8,"訂單完成"),
EXCEPTION(9,"訂單異常"),
CLOSED(10,"訂單關(guān)閉");
private int code;
private String value;
PaymentOrderStates(int code, String value) {
this.code = code;
this.value = value;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Event定義
State 由事件觸發(fā),引起變化赘被。
public enum PaymentOrderEvents {
//創(chuàng)建訂單
CREATE_ORDER,
//創(chuàng)建交易
CREATE_TRANSACTION,
//正在支付
PAYING,
//正在退款
REFUNDING,
//失敗
FAIL,
//確認(rèn)
CONFIRM,
//取消
CANCEL
}
大家一定要記住,這個(gè)是事件的定義是整。
狀態(tài)機(jī)定義
代碼中我已經(jīng)詳細(xì)的標(biāo)注了不同事件對(duì)應(yīng)的不同狀態(tài)的變化。
@Configuration
public class StateMachineConfiguration {
@Bean
public StateMachine<PaymentOrderStates, PaymentOrderEvents, OrderContext> paymentOrderStateMachine(PaymentOrderStateMachineService orderService) {
StateMachineBuilder<PaymentOrderStates, PaymentOrderEvents, OrderContext> builder = StateMachineBuilderFactory.create();
//訂單創(chuàng)建
builder.internalTransition()
.within(PaymentOrderStates.CREATED)
.on(PaymentOrderEvents.CREATE_ORDER)
.perform(((from, to, event, ctx) -> orderService.doActionCreate(from,to,event,ctx)));
//訂單創(chuàng)建 =》 待支付
builder.externalTransition()
.from(PaymentOrderStates.CREATED)
.to(PaymentOrderStates.WAIT_PAYMENT)
.on(PaymentOrderEvents.CREATE_TRANSACTION)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//待支付 =》 支付成功
builder.externalTransition()
.from(PaymentOrderStates.WAIT_PAYMENT)
.to(PaymentOrderStates.PAYMENT_SUCCESS)
.on(PaymentOrderEvents.PAYING)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//待支付 =》 支付失敗
builder.externalTransition()
.from(PaymentOrderStates.WAIT_PAYMENT)
.to(PaymentOrderStates.PAYMENT_FAILED)
.on(PaymentOrderEvents.FAIL)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//待支付 =》 取消支付
builder.externalTransition()
.from(PaymentOrderStates.WAIT_PAYMENT)
.to(PaymentOrderStates.PAYMENT_CANCELED)
.on(PaymentOrderEvents.CANCEL)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//支付成功 =》 訂單完成
builder.externalTransition()
.from(PaymentOrderStates.PAYMENT_SUCCESS)
.to(PaymentOrderStates.FINISHED)
.on(PaymentOrderEvents.CONFIRM)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//訂單取消 =》 訂單關(guān)閉
builder.externalTransition()
.from(PaymentOrderStates.PAYMENT_CANCELED)
.to(PaymentOrderStates.CLOSED)
.on(PaymentOrderEvents.CONFIRM)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//支付失敗 =》 訂單異常
builder.externalTransition()
.from(PaymentOrderStates.PAYMENT_FAILED)
.to(PaymentOrderStates.EXCEPTION)
.on(PaymentOrderEvents.CONFIRM)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
StateMachine<PaymentOrderStates, PaymentOrderEvents, OrderContext> paymentOrderStateMachine = builder.build("paymentOrderStateMachine");
return paymentOrderStateMachine;
}
}
狀態(tài)機(jī)上下文
這里為了省事民假,我直接繼承了訂單表的映射類浮入。
狀態(tài)上下文
public class OrderContext extends PaymentProxyOrder {
}
數(shù)據(jù)庫(kù)訂單表映射類
@TableName(value ="payment_proxy_order")
@Data
public class PaymentProxyOrder implements Serializable {
/**
* 自增主鍵ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 訂單號(hào)
*/
private String payOrderNo;
/**
* 支付狀態(tài) 與狀態(tài)機(jī)匹配
*/
private Integer payStatus;
/**
* 金額
*/
private String amount;
/**
* 幣種
*/
private Integer currency;
/**
* 交易類型
*/
private Integer transType;
/**
* 支付方法
*/
private Integer payMethod;
/**
* 支付來源 APP 或者H5
*/
private Integer paySource;
/**
* 交易業(yè)務(wù)號(hào)
*/
private String transactionNo;
/**
* 業(yè)務(wù)號(hào) 用于業(yè)務(wù)之間的綁定
*/
private String businessNo;
/**
* 第三方返回的number, 比如MPGS會(huì)返回一個(gè)字符串作為支付狀態(tài)校驗(yàn)
*/
private String thirdPartyNo;
/**
* 傳給第三方的字符串 用業(yè)務(wù)mapping
*/
private String thirdPartyCustomerNo;
/**
* 支付成功實(shí)踐
*/
private Date payDate;
/**
* 業(yè)務(wù)刪除 Is delete Y:yes N:no
*/
private String isDeleted;
/**
* Creator
*/
private String creator;
/**
* created time
*/
private Date gmtCreated;
/**
* Modifier
*/
private String modifier;
/**
* modified time
*/
private Date gmtModified;
/**
* 備注
*/
private String remark;
/**
* 擴(kuò)展字段
*/
private String extraInfo;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
PaymentProxyOrder other = (PaymentProxyOrder) that;
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
&& (this.getPayOrderNo() == null ? other.getPayOrderNo() == null : this.getPayOrderNo().equals(other.getPayOrderNo()))
&& (this.getPayStatus() == null ? other.getPayStatus() == null : this.getPayStatus().equals(other.getPayStatus()))
&& (this.getAmount() == null ? other.getAmount() == null : this.getAmount().equals(other.getAmount()))
&& (this.getCurrency() == null ? other.getCurrency() == null : this.getCurrency().equals(other.getCurrency()))
&& (this.getTransType() == null ? other.getTransType() == null : this.getTransType().equals(other.getTransType()))
&& (this.getPayMethod() == null ? other.getPayMethod() == null : this.getPayMethod().equals(other.getPayMethod()))
&& (this.getPaySource() == null ? other.getPaySource() == null : this.getPaySource().equals(other.getPaySource()))
&& (this.getTransactionNo() == null ? other.getTransactionNo() == null : this.getTransactionNo().equals(other.getTransactionNo()))
&& (this.getBusinessNo() == null ? other.getBusinessNo() == null : this.getBusinessNo().equals(other.getBusinessNo()))
&& (this.getThirdPartyNo() == null ? other.getThirdPartyNo() == null : this.getThirdPartyNo().equals(other.getThirdPartyNo()))
&& (this.getThirdPartyCustomerNo() == null ? other.getThirdPartyCustomerNo() == null : this.getThirdPartyCustomerNo().equals(other.getThirdPartyCustomerNo()))
&& (this.getPayDate() == null ? other.getPayDate() == null : this.getPayDate().equals(other.getPayDate()))
&& (this.getIsDeleted() == null ? other.getIsDeleted() == null : this.getIsDeleted().equals(other.getIsDeleted()))
&& (this.getCreator() == null ? other.getCreator() == null : this.getCreator().equals(other.getCreator()))
&& (this.getGmtCreated() == null ? other.getGmtCreated() == null : this.getGmtCreated().equals(other.getGmtCreated()))
&& (this.getModifier() == null ? other.getModifier() == null : this.getModifier().equals(other.getModifier()))
&& (this.getGmtModified() == null ? other.getGmtModified() == null : this.getGmtModified().equals(other.getGmtModified()))
&& (this.getRemark() == null ? other.getRemark() == null : this.getRemark().equals(other.getRemark()))
&& (this.getExtraInfo() == null ? other.getExtraInfo() == null : this.getExtraInfo().equals(other.getExtraInfo()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
result = prime * result + ((getPayOrderNo() == null) ? 0 : getPayOrderNo().hashCode());
result = prime * result + ((getPayStatus() == null) ? 0 : getPayStatus().hashCode());
result = prime * result + ((getAmount() == null) ? 0 : getAmount().hashCode());
result = prime * result + ((getCurrency() == null) ? 0 : getCurrency().hashCode());
result = prime * result + ((getTransType() == null) ? 0 : getTransType().hashCode());
result = prime * result + ((getPayMethod() == null) ? 0 : getPayMethod().hashCode());
result = prime * result + ((getPaySource() == null) ? 0 : getPaySource().hashCode());
result = prime * result + ((getTransactionNo() == null) ? 0 : getTransactionNo().hashCode());
result = prime * result + ((getBusinessNo() == null) ? 0 : getBusinessNo().hashCode());
result = prime * result + ((getThirdPartyNo() == null) ? 0 : getThirdPartyNo().hashCode());
result = prime * result + ((getThirdPartyCustomerNo() == null) ? 0 : getThirdPartyCustomerNo().hashCode());
result = prime * result + ((getPayDate() == null) ? 0 : getPayDate().hashCode());
result = prime * result + ((getIsDeleted() == null) ? 0 : getIsDeleted().hashCode());
result = prime * result + ((getCreator() == null) ? 0 : getCreator().hashCode());
result = prime * result + ((getGmtCreated() == null) ? 0 : getGmtCreated().hashCode());
result = prime * result + ((getModifier() == null) ? 0 : getModifier().hashCode());
result = prime * result + ((getGmtModified() == null) ? 0 : getGmtModified().hashCode());
result = prime * result + ((getRemark() == null) ? 0 : getRemark().hashCode());
result = prime * result + ((getExtraInfo() == null) ? 0 : getExtraInfo().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", payOrderNo=").append(payOrderNo);
sb.append(", payStatus=").append(payStatus);
sb.append(", amount=").append(amount);
sb.append(", currency=").append(currency);
sb.append(", transType=").append(transType);
sb.append(", payMethod=").append(payMethod);
sb.append(", paySource=").append(paySource);
sb.append(", transactionNo=").append(transactionNo);
sb.append(", businessNo=").append(businessNo);
sb.append(", thirdPartyNo=").append(thirdPartyNo);
sb.append(", thirdPartyCustomerNo=").append(thirdPartyCustomerNo);
sb.append(", payDate=").append(payDate);
sb.append(", isDeleted=").append(isDeleted);
sb.append(", creator=").append(creator);
sb.append(", gmtCreated=").append(gmtCreated);
sb.append(", modifier=").append(modifier);
sb.append(", gmtModified=").append(gmtModified);
sb.append(", remark=").append(remark);
sb.append(", extraInfo=").append(extraInfo);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
狀態(tài)持久化層
@Service
public class PaymentOrderStateMachineService {
//訂單表持久化層
private final PaymentProxyOrderService proxyOrderService;
//訂單本地緩存
private final Cache<String, PaymentOrderStates> orderStateCache = CacheBuilder.newBuilder().expireAfterWrite(15, TimeUnit.MINUTES).build();
public PaymentOrderStateMachineService(PaymentProxyOrderService proxyOrderService) {
this.proxyOrderService = proxyOrderService;
}
//創(chuàng)建訂單
public void doActionCreate(PaymentOrderStates from, PaymentOrderStates to, PaymentOrderEvents event, OrderContext ctx) {
//當(dāng)訂單創(chuàng)建成功 將下一個(gè)狀態(tài)放入緩存
if(proxyOrderService.save(ctx)) {
orderStateCache.put(ctx.getPayOrderNo(),to);
}
}
public void doActionUpdate(PaymentOrderStates from, PaymentOrderStates to, PaymentOrderEvents event, OrderContext ctx) {
//如果 當(dāng)前訂單狀態(tài)與 狀態(tài)機(jī)from 狀態(tài)一致, 則更新初始化表. 證明訂單狀態(tài)改變并且持久化到數(shù)據(jù)庫(kù)中
if(null != orderStateCache.getIfPresent(ctx.getPayOrderNo())
&& from == orderStateCache.getIfPresent(ctx.getPayOrderNo())) {
proxyOrderService.updateById(ctx);
}
}
}
這里我先暫時(shí)不貼對(duì)應(yīng)的Mybatis Plus 對(duì)應(yīng)的代碼, 簡(jiǎn)單的將判斷邏輯寫在了注釋里羊异。 這里重要關(guān)注一下事秀, 由于我們可能會(huì)多次請(qǐng)求第三方接口查詢訂單狀態(tài)進(jìn)而改變我們自己維護(hù)的訂單狀態(tài),為了避免頻繁更新數(shù)據(jù)庫(kù)野舶, 我這里做了緩存處理易迹,暫時(shí)跳過訂單狀態(tài)重復(fù)更新的情況。
第三方支付狀態(tài)與狀態(tài)機(jī)匹配
由于我們需要根據(jù)第三方的支付狀態(tài)來改變我們自己的訂單狀態(tài)平道。因此需要一種思路去匹配睹欲。 這里我只暫時(shí)給出自己在項(xiàng)目中的實(shí)現(xiàn)思路。
假設(shè)請(qǐng)求第三方查詢支付狀態(tài)接口中 有個(gè)字段為Status 并且他們對(duì)應(yīng)的值解釋為:
Status | 對(duì)應(yīng)狀態(tài) |
---|---|
1 | 正在支付 |
2 | 正在支付 |
3 | 支付成功 |
4 | 支付失敗 |
5 | 支付取消 |
那么我們可以根據(jù)第三方查詢接口來定義一個(gè)枚舉類一屋。 其中包括了第三方接口返回來的status值窘疮, 本地服務(wù)訂單的狀態(tài)和事件類別。這樣可以更快的返回來實(shí)現(xiàn)第三方與本地的訂單的狀態(tài)匹配陆淀。
public enum ThirdPartyPayStatusCodeEnum {
CREATE(1,
"Pending,transaction created",
PaymentOrderStates.CREATED,
PaymentOrderStates.WAIT_PAYMENT,
PaymentOrderEvents.CREATE_TRANSACTION),
PENDING_PAYMENT(2,
"Pending,Waiting for user action",
PaymentOrderStates.CREATED,
PaymentOrderStates.WAIT_PAYMENT,
PaymentOrderEvents.CREATE_TRANSACTION),
SUCCESS(3,
"Success, payment success",
PaymentOrderStates.WAIT_PAYMENT,
PaymentOrderStates.PAYMENT_SUCCESS,
PaymentOrderEvents.PAYING),
FAIL(4, "Fail, general payment fail",
PaymentOrderStates.WAIT_PAYMENT,
PaymentOrderStates.PAYMENT_FAILED,
PaymentOrderEvents.FAIL),
CANCEL(5, "Cancel, cancel before pay or the end gateway support cancel paid transaction directly",
PaymentOrderStates.WAIT_PAYMENT,
PaymentOrderStates.PAYMENT_CANCELED,
PaymentOrderEvents.CANCEL);
//第三方Status 值
private int code;
//對(duì)應(yīng)含義
private String meaning;
//對(duì)應(yīng)狀態(tài)機(jī)的From 狀態(tài)
private PaymentOrderStates fromOrderStates;
//對(duì)應(yīng)狀態(tài)機(jī)要改變的狀態(tài)
private PaymentOrderStates toOrderStates;
//該狀態(tài)機(jī)需要改變的事件
private PaymentOrderEvents orderEvents;
ThirdPartyPayStatusCodeEnum(int code,
String meaning,
PaymentOrderStates fromOrderStates,
PaymentOrderStates toOrderStates,
PaymentOrderEvents orderEvents) {
this.code = code;
this.meaning = meaning;
this.fromOrderStates = fromOrderStates;
this.toOrderStates = toOrderStates;
this.orderEvents = orderEvents;
}
public int getCode() {
return code;
}
public String getMeaning() {
return meaning;
}
public PaymentOrderStates getFromOrderStates() {
return fromOrderStates;
}
public PaymentOrderStates getToOrderStates() {
return toOrderStates;
}
public PaymentOrderEvents getOrderEvents() {
return orderEvents;
}
/**
* @author Neal
* @description 根據(jù)第三方 status code返回枚舉
* @date 2023/11/15
* @param code:
* @return ThirdPartyPayStatusCodeEnum
*/
public static ThirdPartyPayStatusCodeEnum getOrderStatus(int code) {
for (ThirdPartyPayStatusCodeEnum value : ThirdPartyPayStatusCodeEnum.values()) {
if(value.code == code) {
return value;
}
}
return null;
}
}
小結(jié)
今天簡(jiǎn)單闡述了訂單狀態(tài)機(jī)的demo 代碼考余。 由于代碼比較多, 可能比較亂轧苫,但是只要縷清思路楚堤,很好理解∫呗現(xiàn)在我們自己的訂單狀態(tài)機(jī)已經(jīng)初步成型,下一步就是基于Spring Cloud Gateway來實(shí)現(xiàn)第三方的支付對(duì)接和擴(kuò)展身冬。