SpringCloud(三) Zuul

Zuul

有了eureka 、 feign 和 hystrix 后,基本上就搭建了简易版的分布式项目,但仍存在一些问题,比如:

1、如果我们的微服务中有很多个独立服务都要对外提供服务,那么我们要如何去管理这些接口?特别是当项目非常庞大的情况下要如何管理?

2、在微服务中,一个独立的系统被拆分成了很多个独立的服务,为了确保安全,权限管理也是一个不可回避的问题,如果在每一个服务上都添加上相同的权限验证代码来确保系统不被非法访问,那么工作量也就太大了,而且维护也非常不方便。

所以出现了网关,它就像一个安检站一样,所有外部的请求都需要经过它的调度与过滤,然后 API 网关来实现请求路由、负载均衡、权限验证等功能。

使用 Zuul 构建 API 网关

  1. 创建spring boot工程并添加依赖:

    <!--添加 spring cloud 的 zuul 的起步依赖--> <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
    <!--添加 spring cloud 的 eureka 的客户端依赖--> <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  2. 在入口类上添加@EnableZuulProxy 注解,开启 Zuul 的 API 网关服务功能:

    @SpringBootApplication
    @EnableEurekaClient
    @EnableZuulProxy
    public class SpringcloudZuulApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudZuulApplication.class, args);
        }
    
    }
    
  3. 在application.yml中添加路由规则

    # 配置路由规则
    zuul:
      routes:
        hello:
          path: /api-zuul/**
          serviceId: springcloud-consumer
    

    说明:以上配置的路由规则就是匹配所有符合/api-zuul/**的请求,只要路径中带有/api-zuul/都将被转发到 springcloud-consumer 服务上。比如:localhost:8766/api-zuul/web/hello 转发到 http://localhost:8764/web/hello

  4. 构建成功

使用 Zuul 进行请求过滤

  1. 定义一个过滤器类并继承自 ZuulFilter,并将该 Filter 作为一个 Bean:

    @Component
    public class AuthFilter extends ZuulFilter {
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
            RequestContext     ctx     = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            String             token   = request.getParameter("token");
            if (token == null) {
                ctx.setSendZuulResponse(false);
                ctx.setResponseStatusCode(401);
    
                ctx.addZuulResponseHeader("content-type","text/html;charset=utf-8");
                ctx.setResponseBody("非法访问");
            }
            return null;
        }
    }
    
    • filterType 方法的返回值为过滤器的类型,决定了过滤器在哪个生命周期执行,pre 表示在路由之前执行过滤器,其他值还有 post、error、route 和 static,当然也可以自定义。
    • filterOrder 方法表示过滤器的执行顺序,当过滤器很多时,我们可以通过该方法的返回值来指定过滤器的执行顺序。
    • shouldFilter 方法用来判断过滤器是否执行,true 表示执行,false 表示不执行。
    • run 方法则表示过滤的具体逻辑,如果请求地址中携带了 token 参数的话,则认为是合法请求,否则为非法请求,如果是非法请求的话,首先设置ctx.setSendZuulResponse(false),表示不对该请求进行路由,然后设置响应码和响应值。这个 run 方法的返回值目前暂时没有任何意义,可以返回任意值。
  2. 不携带token,localhost:8766/api-zuul/web/hello

  3. 携带token,localhost:8766/api-zuul/web/hello?token=213

Zuul 的路由规则

  1. 在前面的例子中,

    zuul:
      routes:
        hello:
          path: /api-zuul/**
          serviceId: springcloud-consumer
    

    当访问地址符合 /api-zuul/ 规则的时候,会被自动定位到springcloud-consumer 服务上,有点麻烦,还可以简化为:

    zuul:
      routes:
        springcloud-consumer: /api-zuul/**
    

    zuul.routes 后面跟着的是服务名,服务名后面跟着的是路径规则,这种配置方式更简单。

  2. 默认情况下,Eureka 上所有注册的服务都会被 Zuul 创建映射关系来进行路由。

    #默认的规则
    zuul.routes.springcloud-consumer.path=/springcloud-consumer/**
    zuul.routes.springcloud-consumer.serviceId=springcloud-consumer
    

    但如果希望 springcloud-service-provider 作为服务提供者只对服务消费者提供服务,不对外提供服务:

    zuul.ignored-services=springcloud-service-provider
    

    还可以进一步细化,比如不想给/hello 接口路由:

    zuul.ignored-patterns=/**/hello/**
    

    也可以统一的为路由规则增加前缀:

    zuul.prefix=/myapi
    

    路由规则通配符:

  3. 一般情况下 API 网关只是作为各个微服务的统一入口,但是有时候我们可能也需要在 API 网关服务上做一些特殊的业务逻辑处理,那么我们可以让请求到达 API 网关后,再转发给自己本身,由 API 网关自己来处理,那么我们可以进行如下的操作:

@RestController
public class GateWayController {
  @RequestMapping("/api/local")
  public String hello() {
  	return "exec the api gateway.";
  } 
}

在 application.yml 中:

zuul:
  routes:
    gateway:
      path: /gateway/**
      url: forward:/api/local

Zuul 的异常处理

首先看一下Zuul 请求的生命周期:

  • 正常情况下所有的请求都是按照 pre、route、post 的顺序来执行,然后由 post 返回 response
  • 在 pre 阶段,如果有自定义的过滤器则执行自定义的过滤器
  • pre、routing、post 的任意一个阶段如果抛异常了,则执行 error 过滤器
    有两种方式统一处理异常:
  1. 禁用 zuul 默认的异常处理 SendErrorFilter 过滤器,然后自定义我们自己的 Errorfilter 过滤器

    zuul:
      routes:
        springcloud-consumer: /api-zuul/**
      SendErrorFilter:
        error:
          disable: true
    
    @Component
    public class ErrorFilter extends ZuulFilter {
        private static final Logger logger =
                LoggerFactory.getLogger(ErrorFilter.class);
        @Override
        public String filterType() {
            return "error";
        }
        @Override
        public int filterOrder() {
            return 1;
        }
        @Override
        public boolean shouldFilter() {
            return true;
        }
        @Override
        public Object run() throws ZuulException {
            try {
                RequestContext context = RequestContext.getCurrentContext();
                ZuulException exception = (ZuulException)context.getThrowable();
                logger.error("进入系统异常拦截", exception);
                HttpServletResponse response = context.getResponse();
                response.setContentType("application/json; charset=utf8");
                response.setStatus(exception.nStatusCode);
                PrintWriter writer = null;
                try {
                    writer = response.getWriter();
                    writer.print("{code:"+ exception.nStatusCode +",message:\""+
                            exception.getMessage() +"\"}");
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if(writer!=null){
                        writer.close();
                    }
                }
            } catch (Exception e) {
                ReflectionUtils.rethrowRuntimeException(e);
            }
            return null;
        }
    }
    

    在 AuthFiler 里的run()方法添加异常 int i = 10 / 0

  2. 自定义全局 error 错误页面

    开启 zuul 默认的异常处理 SendErrorFilter 过滤器,并注释掉 ErrorFilter 类

@RestController
public class ErrorHandlerController implements ErrorController {
    /**
     * 出异常后进入该方法,交由下面的方法处理
     */
    @Override
    public String getErrorPath() {
        return "/error";
    }
    @RequestMapping("/error")
    public Object error(){
        RequestContext ctx       = RequestContext.getCurrentContext();
        ZuulException  exception = (ZuulException)ctx.getThrowable();
        return exception.nStatusCode + "--" + exception.getMessage();
    } 
}

posted @ 2021-12-26 00:03  西凉马戳戳  阅读(244)  评论(0编辑  收藏  举报