springboot中請求地址轉(zhuǎn)發(fā)的兩種方案
一、背景需求
現(xiàn)有一個(gè)平臺,如果在上面發(fā)布軟件,需要在平臺注冊所有的接口,注冊好后平臺會給每一個(gè)接口都提供一個(gè)不同的新地址(所有的請求在平臺注冊后都是類似"http://localhost:8080/{appkey}/{token}"的格式,每個(gè)接口都擁有一個(gè)不同的appkey作為標(biāo)識,token可通過另一個(gè)請求獲?。?,在前端調(diào)用請求的時(shí)候,必須請求平臺提供的地址,然后平臺會替前端轉(zhuǎn)發(fā)到真實(shí)的地址去請求后端。
為了減少注冊和審核的工作量,我們可以只注冊少量接口,然后在這些接口內(nèi)我們自行轉(zhuǎn)發(fā)。
二、方案一
zuul轉(zhuǎn)發(fā):
在平臺注冊增刪改查等若干個(gè)虛擬的接口地址,然后在前端將所有接口封裝成這些虛擬接口,并在請求參數(shù)內(nèi)傳遞真實(shí)的接口地址,通過平臺轉(zhuǎn)發(fā)到后端之后我們通過zuul過濾器再轉(zhuǎn)發(fā)到自己真實(shí)的接口地址上。(注:登錄接口比較特殊,登錄在后端是寫在主服務(wù)內(nèi)的,zuul網(wǎng)關(guān)不會進(jìn)行攔截,這里單獨(dú)注冊;其余接口統(tǒng)一寫在同一個(gè)服務(wù)內(nèi),便于統(tǒng)一轉(zhuǎn)發(fā)配置)
前端代碼演示:
這里是在vue中寫的一個(gè)axios的請求攔截器,統(tǒng)一對真實(shí)接口進(jìn)行封裝
舉個(gè)例:
我們在平臺上注冊一個(gè)虛擬地址:
http://localhost:8080/comSelect/getData
=>
注冊后請求地址變?yōu)椋?br />http://xxx.xxx.xxx:xxxx/appKeySelect123/{token}
axios.interceptors.request.use(
config => {
if (!config.url.startsWith("http")) {
//模擬一個(gè)token,真實(shí)token可通過平臺提供的另一請求獲取
let token = "token";
//將接口地址放在covertUrl參數(shù)內(nèi)傳遞給后端
if (config.method == "post") {
//post請求的兩種content-type格式
if (typeof config.data == "string") {
//請求參數(shù)表單格式
//qs可用于格式化參數(shù)
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中g(shù)et請求可用params指定url傳值
config.params.covertUrl = config.url;
}
//封裝成平臺要求的請求地址,真實(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的路由配置里添加平臺轉(zhuǎn)發(fā)之后傳遞過來的虛擬路由,不然zuul會報(bào)出找不到路由的錯(cuò)
zuul:
#路由添加
routes:
#虛擬服務(wù)地址
comSelect:
path: /comSelect/**
#真實(shí)的路由服務(wù),這里的地址是真實(shí)注冊到了eureka的服務(wù)地址,也可以動態(tài)獲取
appcommon:
path: /appcommon/**
serviceId: appcommon
/**
* 轉(zhuǎn)換成真正的url地址,路由轉(zhuǎn)發(fā)
*/
@Slf4j
@Component
public class ZuulAppRouteFilter extends ZuulFilter {
/**
* filterType:返回一個(gè)字符串代表過濾器的類型,在zuul中定義了四種不同生命周期的過濾器類型,具體如下:
* pre:路由之前
* routing:路由之時(shí)
* post: 路由之后
* error:發(fā)送錯(cuò)誤調(diào)用
*/
@Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
/**
* 過濾器優(yōu)先級,同一filterType下的過濾器,數(shù)值越大優(yōu)先級越低
*/
@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 {
//請求參數(shù)(url傳值或表單傳值)
Map<String, String[]> parameterMap = request.getParameterMap();
//請求參數(shù)(請求體)
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ù)則通過判斷path來寫判斷
String serviceId = "";
if (path.contains("appcommon")) {
serviceId = "appcommon";
} else if (path.contains("sync")) {
serviceId = "sync";
}
//請求地址轉(zhuǎn)發(fā)到真實(shí)的接口上
ctx.put(FilterConstants.REQUEST_URI_KEY, path);
} catch (Exception ignored) {
log.error(request.getRequestURL().toString() + "解析失敗");
}
return null;
}
}
三、方案二:
java反射:
在平臺注冊增刪改查等若干個(gè)接口地址,并在后端編寫這些接口作為統(tǒng)一分發(fā)接口,然后在前端將所有接口封裝成這些接口,并在請求參數(shù)內(nèi)傳遞接口的類名和對應(yīng)的方法名,通過平臺轉(zhuǎn)發(fā)傳遞到后端之后,后端利用Java的反射機(jī)制調(diào)用真實(shí)的接口地址,轉(zhuǎn)發(fā)到對應(yīng)的接口上。
前端代碼演示:
舉個(gè)例:
我們在平臺上注冊的地址:
http://localhost:8080/appcommon/common/query
=>
注冊后請求地址變?yōu)椋?br />http://localhost:8080/appKeySelect123/{token}
axios.interceptors.request.use(
config => {
if (!config.url.startsWith("http")) {
//模擬一個(gè)token,真實(shí)token可通過平臺提供的另一請求獲取
let token = "token";
let req;
if (config.method == "post") {
if (typeof config.data == "string") {
//請求參數(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 {
//請求體格式
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;
}
//封裝成平臺要求的請求地址,真實(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
//請求參數(shù)
let reqData = {
//類名
className: "",
//方法名
methodName: "",
//接口所需參數(shù)
params: data
}
//指定不同接口的類名和方法名,用于分發(fā)調(diào)用
switch (url) {
//登錄請求比較特殊,單獨(dú)注冊,參數(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 {
/**
* 類名
*/
private String className;
/**
* 方法名
*/
private String methodName;
/**
* 實(shí)際參數(shù)
*/
private Map<String,Object> params;
}
/**
* Spring定義的類實(shí)現(xiàn)ApplicationContextAware接口會自動的將應(yīng)用程序上下文加入
*/
@Slf4j
@Component
public class MySpringUtil implements ApplicationContextAware {
//上下文對象實(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;
}
//通過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公共接口調(diào)用,通過反射分發(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
//類名首字母小寫
className = StringUtils.uncapitalize(className);
Object proxyObject = MySpringUtil.getBean(className);
//2、利用bean獲取class對象,進(jìn)而獲取本類以及父類或者父接口中所有的公共方法(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("未找到對應(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);
}
/**
* 公共查詢接口
*/
@PostMapping("/query")
public Response commonQuery(@RequestBody CommonObj commonObj) {
return reflectControl(commonObj);
}
}到此這篇關(guān)于springboot中請求地址轉(zhuǎn)發(fā)的兩種方案的文章就介紹到這了,更多相關(guān)springboot 請求地址轉(zhuǎn)發(fā)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
RocketMq同組消費(fèi)者如何自動設(shè)置InstanceName
這篇文章主要介紹了RocketMq同組消費(fèi)者如何自動設(shè)置InstanceName問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
Java中通過ZipOutputStream類如何將多個(gè)文件打成zip
ZipOutputStream?是Java中用于創(chuàng)建ZIP文件的類,它是?java.util.zip?包中的一部分,通過使用?ZipOutputStream?,可以將多個(gè)文件壓縮到一個(gè)ZIP文件中,這篇文章主要介紹了Java中(ZipOutputStream)如何將多個(gè)文件打成zip,需要的朋友可以參考下2023-09-09
詳解Spring依賴注入的三種方式使用及優(yōu)缺點(diǎn)
這篇文章主要介紹了spring依賴注入的三種方式的使用方法,以及優(yōu)缺點(diǎn)的介紹,通過代碼示例介紹的非常詳細(xì),感興趣的小伙伴可以參考一下2023-04-04
java分析html算法(java網(wǎng)頁蜘蛛算法示例)
近來有些朋友在做蜘蛛算法,或者在網(wǎng)頁上面做深度的數(shù)據(jù)挖掘,下面使用示例2014-03-03
Java異常處理3個(gè)避坑神操作總結(jié)(告別崩潰式報(bào)錯(cuò))
日常Java開發(fā)中,有些細(xì)節(jié)看似微小,卻能大幅影響代碼質(zhì)量,這篇文章主要介紹了Java異常處理3個(gè)避坑神操作總結(jié)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2026-04-04

