一起来学SpringCloud之整合Zuul网关服务(二)

简介: 一起来学SpringCloud之整合Zuul网关服务(二)

微信截图_20230209163713.png


前言

大家好,一直以来我都本着用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 基础知识 的铺垫。目前正在出一个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语言写的,很轻量,方便集成,下期就带大家集成一下。 关注我,不迷路, 下期不见不散 ~


相关文章
|
9天前
|
监控 负载均衡 安全
微服务(五)-服务网关zuul(一)
微服务(五)-服务网关zuul(一)
|
20天前
|
消息中间件 存储 Java
SpringCloud基础9——服务异步通信-高级篇
消息可靠性、死信交换机、惰性队列、MQ集群
SpringCloud基础9——服务异步通信-高级篇
|
7天前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
20 5
|
9天前
|
Java API 对象存储
微服务魔法启动!Spring Cloud与Netflix OSS联手,零基础也能创造服务奇迹!
这段内容介绍了如何使用Spring Cloud和Netflix OSS构建微服务架构。首先,基于Spring Boot创建项目并添加Spring Cloud依赖项。接着配置Eureka服务器实现服务发现,然后创建REST控制器作为API入口。为提高服务稳定性,利用Hystrix实现断路器模式。最后,在启动类中启用Eureka客户端功能。此外,还可集成其他Netflix OSS组件以增强系统功能。通过这些步骤,开发者可以更高效地构建稳定且可扩展的微服务系统。
24 1
|
9天前
|
测试技术 微服务
微服务(八)-服务网关zuul(四)
微服务(八)-服务网关zuul(四)
|
9天前
|
监控 前端开发 Java
微服务(七)-服务网关zuul(三)
微服务(七)-服务网关zuul(三)
|
9天前
|
负载均衡 前端开发 安全
微服务(六)-服务网关zuul(二)
微服务(六)-服务网关zuul(二)
|
2月前
|
Java Spring 容器
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
|
9天前
|
SpringCloudAlibaba API 开发者
新版-SpringCloud+SpringCloud Alibaba
新版-SpringCloud+SpringCloud Alibaba
|
2月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
定时任务在企业应用中至关重要,常用于异步数据处理、自动化运维等场景。在单体应用中,利用Java的`java.util.Timer`或Spring的`@Scheduled`即可轻松实现。然而,进入微服务架构后,任务可能因多节点并发执行而重复。Spring Cloud Alibaba为此发布了Scheduling模块,提供轻量级、高可用的分布式定时任务解决方案,支持防重复执行、分片运行等功能,并可通过`spring-cloud-starter-alibaba-schedulerx`快速集成。用户可选择基于阿里云SchedulerX托管服务或采用本地开源方案(如ShedLock)
下一篇
无影云桌面