(六)Spring Cloud 网关Zuul

Zuul作为网关,在Spring Cloud中 常可以作为以下的作用使用

1. 和eureka-client,Ribbon,Feign结合可以实现智能路由和负载均衡的功能

2. 将所有服务的API统一聚合,外界调用API时由网关统一对外暴露,能起到保护API接口的作用

3. 网关可以做统一的身份和权限验证

 

 下面我们将对网关Zuul进行学习,主要有路由,负载均衡,熔断器,过滤器等,设计到的模块有

  eureka-server 7001 

  eureka-client  7002 7003

  eureka-ribbon-client 7004

  eureka-zuul-client  8000

 其中 eureka-server, eureka-client, eureka-ribbion-client 和我们前几章的module一样,为了加强记忆,我们再来做一次完整的配置

1. 新建maven项目eurekazuul

 删掉src目录,在pom中添加 

<packaging>pom</packaging>

2. 配置eureka-server

    2.1 pom.xml 

  

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>

   2.2 启动类

   

package com.devin.eurekaserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

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

}

 2.3 权限配置类

    

package com.devin.eurekaserver.config;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭csrf
        super.configure(http);
        //开启认证
        http.csrf().disable();
    }
}

 

   2.4  application.yml 配置文件

   eureka-server的启动端口 7001

server:
  port: 7001 #启动端口
spring:
  #应用名称
  application:
    name: eureka-server
  #安全配置
  security:
    basic:
      enabled: true
    user:
      name: dev
      password: 123456

#eureka配置
eureka:
  server:
    #设置扫描失效服务的间隔时间
    eviction-interval-timer-in-ms: 20000
    enable-self-preservation: true
  instance:
    hostname: localhost
    leaseRenewalIntervalInSeconds: 10
    health-check-url-path: /actuator/health
  client:
    register-with-eureka: false  #false:不作为一个客户端注册到注册中心
    fetch-registry: false        #为true时,可以启动,但报异常:Cannot execute request on any known server
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

# health endpoint是否必须显示全部细节。默认情况下, /actuator/health 是公开的,并且不显示细节。
# 设置actuator开关
management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: ALWAYS

 3. eureka-client配置

     3.1 pom.xml

   

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>

     3.2  启动类

    

package com.devin.eurekaclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class EurekaClientApplication {

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

}

 

     3.3 controller类

   

package com.devin.eurekaclient.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Devin Zhang
 * @className HelloController
 * @description TODO
 * @date 2020/3/31 10:26
 */
@RestController
public class HelloController {

    @Value("${server.port}")
    private String port;

    @GetMapping("/sayHello")
    public String sayHello(String name) {
        return "hello " + name + ", Warmly welcome from port:" + port;
    }
}

 

     3.4 application.yml 配置类

     后续eureka-client将启动2个实例,7002 , 7003 

  

eureka:
  auth:
    user: dev
    password: 123456
  client:
    serviceUrl:
      defaultZone: http://${eureka.auth.user}:${eureka.auth.password}@localhost:7001/eureka/
  instance:
    #使用IP进行注册
    prefer-ip-address: true
    #配置实例的注册ID
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    #心跳时间,即服务续约间隔时间(缺省为30s)
    lease-renewal-interval-in-seconds: 5
    #发呆时间,即服务续约到期时间(缺省为90s)
    lease-expiration-duration-in-seconds: 10
    health-check-url-path: /actuator/health
server:
  port: 7002
spring:
  application:
    name: eureka-client

4. eureka-ribbon-client

 新建该项目主要用于测试zuul网关路由到eureka-ribbon-client

   4.1 pom.xml 

    

  <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>

     4.2 启动类

        

package com.devin.eurekaribbonclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class EurekaRibbonClientApplication {

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

}

  

          4.3 RibbonConfig

        

package com.devin.eurekaribbonclient.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @author Devin Zhang
 * @className RibbonConfig
 * @description TODO
 * @date 2020/3/31 10:48
 */
@Configuration
public class RibbonConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

  

     4.4  service 通过RestTemplate去负载均衡调用eureka-client

      

package com.devin.eurekaribbonclient.service;

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

/**
 * @author Devin Zhang
 * @className HelloService
 * @description TODO
 * @date 2020/3/31 10:50
 */

@Service
public class HelloService {

    @Resource
    private RestTemplate restTemplate;

    public String sayHello(String name) {
        return restTemplate.getForObject("http://eureka-client/sayHello?name=" + name, String.class);
    }
}

    4.5 controller  

     

package com.devin.eurekaribbonclient.controller;

import com.devin.eurekaribbonclient.service.HelloService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author Devin Zhang
 * @className RibbonHelloController
 * @description TODO
 * @date 2020/3/31 10:50
 */
@RestController
public class RibbonHelloController {

    @Resource
    private HelloService helloService;

    @GetMapping("/sayHelloFromRibbon")
    public String sayHelloFromRibbon(String name) {
        return helloService.sayHello(name);
    }

}

  

 4.6 application.yml配置类

   eureka-ribbon-client的穷端口为7004 

   

eureka:
  auth:
    user: dev
    password: 123456
  client:
    serviceUrl:
      defaultZone: http://${eureka.auth.user}:${eureka.auth.password}@localhost:7001/eureka/
  instance:
    #使用IP进行注册
    prefer-ip-address: true
    #配置实例的注册ID
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    #心跳时间,即服务续约间隔时间(缺省为30s)
    lease-renewal-interval-in-seconds: 5
    #发呆时间,即服务续约到期时间(缺省为90s)
    lease-expiration-duration-in-seconds: 10
    health-check-url-path: /actuator/health
server:
  port: 7004
spring:
  application:
    name: eureka-ribbon-client

  5. eureka-zuul-client

     在eureka-zuul-client中定义了 熔断器,过滤器,以及路由的配置

     5.1 pom.yml

    

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

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>

    5.2 启动类

    

package com.devin.eurekazuulclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableEurekaClient
@EnableZuulProxy
@SpringBootApplication
public class EurekaZuulClientApplication {

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

}

 

   5.3 定义熔断器处理类,当通过zuul访问的服务不可用时,就进入熔断器方法

   

package com.devin.eurekazuulclient.service;

import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author Devin Zhang
 * @className BaseZuulFallbackProvider
 * @description TODO
 * @date 2020/3/31 11:05
 */
@Component
public class BaseZuulFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        //返回的为服务的名称,如果所有client都走这个熔断器的话则返回*
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("sorry, the system is busy now ,pls try later ,response by zuul hystrix".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

 

    5.4 定义filter,用于统一的权限校验等

    比如我们做简单的token不能为空校验

     

package com.devin.eurekazuulclient.filter;


import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;


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

/**
 * @author Devin Zhang
 * @className ZuulBaseFilter
 * @description TODO
 * @date 2020/3/31 11:18
 */
@Component
public class ZuulBaseFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String token = request.getParameter("token");
        if (token == null) {
            try {
                currentContext.setSendZuulResponse(false);
                currentContext.setResponseStatusCode(301);
                currentContext.getResponse().getWriter().write("token is empty!");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

  

  5.5 application.yml配置路由映射

    /zuulapi/ 的请求将被路由到eureka-client 服务

   /ribbonapi/ 的请求将被路由到 eureka-ribbon-client 服务

    

eureka:
  auth:
    user: dev
    password: 123456
  client:
    serviceUrl:
      defaultZone: http://${eureka.auth.user}:${eureka.auth.password}@localhost:7001/eureka/
  instance:
    #使用IP进行注册
    prefer-ip-address: true
    #配置实例的注册ID
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    #心跳时间,即服务续约间隔时间(缺省为30s)
    lease-renewal-interval-in-seconds: 5
    #发呆时间,即服务续约到期时间(缺省为90s)
    lease-expiration-duration-in-seconds: 10
    health-check-url-path: /actuator/health
server:
  port: 8000
spring:
  application:
    name: zuul-client
zuul:
  routes:
    zuulapi:
      path: /zuulapi/**
      serviceId: eureka-client
    ribbonapi:
      path: /ribbonapi/**
      serviceId: eureka-ribbon-client

  6. 测试

  启动eureka-server  7001

  启动eureka-client  7002 7003 

  java -jar eureka-client-0.0.1-SNAPSHOT.jar --server.port=7002

  java -jar eureka-client-0.0.1-SNAPSHOT.jar --server.port=7003

  启动eureka-ribbon-client 7004

  启动eureka-zuul-client  8000

  启动后的注册信息如下 http://localhost:7001/

  

  6.1 路由转发校验 

    分别访问

   http://localhost:8000/zuulapi/sayHello?name=devin&token=123

   http://localhost:8000/ribbonapi/sayHelloFromRibbon?name=devin&token=123

   可以看到已经路由到eureka-client 和 eureka-ribbon-client的接口

  6.2 token验证

 访问  http://localhost:8000/zuulapi/sayHello?name=devin 可以看到会提示token为空

 

 访问  http://localhost:8000/zuulapi/sayHello?name=devin&token=123 可以看到访问正常,

  说明请求已经被转发到eureka-client同时过滤器校验也已经通过,并且在7002和7003之间进行切换

 从访问结果可以看出,zuul自动在eureka-client之间进行了负载均衡

 

  同样访问 http://localhost:8000/ribbonapi/sayHelloFromRibbon?name=devin&token=123 也能得到同样的结果

 6.3 zuul熔断器验证

   分别停掉两台eureka-client,然后分别访问如下地址,可以看到zuul的熔断器已经生效

   http://localhost:8000/zuulapi/sayHello?name=devin&token=123

   http://localhost:8000/ribbonapi/sayHelloFromRibbon?name=devin&token=123

   

 

至此,Zuul网关搭建并验证完毕

 

   

猜你喜欢

转载自www.cnblogs.com/DevinZhang1990/p/12604589.html