springboot中請(qǐng)求地址轉(zhuǎn)發(fā)的兩種方案
一、背景需求
現(xiàn)有一個(gè)平臺(tái),如果在上面發(fā)布軟件,需要在平臺(tái)注冊(cè)所有的接口,注冊(cè)好后平臺(tái)會(huì)給每一個(gè)接口都提供一個(gè)不同的新地址(所有的請(qǐng)求在平臺(tái)注冊(cè)后都是類(lèi)似"http://localhost:8080/{appkey}/{token}"的格式,每個(gè)接口都擁有一個(gè)不同的appkey作為標(biāo)識(shí),token可通過(guò)另一個(gè)請(qǐng)求獲?。?,在前端調(diào)用請(qǐng)求的時(shí)候,必須請(qǐng)求平臺(tái)提供的地址,然后平臺(tái)會(huì)替前端轉(zhuǎn)發(fā)到真實(shí)的地址去請(qǐng)求后端。
為了減少注冊(cè)和審核的工作量,我們可以只注冊(cè)少量接口,然后在這些接口內(nèi)我們自行轉(zhuǎn)發(fā)。
二、方案一
zuul轉(zhuǎn)發(fā):
在平臺(tái)注冊(cè)增刪改查等若干個(gè)虛擬的接口地址,然后在前端將所有接口封裝成這些虛擬接口,并在請(qǐng)求參數(shù)內(nèi)傳遞真實(shí)的接口地址,通過(guò)平臺(tái)轉(zhuǎn)發(fā)到后端之后我們通過(guò)zuul過(guò)濾器再轉(zhuǎn)發(fā)到自己真實(shí)的接口地址上。(注:登錄接口比較特殊,登錄在后端是寫(xiě)在主服務(wù)內(nèi)的,zuul網(wǎng)關(guān)不會(huì)進(jìn)行攔截,這里單獨(dú)注冊(cè);其余接口統(tǒng)一寫(xiě)在同一個(gè)服務(wù)內(nèi),便于統(tǒng)一轉(zhuǎn)發(fā)配置)
前端代碼演示:
這里是在vue中寫(xiě)的一個(gè)axios的請(qǐng)求攔截器,統(tǒng)一對(duì)真實(shí)接口進(jìn)行封裝
舉個(gè)例:
我們?cè)谄脚_(tái)上注冊(cè)一個(gè)虛擬地址:
http://localhost:8080/comSelect/getData
=>
注冊(cè)后請(qǐng)求地址變?yōu)椋?br />http://xxx.xxx.xxx:xxxx/appKeySelect123/{token}
axios.interceptors.request.use( config => { if (!config.url.startsWith("http")) { //模擬一個(gè)token,真實(shí)token可通過(guò)平臺(tái)提供的另一請(qǐng)求獲取 let token = "token"; //將接口地址放在covertUrl參數(shù)內(nèi)傳遞給后端 if (config.method == "post") { //post請(qǐng)求的兩種content-type格式 if (typeof config.data == "string") { //請(qǐng)求參數(shù)表單格式 //qs可用于格式化參數(shù) let conData = qs.parse(config.data); conData.covertUrl = config.url; config.data = qs.stringify(conData); } else { //請(qǐng)求體格式 config.data.covertUrl = config.url; } } else if (config.method == "get"){ //axios中g(shù)et請(qǐng)求可用params指定url傳值 config.params.covertUrl = config.url; } //封裝成平臺(tái)要求的請(qǐng)求地址,真實(shí)的url存于參數(shù)covertUrl中 config.url = urlPack(token, config.url); } return config; }, error => { return Promise.reject(error); } ); //接口地址封裝,將所有接口統(tǒng)一分為增刪改查四個(gè)接口 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 //隨便拿幾個(gè)接口舉例 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的路由配置里添加平臺(tái)轉(zhuǎn)發(fā)之后傳遞過(guò)來(lái)的虛擬路由,不然zuul會(huì)報(bào)出找不到路由的錯(cuò) zuul: #路由添加 routes: #虛擬服務(wù)地址 comSelect: path: /comSelect/** #真實(shí)的路由服務(wù),這里的地址是真實(shí)注冊(cè)到了eureka的服務(wù)地址,也可以動(dòng)態(tài)獲取 appcommon: path: /appcommon/** serviceId: appcommon
/** * 轉(zhuǎn)換成真正的url地址,路由轉(zhuǎn)發(fā) */ @Slf4j @Component public class ZuulAppRouteFilter extends ZuulFilter { /** * filterType:返回一個(gè)字符串代表過(guò)濾器的類(lèi)型,在zuul中定義了四種不同生命周期的過(guò)濾器類(lèi)型,具體如下: * pre:路由之前 * routing:路由之時(shí) * post: 路由之后 * error:發(fā)送錯(cuò)誤調(diào)用 */ @Override public String filterType() { return FilterConstants.ROUTE_TYPE; } /** * 過(guò)濾器優(yōu)先級(jí),同一filterType下的過(guò)濾器,數(shù)值越大優(yōu)先級(jí)越低 */ @Override public int filterOrder() { return 1; } /** * 是否啟用過(guò)濾器,這里可以做一些邏輯判斷 */ @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); //真正的接口地址 String path = ""; try { //請(qǐng)求參數(shù)(url傳值或表單傳值) Map<String, String[]> parameterMap = request.getParameterMap(); //請(qǐng)求參數(shù)(請(qǐng)求體) 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格式數(shù)據(jù),返回信息:" + requestBody); } } } else { if (parameterMap.get("covertUrl") != null) { String[] description = parameterMap.get("covertUrl"); path = URLDecoder.decode(description[0]); } } //所有的服務(wù)全部指向serviceId為appcommon這個(gè)路由 //如果需要轉(zhuǎn)發(fā)到其他服務(wù)則通過(guò)判斷path來(lái)寫(xiě)判斷 String serviceId = ""; if (path.contains("appcommon")) { serviceId = "appcommon"; } else if (path.contains("sync")) { serviceId = "sync"; } //請(qǐng)求地址轉(zhuǎn)發(fā)到真實(shí)的接口上 ctx.put(FilterConstants.REQUEST_URI_KEY, path); } catch (Exception ignored) { log.error(request.getRequestURL().toString() + "解析失敗"); } return null; } }
三、方案二:
java反射:
在平臺(tái)注冊(cè)增刪改查等若干個(gè)接口地址,并在后端編寫(xiě)這些接口作為統(tǒng)一分發(fā)接口,然后在前端將所有接口封裝成這些接口,并在請(qǐng)求參數(shù)內(nèi)傳遞接口的類(lèi)名和對(duì)應(yīng)的方法名,通過(guò)平臺(tái)轉(zhuǎn)發(fā)傳遞到后端之后,后端利用Java的反射機(jī)制調(diào)用真實(shí)的接口地址,轉(zhuǎn)發(fā)到對(duì)應(yīng)的接口上。
前端代碼演示:
舉個(gè)例:
我們?cè)谄脚_(tái)上注冊(cè)的地址:
http://localhost:8080/appcommon/common/query
=>
注冊(cè)后請(qǐng)求地址變?yōu)椋?br />http://localhost:8080/appKeySelect123/{token}
axios.interceptors.request.use( config => { if (!config.url.startsWith("http")) { //模擬一個(gè)token,真實(shí)token可通過(guò)平臺(tái)提供的另一請(qǐng)求獲取 let token = "token"; let req; if (config.method == "post") { if (typeof config.data == "string") { //請(qǐng)求參數(shù)表單格式 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 { //請(qǐng)求體格式 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; } //封裝成平臺(tái)要求的請(qǐng)求地址,真實(shí)的url存于參數(shù)covertUrl中 config.url = urlPack(token, config.url); } return config; }, error => { return Promise.reject(error); } ); //接口地址封裝,將所有接口統(tǒng)一分為增刪改查四個(gè)接口 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 //請(qǐng)求參數(shù) let reqData = { //類(lèi)名 className: "", //方法名 methodName: "", //接口所需參數(shù) params: data } //指定不同接口的類(lèi)名和方法名,用于分發(fā)調(diào)用 switch (url) { //登錄請(qǐng)求比較特殊,單獨(dú)注冊(cè),參數(shù)不封裝 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 }; }
后端代碼演示:
/** * 公共接口實(shí)例 */ @Data public class CommonObj { /** * 類(lèi)名 */ private String className; /** * 方法名 */ private String methodName; /** * 實(shí)際參數(shù) */ private Map<String,Object> params; }
/** * Spring定義的類(lèi)實(shí)現(xiàn)ApplicationContextAware接口會(huì)自動(dòng)的將應(yīng)用程序上下文加入 */ @Slf4j @Component public class MySpringUtil implements ApplicationContextAware { //上下文對(duì)象實(shí)例 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; } //通過(guò)name獲取 Bean. public static Object getBean(String name) { return getApplicationContext().getBean(name); } //通過(guò)class獲取Bean. public static <T> T getBean(Class<T> clazz) { return getApplicationContext().getBean(clazz); } //通過(guò)name,以及Clazz返回指定的Bean public static <T> T getBean(String name, Class<T> clazz) { return getApplicationContext().getBean(name, clazz); } }
/** * app公共接口調(diào)用,通過(guò)反射分發(fā)調(diào)用接口 * * @author xht */ @RestController @RequestMapping("/common") @Slf4j public class CommonController { /** * 利用反射調(diào)用接口 */ 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 //類(lèi)名首字母小寫(xiě) className = StringUtils.uncapitalize(className); Object proxyObject = MySpringUtil.getBean(className); //2、利用bean獲取class對(duì)象,進(jìn)而獲取本類(lèi)以及父類(lèi)或者父接口中所有的公共方法(public修飾符修飾的) Method[] methods = proxyObject.getClass().getMethods(); //3、獲取指定的方法 Method myMethod = null; for (Method method : methods) { if (method.getName().equalsIgnoreCase(methodName)) { myMethod = method; break; } } //4、封裝方法需要的參數(shù) if (myMethod != null) { Object resObj; resObj = myMethod.invoke(proxyObject, params); response = (Response) resObj; } else { response = Response.error("未找到對(duì)應(yīng)方法"); } } 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); } /** * 公共查詢(xún)接口 */ @PostMapping("/query") public Response commonQuery(@RequestBody CommonObj commonObj) { return reflectControl(commonObj); } }
到此這篇關(guān)于springboot中請(qǐng)求地址轉(zhuǎn)發(fā)的兩種方案的文章就介紹到這了,更多相關(guān)springboot 請(qǐng)求地址轉(zhuǎn)發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java中LinkedStack鏈棧的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Java中LinkedStack鏈棧的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定幫助,需要的可以參考一下2022-11-11Java中instanceOf關(guān)鍵字的用法及特性詳解
當(dāng)我們?cè)谶M(jìn)行向下轉(zhuǎn)型時(shí),如果兩個(gè)對(duì)象之間沒(méi)有直接或間接的繼承關(guān)系,在轉(zhuǎn)換時(shí)有可能會(huì)產(chǎn)生強(qiáng)制類(lèi)型轉(zhuǎn)換異常,我們可以使用java中自帶的instanceOf關(guān)鍵字來(lái)解決這個(gè)問(wèn)題,所以本篇文章,會(huì)帶大家學(xué)習(xí)instanceOf的用法及特性,需要的朋友可以參考下2023-05-05java學(xué)習(xí)DongTai被動(dòng)型IAST工具部署過(guò)程
被動(dòng)型IAST被認(rèn)為是DevSecOps測(cè)試階段實(shí)現(xiàn)自動(dòng)化安全測(cè)試的最佳工具,而就在前幾天,洞態(tài)IAST正式開(kāi)源了,這對(duì)于甲方構(gòu)建安全工具鏈來(lái)說(shuō),絕對(duì)是一個(gè)大利好2021-10-10淺析Java中SimpleDateFormat為什么是線程不安全的
SimpleDateFormat是Java中用于日期時(shí)間格式化的一個(gè)類(lèi),它提供了對(duì)日期的解析和格式化能力,本文主要來(lái)和大家一起探討一下SimpleDateFormat為什么是線程不安全的,感興趣的可以了解下2024-02-02