springboot中请求地址转发的两种方案

 更新时间:2023年11月24日 15:29:02   作者:Mr.var  
在开发过程中,我们经常需要将请求从一个服务转发到另一个服务,以实现不同服务之间的协作,本文主要介绍了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;
					}
				} else if (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);
	}
);

//接口地址封装,将所有接口统一分为增删改查四个接口
function urlPack(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
@Component
public class ZuulAppRouteFilter extends ZuulFilter {
    /**
     * filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
     *     pre:路由之前
     *     routing:路由之时
     *     post: 路由之后
     *     error:发送错误调用
     */
    @Override
    public String filterType() {
        return FilterConstants.ROUTE_TYPE;
    }

    /**
     * 过滤器优先级,同一filterType下的过滤器,数值越大优先级越低
     */
    @Override
    public int filterOrder() {
        return 1;
    }

    /**
     * 是否启用过滤器,这里可以做一些逻辑判断
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {

        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";
             } else if (path.contains("sync")) {
                    serviceId = "sync";
            }
            //请求地址转发到真实的接口上
            ctx.put(FilterConstants.REQUEST_URI_KEY, path);

        } catch (Exception ignored) {
            log.error(request.getRequestURL().toString() + "解析失败");
        }

        return null;
    }
}

三、方案二:

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

//接口地址封装,将所有接口统一分为增删改查四个接口
function urlPack(token, url, data) {
	//总线所需的key
	let 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
	};
}

后端代码演示:

/**
 * 公共接口实例
 */
@Data
public class CommonObj {
    /**
     * 类名
     */
    private String className;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 实际参数
     */
    private Map<String,Object> params;
}
/**
 * Spring定义的类实现ApplicationContextAware接口会自动的将应用程序上下文加入
 */
@Slf4j
@Component
public class MySpringUtil implements ApplicationContextAware {
    //上下文对象实例
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (MySpringUtil.applicationContext == null) {
            MySpringUtil.applicationContext = applicationContext;
        }
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取 Bean.
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

}
/**
 * app公共接口调用,通过反射分发调用接口
 *
 * @author xht
 */
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {

    /**
     * 利用反射调用接口
     */
    public Response reflectControl(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")
    public Response commonAdd(@RequestBody CommonObj commonObj) {
        return reflectControl(commonObj);
    }
    /**
     * 公共删除接口
     */
    @PostMapping("/delete")
    public Response commonDelete(@RequestBody CommonObj commonObj) {
        return reflectControl(commonObj);
    }
    /**
     * 公共修改接口
     */
    @PostMapping("/edit")
    public Response commonEdity(@RequestBody CommonObj commonObj) {
        return reflectControl(commonObj);
    }
    /**
     * 公共查询接口
     */
    @PostMapping("/query")
    public Response commonQuery(@RequestBody CommonObj commonObj) {
        return reflectControl(commonObj);
    }
}

到此这篇关于springboot中请求地址转发的两种方案的文章就介绍到这了,更多相关springboot 请求地址转发内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • 使用Springboot打成jar包thymeleaf的问题

    使用Springboot打成jar包thymeleaf的问题

    这篇文章主要介绍了使用Springboot打成jar包thymeleaf的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • springMvc和mybatis-plus中枚举值和字段的映射

    springMvc和mybatis-plus中枚举值和字段的映射

    这篇文章主要为大家介绍了springMvc和mybatis-plus中枚举值和字段的映射示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • java 动态加载的实现代码

    java 动态加载的实现代码

    这篇文章主要介绍了java 动态加载的实现代码的相关资料,Java动态加载类主要是为了不改变主程序代码,通过修改配置文件就可以操作不同的对象执行不同的功能,需要的朋友可以参考下
    2017-07-07
  • java编程中实现调用js方法分析

    java编程中实现调用js方法分析

    这篇文章主要介绍了java编程中实现调用js方法,结合具体实例形式较为详细的分析了java编程中调用js方法的常用操作技巧与注意事项,需要的朋友可以参考下
    2017-09-09
  • Java中的length和length()深入分析

    Java中的length和length()深入分析

    java中的length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了length这个属性。java中的length()方法是针对字符串String说的,如果想看这个字符串的长度则用到length()这个方法。这篇文章将介绍几个关于Java数组的关键概念。
    2016-11-11
  • Java集合排序规则接口Comparator用法解析

    Java集合排序规则接口Comparator用法解析

    这篇文章主要介绍了Java集合排序规则接口Comparator用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • java web中图片验证码功能的简单实现方法

    java web中图片验证码功能的简单实现方法

    下面小编就为大家带来一篇java web 验证码的简单实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • 数据同步利器DataX简介及如何使用

    数据同步利器DataX简介及如何使用

    DataX 是阿里云 DataWorks数据集成 的开源版本,使用Java 语言编写,在阿里巴巴集团内被广泛使用的离线数据同步工具/平台,今天给大家分享一个阿里开源的数据同步工具DataX,在Github拥有14.8k的star,非常受欢迎
    2024-02-02
  • ssm项目实现用户登陆持久化(token)

    ssm项目实现用户登陆持久化(token)

    这篇文章主要介绍了ssm项目实现用户登陆持久化(token),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • Java中Static关键字的五种用法详解

    Java中Static关键字的五种用法详解

    这篇文章主要介绍了Java中static的五种用法:修饰成员变量,修饰成员方法,修饰内部类,静态代码块,静态导包,想详细了解的小伙伴可以参考阅读本文
    2023-03-03

最新评论