0


springboot中请求地址转发方案

一、背景需求

现有一个平台,如果在上面发布软件,需要在平台注册所有的接口,注册好后平台会给每一个接口都提供一个不同的新地址(所有的请求在平台注册后都是类似"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);}}

本文转载自: https://blog.csdn.net/var200/article/details/130962172
版权归原作者 Mr.var 所有, 如有侵权,请联系我们删除。

“springboot中请求地址转发方案”的评论:

还没有评论