当前位置:首页 > 科技  > 软件

转转游戏的账号订单流程重构之路

来源: 责编: 时间:2023-08-20 23:16:42 636观看
导读1、背景随着需求的不断迭代,项目代码的复杂度也会越来越高,“屎山”也一天一天慢慢的堆积起来,对于游戏业务的账号订单流程也是如此。游戏订单类型由原来的俩种增加到了现在的七种,早就已经到了需要重构的地步。但是由于

图片FjT28资讯网——每日最新资讯28at.com

既然有七种订单类型,这好办啊。可以采用策略+模板模式啊,一个抽象模板+七个子类就可以啦。但是后来仔细一想,如果将所有的处理逻辑都放在父类和子类当中,其实代码整体也显得十分臃肿。FjT28资讯网——每日最新资讯28at.com

为了想出更好的解决方案,于是对原有代码和业务流程进行了深入的梳理和总结,主要有以下几点:FjT28资讯网——每日最新资讯28at.com

  1. 所有订单流程都是在客服发货和自主发货基础上衍生出来的。
  2. 所有订单流程都包含下单、支付、上传账密、发货、确认收货等节点。
  3. 在这些节点里不同订单类型大多会有各自一些特定操作,但是这些操作其实并不属于订单的主流程。

通过以上分析,是不是可以将下单到确认收货作为一层,将不同订单类型的特定处理实现作为一层呢?这样不就将订单流程中各种特殊处理从订单主流程剥离开了吗,因此最终决定采用三层接口+策略模板的设计方案。FjT28资讯网——每日最新资讯28at.com

2.2 三层接口+策略模板模式

接口设计如下:FjT28资讯网——每日最新资讯28at.com

图片FjT28资讯网——每日最新资讯28at.com

  • 第一层接口

包含前端用户进行交互、处理mq消息以及给其它服务调用的接口。FjT28资讯网——每日最新资讯28at.com

  • 第二层接口

订单核心主流程能力接口。将下单、支付到确认收货等“不变”的基础能力提供给顶层接口调用,这层接口有自主发货流程和客服发货流程两个实现类。FjT28资讯网——每日最新资讯28at.com

public interface IGameAccountOrderDealProcess {    /**     * 处理下单未支付订单     */    int handlePlaceOrder(GameAccountOrderContext orderContext) throws Exception;    /**     * 处理支付成功订单     */    int handlePaySuccessOrder(GameAccountOrderContext orderContext) throws Exception;    /**     * 处理已发货订单     */    int handleDeliverOrder(GameAccountOrderContext orderContext) throws Exception;    /**     * 处理支付前取消订单     */    int handleCancelBeforePayOrder(GameAccountOrderContext orderContext) throws Exception;    /**     * 处理支付后取消订单     */    int handleCancelAfterPayOrder(GameAccountOrderContext orderContext) throws Exception;    /**     * 处理交易成功订单     */    int handleConfirmReceiptOrder(GameAccountOrderContext orderContext) throws Exception;    /**     * 账号交易窗数据     */    <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;    /**     * 发货     * @param orderContext     */    boolean deliverOrder(GameAccountOrderContext orderContext) throws Exception;    /**     * 订单确认收货     */    ZZOpenScfBaseResult<String> confirmReceiptOrder(GameAccountOrderContext orderContext, Long uid, boolean needCheckRisk) throws Exception;}
  • 第三层接口

各种订单类型的特殊处理,每一种订单模式都对应一个实现类。FjT28资讯网——每日最新资讯28at.com

public interface ITradeSelfHandler {    GameAccountTradeFlow.GameAccountTradeType getOrderTrade();    /*------------处理mq消息相关---------------*/    /**     *1.插入表之前设置客服和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);    /**     * 确认收货处理     */    int handleAfterConfirmReceipt(GameAccountOrderContext orderContext) throws Exception;    /*---------------------------------*/    /**     * 获取提现时间     */    Date getWithDrawlTime();    /**     * 发送支付成功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;    /**     * 确认收货后一些处理     */    void otherOperationAfterReceipt(GameAccountOrderContext orderContext, Long uid);}

2.3 具体实现

  • 核心代码收拢到一个服务,相关接口进行聚合

原先在客服后台、定时任务、mq集群都有一些订单的操作,但是这些代码基本都是重复的,所以此次重构在订单核心服务中新增相应的订单操作功能,统一由其它服务进行RPC调用。FjT28资讯网——每日最新资讯28at.com

图片FjT28资讯网——每日最新资讯28at.com

将订单相关的接口、工具类集中到同一个包下,方便定位。FjT28资讯网——每日最新资讯28at.com

图片FjT28资讯网——每日最新资讯28at.com

  • 整体类图及设计原则图片
  1. 命名规范:类名、变量名、方法名尽量见名知义。
  2. 单一职责:各个模块各司其职,避免与其它模块过度耦合。
  3. 准备订单上下文,清除RPC重复调用问题。
//上下文实体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;//对应订单spi状态}//上下文准备GameAccountOrderContext orderContext = orderContextBuilder.buildAccountOrderContext(order, zzProduct, logStr);

3、上线保障

订单流程不管对于什么业务,基本都是最重要的一个环节,为了避免产生重大问题,需要做到以下两点:FjT28资讯网——每日最新资讯28at.com

  1. 严格保证线下测试的准确性。
  2. 出现线上问题,影响范围要尽可能小。

3.1 流程测试

根据账号订单流程的特点,在测试的时候遵循以下原则:FjT28资讯网——每日最新资讯28at.com

  • 订单流程正常跑通
  • 订单分帐正确
  • 订单保险正常
  • 各个节点与原来保持一致
  • 相关push、私信正常发送
  • 统计日志正常打印

对于每一种订单流程,同时进行新、老流程订单的测试。逐一对比新、老流程的买家侧和卖家侧各个流程节点的页面、按钮、跳转、push、私信等是否保持一致。FjT28资讯网——每日最新资讯28at.com

3.2 灰度策略

为了避免产生重大问题,上线后必须采取灰度策略,不然出了问题就可能就是事故了。本次采用的灰度策略是上线后按订单类型、订单量进行灰度,同时将灰度订单落表记录,配置如下:FjT28资讯网——每日最新资讯28at.com

[  {    "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;}

3.3 异常机制

在一些重要的节点设置告警机制,比如上传账密、发货、提现等节点出现异常时会发送企业微信告警通知,可以第一时间关闭灰度,查找问题。FjT28资讯网——每日最新资讯28at.com

图片FjT28资讯网——每日最新资讯28at.com

不过对于分帐正确性保障这块只是通过测试确保正确,这种最好是可以接入中台的BCP(Business Check Platform)系统。它是一种标准化数据校对平台,支持标准化数据源接入,基于事件触发规则执行,进行业务数据校对,可以及时快速的发现业务异常数据并实时告警。FjT28资讯网——每日最新资讯28at.com

4 总结

在对订单流程进行重构之后,新增或修改某种订单模式,只需增改相应的订单类型处理类就可以了,也不用担心本次修改会影响到其它的订单模式,大大提高了开发效率。此外,重构代码可以帮助我们进一步深入了解整个业务流程,发现代码的坏味道,提升代码结构设计能力。FjT28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-6161-0.html转转游戏的账号订单流程重构之路

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 谷歌的Project IDX会扼杀其他应用程序开发框架吗?

下一篇: 基于模块联邦与大仓模式的商家巨石应用拆分实践

标签:
  • 热门焦点
  • K60 Pro官方停产 第三方瞬间涨价

    虽然没有官方宣布,但Redmi的一些高管也已经透露了,Redmi K60 Pro已经停产且不会补货,这一切都是为了即将到来的K60 Ultra铺路,属于厂家的正常操作。但有意思的是该机在停产之后
  • 6月安卓手机性能榜:vivo/iQOO霸占旗舰排行榜前三

    2023年上半年已经正式过去了,我们也迎来了安兔兔V10版本,在新的骁龙8Gen3和天玑9300发布之前,性能榜的榜单大体会以骁龙8Gen2和天玑9200+为主,至于那颗3.36GHz的骁龙8Gen2领先
  • Rust中的高吞吐量流处理

    作者 | Noz编译 | 王瑞平本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库,还使用这些库实现了一个流处理程序
  • 三言两语说透柯里化和反柯里化

    JavaScript中的柯里化(Currying)和反柯里化(Uncurrying)是两种很有用的技术,可以帮助我们写出更加优雅、泛用的函数。本文将首先介绍柯里化和反柯里化的概念、实现原理和应用
  • Temu起诉SHEIN,跨境电商战事升级

    来源 | 伯虎财经(bohuFN)作者 | 陈平安日前据外媒报道,拼多多旗下跨境电商平台Temu正对竞争对手SHEIN提起新诉讼,诉状称Shein&ldquo;利用市场支配力量强迫服装厂商与之签订独家
  • 腾讯盖楼,字节拆墙

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之&ldquo;想重温暴刷深渊、30+技能搭配暴搓到爽的游戏体验吗?一起上晶核,即刻暴打!&rdquo;曾凭借直播腾讯旗下代理格斗游戏《DNF》一
  • 到手价3099元起!iQOO Neo8 Pro今日首销:安卓性能最强旗舰

    5月23日,iQOO如期举行了新品发布会,全新的iQOO Neo8系列也正式与大家见面,包含iQOO Neo8和iQOO Neo8 Pro两个版本,其中标准版搭载高通骁龙8+,而Pro版更
  • 联想小新Pad Pro 12.6将要推出,搭载高通骁龙 870 处理器

    联想小新Pad Pro 12.6将于秋季新品会上推出,官方按照惯例直接在发布会前给出了机型的所有参数。联想小新 Pad Pro 12.6 将搭载高通骁龙 870 处理器,重量为 5
  • DRAM存储器10月价格下跌,NAND闪存本月价格与上月持平

    10月30日,据韩国媒体消息,自今年年初以来一直在上涨的 DRAM 存储器的交易价格仅在本月就下跌了近 10%,此次是全年首次降价,而NAND 闪存本月价格与上月持平。市
Top