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
相關文章
相關標籤/搜索