springCloud学习【三】:路由网关Gateway+自定义路由过滤,全局过滤,全局日志【实例】

☆本文章的代码地址
★项目完整代码——gitee地址:不断更新
本文基于上一篇文章Demo代码编写

网关路由(个人理解)点赞不收啊啊啊啊啊啊啊

根据其他各位大佬的Blog和官网解释自己总结:

1、如上一篇blog说写的,ribbon+resttemplate方式消费服务或者feign方式消费服务,都需要指定一个动态的ip:port/映射路径/具体的接口
2、麻烦,有了网关这东西,各种服务哪怕IP和端口不一样,让他请求同一个IP+端口。
3、举例:gatewayIP:port/service/consume/getUser

gatewayIP是网关的总IP。
port是网关的端口。
service不同服务新增的一个context-path路径
consume/getUser 还是原来的请求路径,
综合起来就是网关IP+网管端口+不同的服务名+服务下的请求路径
有可能呢,用户userService服务有3个相同的节点,订单orderServcie服务呢有5个相同的节点。那就是8个服务了,但是只需要改gatewayIP:port/service/consume/getUser标红的部位便可,在提取一下到一个常量里,要修改的话不就是只该一个常量就可以了么
个人瞎理解,还请移步百度自行查询各种名词释义,有自己的理解便可
在使用网关的时候呢,还能对请求做一些过滤filter的效果。这篇文章练习网关Gataway网关,下一篇再练习Zuul网关

新建一个模块cloud-gateway项目

  1. 新建项目模块于之前方式一致,依赖有所改动。如下

在这里插入图片描述
pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.crazy</groupId>
    <artifactId>cloud-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cloud-gateway</name>
    <description>网关gateway-Demo</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
    </properties>

    <dependencies>
        <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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

</project>

gateway的配置文件

server:
  port: 8776                            # 设置服务提供者的端口号

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8770/eureka/  #注册到那里的注册中心

spring:
  application:
    name: Eureka-Gateway                # 网关项目模块所提供服务的名称
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true                 # 是否适用默认路由(通过gatewayUri:port/服务名/path直接访问服务接口)
          lower-case-service-id: true   # 是否忽略服务名大小写
      routes:                           # 路由,构建网关的基本模块,由ID,目标URI,一系列断言和过滤器组成,如果断言为true则匹配该路由
        # 第一个路由的作用是/service/**的路径全部路由到Eureka-Service服务去,会进行负载均衡。
        - id: gateway1                  # 路由ID 要求上下文唯一性【可以配置多个路由】
          uri: lb://Eureka-Service      # 目标服务(lb:貌似是负载均衡的含义loadbalancer)
          predicates:                   # 断言:可以配置多个谓词用来匹配请求的路径规则,匹配到了之后路由到uri的服务/网址去
            - Path=/service/**          # 访问gateway的路由【谓词,这里还有各种各样的谓词,详细不多练,用的时候去官网看,一大堆,这里用Path】
        # 第2个路由的左右,直接访问网关IP+端口的话,扔到baidu去,我没弄其他的服务,模拟有个baidu服务吧。空链接直接跳百度页面去
        - id: gateway2
          uri: http://www.baidu.com
          predicates:
            - Path=/
        # 第3个路由,自定义路由过滤器:package com.crazy.config.beans;看看每个请求的耗时多少ms

自定义路由过滤

看某个请求的用时

在这里插入图片描述
Beans.java

package com.crazy.config.beans;

import com.crazy.config.RequestTimeFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 * @Description 自定义过滤器路由3
 * @Author Crazy
 * @Date 2020/10/13
 * @Copyright
 */
@Configuration
public class Beans {

    /**
     * 看看/service/test/getUser请求的时间用了多少ms
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
        RouteLocator gateway2 = builder.routes()
                .route(r -> r.path("/service/test/getUser")
                        .filters(f -> f.filter(new RequestTimeFilter())
                                .addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
                        .uri("lb://Eureka-Service")
                        .order(0)
                        .id("gateway3")
                )
                .build();
        return gateway2;
    }
}

RequestTimeFilter.java 需要实现GatewayFilter, Ordered

package com.crazy.config;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;


/**
 * @Description 看看/service/test/string请求的时间用了多少ms
 * @Author Crazy
 * @Date 2020/10/13
 * @Copyright
 */
public class RequestTimeFilter implements GatewayFilter, Ordered {

    private static final Log logger = LogFactory.getLog(RequestTimeFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //定个开始开始时间
        exchange.getAttributes().put("beginTime",System.currentTimeMillis());
        return chain.filter(exchange).then(
                Mono.fromRunnable(()->{
                    Long beginTime = exchange.getAttribute("beginTime");
                    if (beginTime!=null){
                        logger.info(">>>>>>请求"+exchange.getRequest().getURI().getRawPath()+"耗时: "+(System.currentTimeMillis() - beginTime) + "ms");
                    }
                })
        );
    }

    @Override
    public int getOrder() {
        //定义优先级
        return 0;
    }

}

最后启动类不要忘了加上注解,还有服务提供者增加了一层上下文路径/service
**还好我是不用消费端了(暂时),要么消费端的也得去加/service,提交代码的时候在整整吧

在这里插入图片描述
在这里插入图片描述

然后我的启动项配置该了一下,不容易混乱,如下:要启动的名字格式,端口+服务名

在这里插入图片描述
依次启动我们需要的,一个registry,三个服务,一个网关即可
postman测试如下
路由:gateway1
在这里插入图片描述
在这里插入图片描述
路由:gateway2

在这里插入图片描述
路由:gateway3
在这里插入图片描述
在这里插入图片描述

自定义路由过滤器:校验token(重定向跳转)

  1. 根上面的那个过滤器一样,自定义个过滤器实现GatewayFilter, Ordered
    在这里插入图片描述
package com.crazy.config.beans.filter;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author Crazy
 * @Description 检查参数和token重定向uri
 * @date 2020/10/14-2:48
 * @Copyright HomeComputer
 */
public class CheckTokenFilter implements GatewayFilter, Ordered {
    private static final Logger logger = LoggerFactory.getLogger(CheckTokenFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求头Headers里面的token是否为空
        String token = exchange.getRequest().getHeaders().getFirst("token");
        //获取某一个参数,是否有传
        String str = exchange.getRequest().getQueryParams().getFirst("str");
        logger.info("传入的token:" + token + ",传入的参数str:" + str);
        if (StringUtils.isBlank(token) || StringUtils.isBlank(str)) {
            //重定向到某个页面或者某个提醒错误的服务请求地址,使用Http的请求头location字段
            String url = "http://www.baidu.com";
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.SEE_OTHER);
            response.getHeaders().set(HttpHeaders.LOCATION, url);
            return response.setComplete();
        }
        return chain.filter(exchange);
    }

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

  1. 修改Beans的代码,增加一个路由的配置信息,过滤器选择刚建的那个CheckTokenFilter
    在这里插入图片描述
/**
     * 检查/service/test/baidu请求token和参数。如果空跳转到百度去
     *
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator routeFilterToken(RouteLocatorBuilder builder) {
        RouteLocator gateway4 = builder.routes()
                .route(r -> r.path("/service/test/baidu")
                        .filters(f -> f.filter(new CheckTokenFilter())
                                .addRequestHeader("X-Response-Default-Foo", "Default-Bar"))
                        .uri("lb://Eureka-Service")
                        .order(0)
                        .id("gateway4")
                )
                .build();
        return gateway4;
    }

然后当访问网关IP:端口/service/test/baidu的时候,会被这个路由所匹配到,里面做的过滤器校验如果token携带了,并且参数也传了,就正常执行负载均衡到服务【lb://Eureka-Service】,如果却了一个参数就给他重定向到baidu去。

自定义全局过滤器:全局日志收集

全局过滤器是实现的GolbalFilter接口,不是GatewayFilter
(我是打到控制台,没写到文件去)
在这里插入图片描述
在这里插入图片描述

LogFilter.java

package com.crazy.config.beans.filter;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import com.alibaba.fastjson.JSON;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
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 java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Map;

/**
 * @Description 全局请求-响应日志打印  实现的 GlobalFilter
 * @Author Crazy
 * @Date 2020/10/14
 * @Copyright
 */
@Component
public class LogFilter implements GlobalFilter, Ordered {


    private static final Logger logger = LoggerFactory.getLogger(LogFilter.class);

    private static final String REQ_HEAD = "\n====================请求信息Begin====================";

    private static final String REQ_SPILT = "\n====================请求信息  End====================";

    private static final String RESP_HEAD = "\n====================响应信息Begin====================";

    private static final String RESP_SPLIT = "\n====================响应信息  End====================";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //计时器
        TimeInterval timer = DateUtil.timer();

        StringBuilder reqMsg = new StringBuilder();
        StringBuilder respMsg = new StringBuilder();

        // 获取请求的各项信息
        ServerHttpRequest req = exchange.getRequest();
        InetSocketAddress address = req.getRemoteAddress();
        String method = req.getMethodValue();
        URI uri = req.getURI();
        HttpHeaders headers = req.getHeaders();

        Map queryMap = req.getQueryParams();
        String query = JSON.toJSONString(queryMap);

        reqMsg.append(REQ_HEAD + "时间:" + DateUtil.now());
        reqMsg.append("\n 请求头=").append(headers);
        reqMsg.append("\n 参数信息=").append(query);
        reqMsg.append("\n 请求方式=").append(method);
        reqMsg.append("\n 请求路径=").append(uri.getPath());
        reqMsg.append(REQ_SPILT);
        logger.info(reqMsg.toString());

        //以下为响应
        ServerHttpResponse response = exchange.getResponse();
        DataBufferFactory bufferFactory = response.bufferFactory();
        respMsg.append(RESP_HEAD);
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
            @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);
                        String responseResult = new String(content, Charset.forName("UTF-8"));
                        respMsg.append("\n 响应状态码=").append(this.getStatusCode());
                        respMsg.append("\n 响应头信息=").append(this.getHeaders());
                        respMsg.append("\n 响应结果=").append(responseResult);
                        respMsg.append(RESP_SPLIT);
                        respMsg.append("耗时ms:").append(timer.intervalRestart());
                        logger.info(respMsg.toString());
                        return bufferFactory.wrap(content);
                    }));
                }
                return super.writeWith(body);
            }
        };

        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    @Override
    public int getOrder() {
        return -10;
    }

}

结束:总结

两种路由网关的区别:zuul和Gateway
Zuul:
阻塞式的 API,不支持长连接
底层是servlet,Zuul处理的是http请求
没有异步支持,流控等均由hystrix支持。
依赖包spring-cloud-starter-netflix-zuul。
Gateway:
底层是servlet,但使用了webflux,多嵌套了一层框架
依赖spring-boot-starter-webflux和/ spring-cloud-starter-gateway
提供异步支持,提供抽象负载均衡,提供抽象流控,并默认实现了RedisRateLimiter
相同点:底层都是servlet,都是web网关,处理http请求
不同点:
1、gateway比zuul多依赖了spring-webflux,变得更强大,还实现了限流和负载均衡等,但是也仅适合于springCloud这一套东西
2、zuul可以扩展到其他的微服务框架中,没有实现限流和负载均衡这些。
3、zuul不支持异步,gateway支持异步,gateway也具有更好的拓展性
4、Zuul的2.x版本基于Netty实现了非阻塞,支持长连接了

写在前面了更详细的总结写在Demo项目里面,在最上面

未完待续——如果看到这里了,(●'◡'●)该去点赞了啊喂

有空写完其他的服务相关的再来补充,啥玩意路由啊,断路器啊,消息总线啊,服务链路啊啥的。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值