前言
大家好,一直以来我都本着用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 基础知识 的铺垫。目前正在出一个SpringCloud
长期系列教程,从入门到进阶, 篇幅会较多~
适合人群
- 有一定的Java基础
- 想尝试微服务开发
- 有SpringBoot开发基础
- 想学习或了解SpringCloud
- 想提高自己的同学
大佬可以绕过 ~
背景
如果你是一路看过来的,很高兴你能够耐心看完。之前带大家学了Springboot
这门框架,熟练掌握了单体应用
的开发,如今微服务
开发盛行,对我们的技术要求也是越来越高,薪资也是令人兴奋。这个系列将会带大家学习SpringCloud
微服务开发,我会带大家一步一步的入门,耐心看完你一定会有收获
~
情景回顾
上期带大家一起认识了Zuul微服务网关
以及带大家体验了它的常用配置,本期学习Zuul网关过滤器
,我们一起来看一下吧~
PRE过滤器
本期代码依然沿用上期的模块。在实现之前,先给大家简单普及一下它的生命周期的各个阶段,一个有四个阶段 pre、post、route和error
PRE
:PRE过滤器用于将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址,并做一些前置加工,比如请求的校验等;ROUTING
:ROUTING过滤器用于将外部请求转发到具体服务实例上去;POST
:POST过滤器用于将微服务的响应信息返回到客户端,这个过程种可以对返回数据进行加工处理;ERROR
:上述的过程发生异常后将调用ERROR过滤器。ERROR过滤器捕获到异常后需要将异常信息返回给客户端,所以最终还是会调用POST过滤器。
下面我们就手动实现一个pre
过滤器
@Component public class PreZuulFilter extends ZuulFilter { private final Logger log = LoggerFactory.getLogger(this.getClass()); /** * 对应Zuul生命周期的四个阶段:pre、post、route和error; */ @Override public String filterType() { return "pre"; } /** * 过滤器的优先级,数字越小,优先级越高; * @return */ @Override public int filterOrder() { return 1; } /** * 方法返回boolean类型,true时表示是否执行该过滤器的run方法,false则表示不执行; * @return */ @Override public boolean shouldFilter() { // 进行跨域请求的时候,并且请求头中有额外参数,比如token,客户端会先发送一个OPTIONS请求来探测后续需要发起的跨域POST请求是否安全可接受 // 所以这个请求就不需要拦截,下面是处理方式 RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); if (request.getMethod().equals(RequestMethod.OPTIONS.name())) { log.info("OPTIONS请求不做拦截操作"); return false; } return true; } /** * 过滤器的过滤逻辑 * @return */ @Override public Object run() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); String userToken = request.getHeader("apikey"); if (StringUtils.isBlank(userToken)) { log.warn("apikey为空"); sendError(requestContext, 99001, "请传输参数apikey", null); return null; } String host = request.getRemoteHost(); String method = request.getMethod(); String uri = request.getRequestURI(); log.info("请求URI:{},HTTP Method:{},请求IP:{}", uri, method, host); return null; } /** * 发送错误消息 * todo: 如果是重定向到本地 还是会正常返回对应的api数据,状态码会变成返回的状态码 * todo: 如果是转发到其它服务的, 会被拦截下来返回对应的错误信息 * * @param requestContext * @param status * @param msg */ private void sendError(RequestContext requestContext, int status, String msg, String userToken) { requestContext.setSendZuulResponse(false); //不对请求进行路由 requestContext.setResponseStatusCode(status);//设置返回状态码 requestContext.setResponseBody(JSONObject.toJSONString(msg));//设置返回响应体 requestContext.getResponse().setContentType("application/json;charset=UTF-8");//设置返回响应体格式,可能会乱码 } } 复制代码
我们要实现的逻辑是run()
部分,这个是不是很像我们之前给大家讲的Shiro拦截器
,我们可以借助这一过滤器实现自己的特定功能
POST 过滤器
- post过滤器可以在请求转发后获取请求信息和响应,这里给大家写一个例子,可以用来记录请求日志
@Component public class PostZuulFilter extends ZuulFilter { private final Logger log = LoggerFactory.getLogger(this.getClass()); @Override public String filterType() { return "post"; } @Override public int filterOrder() { return 2; } @Override public boolean shouldFilter() { // 进行跨域请求的时候,并且请求头中有额外参数,比如token,客户端会先发送一个OPTIONS请求来探测后续需要发起的跨域POST请求是否安全可接受 // 所以这个请求就不需要拦截,下面是处理方式 RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); if (request.getMethod().equals(RequestMethod.OPTIONS.name())) { log.info("OPTIONS请求不做拦截操作"); return false; } // 如果前面的拦截器不进行路由,那么后面的过滤器就没必要执行 if (!requestContext.sendZuulResponse()) { return false; } return true; } @Override public Object run() throws ZuulException { RequestContext requestContext = RequestContext.getCurrentContext(); InputStream stream = requestContext.getResponseDataStream(); if (stream == null) { return null; } HttpServletRequest request = requestContext.getRequest(); String requestParams = getRequestParams(requestContext, request); log.warn(requestParams); try { String responseBody = IOUtils.toString(stream); RequestContext.getCurrentContext().setResponseBody(responseBody); } catch (IOException e) { e.printStackTrace(); } return null; } //获取请求参数,适用于POST请求/GET请求,以及参数拼接在URL后面的POST请求 private String getRequestParams(RequestContext requestContext, HttpServletRequest request) { String requestParams = null; String requestMethod = request.getMethod(); StringBuilder params = new StringBuilder(); Enumeration<String> names = request.getParameterNames(); if (requestMethod.equals("GET")) { while (names.hasMoreElements()) { String name = (String) names.nextElement(); params.append(name); params.append("="); params.append(request.getParameter(name)); params.append("&"); } requestParams = params.delete(params.length() - 1, params.length()).toString(); } else { Map<String, String> res = new HashMap<>(); Enumeration<?> temp = request.getParameterNames(); if (null != temp) { while (temp.hasMoreElements()) { String en = (String) temp.nextElement(); String value = request.getParameter(en); res.put(en, value); } requestParams = JSON.toJSONString(res); } if (StringUtils.isBlank(requestParams) || "{}".equals(requestParams)) { BufferedReader br = null; StringBuilder sb = new StringBuilder(""); try { br = request.getReader(); String str; while ((str = br.readLine()) != null) { sb.append(str); } br.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } requestParams = sb.toString(); } } return requestParams; } } 复制代码
Error 过滤器
- error过滤器是在服务网关出现异常的时候起作用的
@Component public class ErrorZuulFilter extends ZuulFilter { private final Logger log = LoggerFactory.getLogger(this.getClass()); @Override public String filterType() { return "error"; } @Override public int filterOrder() { //需要在默认的 SendErrorFilter 之前 return 5; } @Override public boolean shouldFilter() { // 只有在抛出异常时才会进行拦截 return RequestContext.getCurrentContext().containsKey("throwable"); } @Override public Object run() { try { RequestContext requestContext = RequestContext.getCurrentContext(); Object e = requestContext.get("throwable"); if (e instanceof ZuulException) { ZuulException zuulException = (ZuulException) e; // 删除该异常信息,不然在下一个过滤器中还会被执行处理 requestContext.remove("throwable"); // 响应给客户端信息 HttpServletResponse response = requestContext.getResponse(); response.setHeader("Content-type", "application/json;charset=UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter pw = null; pw = response.getWriter(); pw.write(JSONObject.toJSONString("系统出现异常")); pw.close(); } } catch (Exception ex) { log.error("Exception filtering in custom error filter", ex); ReflectionUtils.rethrowRuntimeException(ex); } return null; } } 复制代码
上面就是常用的过滤器,大家可以启动服务请求一下试试, 也可以借助它实现一些自己想到的小功能,或者整合一些其它的工具库
结束语
本期到这里就结束了, 总结一下,本节主要讲了常用的Zuul网关过滤器
, 以及带大家实现了自定义的过滤器, 建议大家自己多去尝试 ~
下期预告
俗话说,没有仪表盘的车不敢开, 微服务也是如此,这么多的服务,我怎么知道它的链路调用关系,下期就给大家介绍一下它的链路追踪
方案, 其实开源的产品比较多,比如ZipKin,jaeger
, 这里给大家介绍jeager
, 它是uber
开源的,go语言写的,很轻量,方便集成,下期就带大家集成一下。 关注我,不迷路, 下期不见不散 ~