springcloud简单微服务框架 | 第四章 网关(zuul与gateway)

网关可以做这些功能:参数校验、权限校验、流量监控、日志输出、协议转换、响应内容、响应头的修改,处理请求时发生错误时的逻辑等

一. 创建两种网关

1. 创建zuul网关

右键点击父项目,点击new,选择module),再选择maven,然后点击next
出现:
在这里插入图片描述
填写artifactid,然后点击next,最后点击finish

2. 引入依赖及配置

2.1 引入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">
    <parent>
        <artifactId>study-springcloudframework</artifactId>
        <groupId>com.lzr</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>study-zuul-gateway</artifactId>
    <name>zuul网关</name>
    <description>zuul网关</description>

    <properties>
        <!-- 其实应该放入父pom中,统一版本 -->
        <swagger-api-version>1.9.0.RELEASE</swagger-api-version>
    </properties>

    <dependencies>
        <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>
        </dependency>
        <dependency>
            <groupId>com.spring4all</groupId>
            <artifactId>swagger-spring-boot-starter</artifactId>
            <version>${swagger-api-version}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.google.guava</groupId>
                    <artifactId>guava</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
        </dependency>
        <!-- 限流 -->
        <dependency>
            <groupId>com.marcosbarbero.cloud</groupId>
            <artifactId>spring-cloud-zuul-ratelimit</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <!-- redis配合限流 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

</project>

2.2 application.yml文件配置

## 网关服务器端口
server:
  port: 81
## eureka
eureka:
  instance:
    ##eureka主机名,会在控制页面中显示
    hostname: localhost
    prefer-ip-address: true
    ## 心跳检测,检测与持续时间
    ## 发送心跳间隔时间 单位秒 (测试的时候设置小一点,让eureka服务端及时踢出该服务)
    lease-renewal-interval-in-seconds: 10
    ## eureka服务端收到最后一次心跳后等待时间上限 单位秒 (告诉eureka服务端按照规则执行)
    lease-expiration-duration-in-seconds: 20
    ##eureka服务器页面中status的请求路径(查看swagger页面的信息)
    status-page-url: http://${eureka.instance.hostname}:${server.port}/swagger-ui.html
  client:
    serviceUrl:
      defaultZone: http://lzr:123456@localhost:1111/eureka/
## 服务名
spring:
  application:
    name: gateway
  redis:
    host: 127.0.0.1
    password: 123456789
    port: 6379
    timeout: 20000
    database: 9
## zuul网关配置
zuul:
  routes:
    api-a:
      ## 以这个/consumer/ 开头,则转发到 消费者 服务器上
      path: /consumer/**
      serviceId: study-service-consumer
  ## 限流配置
  ratelimit:
    ## 对应用来标识请求的key的前缀
    key-prefix: gateway
    ## 开启
    enabled: true
    ## 存储方式(存进redis中)
    repository: redis
    ## 与X-Forwarded-For(简称XFF头,它代表客户端,也就是HTTP的请求端真实的IP)合用,
    ## 如果不设置为true 或者 XFF请求头不存在(客户端不发送),当使用origin规则的时候,redis中存储的条件统计使用的是本服务器ip地址
    behind-proxy: true
    policy-list:
      ## 注意这里的名称要与routes下的名称对应
      api-a:
        ## 每(refresh-interval)秒允许多少个请求
        - limit: 20
        ## 每个用户(也可以是路径,用户+路径+ip等,根据type来判断)对应的请求总时间的限制(秒)
        ## (就如:quota=10 时,当前用户一次请求花费1秒,那么该用户还能请求的总时间是9(10-1)秒)
          quota: 1000
        ## 刷新时间
          refresh-interval: 60
        ## 根据条件统计(路径)
          type:
            - user
            - origin
            - url
#    ## 默认配置(这个配置在policy-list中不存在时处理)
#    default-policy-list:
#      - limit: 2
#        quota: 1000
#        refresh-interval: 60
#        type:
#          - url
## 日志配置文件
logging:
  config: classpath:logback.xml

2.3 logback日志文件配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>logback</contextName>
    <property name="log.path" value="logs/study-zuul-gateway/logback/" />
    <property name="log.file" value="logs/study-zuul-gateway/logback.log" />

    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />

    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%contextName) [%thread] %clr(%-5level) %logger{36} - %msg%n" />

    <!--输出到控制台 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level>
            </filter> -->
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--输出到文件 -->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.file}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}%d{yyyy-MM-dd_HH}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <!-- 指定日志文件的上限大小,例如设置为1GB的话,那么到了这个值,就会删除旧的日志 -->
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>

</configuration>

2.3 启动类

package com.lzr;

import com.spring4all.swagger.EnableSwagger2Doc;
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;

/**
 * 网关(可以集群哟,用nginx或node就行,看项目需求)
 * @author lzr
 * @date 2019/11/18 0018 16:51
 */
@SpringBootApplication
@EnableEurekaClient// 注入到注册中心
@EnableZuulProxy// 开启zuul网关(默认集成了ribbon,启用负载均衡机制)
@EnableSwagger2Doc// swagger开启注解
public class ZuulGatewayApplication {

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

}

2.4 过滤器配置(示例)

2.4.1 请求前置过滤器
package com.lzr.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;

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

/**
 * @author lzr
 * @date 2019/11/18 0018 18:00
 */
@Log4j2
@Component
public class RequestFilter extends ZuulFilter {

    // 过滤逻辑
    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        log.info("网关[请求时]过滤器,请求方法:[{}],请求路径[{}]",request.getMethod(),request.getRequestURI());
        // 可以获取一般可以获取token数据进行验证
        /*String token = request.getHeader("token");
        if (token == null) {
            log.warn("token未传");
            //令zuul过滤该请求 不进行转发
            currentContext.setSendZuulResponse(false);
            //返回状态码 401
            currentContext.setResponseStatusCode(HttpServletResponse.SC_UNAUTHORIZED);
            //防止乱码
            currentContext.getResponse().setContentType("application/json;charset=UTF-8");
            //设置body
            currentContext.setResponseBody("token未传");
        }*/
        // 下面这个可以不需要,默认执行完所有过滤器
        /*currentContext.setSendZuulResponse(true);
        currentContext.set("isSuccess",true);
        currentContext.setResponseStatusCode(200);*/
        return null;
    }

    // 是否开启拦截
    @Override
    public boolean shouldFilter() {
        return true;
    }

    // 过滤类型
    // pre:表示在请求之前执行,在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等。
    // post:表示在请求之后执行,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等。
    // route: 在请求时被调用。
    // error: 处理请求时发生错误时被调用。
    @Override
    public String filterType() {
        return "pre";
    }

    // 过滤器执行顺序(当多个过滤器,配置过滤顺序(按照 小-》大 执行))
    @Override
    public int filterOrder() {
        return 0;
    }
}

2.4.2 请求响应过滤器
package com.lzr.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;

/**
 * 在请求之后执行
 * @author lzr
 * @date 2019/11/18 0018 18:00
 */
@Log4j2
@Component
public class ResponseFilter extends ZuulFilter {

    // 过滤逻辑
    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletResponse response = currentContext.getResponse();
        log.info("网关[响应返回]过滤器,contentType为:[{}],返回的状态为:[{}]",response.getContentType(),response.getStatus());
        // 返回的数据是以流的形式存在,也可以在这里处理多请求拦截(请求频繁)
        return null;
    }

    // 是否开启拦截
    @Override
    public boolean shouldFilter() {
        return true;
    }

    // 过滤类型
    // pre:表示在请求之前执行,在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等。
    // post:表示在请求之后执行,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等。
    // route: 在请求时被调用。
    // error: 处理请求时发生错误时被调用。
    @Override
    public String filterType() {
        return "post";
    }

    // 过滤器执行顺序(当多个过滤器,配置过滤顺序(按照 小-》大 执行))
    @Override
    public int filterOrder() {
        return 0;
    }
}

2.4.3 请求时发生错误调用执行过滤器
package com.lzr.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;

/**
 * 在请求时发生错误时被调用执行
 * @author lzr
 * @date 2019/11/20 0020 15:25
 */
@Log4j2
@Component
public class ErrorFilter extends ZuulFilter {

    // 执行逻辑
    @Override
    public Object run() throws ZuulException {// 可以将错误写入日志保存,方便查看
        log.info("发生错误调用!!");
        return null;
    }

    // 是否开启拦截
    @Override
    public boolean shouldFilter() {
        return true;
    }

    // 过滤类型
    // pre:表示在请求之前执行,在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等。
    // post:表示在请求之后执行,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等。
    // route: 在请求时被调用。
    // error: 处理请求时发生错误时被调用。
    @Override
    public String filterType() {
        return "error";
    }

    // 过滤器执行顺序(当多个过滤器,配置过滤顺序(按照 小-》大 执行))
    @Override
    public int filterOrder() {
        return 0;
    }
}

2.5 代码配置类

2.5.1 网关swagger配置
package com.lzr.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;
import java.util.List;

/**
 * 网关swagger配置
 * @author lzr
 * @date 2019/11/18 0018 15:30
 */
@Component
@Primary // SwaggerResourcesProvider自带一个实现类,防止多个实现实例引起报错,加上这个注解,代表,这才是唯一实现类
public class SwaggerResourceConfig implements SwaggerResourcesProvider {

    @Autowired
    private DiscoveryClient discoveryClient;// 获取注册中心所有实例(注意:这里获取的是缓存)

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> services = discoveryClient.getServices();
        // 动态设置
        for (String service : services) {// 注意:没有配置swagger的服务,在页面上会报“Failed to load API definition”,不需要就不管就行
            List<ServiceInstance> instances = discoveryClient.getInstances(service);
            for (ServiceInstance instance : instances) {
                resources.add(createSwaggerResource(instance.getInstanceId(),"/"+service+"/v2/api-docs","2.0"));
            }
        }
        // 静态设置(注意静态设置时,集群不会全部显现出来,只会出现一个,也是负载均衡产生的效果)
        //resources.add(createSwaggerResource("服务消费者","/study-service-consumer/v2/api-docs","2.0"));
        return resources;
    }

    /**
     * @param name 名称
     * @param location 路径
     * @param version 版本
     * @return
     */
    private SwaggerResource createSwaggerResource(String name,String location,String version){
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion(version);
        return swaggerResource;
    }
}

2.5.2 自定义被限流数据返回配置
package com.lzr.config;

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;

import java.util.HashMap;
import java.util.Map;

/**
 * 自定义被限流数据返回
 * @author lzr
 * @date 2019/11/19 0019 17:55
 */
@Component
public class CustomizeRateLimit extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
        if(errorAttributes.get("status").equals(429)){// 如果状态是429,则代表是多请求拦截(请求频繁),注意:这里的429必须是数字
            // 修改返回状态码为200
            webRequest.setAttribute("javax.servlet.error.status_code",200, RequestAttributes.SCOPE_REQUEST);
            Map<String, Object> result = new HashMap<>();
            result.put("code","-1");
            result.put("msg","请求频繁,请休息一下");
            result.put("data",null);
            return result;
        }
        return errorAttributes;
    }

}

2.5.3 自定义用户限流配置
package com.lzr.config;

import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitUtils;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.DefaultRateLimitKeyGenerator;
import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * 自定义 用户限流(配置了这里,其实可以不用设置type为user了)
 * @author lzr
 * @date 2019/11/20 0020 11:26
 */
@Log4j2
@Component
public class CustomizeRateLimitKeyGenerator extends DefaultRateLimitKeyGenerator {

    public CustomizeRateLimitKeyGenerator(RateLimitProperties properties, RateLimitUtils rateLimitUtils) {
        super(properties, rateLimitUtils);
    }

    /**
     * 数据是json格式不能得到参数,数据是以流的方式传递,(表单提交方式可以得到参数)
     * 最好是得到token,然后获取用户id,
     * 在网关集群模式中,可以选择单独开一个redis服务器处理逻辑
     * 然后网关获取redis服务器中的数据,当然也需要根据项目大小
     */
    @Override
    public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
        return super.key(request, route, policy)+":"+request.getHeader("token");
    }
}

3. zuul网关项目结构图

在这里插入图片描述

二. 创建gateway网关

1. 创建springcloudgateway网关

右键点击父项目,点击new,选择module),再选择maven,然后点击next
出现:
在这里插入图片描述
填写artifactid,然后点击next,最后点击finish

2. 引入依赖及配置

2.1 引入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">
    <parent>
        <artifactId>study-springcloudframework</artifactId>
        <groupId>com.lzr</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>study-springcloud-gateway</artifactId>
    <name>gateway网关</name>
    <description>SpringCloudGateway网关</description>

    <properties>
        <!-- 其实应该放入父pom中,统一版本 -->
        <swagger-api-version>1.9.0.RELEASE</swagger-api-version>
    </properties>

    <dependencies>
        <!-- 这个依赖包含了webflux,所以不需要引入web包了,注意:如果引入会报错的哟(加载冲突) -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 这个限流jar包已经包含了redis以及连接池,不要要引入redis包了 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <!-- swagger依赖 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
    </dependencies>

</project>

2.2 application.yml文件配置

## 网关服务器端口
server:
  port: 82
## eureka
eureka:
  instance:
    ##eureka主机名,会在控制页面中显示
    hostname: localhost
    prefer-ip-address: true
    ## 心跳检测,检测与持续时间
    ## 发送心跳间隔时间 单位秒 (测试的时候设置小一点,让eureka服务端及时踢出该服务)
    lease-renewal-interval-in-seconds: 10
    ## eureka服务端收到最后一次心跳后等待时间上限 单位秒 (告诉eureka服务端按照规则执行)
    lease-expiration-duration-in-seconds: 20
    ##eureka服务器页面中status的请求路径(查看swagger页面的信息)
    status-page-url: http://${eureka.instance.hostname}:${server.port}/swagger-ui.html
  client:
    serviceUrl:
      defaultZone: http://lzr:123456@localhost:1111/eureka/
## 服务名
spring:
  application:
    name: gateway
  redis:
    host: 127.0.0.1
    password: 123456789
    port: 6379
    timeout: 20000
    database: 9
  cloud:
    gateway:
      discovery:
        locator:
          ## 是否可以通过其他服务的serviceId来转发到具体的服务实例。默认为false
          ## 为true,自动创建路由,路由访问方式:http://Gateway_HOST:Gateway_PORT/大写的serviceId/**,其中微服务应用名默认大写访问
          enabled: true
          ## 是否小写
          lower-case-service-id: true
      routes:
        ## 控制路由跳转到其他服务
        - id: consumer
          uri: lb://study-service-consumer
          predicates:
            ## 请求路径匹配规则
            - Path=/consumer/**
          ## 顺序执行过滤,注意过滤顺序!!
          filters:
            ## 如果使用RequestRateLimiter则是RequestRateLimiterGatewayFilterFactory
            ## 这里我使用自定义处理,其实大部分代码还是原代码,使用RateLimiter(该名称+GatewayFilterFactory就是bean名称)则是RateLimiterGatewayFilterFactory
            ## 重写 429 too many request 返回数据
            - name: RateLimiter
              args:
                ## 允许用户每秒处理多少个请求
                redis-rate-limiter.replenishRate: 1
                ## 允许在一秒钟内完成的最大请求数
                redis-rate-limiter.burstCapacity: 1
                ## 使用SpEL按名称引用bean(自己设置的bean)
                key-resolver: "#{@userKeyResolver}"
            ## 代表截取路径的个数
            - StripPrefix=1
## 日志配置文件
logging:
  config: classpath:logback.xml

2.3 logback日志文件配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>logback</contextName>
    <property name="log.path" value="logs/study-springcloud-gateway/logback/" />
    <property name="log.file" value="logs/study-springcloud-gateway/logback.log" />

    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />

    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%contextName) [%thread] %clr(%-5level) %logger{36} - %msg%n" />

    <!--输出到控制台 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level>
            </filter> -->
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--输出到文件 -->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.file}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}%d{yyyy-MM-dd_HH}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <!-- 指定日志文件的上限大小,例如设置为1GB的话,那么到了这个值,就会删除旧的日志 -->
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>

</configuration>

2.3 启动类

package com.lzr;

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

/**
 * springcloud-gateway网关
 * @author lzr
 * @date 2019/11/20 0020 16:41
 */
@EnableEurekaClient
@SpringBootApplication
public class SpringCloudGatewayApplication {

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

}

2.4 全局异常拦截

package com.lzr.exception;

import lombok.extern.log4j.Log4j2;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 全局exception拦截
 * @author lzr
 * @date 2019/12/02 0002 16:00
 */
@Log4j2
@Component
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {

    @Override
    public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) {
        log.info("GLOBAL EXCEPTION URL:{}", serverWebExchange.getRequest().getPath());
        if(serverWebExchange.getResponse().isCommitted()){// 如果已经提交,则不处理response
            return Mono.error(throwable);
        }
        // 未提交,则处理返回参数
        String msg = "";
        if (throwable instanceof NotFoundException) {
            msg = "{\"msg\":\"not found exception\"}";
        } else if (throwable instanceof ResponseStatusException) {
            msg = "{\"msg\":\"response status exception\"}";
        } else {
            msg = "{\"msg\":\"other exception\"}";
        }
        DataBufferFactory bufferFactory = serverWebExchange.getResponse().bufferFactory();
        serverWebExchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
        return serverWebExchange.getResponse().writeWith(Flux.just(bufferFactory.wrap(msg.getBytes())));
    }
}

2.5 提供者响应过滤器

package com.lzr.filter;

import lombok.extern.log4j.Log4j2;
import org.reactivestreams.Publisher;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadata;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.HttpMessageWriterResponse;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * 提供者响应过滤器
 * 请求到数据的时候,重写数据
 * @author lzr
 * @date 2019/12/3 0003 16:41
 */
@Log4j2
@Component
public class ProviderResponseFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        // 请求到数据的时候,重写数据
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {// 代表是请求过来的数据
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.map(dataBuffer -> {
                        byte[] content = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(content);
                        //释放掉内存
                        DataBufferUtils.release(dataBuffer);
                        // 这里就是处理成自己定义的数据,可以根据需求修改
                        String s = new String(content, Charset.forName("UTF-8"));
                        log.info("请求数据状态:[{}],请求得到数据:[{}]",originalResponse.getStatusCode().value(),s);
                        byte[] uppedContent = s.getBytes();
                        // 重新写入
                        return bufferFactory.wrap(uppedContent);
                    }));
                }
                // 不修改
                return super.writeWith(body);
            }
        };
        return chain.filter(exchange.mutate().response(decoratedResponse).build()).then();
    }

    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
    }
}

2.6 网关限流过滤工厂

重写 RequestRateLimiterGatewayFilterFactory,主要处理 429 too many request 返回数据为空的情况,代码基本和RequestRateLimiterGatewayFilterFactory一样,只是处理了响应为429的情况,注意:要使用这个,则需要将application配置文件中的spring->cloud->gateway->routes->filters->-name 改为你自定义的过滤工厂名称前缀(该名称+GatewayFilterFactory就是bean名称),如:自定义限流过滤工厂Bean名称为rateLimiterGatewayFilterFactory ,前缀就为:rateLimiter

package com.lzr.gatewayfilterfactory;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.HttpStatusHolder;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.Iterator;
import java.util.Map;

/**
 * 重写 RequestRateLimiterGatewayFilterFactory
 * 主要处理 429 too many request 返回数据为空的情况
 * @author lzr
 * @date 2019/12/04 0004 18:00
 */
@Primary
@Component
public class RateLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory<RateLimiterGatewayFilterFactory.Config> {

    public static final String KEY_RESOLVER_KEY = "keyResolver";
    private static final String EMPTY_KEY = "____EMPTY_KEY__";
    private final RateLimiter defaultRateLimiter;
    private final KeyResolver defaultKeyResolver;
    private boolean denyEmptyKey = true;
    private String emptyKeyStatusCode;

    public RateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
        super(RateLimiterGatewayFilterFactory.Config.class);
        this.emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();
        this.defaultRateLimiter = defaultRateLimiter;
        this.defaultKeyResolver = defaultKeyResolver;
    }

    public KeyResolver getDefaultKeyResolver() {
        return this.defaultKeyResolver;
    }

    public RateLimiter getDefaultRateLimiter() {
        return this.defaultRateLimiter;
    }

    public boolean isDenyEmptyKey() {
        return this.denyEmptyKey;
    }

    public void setDenyEmptyKey(boolean denyEmptyKey) {
        this.denyEmptyKey = denyEmptyKey;
    }

    public String getEmptyKeyStatusCode() {
        return this.emptyKeyStatusCode;
    }

    public void setEmptyKeyStatusCode(String emptyKeyStatusCode) {
        this.emptyKeyStatusCode = emptyKeyStatusCode;
    }

    public GatewayFilter apply(RateLimiterGatewayFilterFactory.Config config) {
        // 这里获得你自定义的 限流方式(用户限流,ip限流等)
        KeyResolver resolver = (KeyResolver)this.getOrDefault(config.keyResolver, this.defaultKeyResolver);
        RateLimiter<Object> limiter = (RateLimiter)this.getOrDefault(config.rateLimiter, this.defaultRateLimiter);
        boolean denyEmpty = (Boolean)this.getOrDefault(config.denyEmptyKey, this.denyEmptyKey);
        HttpStatusHolder emptyKeyStatus = HttpStatusHolder.parse((String)this.getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode));
        return (exchange, chain) -> {
            Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
            return resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap((key) -> {
                if (EMPTY_KEY.equals(key)) {
                    if (denyEmpty) {
                        ServerWebExchangeUtils.setResponseStatus(exchange, emptyKeyStatus);
                        return exchange.getResponse().setComplete();
                    } else {
                        return chain.filter(exchange);
                    }
                } else {
                    return limiter.isAllowed(route.getId(), key).flatMap((response) -> {
                        Iterator var4 = response.getHeaders().entrySet().iterator();

                        while(var4.hasNext()) {
                            Map.Entry<String, String> header = (Map.Entry)var4.next();
                            exchange.getResponse().getHeaders().add((String)header.getKey(), (String)header.getValue());
                        }

                        if (response.isAllowed()) {
                            return chain.filter(exchange);
                        } else {
                            // 重写 429 too many request 返回数据
                            ServerHttpResponse serverHttpResponse = exchange.getResponse();
                            String data = "{\"code\":\"429\",\"msg\":\"请求过快,请休息一下\",\"data\":\"\"}";
                            DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(data.getBytes());
                            serverHttpResponse.setStatusCode(HttpStatus.OK);// 还是设置为ok
                            serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
                            return serverHttpResponse.writeWith(Mono.just(buffer));
                        }
                    });
                }
            });
        };
    }

    private <T> T getOrDefault(T configValue, T defaultValue) {
        return configValue != null ? configValue : defaultValue;
    }

    public static class Config {
        private KeyResolver keyResolver;
        private RateLimiter rateLimiter;
        private HttpStatus statusCode;
        private Boolean denyEmptyKey;
        private String emptyKeyStatus;

        public Config() {
            this.statusCode = HttpStatus.TOO_MANY_REQUESTS;
        }

        public KeyResolver getKeyResolver() {
            return this.keyResolver;
        }

        public RateLimiterGatewayFilterFactory.Config setKeyResolver(KeyResolver keyResolver) {
            this.keyResolver = keyResolver;
            return this;
        }

        public RateLimiter getRateLimiter() {
            return this.rateLimiter;
        }

        public RateLimiterGatewayFilterFactory.Config setRateLimiter(RateLimiter rateLimiter) {
            this.rateLimiter = rateLimiter;
            return this;
        }

        public HttpStatus getStatusCode() {
            return this.statusCode;
        }

        public RateLimiterGatewayFilterFactory.Config setStatusCode(HttpStatus statusCode) {
            this.statusCode = statusCode;
            return this;
        }

        public Boolean getDenyEmptyKey() {
            return this.denyEmptyKey;
        }

        public RateLimiterGatewayFilterFactory.Config setDenyEmptyKey(Boolean denyEmptyKey) {
            this.denyEmptyKey = denyEmptyKey;
            return this;
        }

        public String getEmptyKeyStatus() {
            return this.emptyKeyStatus;
        }

        public RateLimiterGatewayFilterFactory.Config setEmptyKeyStatus(String emptyKeyStatus) {
            this.emptyKeyStatus = emptyKeyStatus;
            return this;
        }
    }

}

2.6 自定义限流配置

注意:要使用这个,则需要在application配置文件配置key-resolver: “#{@userKeyResolver}”
这里面的名称是自己定义的文件的bean名称

package com.lzr.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Primary;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;
import java.util.List;

/**
 * 自定义 用户限流
 * @author lzr
 * @date 2019/11/21 0021 10:59
 */
@Primary
@Component
public class UserKeyResolver implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getHeaders().getFirst("token");
        return Mono.just(token);
    }
}

2.7 swagger配置

2.7.1 swagger处理请求
package com.lzr.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;

import java.util.Optional;

/**
 * 网关swagger处理请求
 * @author lzr
 * @date 2019/11/21 0021 10:00
 */
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {

    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;

    @Autowired(required = false)
    private UiConfiguration uiConfiguration;

    private final SwaggerResourcesProvider swaggerResources;

    @Autowired
    public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
        this.swaggerResources = swaggerResources;
    }


    @GetMapping("/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping("/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping("")
    public Mono<ResponseEntity> swaggerResources() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }
}
2.7.2 swagger实例获取配置类
package com.lzr.config;

import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;
import java.util.List;

/**
 * 网关swagger配置
 * @author lzr
 * @date 2019/11/21 0021 10:00
 */
@Component
@Primary
public class SwaggerProvider implements SwaggerResourcesProvider {

    @Autowired
    private DiscoveryClient discoveryClient;// 获取注册中心所有实例(注意:这里获取的是缓存)

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> services = discoveryClient.getServices();
        // 动态设置(静态设置好一些)(集群方式,swagger的请求也会"负载均衡")
        // 注意:没有配置swagger的服务,在页面上会报“Failed to load API definition”,不需要就不管就行
        services.stream().forEach(service -> discoveryClient.getInstances(service).stream().
                forEach(instance -> resources.add(createSwaggerResource(instance.getInstanceId(),"/"+service+"/v2/api-docs","2.0"))));
        return resources;
    }

    /**
     * @param name 名称
     * @param location 路径
     * @param version 版本
     * @return
     */
    private SwaggerResource createSwaggerResource(String name,String location,String version){
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion(version);
        return swaggerResource;
    }
}

3. gateway网关项目结构图

在这里插入图片描述

五. 项目地址

项目 git地址 将在最后一章附上。
若有疑问,请在评论区留言,谢谢

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值