一、引子

  • 二、Gateway设计思想
  • 三、Gateway简单使用
  • 四、总结
  •  

    正文

    一、引子

    2年前有幸使用过一次Spring Cloud (1.5.9),1.* 集成的是ZUUL做网关。终于在2年后,这次果断使用Spring Cloud Gateway。

    区别:

    Zuul构建于 Servlet 2.5,兼容 3.x,使用的是阻塞式的 API,不支持长连接,比如 websockets。

    Spring Cloud Gateway构建于 Spring 5+,基于 Spring Boot 2.x 响应式的、非阻塞式的 API。支持 websockets,和Spring 框架紧密集成。底层使用netty模型,性能极高。

    备注
    spring cloud 已放弃zuul 2.0,使用了自己的亲儿子gateway.后续估计也不会再集成2.0了,建议从zuul转向gateway.

    一个简单的创业项目架构图如下:

    二、Gateway设计思想

    2.1 官网设计

    自从撇开netflex zuul后,spring Cloud速度搜搜的。我开发时还是用2.1.4,目前最新已经到2.2.1,附上官网飞机票

    2.1.1 特性

    • Built on Spring Framework 5, Project Reactor and Spring Boot 2.0:基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0 

    • Able to match routes on any request attribute.:能匹配任意请求属性的路由

    • Predicates and filters are specific to routes.:针对特定路由使用匹配策略和过滤器

    • Hystrix Circuit Breaker integration. :集成Hystri断路器

    • Spring Cloud DiscoveryClient integration:集成服务发现(gateway一样可注册到eureka)

    • Easy to write Predicates and Filters:易于写策略(断言)+过滤器

    • Request Rate Limiting 请求限流

    • Path Rewriting:重写path

    简单来说就是Route、Predicate、Filter三大核心组件。

    2.1.2 流程图

    如上图,Gateway Client客户端发送请求在Gateway Handler Mapping中查找是否命中路由策略,命中的话请求转发给Gateway Web Handler来处理。根据定义的多个Filter链,执行顺序:Pre Filter->代理请求->Post Filter。

    2.1.3 内置Predicates+Filter

    Gateway内置了11个Predicates Factories路由策略(断言)工厂类。

    Filter分2类:

    • 31个GatewayFilter Factories网关过滤器工厂类
    • 10个GlobalFilter 全局过滤器接口

    这里就不在过多介绍,建议有需求时可以去官网找找,没有的话再自己开发。

    2.2 我们的使用

    1.使用Route结合Hystrix实现默认降级策略

    2.使用GatewayFilter接口,自定义过滤器类,实现登录态(token)校验

    三、Gateway简单使用

    3.1 实现微服务的默认降级策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    spring:
      cloud:
        gateway:
          discovery:
            locator:
              enabled: false
              #开启小写验证,默认feign根据服务名查找都是用的全大写
              lowerCaseServiceId: true
          default-filters:
            - AddResponseHeader=X-Response-Default-Foo, Default-Bar
          routes:
            - id: OLOAN-FINANCIAL-PRODUCT-SERVICE
              # lb代表从注册中心获取服务
              uri: lb://OLOAN-FINANCIAL-PRODUCT-SERVICE
              predicates:
                # 转发该路径
                - Path=/gateway/financialProduct/**
              # 带前缀
              filters:
                - StripPrefix=1
                - name: Hystrix
                  args:
                    name: fallbackcmd
                    fallbackUri: forward:/defaultfallback
            - id: ADMIN-SERVICE
              uri: lb://ADMIN-SERVICE
              predicates:
                - Path=/gateway/auth/**
              filters:
                - StripPrefix=2
                - name: Hystrix
                  args:
                    name: fallbackcmd
                    fallbackUri: forward:/defaultfallback

     如上图,我们开启了2个微服务route路由。

    • 1)前端请求时path带/gateway/,在gateway层使用StripPrefix=1,去掉gateway,最终微服务上的path不带"/gateway/".
    • 2)使用Hystrix实现默认降级策略,降级接口实现如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Slf4j
    @RestController
    public class DefaultHystrixController {
     
        @RequestMapping("/defaultfallback")
        public ApiResult defaultfallback(){
     
            log.info("服务降级中");
            return ApiResult.failure("服务异常");
        }
    }

     3.2 实现登录态(token)校验

    3.2.1 自定义过滤器

    自定义过滤器,实现GatewayFilter, Ordered 2个接口。

    复制代码
     1 import com.*.auth.UserTokenTools;
     2 import lombok.extern.slf4j.Slf4j;
     3 import org.apache.commons.lang3.StringUtils;
     4 import org.springframework.cloud.gateway.filter.GatewayFilter;
     5 import org.springframework.cloud.gateway.filter.GatewayFilterChain;
     6 import org.springframework.core.Ordered;
     7 import org.springframework.http.HttpHeaders;
     8 import org.springframework.http.HttpStatus;
     9 import org.springframework.http.server.reactive.ServerHttpRequest;
    10 import org.springframework.http.server.reactive.ServerHttpResponse;
    11 import org.springframework.stereotype.Component;
    12 import org.springframework.web.server.ServerWebExchange;
    13 import reactor.core.publisher.Mono;
    14 
    15 /**
    16  * @author denny
    17  * @Description token过滤器
    18  * @date 2019/12/12 13:55
    19  */
    20 @Slf4j
    21 @Component
    22 public class LoginTokenFilter implements GatewayFilter, Ordered {
    23 
    24     private static final String AUTHORIZE_TOKEN = "Authorization";
    25     private static final String BEARER = "Bearer ";
    26 
    27     /**
    28      * token过滤
    29      *
    30      * @param exchange
    31      * @param chain
    32      * @return
    33      */
    34     @Override
    35     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    36         log.info("当前环境已开启token校验");
    37         ServerHttpRequest request = exchange.getRequest();
    38         HttpHeaders headers = request.getHeaders();
    39         ServerHttpResponse response = exchange.getResponse();
    40         // 取Authorization
    41         String tokenHeader = headers.getFirst(AUTHORIZE_TOKEN);
    42         log.info("tokenHeader=" + tokenHeader);
    43         // token不存在
    44         if (StringUtils.isEmpty(tokenHeader)) {
    45             response.setStatusCode(HttpStatus.UNAUTHORIZED);
    46             return response.setComplete();
    47         }
    48         // 取token
    49         String token = this.getToken(tokenHeader);
    50         log.info("token=" + token);
    51 
    52         // token不存在
    53         if (StringUtils.isEmpty(token)) {
    54             log.info("token不存在");
    55             response.setStatusCode(HttpStatus.UNAUTHORIZED);
    56             return response.setComplete();
    57         }
    58         // 校验 token是否失效
    59         if (UserTokenTools.isTokenExpired(token, null)) {
    60             log.info("token失效");
    61             response.setStatusCode(HttpStatus.UNAUTHORIZED);
    62             return response.setComplete();
    63         }
    64         // 校验 token是否正确
    65         if (!UserTokenTools.checkToken(token, null)) {
    66             response.setStatusCode(HttpStatus.UNAUTHORIZED);
    67             return response.setComplete();
    68         }
    69 
    70 //        //有token 这里可根据具体情况,看是否需要在gateway直接把解析出来的用户信息塞进请求中,我们最终没有使用
    71 //        UserTokenInfo userTokenInfo = UserTokenTools.getUserTokenInfo(token);
    72 //        log.info("token={},userTokenInfo={}",token,userTokenInfo);
    73 //        request.getQueryParams().add("token",token);
    74         //request.getHeaders().set("token", token);
    75         return chain.filter(exchange);
    76     }
    77 
    78 
    79     @Override
    80     public int getOrder() {
    81         return -10;
    82     }
    83 
    84     /**
    85      * 解析Token
    86      */
    87     public String getToken(String requestHeader) {
    88         //2.Cookie中没有从header中获取
    89         if (requestHeader != null && requestHeader.startsWith(BEARER)) {
    90             return requestHeader.substring(7);
    91         }
    92         return "";
    93     }
    94 }
    复制代码

    上图中,UserTokenTools是我们自定义的一个JWT工具类,用来生成token,校验token过期、正确等。

    3.2.2 配置路由

    大家可根据具体情况,如果只有一套登录态,那就用一个filter即可。

    复制代码
     1 import com.*.gateway.filter.AuthorizeGatewayFilter;
     2 import com.*.gateway.filter.LoginTokenFilter;
     3 import org.springframework.cloud.gateway.route.RouteLocator;
     4 import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
     5 import org.springframework.context.annotation.Bean;
     6 import org.springframework.context.annotation.Configuration;
     7 
     8 
     9 @Configuration
    10 public class GatewayConfig {
    11 
    12     @Bean
    13     public RouteLocator getRouteLocator(RouteLocatorBuilder builder) {
    14         return builder.routes()
    15                 // token校验1
    16                 .route(predicateSpec -> predicateSpec
    17                         .path("/gateway/pay/card/**", "/gateway/app/**")
    18                         .filters(gatewayFilterSpec -> gatewayFilterSpec.stripPrefix(1).filter(new AuthorizeGatewayFilter()))
    19                         .uri("lb://OLOAN-PAY-SERVICE")
    20                         .id("OLOAN-PAY-SERVICE-token"))
    21 
    22                 // token校验2
    23                 .route(predicateSpec -> predicateSpec
    24                         .path("/gateway/order-audit/**", "/gateway/order/**", "/gateway/order-payment/**")
    25                         .filters(gatewayFilterSpec -> gatewayFilterSpec.stripPrefix(1).filter(new LoginTokenFilter()))
    26                         .uri("lb://OLOAN-ORDER-SERVICE")
    27                         .id("OLOAN-ORDER-ORDER-token"))
    28                 .build();
    29     }
    30 }
    复制代码

    四、总结

    4.1.WebFlux

    Spring Cloud Gateway使用WebFlux,和spring boot web包冲突,使用时一定记得pom中排除原来老WEB那一套(servlet)相关jar,否则会报错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

     

    4.2.Gateway Filter

    Gateway Filter 自带的源码支撑错误码response.setStatusCode(HttpStatus.UNAUTHORIZED);并不是那么的友好。错误码枚举使用的是spring自带框架的枚举类:

    org.springframework.http.HttpStatus:

    UNAUTHORIZED(401, "Unauthorized")

    这样请求返回的结构体和一般定义的JSON格式(code message data)不同。当然官方也是提供了解决方案。后续再去优化吧。

    4.3 限流

    gateway默认实现了几个简单的限流策略(依赖redis),后续可以使用一下。

     

    点赞(0)

    评论列表 共有 0 条评论

    暂无评论
    立即
    投稿
    发表
    评论
    返回
    顶部