从零搭建Spring Cloud Gateway网关(一)

  • 2020 年 3 月 18 日
  • 筆記

新建Spring Boot项目

怎么新建Spring Boot项目这里不再具体赘述,不会的可以翻看下之前的博客或者直接百度。这里直接贴出对应的pom文件。

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 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.2.5.RELEASE</version>          <relativePath/> <!-- lookup parent from repository -->      </parent>      <groupId>com.lifengdi</groupId>      <artifactId>gateway</artifactId>      <version>0.0.1-SNAPSHOT</version>      <name>gateway</name>      <description>Demo project for Spring Boot</description>        <properties>          <java.version>1.8</java.version>          <spring-cloud.version>Hoxton.SR3</spring-cloud.version>      </properties>        <dependencies>          <dependency>              <groupId>org.springframework.cloud</groupId>              <artifactId>spring-cloud-starter-gateway</artifactId>          </dependency>            <dependency>              <groupId>org.projectlombok</groupId>              <artifactId>lombok</artifactId>              <version>1.18.12</version>              <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>            <dependency>              <groupId>org.springframework.cloud</groupId>              <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>          </dependency>            <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-actuator</artifactId>          </dependency>    <!--        <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-data-redis-reactive</artifactId>          </dependency>            <dependency>              <groupId>org.springframework.session</groupId>              <artifactId>spring-session-data-redis</artifactId>          </dependency>-->            <dependency>              <groupId>org.apache.commons</groupId>              <artifactId>commons-pool2</artifactId>          </dependency>            <dependency>              <groupId>commons-io</groupId>              <artifactId>commons-io</artifactId>              <version>2.5</version>              <scope>compile</scope>          </dependency>            <dependency>              <groupId>com.alibaba</groupId>              <artifactId>fastjson</artifactId>              <version>1.2.58</version>          </dependency>            <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-mail</artifactId>          </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>

由于是网关项目,所以不需要spring-boot-starter-web相关的依赖。

配置文件如下:

server:    port: 8080  spring:    application:      name: spring-cloud-gateway-demo    cloud:      gateway:        discovery:          locator:            enabled: true #启用路由访问        routes:          - id: path_route            # 指定域名            uri: http://localhost:8081            predicates:              - Path=/jar/**            filters:              # 熔断配置              - name: Hystrix                args:                  name: default                  fallbackUri: forward:/fallback          - id: path_route2            # 指定域名            uri: http://localhost:8082            predicates:              - Path=/war/**            filters:              # 熔断配置              - name: Hystrix                args:                  name: hystrix1                  fallbackUri: forward:/fallback      mvc:      throw-exception-if-no-handler-found: true    # 默认熔断超时时间30s  hystrix:    command:      default:        execution:          isolation:            thread:              timeoutInMilliseconds: 3000      hystrix1:        execution:          isolation:            thread:              timeoutInMilliseconds: 1000

熔断(接口或者项目)

熔断相关jar包如下:

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

默认的熔断回调接口:

package com.lifengdi.gateway.hystrix;    import com.lifengdi.gateway.exception.BaseException;  import com.lifengdi.gateway.response.ResponseResult;  import lombok.extern.slf4j.Slf4j;  import org.springframework.web.bind.annotation.RequestMapping;  import org.springframework.web.bind.annotation.RestController;    /**   * @author: Li Fengdi   * @date: 2020-03-18 16:35   */  @RestController  @Slf4j  public class DefaultHystrixController {      @RequestMapping("/fallback")      public ResponseResult<Object> fallback(){            log.error("触发熔断......");          return ResponseResult.fail(BaseException.DEFAULT_HYSTRIX.build());      }  }

具体配置文件说明如下:

      routes:          - id: path_route            # 指定域名            uri: http://localhost:8081            predicates:              - Path=/jar/**            filters:              # 熔断配置              - name: Hystrix                args:                  name: default                  fallbackUri: forward:/fallback          - id: path_route2            # 指定域名            uri: http://localhost:8082            predicates:              - Path=/war/**            filters:              # 熔断配置              - name: Hystrix                args:                  name: hystrix1                  fallbackUri: forward:/fallback      mvc:      throw-exception-if-no-handler-found: true    # 默认熔断超时时间30s  hystrix:    command:      default:        execution:          isolation:            thread:              timeoutInMilliseconds: 3000      hystrix1:        execution:          isolation:            thread:              timeoutInMilliseconds: 1000

defaulthystrix1为自定义的参数,可以配置多个熔断策略,不同的接口、服务可以单独配置对应的超时时间,不需要额外的进行开发,不过需要增加额外的配置文件。

全局session共享

依赖jar包:

<dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-data-redis-reactive</artifactId>  </dependency>    <dependency>      <groupId>org.springframework.session</groupId>      <artifactId>spring-session-data-redis</artifactId>  </dependency>    <dependency>      <groupId>org.apache.commons</groupId>      <artifactId>commons-pool2</artifactId>  </dependency>    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-security</artifactId>  </dependency>

相关yml配置:

spring:    redis:      database: 0      host: localhost      port: 6379      password: 123456      lettuce:        pool:          max-active: 300          max-idle: 8          max-wait: -1ms          min-idle: 0    session:      store-type: redis

spring.session.store-typeSpring默认就是redis实现的,也有其他的,配置不同罢了。

增加代码如下:

权限相关,这里默认全部放行:

package com.lifengdi.gateway.config;    import org.springframework.context.annotation.Bean;  import org.springframework.context.annotation.Configuration;  import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;  import org.springframework.security.config.web.server.ServerHttpSecurity;  import org.springframework.security.web.server.SecurityWebFilterChain;    @Configuration  @EnableWebFluxSecurity  public class GatewaySecurityConfig {      @Bean      SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity serverHttpSecurity)              throws Exception {          serverHttpSecurity                  .csrf().disable()                  .authorizeExchange().pathMatchers("/**").permitAll()                  .anyExchange()                  .authenticated();          return serverHttpSecurity.build();      }  }  

session相关:

package com.lifengdi.gateway.config;    import com.lifengdi.gateway.resolver.MyCookieWebSessionIdResolver;  import org.springframework.context.annotation.Bean;  import org.springframework.context.annotation.Configuration;  import org.springframework.http.ResponseCookie;  import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;  import org.springframework.web.server.session.CookieWebSessionIdResolver;  import org.springframework.web.server.session.WebSessionIdResolver;    import java.util.function.Consumer;    @Configuration  @EnableRedisWebSession(maxInactiveIntervalInSeconds = 10*60*60, redisNamespace = "my:spring:session")  public class WebSessionConfig {        @Bean      public WebSessionIdResolver webSessionIdResolver() {          CookieWebSessionIdResolver resolver = new MyCookieWebSessionIdResolver();          resolver.setCookieName("SESSIONID");            Consumer<ResponseCookie.ResponseCookieBuilder> consumer = responseCookieBuilder -> {              responseCookieBuilder.path("/");          };          resolver.addCookieInitializer(consumer);          return resolver;      }    }  

注意这里使用的是@EnableRedisWebSession注解,而不是@EnableRedisHttpSession,这个是和zuul不一样的地方。

用zuul做网关的时候,直接使用@EnableRedisHttpSession在配置里面就可以通过redis共享session信息

Spring同时提供了@EnableRedisWebSession来对WebFlux的支持。

值得一提的是这两个注解内部实现并不相同,需要自定义的配置也不一样。

这里自定义cookieName、path等是自定义了webSessionIdResolver来实现的,而不是cookieSerializer。如果使用cookieSerializer的话,对@EnableRedisWebSession来说是不起作用的。这个坑之前坑了好半天!

MyCookieWebSessionIdResolver代码如下:

package com.lifengdi.gateway.resolver;    import lombok.extern.slf4j.Slf4j;  import org.springframework.http.HttpCookie;  import org.springframework.session.web.http.DefaultCookieSerializer;  import org.springframework.util.MultiValueMap;  import org.springframework.web.server.ServerWebExchange;  import org.springframework.web.server.session.CookieWebSessionIdResolver;    import java.util.Base64;  import java.util.Collections;  import java.util.List;  import java.util.stream.Collectors;    /**   * 自定义WebSessionId解析器,以兼容{@link org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession}   * <p>   * 使用EnableRedisHttpSession时{@link DefaultCookieSerializer}中useBase64Encoding默认为true,将cookie中的sessionId使用base64   * 加密,但是如果使用{@link org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession},默认   * 的解析器没有将sessionId解密,导致获取不到正确的session   * </p>   *   * @author: Li Fengdi   * @date: 2020/3/16 15:41   */  @Slf4j  public class MyCookieWebSessionIdResolver extends CookieWebSessionIdResolver {        @Override      public List<String> resolveSessionIds(ServerWebExchange exchange) {          MultiValueMap<String, HttpCookie> cookieMap = exchange.getRequest().getCookies();          List<HttpCookie> cookies = cookieMap.get(getCookieName());          if (cookies == null) {              return Collections.emptyList();          }          return cookies.stream().map(HttpCookie::getValue).map(this::base64Decode).collect(Collectors.toList());      }        /**       * base64解码       *       * @param base64Value base64Value       * @return 解码后的字符串       */      private String base64Decode(String base64Value) {          try {              byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value);              return new String(decodedCookieBytes);          } catch (Exception ex) {              log.debug("Unable to Base64 decode value: " + base64Value);              return null;          }      }    }

其实这段代码本就是参考了cookieSerializer中的代码来实现的。

如果指定了useBase64Encoding为false,即不加密sessionId,那么就不需要这一段代码了。

代码已上传到git上,需要的可以去看看。

git代码地址:https://github.com/lifengdi/spring-cloud-gateway-demo

原文地址:https://www.lifengdi.com/archives/article/1776