mPaas-RPC拦截器各类场景下的使用指南

一 背景

 金融级移动开发平台 mPaaS(Mobile PaaS)为 App 开发、测试、运营及运维提供云到端的一站式解决方案,能有效下降技术门槛、减小研发成本、提高开发效率,协助企业快速搭建稳定高质量的移动应用。其中移动网关服务(Mobile Gateway Service,简称 MGS)做为mPaas最重要的组件之一,链接了移动客户端与服务端,简化了移动端与服务端的数据协议和通信协议,从而可以显著提高开发效率和网络通信效率。在咱们平常运维过程当中发现,不少用户在使用客户端RPC组件的时候,有不少不一样场景的诉求,好比拦截请求添加业务请求标记,免登,返回结果模拟,异常处理,限流等。本文旨在介绍经过利用RPC提供的拦截器机制,经过不一样实际场景的描述,供业务参考使用。java

二 RPC调用原理

 当 App 在移动网关控制台接入后台服务后,调用RPC的示例代码以下:api

RpcDemoClient client = MPRpc.getRpcProxy(RpcDemoClient.class);
// 设置请求
GetIdGetReq req = new GetIdGetReq();
req.id = "123";
req.age = 14;
req.isMale = true;
// 发起 rpc 请求
String response = client.getIdGet(req);

值得好奇的是,整个调用过程当中其实咱们并无去实现 RpcDemoClient 这个接口,而是经过 MPRpc.getRpcProxy 获取了一个代理,经过代理对象完成了调用。在这里其实主要使用了 Java 动态代理的技术。以下图所示,当调用RPC接口的时候,会经过动态代理的RpcInvocationHandler,回调其实现的invoke方法,最终在invoke内实现数据的序列化处理最后经过网络库发到服务端。
网络

public <T> T getRpcProxy(Class<T> clazz) {
        LogCatUtil.info("RpcFactory", "clazz=[" + clazz.getName() + "]");
        return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new RpcInvocationHandler(this.mConfig, clazz, this.mRpcInvoker));
    }

 在业务开发中,若是在某些状况下须要控制客户端的网络请求(拦截网络请求,禁止访问某些接口,或者限流),能够经过 RPC 拦截器实现。运维

RpcService rpcService = getMicroApplicationContext().findServiceByInterface(RpcService.class.getName());
        rpcService.addRpcInterceptor(OperationType.class, new CommonInterceptor());


三 拦截器
ide

1. 原理

   RPC目前采用了拦截器机制实现RPC的自定义处理,以下图所示,业务能够经过设置自定义RpcIntercept实如今请求前,请求异常,请求返回三个阶段对RPC的定制处理。oop

                         

874b4be3de42d320baa0fb126ae4af34.png


四 preHandle场景

1. 全局添加业务自定义请求header

 典型使用场景:业务添加自定义的业务全局标识或者其余统计字段post

1
 @Override
2
    public boolean preHandle(Object proxy,
3
                             ThreadLocal<Object> retValue,
4
                             byte[] retRawValue,
5
                             Class<?> aClass,
6
                             Method method,
7
                             Object[] args,
8
                             Annotation annotation,
9
                             ThreadLocal<Map<String, Object>> threadLocal)
10
            throws RpcException {
11
        //Do something...
12
        RpcInvocationHandler handler = (RpcInvocationHandler) Proxy.getInvocationHandler(proxy);
13
        handler.getRpcInvokeContext().addRequestHeader("header", "headerCustom");
14
        return true;
15
    }


2. 阻断当前请求rpc流程

 典型使用场景:好比若是当前未登陆,对须要登陆的rpc先阻断,统一提示登陆测试

1
 @Override
2
    public boolean preHandle(Object proxy,
3
                             ThreadLocal<Object> retValue,
4
                             byte[] retRawValue,
5
                             Class<?> aClass,
6
                             Method method,
7
                             Object[] args,
8
                             Annotation annotation,
9
                             ThreadLocal<Map<String, Object>> threadLocal)
10
            throws RpcException {
11
        //Do something...
12
        String operationType = getOperationType(aClass, method, args);
13
        if ("operationType1".equals(operationType)) {
14
            boolean isLogin = false;
15
            if (!isLogin) {
16
                Handler handler = new Handler(Looper.getMainLooper());
17
                handler.post(new Runnable() {
18
                    @Override
19
                    public void run() {
20
                        Toast.makeText(LauncherApplicationAgent.getInstance().getApplicationContext(),
21
                                "当前未登陆,请登陆", Toast.LENGTH_SHORT).show();
22
                    }
23
                });
24
                // 返回给上层调用登陆失败的异常,上层作业务处理
25
                throw new RpcException(RpcException.ErrorCode.CLIENT_LOGIN_FAIL_ERROR, "login fail.");
26
            }
27
        }
28
        return true;
29
    }
30
     private String getOperationType(Class<?> aClass, Method method, Object[] args) {
31
        if (aClass == null || null == method) return "";
32
        OperationType operationType = method.getAnnotation(OperationType.class);
33
        return operationType == null ? "" : operationType.value();
34
    }

五 postHandle场景

1. 拦截接口返回

 典型使用场景:全局修改服务端的返回结果,好比mock服务端的数据this

 @Override
2
    public boolean postHandle(Object proxy,
3
                              ThreadLocal<Object> threadLocal,
4
                              byte[] retRawValue,
5
                              Class<?> aClass,
6
                              Method method,
7
                              Object[] args,
8
                              Annotation annotation) throws RpcException {
9
        //Do something...
10
        // 场景:修改服务端返回的数据,好比mock数据,或者修改服务端数据
11
        String operationType = getOperationType(aClass, method, args);
12
        LoggerFactory.getTraceLogger().debug(TAG, "postHandle:" + operationType);
13
        if ("operationType1".equals(operationType)) {
14
            String value = JSON.parse(retRawValue).toString();
15
            LoggerFactory.getTraceLogger().debug(TAG, "postHandle 原始返回" + value);
16
            String mockData = "{\"img\":\"imgPath\",\"User\":{\"name\":\"我是mock的数据\",\"age\":18}}";
17
            Object mockObj = JSON.parseObject(mockData, method.getReturnType());
18
            threadLocal.set(mockObj);
19
            return true;
20
        }
21
        return true;
22
    }
23

24
    private String getOperationType(Class<?> aClass, Method method, Object[] args) {
25
        if (aClass == null || null == method) return "";
26
        OperationType operationType = method.getAnnotation(OperationType.class);
27
        return operationType == null ? "" : operationType.value();
28
    }
六 exceptionHandle场景

1. 异常统一处理

 好比登陆态失效,服务端会统一返回2000的错误码,客户端能够在exceptionHandle里统一拦截进行登陆态免登操做spa

1
  @Override
2
    public boolean exceptionHandle(Object proxy, ThreadLocal<Object> retValue, byte[] bytes, Class<?> aClass, Method method, Object[] objects,
3
                                   RpcException rpcException, Annotation annotation) throws RpcException {
4
        String operationType = getOperationType(aClass, method, objects);
5
        if (RpcException.ErrorCode.CLIENT_LOGIN_FAIL_ERROR == rpcException.getCode()
6
                && "operationType1".equals(operationType)) {
7
            // 1. 去免登
8
            hasLogin = true;
9
            // 2. 免登后在帮上层重发请求,免登操做对上层业务无感知
10
            try {
11
                LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc begin " + operationType);
12
                // 重发请求
13
                Object object = method.invoke(proxy, objects);
14
                retValue.set(object);
15
                LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc success");
16
                return false;
17
            } catch (Throwable e) {
18
                LoggerFactory.getTraceLogger().error(TAG, "resend rpc occurs illegal argument exception", e);
19
                throw new RpcException(RpcException.ErrorCode.CLIENT_HANDLE_ERROR, e + "");
20
            }
21
        }
22
        return true;
23
    }


七 H5场景

   因为H5场景中使用的jsapi的rpc,须要支持在js环境里传递到native环境,因此在设计上,是统一经过operationType: alipay.client.executerpc 接口进行的转发,因此针对H5发送的RPC请求,须要作特殊判断,经过入参拿到真实的operationType接口,示例代码以下。


1. 获取H5请求的接口名称和入参

1
var params = [{
2
    "_requestBody":{"userName":"", "userId":0}
3
}]
4
var operationType = 'alipay.mobile.ic.dispatch'
5
AlipayJSBridge.call('rpc', {
6
  operationType: operationType,
7
  requestData: params,
8
  headers:{}
9
}, function (result) {
10
  console.log(result);
11
});

 如上图所示,业务经过jsapi去请求rpc,如何获取jsapi请求的rpc名称,能够参考代码以下

1
 @Override
2
    public boolean preHandle(Object o, ThreadLocal<Object> threadLocal, byte[] bytes, Class<?> aClass, Method method, Object[] objects, Annotation annotation, ThreadLocal<Map<String, Object>> threadLocal1) throws RpcException {
3
        String operationType = getOperationType(aClass, method, objects);
4
        if ("alipay.client.executerpc".equals(operationType)) {
5
            // H5的rpc名称
6
            String rpcName = (String) objects[0];
7
            // 入参
8
            String req = (String) objects[1];
9
            LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + rpcName + " " + req);
10

11
        } else {
12
            // Native的rpc
13
        }
14
        LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + operationType);
15
        return true;
16
    }
17
相关文章
相关标签/搜索