侧边栏壁纸
  • 累计撰写 39 篇文章
  • 累计创建 1 个标签
  • 累计收到 3 条评论
标签搜索

【深入浅出-行业分享】(1):SP多渠道并行方案

mousycoder
2019-06-01 / 0 评论 / 0 点赞 / 127 阅读 / 8,705 字
温馨提示:
本文最后更新于 2022-01-20,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

现状

  1. 每种业务均有可能大于1个渠道。
  2. 每个渠道均有一些限制条件,例如:省份屏蔽,且不可及时预知。
  3. 每个业务的渠道,可能会有人工定制优先级的需求。

目的

通过手机号码归属地找到最合适的通道,提高用户的消费转化率。

省份

1、河北省 2、山西省 3、辽宁省 4、吉林省 5、黑龙江省 6、江苏省 7、浙江省 8、安徽省 9、福建省 10、江西省 11、山东省 12、河南省 13、湖北省 14、湖南省 15、广东省 16、海南省 17、四川省 18、贵州省 19、云南省 20、陕西省 21、甘肃省 22、青海省 23、台湾省

OO流程

如果 count(sp_id) = 0

1.返回不支持该类型

**如果count(sp_id) > 1 **

  1. 按照优先级顺序轮询available_id,调用口扣款接口,轮询次数 <N ,调用扣费接口,记录available_id,create_time,status,如果有成功扣款,则返回扣款成功,如果均失败,则返回不支持该类型。

自动隔离机制

  1. 在 x min内,该available_id 失败次数 > N,把该available_id的in_use置为0
  2. 在 x min内,该available_id 接口超时次数 > M ,把该available_id的in_use置为0

手动隔离机制

  1. 业务调整等其他原因,人工把该available_id 的in_use置为0

自动恢复机制

  1. 每隔一个小时,让一部分流量切到available_id状态为in_use = 0上,如果 x min available_id 的成功次数大于 N次,则把该availe_id的in_use置为1。

手动恢复机制
不需要,人工干预恢复,人工只能干预隔离。

通过spark实时统计,把满足规则的available_id 通过mq 给监控程序,让监控程序处理。

HJ流程

直接轮询所有可能性渠道。

特点

  1. 不是所有的渠道都提供专门的手机号是否能下单的接口。
  2. 事后,标记省份屏蔽的订单,来提高订单率,属于被动型策略,每个时期,省份屏蔽的名单也在变化,不利于以后维护统计。
  3. 轮询次数多的话,影响效率。

区别

  1. 事前防御VS事后防御。
  2. 前者轮询是固定的值,是为了拿真实的一部分流量嗅探接口的可用性;后者随着可用渠道接口增加,轮询数加大。

是否支持省份屏蔽告知功能

渠道是否支持现在情况错误码
优易付支持预下单20940
空中网不支持目前内部通知待提供相应的接口
小沃支持错误码待定暂无
赞成不支持目前没有屏蔽省份错误码待确认
爱动漫不支持待提供暂无
翼支付支持提供010061

渠道列表

  1. 移动包月 S1

优易付 AS

  1. 移动点播 S2

优易付 AT

空中网 H1

  1. 联通包月 S3

小沃 UP

  1. 联通点播 S4

赞成 LT

  1. 电信包月 S5

爱动漫 TP

优易付 A4

翼支付 Y1

  1. 电信点播 S6

优易付 AR
翼支付 Y2

方案

方案一:统一处理法。

  1. 设置几个支付类型 S1 S2 S3 S4 S5 S6 含义见上面。
  2. 抽象新的command 结构为:verifyParams doRequest saveOrder dealTemplate
  3. verifyParams 校验公共参数 doRequest 调用工具类得到请求结果以及实际的paytype saveOrder 根据实际的类型,插入实际的渠道表 dealTemplate 根据实际类型处理

工具类 :
方法:getRealPayType
输入:公共请求参数 commonparam包含paytype
输出:请求结果

{"status":1,"code":"success200","msg":"成功","realpaytype":"AS"}

{"status":0,"code":"错误码","msg":"失败","realpaytype":"AR"}

逻辑:

  1. 统一校验参数。
  2. 根据paytype,查找可用的url,按优先级顺序轮询请求url,只有当请求失败且错误码为省被屏蔽的情况下,才轮询。

注意点:

  1. 工具类方法并发同步控制。
  2. 请求第三方接口超时处理。

表设计:

paytype

  1. 移动包月 S1

  2. 移动点播 S2

  3. 联通包月 S3

  4. 联通点播 S4

  5. 电信包月 S5

  6. 电信点播 S6

sp

idsp_nameurlin_usecreate_timeupdate_time
1优易付http://113.31.25.56:23000/sdkfee/api2/create_order12016-06-24 19:25:332016-06-24 19:25:33
2空中网http://113.31.25.56:23000/sdkfee/api2/create_order12016-06-24 19:25:332016-06-24 19:25:33
3小沃http://113.31.25.56:23000/sdkfee/api2/create_order12016-06-24 19:25:332016-06-24 19:25:33
4赞成http://113.31.25.56:23000/sdkfee/api2/create_order12016-06-24 19:25:332016-06-24 19:25:33
5爱动漫http://113.31.25.56:23000/sdkfee/api2/create_order12016-06-24 19:25:332016-06-24 19:25:33
6翼支付http://113.31.25.56:23000/sdkfee/api2/create_order12016-06-24 19:25:332016-06-24 19:25:33

available_sp

idsp_idsp_namepaytypeprovince_codepriorityin_usemodecreate_timeupdate_time
11优易付S111000011Manual2016-06-24 19:25:332016-06-24 19:25:33
21优易付S212000011Manual2016-06-24 19:25:332016-06-24 19:25:33
32空中网S113000021Manual2016-06-24 19:25:332016-06-24 19:25:33

总结:

  1. 改动量比较大,涉及到重构公共模板command,相对应的这些渠道都要重构。
  2. 公共参数不容易校验,不同第三方sp的接口定义不一样,有一些字段可用,有一些不可用。
  3. 难统一不同渠道的返回值,又需要重构定义,前端也要改变。
  4. 还需要配合监控程序去修改available_sp表,系统复杂度比较高。

方案二:提前预判法

  1. 建立一个分发Command,当虚拟类型S1,S2,S3,S4,S5,S6,过来的时候,先提前调用工具类,确定实际支付方式,然后分发到正确的Command,此时Command,不需要再去请求第三方接口,只用处理后续逻辑即可。
  2. command 结构: verifyParams doProcess saveOrder dealTemplate

总结:

  1. 与方案一相比,改动量较小一点,但是相关渠道也要重构。

方案三:事后处理法

  1. 根据策略选择(排除掉最近已经失败N次的且优先级最高的paytype,如果没有直接返回错误)的command。
  2. 每个渠道在请求第三方处理结果的时候,专门处理错误码为省不支持的,记录到表里。

表设计:

sp

|id|real_pay_type|priority|pay_type|create_time|update_time|
|--|--|--|--|--|--|--|--|--|--|--|
|1|AT|1|S2|2016-06-24 19:25:33|2016-06-24 19:25:33|
|2|H1|2|S2|2016-06-24 19:25:33|2016-06-24 19:25:33|

sp_fail

|id|province_code|real_pay_type|create_time|update_time|
|--|--|--|--|
|1|110000|AT|2016-06-24 19:25:33|2016-06-24 19:25:33|
|2|100000|H1|2016-06-24 19:25:33|2016-06-24 19:25:33|

总结:

  1. 改动量比较小,只需要在command前加一层分发,专门处理S1,S2 类型。
  2. 不需要额外的监控程序去更新可用规则表。
  3. 看似简单,实则包含自动隔离以及自动恢复机制,只需要控制最近时间的粒度即可。

方案四:产品流程法(建议)

  1. 给前端提供一个接口(入参:mobile,pay_type 返回real_pay_type),获得真实的支付类型,如果没有可用的支付类型,直接返回不支持,前端就不用调真正的下单接口了;如果有的话,则还是按以前的方式下单即可,可以考虑整合在发验证码接口里,或者点发验证码接口时候调用这个独立的接口。
  2. 获得真实的支付类型原理同方案三。

总结:

  1. 无论前端还是后台,改动量都最小,最适合,甚至为了减轻这个接口的压力,可以考虑缓存一段时间,前端,后端缓存都行。

方案五:等待您的新想法

思考

  1. 如果优先级程序自动根据规则排序的话,可以考虑实现一个优先级队列。

部分代码关于请求第三方原有处理

比较乱、杂,难以统一。

        Map<String, String> result = ArSoftChannelUtils.getResultParams(response);
        if ("0".equals(result.get("status"))) {
            String orderid = result.get("orderid");
            logger.info("xunleiPayId:{} get payed orderid that is {}",
                        request.getOrderId(), orderid);
            channelData.setOrderId(orderid);
            if (MONTHLY_TYPE.equals(getIsMonthly())) {
                //                PayOrder payOrder = payOrderDAO.getPayOrder(unitedPayRequest.getXunleiPayId());
                //                //包月请求建立签约请求
                //                createContactRequest(payOrder);
            }
            saveOrder(channelData);
        } else {
            String status = result.get("status");
            String msg = result.get("msg");
            Template errorTemplate = getErrorTemplate(request, status, msg);
            channelData.setBackTemplate(errorTemplate);
        }



           ExtUniMonthPayResponse response = ExtUniMonthPayUtil.sendIdentifyingCode(payRequest);

            // 解析沃商店下行短信接口响应
            String status = response.getStatus();
            String woorderid = response.getWoorderid();
            Extunimonthpay extunimonthpay = new Extunimonthpay();
            extunimonthpay.setOrderid(request.getXunleipayid());
            extunimonthpay = facade.findExtunimonthpay(extunimonthpay);
            if ("00000".equals(status)) {
                if (null == woorderid || "".equals(woorderid)) {
                    LOG.error("orderid:{},get woorderid is empty!", orderid);
                    throw new Exception("woorderid is empty!");
                }
                // 短信下发成功,将woorderid保存入库
                extunimonthpay.setWoorderid(response.getWoorderid());
                facade.updateExtunimonthpay(extunimonthpay);
                Template resTemplate = getSuccessTemplate(request, orderid);
                channelData.setTemplate(resTemplate);
                return;
            } else {
                // 短信下发失败,订单直接就置为失败了
                extunimonthpay.setOrderstatus("F");// 设置为失败
                extunimonthpay.setErrcode(status);
                extunimonthpay.setErrmsg(ExtUniMonthPayResponseDesc.getDesc(status));
                facade.updateExtunimonthpay(extunimonthpay);

                updateBizorderStatus(request.getXunleipayid(), status, PayProxyFunctionConstant.PAY_STATUS_FAIL);// 支付失败

                LOG.error("orderid:{},queryIdentifyingCode fail!status:{}", new Object[] { orderid, status });
                Template template = getFailTemplate(request, PayproxyRtnCode.ERROR_UNDEFINE_ERROR, ExtUniMonthPayResponseDesc.getDesc(status));
                channelData.setBackTemplate(template);
                return;
            }



                String resp = HttpGetAndPostSender.sendPost(ExtumpAuthorityUtil.getUrl(), params.toString());
            LOG.info("=============response  is[{}]=============", resp);
            LOG.info(resp);
            resp = resp.replace(" ", "");//去空格
            LOG.info(resp);
            LOG.info("=============response  is[{}]=============", resp);
            if(resp.contains("OK")) {
                Map<String,Object> map = new TreeMap<String,Object>();
                map.put("pid", ExtumpAuthorityUtil.getPid());
                map.put("svcid", ExtumpAuthorityUtil.getSvcid());
                map.put("paymentUser", mobile);
                map.put("sign", ExtunionmobilepayUtil.createToken(map,ExtumpAuthorityUtil.getKey()));
                String response = HttpClientUtil.doGet(ExtumpAuthorityUtil.getFee_url(), map, null);
                LOG.info("=============fee response  is[{}]=============", response);
                Map<String,String> result = ExtunionmobilepayUtil.json2map(response);
                LOG.info("=============get params [{}]============="+result.toString());
                String resultCode = result.get("resultCode");
                String outTradeNo = result.get("outTradeNo");
                extunionmobilepayChannelData.setOutTradeNo(outTradeNo);
                LOG.info("=============resultCode  is[{}]============="+resultCode);
                if(!"0".equals(resultCode)) {
                    if(resultCode == null) {
                        String errorCode = result.get("errorCode");
                        String errorDesc = result.get("errorDesc");
                        if(errorCode != null) {
                            throw new ExtumpException(errorDesc, errorCode);
                        } else {
                            throw new ExtumpException("未知错误", ExtumpResCode.RTN99.getCode());
                        }
                    } else {
                        String resultDescription = result.get("resultDescription");
                        throw new ExtumpException(resultDescription, resultCode);
                    }
                }
            } else {
                // 没有通过梦网的号码检测
                String errMsg = "according to WowCheck,mobile[" + mobile + "] was not allowed to order!Errmg is :[" + resp + "]";
                throw new ExtumpException(errMsg, ExtumpResCode.RTN1004.getCode());
            } 




               String mobile = data.getOrderInfo().getMobile();
        String rescode = data.getOrderInfo().getResCode();
        if (rescode.equals(MobileResCode.SUCCESS)) {// 请求爱动漫下发短信成功
            Template resTemplate = getSuccessTemplate(request, orderid, mobile);
            channelData.setTemplate(resTemplate);
            return;
        } else if (rescode.equals(MobileResCode.TOKEN_FAILED)) {// 获取token失败
            LOG.error("orderId:{},resCode:{}", orderid,rescode);
            Template template = getFailTemplate(request, TeleMonthlyResCode.SYSTEM_ERROR, "get token failed");
            channelData.setBackTemplate(template);
            return;
        } else if (rescode.equals(MobileResCode.SMS_FAILED)) {// 获取token正常 但是下行短信出错
            LOG.error("orderId:{},resCode:{}", orderid,rescode);
            Template template = getFailTemplate(request, TeleMonthlyResCode.SYSTEM_ERROR, "sms captcher failed");
            channelData.setBackTemplate(template);
            return;
        } else {// 其他未知错误
            LOG.error("orderId:{},resCode:{}", orderid,rescode);
            Template template = getFailTemplate(request, TeleMonthlyResCode.UNDEFINE_ERROR, "sms captcher failed unknown error");
            channelData.setBackTemplate(template);
            return;
        }
0

评论区