教你快速高效接入SDK——统一sdk服务器的实现

U8Server是采用J2EE框架SSH2实现的。当然你也可以采用任何其他你熟悉的语言实现。上一篇文章中,我们从类的抽象中,大致梳理了一下U8Server所要完成的工作。大的方向上,U8Server需要实现统一的用户登录认证,和支付中心两大功能。更深入的细节,U8Server需要完成以下功能:

游戏管理:查询游戏,创建游戏(同时生成游戏的唯一appID,appKey等),编辑游戏,删除游戏等功能。

渠道商管理:查询渠道商,创建渠道商(设置该渠道商对应的登陆认证地址,支付回调地址等操作。),编辑渠道商,删除渠道商等功能。

渠道管理:查询某个游戏的渠道,创建渠道(将指定的游戏和一个渠道商连接起来,同时生成渠道ID,配置该游戏对应该渠道的appID,appKey等信息),编辑渠道,删除渠道。

登陆认证:完成各个游戏各个渠道的统一登陆认证功能

支付回调:支付之前获取订单号。所有第三方SDK的支付回调,然后将第三方支付回调以统一的格式返回给游戏服务器

对于游戏管理,渠道商管理,渠道管理三个功能,可以做成一个后台管理界面,通过后台操作,实现各个功能的管理。就是简单的数据库查询和数据增删改查功能。本文主要介绍统一的登陆认证和支付两个部分。

对于登陆认证,通过之前的流程图我们知道。所有渠道SDK登陆成功之后,都会返回sid等信息,客户端紧接着拿着这个sid去U8Server进行登录认证,U8Server再去第三方SDK服务器认证,认证成功之后,U8Server会生成一条用户信息,然后将生成一个token给客户端。客户端拿着这个token,登陆游戏服务器,游戏服务器再拿着这个token去U8Server进行认证。如果合法,则用户成功登陆。

那么,这样我们的登陆认证接口就有了,请看代码:

    @Action("getToken")
    public void getLoginToken(){

        try{

            Log.i("The appID is "+this.appID);
            UGame game = gameManager.queryGame(this.appID);
            if(game == null){
                renderState(StateCode.CODE_GAME_NONE, null);
                return;
            }

            UChannel channel = channelManager.queryChannel(this.channelID);
            if(channel == null){

                renderState(StateCode.CODE_CHANNEL_NONE, null);
                return;
            }

            UChannelMaster master = channel.getMaster();
            if(master == null){
                renderState(StateCode.CODE_CHANNEL_NONE, null);
                return;
            }

            ISDKVerifier verifier = Class.forName(master.verifyClass)
            SDKVerifyResult sdkResult = verifier.verify(channel, sid, extension);

            if(sdkResult.isSuccess()){

                UUser user = userManager.getUserByCpID(appID, channelID, sdkResult.getUserID());

                if(user == null){
                    user = userManager.generateUser(channel, sdkResult);
                }else{
                    user.setLastLoginTime(new Date());
                }

                user.setToken(UGenerator.generateToken(user));
                userManager.saveUser(user);

                JSONObject data = new JSONObject();
                data.put("userID", user.getId());
                data.put("token", user.getToken());
                renderState(StateCode.CODE_AUTH_SUCCESS, data);

            }else{
                renderState(StateCode.CODE_AUTH_FAILED, null);
            }

        }catch (Exception e){
            Log.e(e.getMessage());
            renderState(StateCode.CODE_AUTH_FAILED, null);
        }
    }

这个请求处理方法getToken就是第一步,客户端拿到sid时,发起getToken请求。访问getToken时,客户端带上,appId,channelId,sid等数据。

注意这里的appID,channelId是U8Server之前通过后台分配该该游戏的。

在这个方法中,通过appID,查询到当前是哪个游戏,通过channelID查询到当前是哪个渠道。因为各个渠道的登陆认证方式不同,所以,我们抽象出来一个接口:ISDKVerifier 。各个渠道SDK的登陆认证类实现该接口。然后将实现类的完整类名,存储在UChannelMaster对象中。

这样,通过渠道信息,我们就知道当前的SDK登陆认证实现类的完整类名,并进行实例化。然后登陆认证之后,返回一个认证结果。

如果认证成功,我们生成一条用户信息,同时生成一个token。然后将userID和该token返回给客户端。

这样,整个登陆认证过程中,第三方SDK的认证部分就结束了。接下来的流程仅仅是在U8Server和游戏服务器之前了。这就是当客户端拿到这个userID和token信息时,客户端就开始登陆游戏服务器。游戏服务器需要拿着这个userID和token去U8Server进行二次认证。看接口:

    @Action("verifyAccount")
    public void loginVerify(){

        try{

            UUser user = userManager.getUser(this.userID);
            if(user == null){

                renderState(StateCode.CODE_USER_NONE, null);
                return;
            }

            if(StringUtils.isEmpty(this.token)){
                renderState(StateCode.CODE_VERIFY_FAILED, null);
                return;
            }

            long now = System.currentTimeMillis();
            if(!this.userManager.isTokenValid(user, token)){
                renderState(StateCode.CODE_TOKEN_ERROR, null);
                return;
            }

            JSONObject data = new JSONObject();
            data.put("userID", user.getId());
            data.put("username", user.getName());

            renderState(StateCode.CODE_AUTH_SUCCESS, data);
            return;

        }catch (Exception e){
            Log.e(e.getMessage());
        }

        renderState(StateCode.CODE_VERIFY_FAILED, null);
    }

服务器拿着userID和token来U8Server进行认证。U8Server验证token是否合法和是否超时,如果验证通过,则返回userID,username等用户信息给游戏服务器。否则,其他情况,均返回失败信息。

整个登陆认证,也就这两个接口。当接其他第三方SDK时,比如当乐,直接定义一个DLSDK同时实现ISDKVerifier接口,同时将该实现类,设置在当乐对应的UChannelMaster对象中。

接下来,我们再来看支付中心。支付中心之前也说了,也包括两个部分。首先是获取订单号。然后是支付通知回调。先看获取订单号接口:

    @Action("getOrderID")
    public void getOrderID(){

        try{

            UUser user = userManager.getUser(this.userID);

            if(user == null){
                renderState(StateCode.CODE_USER_NONE, null);
                return;
            }

            if(money < 0 ){
                renderState(StateCode.CODE_MONEY_ERROR, null);
                return;
            }

            UOrder order = orderManager.generateOrder(user, money, extension);

            if(order != null){
                JSONObject data = new JSONObject();
                data.put("orderID", order.getOrderID());
                renderState(StateCode.CODE_AUTH_SUCCESS, data);
                return;
            }

        }catch (Exception e){
            Log.e(e.getMessage());
        }

        renderState(StateCode.CODE_ORDER_ERROR, null);
    }

该接口很简单,就是客户端传过来当前的userID,充值金额,已经需要在充值通知回调原封不动返回的extension数据。然后,U8Server生成一条UOrder,同时将该order的状态设置为“正在充值状态”。然后,将这个订单号返回给客户端。

客户端拿到这个订单号时,就开始调用调用第三方SDK的充值接口,同时将该订单号作为第三方SDK的自定义参数传递到第三方SDK。然后第三方SDK充值完成时,会通知到U8Server.(这个就要游戏在申请接入第三方SDK的时候,将通知回调设置为U8Server对应该渠道的支付通知回调地址),U8Server验证后再讲结果通知给游戏服务器。

那么,U8Server承担了第三方SDK通知回调的功能,因为各个渠道的通知参数和请求方式等都不太一样,所以我们选择将各个渠道的通知回调实现分开。下面以UC渠道的支付通知回调为例:

@Controller
@Namespace("/uc")
public class UCPayCallbackAction extends UActionSupport{

    @Autowired
    private UOrderManager orderManager;

    @Action("payCallback")
    public void payCallback(){

        try{

            //BufferedReader br = new BufferedReader(new InputStreamReader(this.request.getInputStream(), "UTF-8"));
            BufferedReader br = this.request.getReader();
            String line;
            StringBuilder sb = new StringBuilder();
            while((line=br.readLine()) != null){
                sb.append(line).append("\r\n");
            }

            Log.d("UC Pay Callback . request params:" + sb.toString());

            PayCallbackResponse rsp = (PayCallbackResponse) JsonUtils.decodeJson(sb.toString(), PayCallbackResponse.class);

            if(rsp == null){
                this.renderState(false);
                return;
            }

            long orderID = Long.parseLong(rsp.getData().getCallbackInfo());
            UOrder order = orderManager.getOrder(orderID);

            if(order == null || order.getChannel() == null){
                Log.d("The order is null or the channel is null.");
                this.renderState(false);
                return;
            }

            UCSDK sdk = new UCSDK();

            if(!sdk.verifyPay(order.getChannel(), rsp)){
                Log.d("The sign is not matched.");
                this.renderState(false);
                return;
            }

            if("S".equals(rsp.getData().getOrderStatus())){
                float money = Float.parseFloat(rsp.getData().getAmount());
                int moneyInt = (int)(money * 100);  //以分为单位

                order.setMoney(moneyInt);
                order.setChannelOrderID(rsp.getData().getOrderId());
                order.setState(PayState.STATE_SUC);

                orderManager.saveOrder(order);

                Log.d("The channnel order is ok. sendToServer.");
                sendToServer(order);

            }else{
                order.setChannelOrderID(rsp.getData().getOrderId());
                order.setState(PayState.STATE_FAILED);
                orderManager.saveOrder(order);
            }

            renderState(true);

        }catch (Exception e){
            try{
                this.renderState(false);
            }catch (Exception e2){
                Log.e(e2.getMessage());
            }

            Log.e(e.getMessage());

        }

    }

    private boolean sendToServer(UOrder order){

        UGame game = order.getGame();
        if(game == null){
            return false;
        }

        if(StringUtils.isEmpty(game.getPayCallback())){

            return false;
        }

        JSONObject data = new JSONObject();
        data.put("orderID", order.getOrderID());
        data.put("userID", order.getUserID());
        data.put("gameID", order.getAppID());
        data.put("money", order.getMoney());
        data.put("currency", order.getCurrency());
        data.put("extension", order.getExtension());

        JSONObject response = new JSONObject();
        response.put("state", StateCode.CODE_AUTH_SUCCESS);
        response.put("data", data);
        response.put("sign", UGenerator.generateSign(order));

        UHttpAgent httpAgent = new UHttpAgent("text/html");
        String serverRes = httpAgent.post(game.getPayCallback(), response.toString());

        if(serverRes.equals("SUCCESS")){
            order.setState(PayState.STATE_COMPLETE);
            orderManager.saveOrder(order);
            return true;
        }

        return false;
    }

    private void renderState(boolean suc) throws IOException{

        String res = "SUCCESS";
        if(!suc){
            res = "FAILURE";
        }

        this.response.getWriter().write(res);
    }

}

可以看到,在payCallback方法中,我们解析了UC渠道支付回调的参数,然后进行验证,同时,通过取出自定义参数中的OrderID,从数据库中取出之前获取订单号时,生成的那条订单数据。

如果,验证合法,我们就会将该订单的状态置为成功状态。同时,将第三方渠道订单号保存到该订单中。

然后,我们就将该订单的数据和以该订单数据某种结合生成的md5校验码一起通知给该游戏的游戏服务器。(游戏服务器在接入U8Server时,就要求他们提供一个支付回调地址)如果,收到游戏服务器成功的通知,则将该订单的状态设置为“完成”。

到此,说明一次支付就完成了。

那这只是正常情况,如果出现错误或者异常情况,我们需要及时返回错误的信息。如果支付回调没有成功,U8Server可能还需要每隔一定得时间,再次尝试请求游戏服务器的回调地址。

那么,到这里,一个统一的登陆认证中心和支付中心的雏形已经有了。剩下来的事情,就是在实践中,不断改善不断重构不断稳定。。。

本文作者:小黑

欢迎访问小黑的博客哦:http://www.uustory.com

时间: 2024-10-24 22:28:25

教你快速高效接入SDK——统一sdk服务器的实现的相关文章

教你快速高效接入SDK——渠道SDK的接入(就是实现抽象层的接口而已)

题记:很多做游戏开发的人,估计都或多或少地接过渠道SDK,什么UC,当乐,91,小米,360......据统计国内市场当前不下于100家渠道,还包括一些没有SDK的小渠道.每个渠道SDK接入的方法呢,多是大同小异.但是,正是这些小异,又让SDK的接入,产生了无穷无尽的变数.所以,接入SDK之前,如果你没有经验,或者没有被SDK坑过,那么当你看到这系列文章的时候,你很幸运,你可以避免这一切了.如果你之前被坑过,而且还在继续被坑着,那么现在,就是你解脱的时刻. 先将之前的每一篇做个索引,方便亲们查阅

教你快速高效接入SDK——SDK接入抽象层的设计

题记:很多做游戏开发的人,估计都或多或少地接过渠道SDK,什么UC,当乐,91,小米,360......据统计国内市场当前不下于100家渠道,还包括一些没有SDK的小渠道.每个渠道SDK接入的方法呢,多是大同小异.但是,正是这些小异,又让SDK的接入,产生了无穷无尽的变数.所以,接入SDK之前,如果你没有经验,或者没有被SDK坑过,那么当你看到这系列文章的时候,你很幸运,你可以避免这一切了.如果你之前被坑过,而且还在继续被坑着,那么现在,就是你解脱的时刻. 上一篇文章,我们总体地分析并设计了一套

教你快速高效接入SDK——游戏接入SDK(只接入抽象框架)

题记:很多做游戏开发的人,估计都或多或少地接过渠道SDK,什么UC,当乐,91,小米,360......据统计国内市场当前不下于100家渠道,还包括一些没有SDK的小渠道.每个渠道SDK接入的方法呢,多是大同小异.但是,正是这些小异,又让SDK的接入,产生了无穷无尽的变数.所以,接入SDK之前,如果你没有经验,或者没有被SDK坑过,那么当你看到这系列文章的时候,你很幸运,你可以避免这一切了.如果你之前被坑过,而且还在继续被坑着,那么现在,就是你解脱的时刻. 上一篇文章我们说了整个U8 SDK抽象

教你快速高效接入SDK——总体思路和架构

题记:很多做游戏开发的人,估计都或多或少地接过渠道SDK,什么UC,当乐,91,小米,360......据统计国内市场当前不下于100家渠道,还包括一些没有SDK的小渠道.每个渠道SDK接入的方法呢,多是大同小异.但是,正是这些小异,又让SDK的接入,产生了无穷无尽的变数.所以,接入SDK之前,如果你没有经验,或者没有被SDK坑过,那么当你看到这系列文章的时候,你很幸运,你可以避免这一切了.如果你之前被坑过,而且还在继续被坑着,那么现在,就是你解脱的时刻. 完成一个SDK的接入并没有多少技术含量

教你快速高效接入SDK——Unity统一接入渠道SDK(Unity篇)

上一篇,我们着重讲解了用Unity开发的手游,在接入U8SDK时,Android中的部分.接下来,这一篇,我们就来看看,在Unity工程中,我们需要怎么调用上一篇中我们提供的各种接口,以及怎么接收Android中的消息通知,比如登录成功,切换帐号成功等. 在写代码之前,我们先要做一个简单的配置,在Project面板中,按照Unity官方的要求,新建一个Plugins文件夹,在Plugins文件夹下,再建立一个Android文件夹.这里,将会放置所有Android工程相关的文件. 1.将上一篇中,

教你快速高效接入SDK——服务器端支付回调的处理方式

今天着重把之前渠道服务器端SDK的时候,遇到的一个蛋疼的问题给解决了. 按照之前我们搭建U8Server的思路,U8Server是可以完美支持多款游戏的.包括登录认证和支付回调.登录认证,没啥好说的.统一的接口即可,和客户端的协议对应上.支付回调呢?各个渠道的支付回调逻辑,对于每款游戏来说都是一样的,然后每个游戏填写的登录回调地址,也都一样. U8Server采用J2EE成熟框架实现,各个游戏的各个渠道的参数,是配置在数据库中,通过后台管理工具来完成添加,修改,删掉等操作. 之前我们的流程,相信

教你快速高效接入SDK——U8Server的初步分析

在U8SDK整套框架总体架构那篇文章,我们就给出了服务器端的解决方案,为此,我们加入了一个U8Server,来作为U8SDK整套框架的服务器端的统一用户认证中心和支付中心.那么,为了方便,我们这里再来引用一下登陆认证的流程图: 回顾下我们之前的分析,U8Server作为统一的登陆认证中心,针对的是多款游戏,那么每款游戏在接入SDK之前,就需要向U8Server申请一个AppID以及AppKey.这样,申请的过程,也就是在U8 Server中加入一条该游戏的数据记录.接下来,游戏需要配置每一个第三

教你快速高效接入SDK——打包工具的实现(反编译资源动态整合打渠道包)

整套u8sdk的核心思想,就是让我们的SDK接入成本,能够控制在1,而不是N.啥意思呢?就是,我开发第一款游戏的时候,我接了这些SDK,当我开发第二款,第三款游戏的时候,这套SDK可以尽可能的复用,而不是每开发一款游戏,又从头去接各个渠道sdk.所有的游戏都能够使用同一套SDK接入框架,来快速完成SDK的接入工作.那么,在U8SDK里,我们怎么做到呢?请看我们总体的思路: 游戏A,游戏B,游戏C是三款不同的游戏,对于他们来说,他们只需要调用U8SDK抽象层提供的接口,就完成了所有SDK的接入工作

教你快速高效接入SDK——关于Application的适配和代理

我们知道,每个android应用程序中都有一个唯一的上下文Application对象,这个Application一般我们不需要去关心他,应用启动时,系统会自动创建一个默认的Application实例.但是,因为Application在整个应用中是唯一的,也就是说,他是一个单例.所以,有的应用就可能希望利用Application来完成一些工作. 好在,在android中,实现一个自定义的Application是很简单的.直接自定义一个类继承Application,然后在AndroidManifes