既然有七種訂單類型,這好辦啊。可以采用策略+模板模式啊,一個抽象模板+七個子類就可以啦。但是后來仔細(xì)一想,如果將所有的處理邏輯都放在父類和子類當(dāng)中,其實代碼整體也顯得十分臃腫。
為了想出更好的解決方案,于是對原有代碼和業(yè)務(wù)流程進(jìn)行了深入的梳理和總結(jié),主要有以下幾點:
通過以上分析,是不是可以將下單到確認(rèn)收貨作為一層,將不同訂單類型的特定處理實現(xiàn)作為一層呢?這樣不就將訂單流程中各種特殊處理從訂單主流程剝離開了嗎,因此最終決定采用三層接口+策略模板的設(shè)計方案。
接口設(shè)計如下:
包含前端用戶進(jìn)行交互、處理mq消息以及給其它服務(wù)調(diào)用的接口。
訂單核心主流程能力接口。將下單、支付到確認(rèn)收貨等“不變”的基礎(chǔ)能力提供給頂層接口調(diào)用,這層接口有自主發(fā)貨流程和客服發(fā)貨流程兩個實現(xiàn)類。
public interface IGameAccountOrderDealProcess { /** * 處理下單未支付訂單 */ int handlePlaceOrder(GameAccountOrderContext orderContext) throws Exception; /** * 處理支付成功訂單 */ int handlePaySuccessOrder(GameAccountOrderContext orderContext) throws Exception; /** * 處理已發(fā)貨訂單 */ int handleDeliverOrder(GameAccountOrderContext orderContext) throws Exception; /** * 處理支付前取消訂單 */ int handleCancelBeforePayOrder(GameAccountOrderContext orderContext) throws Exception; /** * 處理支付后取消訂單 */ int handleCancelAfterPayOrder(GameAccountOrderContext orderContext) throws Exception; /** * 處理交易成功訂單 */ int handleConfirmReceiptOrder(GameAccountOrderContext orderContext) throws Exception; /** * 賬號交易窗數(shù)據(jù) */ <T extends TradeFlowData> T getOrderTradeData(String logStr, Long orderId, Integer device, Long uid); /** * 上傳賬密 */ ZZOpenScfBaseResult<String> uploadAccountAndPwd(GameAccountSelfTrade.AccountPwdArg arg, long uid, String logStr, ServiceHeaderEntity header) throws Exception; /** * 發(fā)貨 * @param orderContext */ boolean deliverOrder(GameAccountOrderContext orderContext) throws Exception; /** * 訂單確認(rèn)收貨 */ ZZOpenScfBaseResult<String> confirmReceiptOrder(GameAccountOrderContext orderContext, Long uid, boolean needCheckRisk) throws Exception;}
各種訂單類型的特殊處理,每一種訂單模式都對應(yīng)一個實現(xiàn)類。
public interface ITradeSelfHandler { GameAccountTradeFlow.GameAccountTradeType getOrderTrade(); /*------------處理mq消息相關(guān)---------------*/ /** *1.插入表之前設(shè)置客服和extendInfo */ void fillExtraOrderInfoBeforeInsert(GameAccountOrderResultEntity orderEntity, GameAccountOrderContext orderContext); /** * 下單后處理 */ void handleAfterPlaceOrder(GameAccountOrderContext orderContext); /** * 支付前取消處理 */ void handleCancelBeforePay(GameAccountOrderContext orderContext); /** * 支付后取消處理 */ int handleCancelAfterPay(GameAccountOrderContext orderContext) throws Exception; /** * 支付后一些額外處理 */ int handleAfterPaySuccess(GameAccountOrderContext orderContext); /** * 確認(rèn)收貨處理 */ int handleAfterConfirmReceipt(GameAccountOrderContext orderContext) throws Exception; /*---------------------------------*/ /** * 獲取提現(xiàn)時間 */ Date getWithDrawlTime(); /** * 發(fā)送支付成功push */ void orderAlreadyPayPushMsgNew(GameAccountOrderContext orderContext, Pair<String, String> jumpUrl); /** * 獲取分帳賬戶、類別信息 */ List<AccountOrderSplitModel> getOrderSplitModelList(GameAccountOrderContext orderContext, OrderMaxSettleInfo settleInfo); /** * 定制各自spiUi */ void buildOrderSpiUiData(GameAccountOrderContext orderContext, GameOrderSpiConfig bConfig, GameOrderSpiConfig sConfig, SpiUiData spiUiData) throws Exception; /** * 確認(rèn)收貨后一些處理 */ void otherOperationAfterReceipt(GameAccountOrderContext orderContext, Long uid);}
原先在客服后臺、定時任務(wù)、mq集群都有一些訂單的操作,但是這些代碼基本都是重復(fù)的,所以此次重構(gòu)在訂單核心服務(wù)中新增相應(yīng)的訂單操作功能,統(tǒng)一由其它服務(wù)進(jìn)行RPC調(diào)用。
將訂單相關(guān)的接口、工具類集中到同一個包下,方便定位。
//上下文實體public class GameAccountOrderContext { private String logStr; private Long orderId; private Integer mqStatus; private Order order; private GameAccountOrderResultEntity accountOrderEntity; private AccountOrderStatusEnum orderStatus; private Boolean hasInsuranceService;//訂單是否有保險 private GameAccountTradeFlow.GameAccountTradeType tradeType; private GameAccountProductData accountProductData; private ZZProduct product; private ZZProductExt productExt; private Map<String, String> extValueMap; private AccountHelpSaleClue helpSaleClue;//幫賣線索 private DistributionShareInfoDTO distributionShareInfo;//分銷信息 private ITradeSelfHandler tradeSelfHandler; private Integer serviceUiStatus;//對應(yīng)訂單spi狀態(tài)}//上下文準(zhǔn)備GameAccountOrderContext orderContext = orderContextBuilder.buildAccountOrderContext(order, zzProduct, logStr);
訂單流程不管對于什么業(yè)務(wù),基本都是最重要的一個環(huán)節(jié),為了避免產(chǎn)生重大問題,需要做到以下兩點:
根據(jù)賬號訂單流程的特點,在測試的時候遵循以下原則:
對于每一種訂單流程,同時進(jìn)行新、老流程訂單的測試。逐一對比新、老流程的買家側(cè)和賣家側(cè)各個流程節(jié)點的頁面、按鈕、跳轉(zhuǎn)、push、私信等是否保持一致。
為了避免產(chǎn)生重大問題,上線后必須采取灰度策略,不然出了問題就可能就是事故了。本次采用的灰度策略是上線后按訂單類型、訂單量進(jìn)行灰度,同時將灰度訂單落表記錄,配置如下:
[ { "orderType": 6,//訂單類型 "dayNum": 50,//每日灰度量 "isTotalGray": true//是否全量 }] /** * 判斷訂單是否走新交易流程 */public boolean isNewOrderProcess(String logStr, GameAccountOrderContext orderContext) { Long orderId = orderContext.getOrderId(); try { if (gameGrayTestService.isNewTradeProcessOrder(orderId)){ return true; } GameAccountOrderResultEntity orderEntity = accountOrderManage.getGameAccountOrderEntity(orderId, logStr); GameAccountTradeFlow.GameAccountTradeType orderTradeType = orderContext.getTradeType(); String orderRedisSet = String.format("account_order_gray_set_%s_%s", Objects.nonNull(orderEntity) ? orderEntity.getSelfType() : orderTradeType.getSelfType(), DateUtil.format(new Date(), "yyyy-MM-dd")); if (ZZGameRedisUtil.sismember(orderRedisSet, orderId.toString())){ return true; } if (newAccountOrderTradeSwitch){ return true; } Optional<OrderGrayConfig> grayConfigOptional = grayConfigList.stream().filter(c->c.getOrderType() == orderTradeType.getSelfType()).findFirst(); if (grayConfigOptional.isPresent()){ OrderGrayConfig grayConfig = grayConfigOptional.get(); if (Objects.nonNull(grayConfig.getIsTotalGray()) && grayConfig.getIsTotalGray()){ return true; } if (orderContext.getOrderStatus() != AccountOrderStatusEnum.place_order){//只處理新訂單 return false; } String dayNumKey = String.format(NEW_ORDER_PROCESS_GRAY_NUM, DateUtil.format(new Date(), "yyyy-MM-dd"), orderTradeType.getSelfType()); if (NumberUtils.toInt(ZZGameRedisUtil.get(dayNumKey)) < grayConfig.getDayNum()){ int result = gameGrayTestService.insertNewTradeProcessOrder(orderId); log.info("{} desc=insert_gray_order_data orderId={} result={}", logStr, orderId, result); if (result > 0){ ZZGameRedisUtil.increAndGet(dayNumKey, 1); ZZGameRedisUtil.expire(dayNumKey, 3600*24); ZZGameRedisUtil.sadd(orderRedisSet, orderId.toString()); ZZGameRedisUtil.expire(orderRedisSet, 3600*24); } return result >= 0; } return false; } } catch (Exception e) { log.error("{} desc=isNewOrderProcess_error orderId={}", orderContext.getLogStr(), orderContext.getOrderId(), e); } return false;}
在一些重要的節(jié)點設(shè)置告警機(jī)制,比如上傳賬密、發(fā)貨、提現(xiàn)等節(jié)點出現(xiàn)異常時會發(fā)送企業(yè)微信告警通知,可以第一時間關(guān)閉灰度,查找問題。
不過對于分帳正確性保障這塊只是通過測試確保正確,這種最好是可以接入中臺的BCP(Business Check Platform)系統(tǒng)。它是一種標(biāo)準(zhǔn)化數(shù)據(jù)校對平臺,支持標(biāo)準(zhǔn)化數(shù)據(jù)源接入,基于事件觸發(fā)規(guī)則執(zhí)行,進(jìn)行業(yè)務(wù)數(shù)據(jù)校對,可以及時快速的發(fā)現(xiàn)業(yè)務(wù)異常數(shù)據(jù)并實時告警。
在對訂單流程進(jìn)行重構(gòu)之后,新增或修改某種訂單模式,只需增改相應(yīng)的訂單類型處理類就可以了,也不用擔(dān)心本次修改會影響到其它的訂單模式,大大提高了開發(fā)效率。此外,重構(gòu)代碼可以幫助我們進(jìn)一步深入了解整個業(yè)務(wù)流程,發(fā)現(xiàn)代碼的壞味道,提升代碼結(jié)構(gòu)設(shè)計能力。
本文鏈接:http://www.www897cc.com/showinfo-26-6161-0.html轉(zhuǎn)轉(zhuǎn)游戲的賬號訂單流程重構(gòu)之路
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com