mPaas-RPC拦截器使用指南

本文涉及的产品
mPaaS订阅基础套餐,标准版 3个月
简介: 金融级移动开发平台 mPaaS(Mobile PaaS)为 App 开发、测试、运营及运维提供云到端的一站式解决方案,能有效降低技术门槛、减少研发成本、提升开发效率,协助企业快速搭建稳定高质量的移动应用。其中移动网关服务(Mobile Gateway Service,简称 MGS)作为mPaas最重要的组件之一,连接了移动客户端与服务端,简化了移动端与服务端的数据协议和通讯协议,从而能够显著提升开发效率和网络通讯效率。在我们日常运维过程中发现,很多用户在使用客户端RPC组件的时候,有很多不同场景的诉求,比如拦截请求添加业务请求标记,免登,返回结果模拟,异常处理,限流等。本文旨在介绍通过利用RP


一 背景

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

二 RPC调用原理

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

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());


三 拦截器

1. 原理

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

四 preHandle场景

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

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

@Override

   public boolean preHandle(Object proxy,

                            ThreadLocal<Object> retValue,

                            byte[] retRawValue,

                            Class<?> aClass,

                            Method method,

                            Object[] args,

                            Annotation annotation,

                            ThreadLocal<Map<String, Object>> threadLocal)

           throws RpcException {

       //Do something...

       RpcInvocationHandler handler = (RpcInvocationHandler) Proxy.getInvocationHandler(proxy);

       handler.getRpcInvokeContext().addRequestHeader("header", "headerCustom");

       return true;

   }


2. 阻断当前请求rpc流程

 典型使用场景:比如如果当前未登录,对需要登录的rpc先阻断,统一提示登录

@Override

   public boolean preHandle(Object proxy,

                            ThreadLocal<Object> retValue,

                            byte[] retRawValue,

                            Class<?> aClass,

                            Method method,

                            Object[] args,

                            Annotation annotation,

                            ThreadLocal<Map<String, Object>> threadLocal)

           throws RpcException {

       //Do something...

       String operationType = getOperationType(aClass, method, args);

       if ("operationType1".equals(operationType)) {

           boolean isLogin = false;

           if (!isLogin) {

               Handler handler = new Handler(Looper.getMainLooper());

               handler.post(new Runnable() {

                   @Override

                   public void run() {

                       Toast.makeText(LauncherApplicationAgent.getInstance().getApplicationContext(),

                               "当前未登录,请登录", Toast.LENGTH_SHORT).show();

                   }

               });

               // 返回给上层调用登录失败的异常,上层做业务处理

               throw new RpcException(RpcException.ErrorCode.CLIENT_LOGIN_FAIL_ERROR, "login fail.");

           }

       }

       return true;

   }

    private String getOperationType(Class<?> aClass, Method method, Object[] args) {

       if (aClass == null || null == method) return "";

       OperationType operationType = method.getAnnotation(OperationType.class);

       return operationType == null ? "" : operationType.value();

   }


五 postHandle场景

1. 拦截接口返回

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

@Override

   public boolean postHandle(Object proxy,

                             ThreadLocal<Object> threadLocal,

                             byte[] retRawValue,

                             Class<?> aClass,

                             Method method,

                             Object[] args,

                             Annotation annotation) throws RpcException {

       //Do something...

       // 场景:修改服务端返回的数据,比如mock数据,或者修改服务端数据

       String operationType = getOperationType(aClass, method, args);

       LoggerFactory.getTraceLogger().debug(TAG, "postHandle:" + operationType);

       if ("operationType1".equals(operationType)) {

           String value = JSON.parse(retRawValue).toString();

           LoggerFactory.getTraceLogger().debug(TAG, "postHandle 原始返回" + value);

           String mockData = "{\"img\":\"imgPath\",\"User\":{\"name\":\"我是mock的数据\",\"age\":18}}";

           Object mockObj = JSON.parseObject(mockData, method.getReturnType());

           threadLocal.set(mockObj);

           return true;

       }

       return true;

   }


   private String getOperationType(Class<?> aClass, Method method, Object[] args) {

       if (aClass == null || null == method) return "";

       OperationType operationType = method.getAnnotation(OperationType.class);

       return operationType == null ? "" : operationType.value();

   }


六 exceptionHandle场景

1. 异常统一处理

 比如登录态失效,服务端会统一返回2000的错误码,客户端可以在exceptionHandle里统一拦截进行登录态免登操作

 @Override

   public boolean exceptionHandle(Object proxy, ThreadLocal<Object> retValue, byte[] bytes, Class<?> aClass, Method method, Object[] objects,

                                  RpcException rpcException, Annotation annotation) throws RpcException {

       String operationType = getOperationType(aClass, method, objects);

       if (RpcException.ErrorCode.CLIENT_LOGIN_FAIL_ERROR == rpcException.getCode()

               && "operationType1".equals(operationType)) {

           // 1. 去免登

           hasLogin = true;

           // 2. 免登后在帮上层重发请求,免登操作对上层业务无感知

           try {

               LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc begin " + operationType);

               // 重发请求

               Object object = method.invoke(proxy, objects);

               retValue.set(object);

               LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc success");

               return false;

           } catch (Throwable e) {

               LoggerFactory.getTraceLogger().error(TAG, "resend rpc occurs illegal argument exception", e);

               throw new RpcException(RpcException.ErrorCode.CLIENT_HANDLE_ERROR, e + "");

           }

       }

       return true;

   }


七 H5场景

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


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

var params = [{

   "_requestBody":{"userName":"", "userId":0}

}]

var operationType = 'alipay.mobile.ic.dispatch'

AlipayJSBridge.call('rpc', {

 operationType: operationType,

 requestData: params,

 headers:{}

}, function (result) {

 console.log(result);

});

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

@Override

   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 {

       String operationType = getOperationType(aClass, method, objects);

       if ("alipay.client.executerpc".equals(operationType)) {

           // H5的rpc名称

           String rpcName = (String) objects[0];

           // 入参

           String req = (String) objects[1];

           LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + rpcName + " " + req);


       } else {

           // Native的rpc

       }

       LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + operationType);

       return true;

   }



相关文章
|
4月前
|
JSON Java API
技术笔记:springboot项目使用拦截器实现一个简单的网关请求透传
技术笔记:springboot项目使用拦截器实现一个简单的网关请求透传
178 0
|
11月前
|
前端开发 Java 对象存储
使用Feign接口实现文件上传的解决方案
一般的情况下,后端有个微服务,暴露出一个文件上传的restful接口给前端,前端调用该接口获取上传后的链接以及oss key值完成上传。假设提供restful接口的这个服务叫做A,现在有个微服务B有个本地文件,需要将本地文件调用A文件文件上传接口上传到文件服务器,该如何做?
106 0
|
移动开发 运维 JavaScript
怎么玩转mPaaS的RPC拦截器
移动网关服务(Mobile Gateway Service,简称 MGS)作为mPaas最重要的组件之一,连接了移动客户端与服务端,简化了移动端与服务端的数据协议和通讯协议,从而能够显著提升开发效率和网络通讯效率。在我们日常运维过程中发现,很多用户在使用客户端RPC组件的时候,有很多不同场景的诉求,比如拦截请求添加业务请求标记,免登,返回结果模拟,异常处理,限流等。本文旨在介绍通过利用RPC提供的拦截器机制,通过不同实际场景的描述,供业务参考使用。
165 0
|
网络协议 Dubbo Java
【远程调用框架概述 一】基于HTTP和RPC的远程调用方式
【远程调用框架概述 一】基于HTTP和RPC的远程调用方式
362 0
|
存储 中间件 测试技术
gRPC(六)进阶:拦截器 interceptor
拦截器本质上就是一个特定类型的函数,所以实现拦截器只需要实现对应类型方法(方法签名相同)即可。
900 1
gRPC(六)进阶:拦截器 interceptor
|
Java API 网络架构
SpringBoot从0到实战2:ApiPost请求工具的引入与简单接口实现
引入开发必备神器ApiPost工具,一款http请求工具,并实现SpringWeb的简单接口实现。
SpringBoot从0到实战2:ApiPost请求工具的引入与简单接口实现
|
前端开发 Java 程序员
如何在SpringBoot中优雅地重试调用第三方API?
如何在SpringBoot中优雅地重试调用第三方API?
314 0
|
监控 Dubbo 应用服务中间件
Dubbo如何支持本地调用?injvm方式解析
Dubbo是一个远程调用的框架,对于一个服务提供者,暴露了一个接口供外部消费者调用,那么对于提供者自己是否可以调用这个接口,需要什么特殊处理吗?
5464 0
|
Dubbo Java 应用服务中间件
SpringBoot整合Dubbo的第二种方式——API(自定义Configuration配置类)
SpringBoot整合Dubbo的第二种方式——API(自定义Configuration配置类)
SpringBoot整合Dubbo的第二种方式——API(自定义Configuration配置类)
|
小程序 API Go
Go+gRPC-Gateway(V2) 微服务实战,小程序登录鉴权服务(五):鉴权 gRPC-Interceptor 拦截器实战
Go+gRPC-Gateway(V2) 微服务实战,小程序登录鉴权服务(五):鉴权 gRPC-Interceptor 拦截器实战
362 0
Go+gRPC-Gateway(V2) 微服务实战,小程序登录鉴权服务(五):鉴权 gRPC-Interceptor 拦截器实战