最后更新于 .

发现自己经常会一篇文章写了(一)之后,很久都不写(二),搞得最后自己都快要忘记了,所以这次赶紧把统一支付的文章给补上。

上次的文章中将统一支付的v1版本已经讲解ok了,但是还剩下两个问题:

  • 服务器端没有办法做分布式
  • 客户端对支付sdk进行插件式管理十分困难

 

我们一个个来说

一. 解决服务器端分布式的问题

解决这个问题的核心思路比较简单:

之前我们是把event的通知放在进程内存中,现在我们做成网络通信

由于支付的请求量本身不属于高并发,所以就放弃了打算直接写通知server的想法,转而看一下有没有什么简单的解决方案。

而由于自己之前redis的使用经历,恰好知道redis有一个pubsub模式,很适合做这种监听和通知的工作。

python的实现示例代码如下:

import time
import config
from share.vals import rds
from share.utils import safe_str
from gevent.timeout import Timeout
from urllib import quote, unquote

class RedisPubSub(object):
    """
    用redis订阅/发布消息
    """
    # 订阅频道
    sub_key = None
    pub_sub = None
    channel = None
    __prefix = 'unity_pay:%s'

    def __init__(self, channel):
        self.channel = channel
        self.sub_key = self.get_sub_key(self.channel)

    def __del__(self):
        self.unsubscribe()

    def subscribe(self):
        if self.pub_sub:
            return
        self.pub_sub = rds.pubsub()
        self.pub_sub.subscribe(self.sub_key)

    def unsubscribe(self):
        if not self.pub_sub:
            return
        self.pub_sub.unsubscribe()
        self.pub_sub = None

    def set(self, *args):
        """
        设置订阅消息
        """
        rds.publish(self.sub_key, self.format(*args))

    def get(self, timeout=config.BILL_RESULT_TIMEOUT):
        """
        获取订阅消息
        """
        self.subscribe()

        stamp = time.time() + timeout
        while time.time() < stamp:
            message = self.pub_sub.get_message(True)
            if message and message['type'] == 'message':
                return self.parse(message.get('data', ''))
            time.sleep(1)

        raise Timeout

    @classmethod
    def format(cls, *args):
        return ','.join([quote(safe_str(arg)) for arg in args])

    @classmethod
    def parse(cls, message):
        args = message.split(',')

        return [unquote(arg) for arg in args]

    @classmethod
    def get_sub_key(cls, channel):
        return cls.__prefix % channel

 

代码很简单,就不多解释了。

这样做的坏处是redis会有热点问题,不过反正redis中也不存放数据,找台热备机随时能切换即可。

 

二. 客户端对支付sdk进行插件式管理十分困难

其实这个问题也不是很难,解决的关键是需要知道一个点:

jar包在编译的时候需要所有的类都存在,但是当程序调用这个jar包时,这个jar包有些类不存在,并不会崩溃,而是报可被捕获的异常

基于这一点,我们就可以做一个同一个工厂函数,将这个工厂函数类封装成一个jar包。

同时,我们对每一种支付方式,都封装出一个统一的接口,而工厂函数返回的即这样一个接口的实现。当某一种支付方式的封装类不存在时,就捕获这个异常,并返回NULL。

统一接口的代码如下:

public abstract class PaymentInterf {
	/**
	 * 初始化统一方法
	 * @param context 上下文
	 * @param parameters 初始化时需要的参数 数组
	 * 1、移动 parameters--》mmid 、mmkey
	 * 2、联通 	parameters:
	 * string appid 应用编号
	 * string cpCode 开发商编号
	 * string cpid 开发商VAC资质编号
	 * string company 开发者公司名字
	 * string phone 开发者客服电话号码
	 * string game 应用名称
	 * UnipayPayResultListener mCallBack 初始化函数回调结果(目前 只有联通多了一个非String类型的参数)
	 *
	 * 3、Amazon初始化
	 * suk
	 * callBack
	 *
	 */
	public abstract void init(Context context,Object...parameters);
	/**
	 * 支付统一函数
	 * @param context 上下文
	 * @param parameters 支付时需要传递的参数  如 payCode billId 。。。
	 */
	public abstract void pay(Context context,Object...parameters);
	
	public static void billDeliver(String appKey, String billID) {
		HttpLobbiesService.g().billDeliver(appKey, billID, new HttpLobbiesService.Callback() {
			@Override
			public void onResult(ResultInfo info) {
				if (info != null && info.getRetCode() == 0) {
					Log.e("billDeliver", info.getRetCode() + "/:" + info.getObj());
				}
			}
		});
	}

}

工厂函数的代码如下:

public class PaymentFactoy {
    public static PaymentInterf producePay(int billTypy){
        try {
            switch (billTypy) {
            case Constant.BILL_TYPE_MMCNCCPAY:
                return MMPayInstance.getInstance();
            case Constant.BILL_TYPE_DIANXIN:
                return CTEStoreInstance.getInstance();
            case Constant.BILL_TYPE_UNIPAY:
                return UnicomInstance.getInstance();
            case Constant.BILL_TYPE_TAOBAO:
                return TaoBaoInstance.getInstance();
            case Constant.BILL_TYPE_WEIPAY:
                return WeiPayInstance.getInstance();
            case Constant.BILL_TYPE_WIMI:
                return WeiMiInstance.getInstance();
            default:
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return null;
    };
}

 

上面的方法是只封装了一个factory函数的jar包,其他的对每种支付的封装还是走源码的方式。

其实最早的时候,我是想将每种支付的封装也做成jar的方式,后来公司同事做成了现在的这种方式,我考虑了一下,可能确实用源码的方式更好,原因如下:

  • 源码的方式,方便调试
  • 源码的方式,当编译进游戏的时候,如果某种支付忘记引入对应的jar包,会直接报错提醒
  • 每个支付方式一个jar包的话,维护成本过高

总体差不多就是这样。

Pingbacks

Pingbacks已关闭。

评论

  1. Moses.Fu

    Moses.Fu on #

    nice essay,对我的android开发提供了很多值得借鉴的地方

    Reply

  2. jamesduan

    jamesduan on #

    大神,我是一名python初学者,我想问一下你上面写的代码中,@classmethod下面的代码和普通的方法有什么区别吗?

    Reply

    1. 朱念洋

      朱念洋 on #

      class Foo(object): passclassmethod 可以 Foo.func(),即第一个参数是 cls 而不是 self。

      Reply

  3. 歪妖内涵网

    歪妖内涵网 on #

    太厉害啦!值得我们学习

    Reply

发表评论