一、背景需求
现有一个平台,如果在上面发布软件,需要在平台注册所有的接口,注册好后平台会给每一个接口都提供一个不同的新地址(所有的请求在平台注册后都是类似"http://localhost:8080/{appkey}/{token}"的格式,每个接口都拥有一个不同的appkey作为标识,token可通过另一个请求获取),在前端调用请求的时候,必须请求平台提供的地址,然后平台会替前端转发到真实的地址去请求后端。
为了减少注册和审核的工作量,我们可以只注册少量接口,然后在这些接口内我们自行转发。
二、方案一
zuul转发:
在平台注册增删改查等若干个虚拟的接口地址,然后在前端将所有接口封装成这些虚拟接口,并在请求参数内传递真实的接口地址,通过平台转发到后端之后我们通过zuul过滤器再转发到自己真实的接口地址上。(注:登录接口比较特殊,登录在后端是写在主服务内的,zuul网关不会进行拦截,这里单独注册;其余接口统一写在同一个服务内,便于统一转发配置)
前端代码演示:
这里是在vue中写的一个axios的请求拦截器,统一对真实接口进行封装
举个例:
我们在平台上注册一个虚拟地址:
http://localhost:8080/comSelect/getData
=>
注册后请求地址变为:
http://xxx.xxx.xxx:xxxx/appKeySelect123/{token}
axios.interceptors.request.use(config=>{if(!config.url.startsWith("http")){//模拟一个token,真实token可通过平台提供的另一请求获取let token ="token";//将接口地址放在covertUrl参数内传递给后端if(config.method =="post"){//post请求的两种content-type格式if(typeof config.data =="string"){//请求参数表单格式//qs可用于格式化参数let conData = qs.parse(config.data);
conData.covertUrl = config.url;
config.data = qs.stringify(conData);}else{//请求体格式
config.data.covertUrl = config.url;}}elseif(config.method =="get"){//axios中get请求可用params指定url传值
config.params.covertUrl = config.url;}//封装成平台要求的请求地址,真实的url存于参数covertUrl中
config.url =urlPack(token, config.url);}return config;},error=>{return Promise.reject(error);});//接口地址封装,将所有接口统一分为增删改查四个接口functionurlPack(token, url){let appKey;//登陆let appKeyLogin ="/appKeyLogin123/";//增let appKeyAdd ="/appKeyAdd123/";//删let appKeyDelete ="/appKeyDelete123/";//改let appKeyUpdate ="/appKeyUpdate123/";//查let appKeySelect ="/appKeySelect123/";//http://localhost:8080/comSelect/getData //随便拿几个接口举例switch(url){case"/sysUser/app_login":
appKey = appKeyLogin;break;case"/appcommon/appVersion/getVersion":
appKey = appKeySelect;break;case"/appcommon/appMenu/getMenu":
appKey = appKeySelect;break;}return"http://localhost:8080"+ appKey + token;}
后端代码演示:
#这里需要注意,必须在zuul的路由配置里添加平台转发之后传递过来的虚拟路由,不然zuul会报出找不到路由的错
zuul:
#路由添加
routes:
#虚拟服务地址
comSelect:
path: /comSelect/**
#真实的路由服务,这里的地址是真实注册到了eureka的服务地址,也可以动态获取
appcommon:
path: /appcommon/**
serviceId: appcommon
/**
* 转换成真正的url地址,路由转发
*/@Slf4j@ComponentpublicclassZuulAppRouteFilterextendsZuulFilter{/**
* filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
* pre:路由之前
* routing:路由之时
* post: 路由之后
* error:发送错误调用
*/@OverridepublicStringfilterType(){returnFilterConstants.ROUTE_TYPE;}/**
* 过滤器优先级,同一filterType下的过滤器,数值越大优先级越低
*/@OverridepublicintfilterOrder(){return1;}/**
* 是否启用过滤器,这里可以做一些逻辑判断
*/@OverridepublicbooleanshouldFilter(){returntrue;}@OverridepublicObjectrun()throwsZuulException{RequestContext ctx =RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();//真正的接口地址String path ="";try{//请求参数(url传值或表单传值)Map<String,String[]> parameterMap = request.getParameterMap();//请求参数(请求体)String requestBody =null;try{
requestBody =StreamUtils.copyToString(request.getInputStream(),StandardCharsets.UTF_8);}catch(IOException e){
e.printStackTrace();}if(parameterMap.size()==0){if(requestBody !=null&&!"".equals(requestBody)){try{JSONObject jsonObj =JSONObject.parseObject(requestBody);if(jsonObj.get("covertUrl")!=null){Object covertUrl = jsonObj.get("covertUrl");
path =String.valueOf(covertUrl);}}catch(Exception e){
log.error("path["+ path +"]返回的不是json格式数据,返回信息:"+ requestBody);}}}else{if(parameterMap.get("covertUrl")!=null){String[] description = parameterMap.get("covertUrl");
path =URLDecoder.decode(description[0]);}}//所有的服务全部指向serviceId为appcommon这个路由//如果需要转发到其他服务则通过判断path来写判断String serviceId ="";if(path.contains("appcommon")){
serviceId ="appcommon";}elseif(path.contains("sync")){
serviceId ="sync";}//请求地址转发到真实的接口上
ctx.put(FilterConstants.REQUEST_URI_KEY, path);}catch(Exception ignored){
log.error(request.getRequestURL().toString()+"解析失败");}returnnull;}}
三、方案二:
java反射:
在平台注册增删改查等若干个接口地址,并在后端编写这些接口作为统一分发接口,然后在前端将所有接口封装成这些接口,并在请求参数内传递接口的类名和对应的方法名,通过平台转发传递到后端之后,后端利用Java的反射机制调用真实的接口地址,转发到对应的接口上。
前端代码演示:
举个例:
我们在平台上注册的地址:
http://localhost:8080/appcommon/common/query
=>
注册后请求地址变为:
http://localhost:8080/appKeySelect123/{token}
axios.interceptors.request.use(config=>{if(!config.url.startsWith("http")){//模拟一个token,真实token可通过平台提供的另一请求获取let token ="token";let req;if(config.method =="post"){if(typeof config.data =="string"){//请求参数表单格式let conData = qs.parse(config.data);
config.data = qs.stringify(conData);
req =reqPack(token, config.url, config.data);
config.url = req.url;
config.data = req.reqData;}else{//请求体格式
req =reqPack(token, config.url, config.data);
config.url = req.url;
config.data = req.reqData;}}else{
req =reqPack(token, config.url, config.params);
config.url = req.url;
config.params = req.reqData;}//封装成平台要求的请求地址,真实的url存于参数covertUrl中
config.url =urlPack(token, config.url);}return config;},error=>{return Promise.reject(error);});//接口地址封装,将所有接口统一分为增删改查四个接口functionurlPack(token, url, data){//总线所需的keylet appKey;//登陆let appKeyLogin ="/appKeyLogin123/";//增let appKeyAdd ="/appKeyAdd123/";//删let appKeyDelete ="/appKeyDelete123/";//改let appKeyUpdate ="/appKeyUpdate123/";//查let appKeySelect ="/appKeySelect123/";//http://localhost:8080/appcommon/common/query//请求参数let reqData ={//类名className:"",//方法名methodName:"",//接口所需参数params: data
}//指定不同接口的类名和方法名,用于分发调用switch(url){//登录请求比较特殊,单独注册,参数不封装case"/sysUser/app_login":
appKey = appLogin;return{url:GLOBAL.$RequestBaseUrl1 + appKey,reqData: data
}break;case"/appcommon/appVersion/getVersion":
appKey = appKeySelect;
reqData.className ="AppVersionController";
reqData.methodName ="getAppVersion";break;case"/sync/risk/road/getAllRoad":
appKey = appKeySelect;break;case"/appcommon/appMenu/getMenu":
appKey = appKeySelect;
reqData.className ="AppMenuController";
reqData.methodName ="getMenu";break;}return{url:GLOBAL.$RequestBaseUrl1 + appKey + token,reqData: reqData
};}
后端代码演示:
/**
* 公共接口实例
*/@DatapublicclassCommonObj{/**
* 类名
*/privateString className;/**
* 方法名
*/privateString methodName;/**
* 实际参数
*/privateMap<String,Object> params;}
/**
* Spring定义的类实现ApplicationContextAware接口会自动的将应用程序上下文加入
*/@Slf4j@ComponentpublicclassMySpringUtilimplementsApplicationContextAware{//上下文对象实例privatestaticApplicationContext applicationContext;@OverridepublicvoidsetApplicationContext(ApplicationContext applicationContext)throwsBeansException{if(MySpringUtil.applicationContext ==null){MySpringUtil.applicationContext = applicationContext;}}//获取applicationContextpublicstaticApplicationContextgetApplicationContext(){return applicationContext;}//通过name获取 Bean.publicstaticObjectgetBean(String name){returngetApplicationContext().getBean(name);}//通过class获取Bean.publicstatic<T>TgetBean(Class<T> clazz){returngetApplicationContext().getBean(clazz);}//通过name,以及Clazz返回指定的Beanpublicstatic<T>TgetBean(String name,Class<T> clazz){returngetApplicationContext().getBean(name, clazz);}}
/**
* app公共接口调用,通过反射分发调用接口
*
* @author xht
*/@RestController@RequestMapping("/common")@Slf4jpublicclassCommonController{/**
* 利用反射调用接口
*/publicResponsereflectControl(CommonObj commonObj){String className = commonObj.getClassName();String methodName = commonObj.getMethodName();Map<String,Object> params = commonObj.getParams();Response response;try{//1、获取spring容器中的Bean//类名首字母小写
className =StringUtils.uncapitalize(className);Object proxyObject =MySpringUtil.getBean(className);//2、利用bean获取class对象,进而获取本类以及父类或者父接口中所有的公共方法(public修饰符修饰的)Method[] methods = proxyObject.getClass().getMethods();//3、获取指定的方法Method myMethod =null;for(Method method : methods){if(method.getName().equalsIgnoreCase(methodName)){
myMethod = method;break;}}//4、封装方法需要的参数if(myMethod !=null){Object resObj;
resObj = myMethod.invoke(proxyObject, params);
response =(Response) resObj;}else{
response =Response.error("未找到对应方法");}}catch(Exception e){
e.printStackTrace();
response =Response.error(e.getMessage());}return response;}/**
* 公共新增接口
*/@PostMapping("/add")publicResponsecommonAdd(@RequestBodyCommonObj commonObj){returnreflectControl(commonObj);}/**
* 公共删除接口
*/@PostMapping("/delete")publicResponsecommonDelete(@RequestBodyCommonObj commonObj){returnreflectControl(commonObj);}/**
* 公共修改接口
*/@PostMapping("/edit")publicResponsecommonEdity(@RequestBodyCommonObj commonObj){returnreflectControl(commonObj);}/**
* 公共查询接口
*/@PostMapping("/query")publicResponsecommonQuery(@RequestBodyCommonObj commonObj){returnreflectControl(commonObj);}}
版权归原作者 Mr.var 所有, 如有侵权,请联系我们删除。