现有一个平台,如果在上面发布软件,需要在平台注册所有的接口,注册好后平台会给每一个接口都提供一个不同的新地址(所有的请求在平台注册后都是类似"http://localhost:8080/{appkey}/{token}"的格式,每个接口都拥有一个不同的appkey作为标识,token可通过另一个请求获取),在前端调用请求的时候,必须请求平台提供的地址,然后平台会替前端转发到真实的地址去请求后端。
为了减少注册和审核的工作量,我们可以只注册少量接口,然后在这些接口内我们自行转发。
在平台注册增删改查等若干个虚拟的接口地址,然后在前端将所有接口封装成这些虚拟接口,并在请求参数内传递真实的接口地址,通过平台转发到后端之后我们通过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传值或表单传值) MapparameterMap = 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的反射机制调用真实的接口地址,转发到对应的接口上。
举个例:
我们在平台上注册的地址:
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 Mapparams; }
/** * 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 staticT getBean(Class clazz) { return getApplicationContext().getBean(clazz); } //通过name,以及Clazz返回指定的Bean public static T getBean(String name, Class 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(); Mapparams = 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); } }
上一篇:Tomcat下载安装及配置教程