Spring Cloud(五)微服务网关Zuul

Zuul简介

Zuul的主要功能是路由转发和过滤器。路由是微服务体系结构的一个组成部分。例如,/可以映射到您的Web应用程序,/api/users映射到用户服务,并将/api/shop映射到商店服务。Zuul是Netflix的基于JVM的路由器和服务器端负载均衡器。

感兴趣的朋友可以访问: Spring Cloud中文网 里面有详细的介绍。

Zuul的使用

网关路由

后面的所有文章基本上都在前面的几篇博文中进行修改,所以,不明白的朋友,可以查看之前的文章。
网关作为一个独立的服务,所以,要创建一个新的Module,命名为api-gateway,然后需要导入相关的约束。

api-gateway的pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.root.project</groupId>
    <artifactId>api-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>api-gateway</name>
    <packaging>jar</packaging>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.root.project</groupId>
        <artifactId>springcloud-project</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

然后在启动类上加入@EnableZuulProxy注解,表示开启Zuul功能

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }

}

接着,就是将它往注册中心注册,需要在yml文件中增加相应的配置。

spring:
  application:
    name: api-gateway

server:
  port: 9999

#指定注册中心
eureka:
  client:
    service-url:
      defaultzone: http://localhost:8761/eureka/

然后,网关就添加完成了,是不是很简单呢?接下来,我们启动所有服务,进行测试。
在这里插入图片描述
在启动服务的时候,出现了问题,原因是:Spring Cloud和Spring Boot的版本不一致。博主使用的Spring Boot版本是2.1.1.RELEASE,Spring Cloud是Finchley.RELEASE,而这个版本对应的是Spring Boot2.0.x。所以我就将Spring Cloud换成了Greenwich.RELEASE版本,成功的解决了问题。如果出现上面问题,可以去官网看它们之间版本是如何对应的。

接着重新启动服务,一切正常,通过网关来访问服务。我这里的网关端口号是:9999。地址是:http://localhost:9999/user-service/v1.0/user/1 网关端口号+服务名称+接口地址。
在这里插入图片描述
可以看到,这个和调用自身服务(http://localhost:8802/v1.0/user/1),返回的结果是一样的。但是,这样有一个弊端,每次访问,都需要加上服务名称,服务庞大的时候,这是非常繁琐的。我们想让/api/user映射到用户服务,并将/api/order映射到订单服务。这该怎么实现呢?
接下来就要用到Zuul的路由了,需要在yml配置文件中给各个服务配置相应的路由策略。

zuul:
  routes:
    order-service: /api/order/**
    admin-service: /api/admin/**
    user-service: /api/user/**
#  #忽略下面正则规则的访问
  ignored-patterns: /*-service/**
#
#  #处理http请求头为空的问题,默认将"Cookie", "Set-Cookie", "Authorization"过滤,所以获取不到Cookie
  sensitive-headers:

新增上面的配置,zuul.routes配置的是路由策略,就是把order-service映射成以/api/order/开头,**是匹配所有。
需要注意的是,不能把所有服务都映射成相同的地址。因为routes对应的源码是一个Map集合,Map中键相同的,越底下的值会把上面的值覆盖。
ignored-patterns:是用正则的方式忽略不必要的请求。既然,已经通过映射给各个服务配置了相应的路由,就把原来的服务名称访问的方式忽略掉。至于sensitive-headers,后面再讲。

配置完成,就可以拿映射后的路径来访问了,重启服务,访问地址:http://localhost:9999/api/order/v1.0/order?userId=3
在这里插入图片描述
得到结果是一样的。

现在来说一下sensitive-headers这个配置,在源码中,它是一个LinkedHashSet集合,意思是将"Cookie", “Set-Cookie”, "Authorization"过滤掉。如果说,你在Cookie里存放了一些信息,不去配置这个,使其为空,就不能拿到Cookie中的值。下面通过代码来证明吧。

先看没有配置之前的响应,将sensitive-headers这个配置注释掉,然后对用户服务进行修改。

    private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);

    @GetMapping(value = "/user")
    public Result findAll(HttpServletRequest request) {
        String token = request.getHeader("token");
        String cookie = request.getHeader("cookie");
        LOGGER.info("token:{}",token);
        LOGGER.info("cookie:{}",cookie);
        Map<Long, User> map = userService.findAll();
        return ResultBean.success("查询全部用户", map);
    }

通过打日志的方式,给大家展示,启动服务。用postman工具进行测试。

在这里插入图片描述
在Headers中输入token和cookie,传递到后台,让后台接收。
在这里插入图片描述
通过日志,可以看到,明明Headers中有设置cookie,但是后台接收的却显示为null,token是有值的。
然后,把sensitive-headers的注释去掉,重启服务。
在这里插入图片描述
结果,不出意外,成功的打印了token和cookie的值。路由基本上就讲到这里,接下给大家讲Zuul的过滤。

服务过滤

假设案例:管理员需要登录之后,将鉴权的token传递到后台,才能访问用户服务。反之,则失败。

按照以前的实现方法,是将登陆后的信息放入session中,然后再对session中的信息进行判断。但是,如果服务过多,难道所有服务都要去判断一遍?此时,Zuul的过滤器就体现了一种“切面”的思想。直接在网关层,对服务进行权限的判断。

要想实现过滤的功能,就要自己写过滤器,来继承ZuulFilter,和以前在Servlet中一样,当然Spring Boot中也是有过滤器的。下面,就写一个处理登录请求的过滤器。

LoginFilter

package com.root.project.apigateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/**
 * @ClassName: LoginFilter
 * @Author: 清风一阵吹我心
 * @Description: TODO  新建类实现ZuulFilter 。将此类注入spring中
 * @Date: 2019/2/13 10:35
 * @Version 1.0
 **/
@Component
public class LoginFilter extends ZuulFilter {

    /**
     * FilterConstants这个类中定义了type的常量。
     * pre:前置
     * post: 后置
     * error: 发生错误
     * route: 路由时
     *
     * @return
     */
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    /**
     * filterOrder 过滤器执行顺序,越小越先执行
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 4;
    }

    /**
     * false: 不生效
     * true: 生效
     * 可以通过一定逻辑判断过滤器是否生效
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String admin = "/api/admin/v1.0/admin";
/*        if (admin.equalsIgnoreCase(request.getRequestURI())){
            return true;
        }
        return false;*/
        return admin.equalsIgnoreCase(request.getRequestURI());
    }

    /**
     * 处理逻辑
     *
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String token = request.getHeader("token");
        //HttpStatus枚举http状态码
        if (StringUtils.isBlank(token)) {
            context.setSendZuulResponse(false);
            context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            HttpServletResponse response = context.getResponse();
            try {
                response.setContentType("text/html;charset=UTF-8");
                response.getWriter().write("接口调用失败,token为空");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

基本上,所有的方法上都写明了注释,大家在进行测试的时候,可以进入每一个方法的源码中进行查看。token为空,就不让服务继续调用了,同时返回一个指定的状态码,并在页面中返回“接口调用失败,token为空”的字符串,增加用户体验。HttpStatus这个工具类,以枚举的方式,封装了各种Http状态码,大家可以去看一下它的使用方法。

顺便给大家拓展一下知识。

	if (admin.equalsIgnoreCase(request.getRequestURI())){
            return true;
    }
    return false;

以前大家写if的时候,是不是成立就返回true,失败就返回false。为了可读性,其实可以用下面的方式,替换上面的结构。

	return admin.equalsIgnoreCase(request.getRequestURI());

可以看到,一句话就解决了问题。当然,这是给不知道的朋友扩展的知识,知道的,就当复习一遍了。

过滤器写完,就该测试是否可行了。然后重启服务。为了方便测试,还是使用postman工具。
在这里插入图片描述
可以看到,这是没有token返回的结果,过滤器阻止了服务的调用。接下来,将token传递到后台,再看结果。
在这里插入图片描述
返回了正确的响应。这就是Zuul的过滤功能。讲到这里,Zuul的基本功能就完成了,更深层次的东西,还需要大家自己去挖掘。努力吧!各位志同道合的猿友。希望你们在码代码的路上,越走越远。

爱生活,爱清风,更爱自己。

猜你喜欢

转载自blog.csdn.net/qq_32101993/article/details/87191769