【若依SpringCloud】学习笔记三(ruoyiCloud SpringCloudGateway使用)

请添加图片描述

一、SpringCloudGateway概述

1、SpringCloudGateway项目技术栈:

Spring 5.0: 这是 Spring 框架的最新版本,提供了许多新的特性和改进。
Spring Boot 2.0: 这是 Spring Boot 的最新版本,简化了 Spring 应用程序的开发和部署。
Project Reactor: 这是一个响应式编程框架,为 Spring WebFlux 提供了基础支持。
Spring WebFlux: 这是 Spring 5.0 引入的一个新的响应式 Web 框架,用于构建反应式 Web 应用程序。
Netty: 这是 Spring WebFlux 的底层实现之一,提供了高性能的非阻塞 I/O 模型。 

2、SpringCloudGateway核心概念:

路由(Route):路由是网关最基础的部分,路由信息由 ID、目标 URI、一组断言和一组过滤器组成。如果断言 路由为真,则说明请求的 URI 和配置匹配。
断言(Predicate):Java8 中的断言函数。Spring Cloud Gateway 中的断言函数输入类型是 Spring 5.0 框架中 的 ServerWebExchange。Spring Cloud Gateway 中的断言函数允许开发者去定义匹配来自于 Http Request 中的任何信息,比如请求头和参数等。
过滤器(Filter):一个标准的 Spring Web Filter。Spring Cloud Gateway 中的 Filter 分为两种类型,分别是 Gateway Filter 和 Global Filter。过滤器将会对请求和响应进行处理.

二、SpringCloudGateway基础

1、SpringCloudGateway工作流程:

在这里插入图片描述
客户端向 Spring Cloud Gateway 发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。过滤器被虚线分割的原因是,过滤器可以在代理请求发送之前和之后运行逻辑。所有的 “pre” (前)过滤器逻辑都被执行。然后发出代理请求。在代理请求发出后,“post” (后)过滤器逻辑被运行。

2、SpringCloudGateway 路由谓词工厂:

SpringCloudGateway提供的所有可用谓词:

Path Predicate Factory: 根据请求路径匹配路由。例如 path("/foo/{segment}") 可以匹配 /foo/1 和 /foo/bar。
Method Predicate Factory: 根据请求方法匹配路由。例如 method(HttpMethod.GET) 可以匹配 GET 请求。
Header Predicate Factory: 根据请求头匹配路由。例如 header("X-Request-Id", "foo") 可以匹配包含指定头的请求。
Host Predicate Factory: 根据请求 Host 头匹配路由。例如 host("**.somehost.org") 可以匹配 www.somehost.org 和 beta.somehost.org。
Query Predicate Factory: 根据请求参数匹配路由。例如 query("name") 可以匹配包含 name 参数的请求。
Cookie Predicate Factory: 根据请求 Cookie 匹配路由。例如 cookie("name", "value") 可以匹配包含指定 Cookie 的请求。
RemoteAddr Predicate Factory: 根据请求源 IP 地址匹配路由。例如 remoteAddr("192.168.1.1/24") 可以匹配来自 192.168.1.0/24 网段的请求。
Weight Predicate Factory: 根据权重匹配路由。例如 weight("group1", 9) 可以将 90% 的请求路由到 "group1"。
After Predicate Factory: 根据请求时间匹配路由。例如 after("2020-01-20T00:00:00.000+08:00") 可以匹配在指定时间之后的请求。
Before Predicate Factory: 根据请求时间匹配路由。例如 before("2020-01-20T00:00:00.000+08:00") 可以匹配在指定时间之前的请求。
Between Predicate Factory: 根据请求时间匹配路由。例如 between("2020-01-20T00:00:00.000+08:00", "2020-01-21T00:00:00.000+08:00") 可以匹配在指定时间范围内的请求。

Path谓词示例:

spring:
  cloud:
    gateway:
      routes:
        - id: ruoyi-auth
          uri: lb://ruoyi-auth
          predicates:
            - Path=/auth/**
          filters:
            - CacheRequestFilter
            - ValidateCodeFilter
            - StripPrefix=1

3、创建全局 Filter

"Pre"Filter 定义前置过滤器

定义前置过滤器需要实现GlobalFilter和Ordered接口并重写filter方法和getOrder方法,filter方法中为自定义过滤器的实现逻辑,getOrder中为控制自定义过滤器Filter 在 过滤器链Filter Chain 中的位置

@Component
public class AuthFilter implements GlobalFilter, Ordered {
    private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);

    // 排除过滤的 uri 地址,nacos自行添加
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Autowired
    private RedisService redisService;

    /**
     * @param exchange 表示当前的 HTTP 请求和响应
     * @param chain 表示过滤器链,可以用它来调用下一个过滤器
     * @return Mono<Void>类型,表示一个异步的无返回值操作
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //获取当前的 HTTP 请求对象
        ServerHttpRequest request = exchange.getRequest();
        //创建一个 ServerHttpRequest.Builder 对象,用于修改请求对象
        ServerHttpRequest.Builder mutate = request.mutate();
        //获取当前请求的 URL 路径
        String url = request.getURI().getPath();
        // 跳过不需要验证的路径
        if (StringUtils.matches(url, ignoreWhite.getWhites())) {
            return chain.filter(exchange);
        }
        String token = getToken(request);
        if (StringUtils.isEmpty(token)) {
            return unauthorizedResponse(exchange, "令牌不能为空");
        }
        Claims claims = JwtUtils.parseToken(token);
        if (claims == null) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
        }
        String userkey = JwtUtils.getUserKey(claims);
        boolean islogin = redisService.hasKey(getTokenKey(userkey));
        if (!islogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }
        String userid = JwtUtils.getUserId(claims);
        String username = JwtUtils.getUserName(claims);
        if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        // 设置用户信息到请求
        addHeader(mutate, SecurityConstants.USER_KEY, userkey);
        addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
        addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
        // 内部请求来源参数清除
        removeHeader(mutate, SecurityConstants.FROM_SOURCE);
        //重新构建请求对象并执行之后的过滤器
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }

    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
        if (value == null) {
            return;
        }
        String valueStr = value.toString();
        String valueEncode = ServletUtils.urlEncode(valueStr);
        mutate.header(name, valueEncode);
    }

    private void removeHeader(ServerHttpRequest.Builder mutate, String name) {
        mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
    }

    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
    }

    /**
     * 获取缓存key
     */
    private String getTokenKey(String token) {
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }

    /**
     * 获取请求token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀,则裁剪掉前缀
        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
            token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
        }
        return token;
    }

    /**
     * 设置该过滤器的优先级
     * 数值越大优先级越低
     * @return
     */
    @Override
    public int getOrder() {
        return -200;
    }
}

"Post"Filter 定义后置过滤器

@Configuration
public class LoggingGlobalFiltersConfigurations {

    final Logger logger =LoggerFactory.getLogger(LoggingGlobalFiltersConfigurations.class);

    @Bean
    public GlobalFilter postGlobalFilter() {
        return (exchange, chain) -> {
            return chain.filter(exchange)
              .then(Mono.fromRunnable(() -> {
                  logger.info("Global Post Filter executed");
              }));
        };
    }
}

“Pre” 和 “Post” 逻辑结合到一个过滤器中

@Component
public class FirstPreLastPostGlobalFilter
  implements GlobalFilter, Ordered {

    final Logger logger =
      LoggerFactory.getLogger(FirstPreLastPostGlobalFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange,
      GatewayFilterChain chain) {
        logger.info("First Pre Global Filter");
        return chain.filter(exchange)
          .then(Mono.fromRunnable(() -> {
              logger.info("Last Post Global Filter");
            }));
    }

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

4、创建局部 GatewayFilter

实现GatewayFilterFactory接口创建局部过滤器

实现GatewayFilterFactory接口定义了一个静态配置类CustomAddRequestHeaderConfig。实现shortcutFieldOrder方法定义配置文件中的字段,实现apply方法实现过滤器的业务逻辑,实现getConfigClass获取配置类的class,实现newConfig创建了一个新的配置类的 Class 对象。

/**
 * @package com.ruoyi.gateway.filter
 * @ClassName Test
 * @Description 这个类实现了 GatewayFilterFactory 接口,并指定了它所使用的配置类为 CustomAddRequestHeaderConfig
 * @Author zhanggh
 * @Date 2024/4/18 9:05
 * @Version 1.0
 */

@Component
public class CustomAddRequestHeaderGatewayFilterFactory implements GatewayFilterFactory<CustomAddRequestHeaderGatewayFilterFactory.CustomAddRequestHeaderConfig> {

    //将配置类的 Class 对象保存在一个私有的成员变量中
    private final Class<CustomAddRequestHeaderConfig> configClass = CustomAddRequestHeaderConfig.class;

    /**
     * 这个方法返回了一个字段名称列表,这些字段可以在 YAML 配置中以简写的形式指定
     * @return
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return new ArrayList<>(Arrays.asList("headerName", "headerValue"));
    }

    /**
     * 这个方法是过滤器工厂的核心,它根据提供的配置对象 CustomAddRequestHeaderConfig 创建一个 GatewayFilter 实例
     * 在这个例子中,它会在请求头中添加一个自定义的头部
     * @param config
     * @return
     */
    @Override
    public GatewayFilter apply(CustomAddRequestHeaderConfig config) {
        return ((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> {
                httpHeaders.set(config.getHeaderName(), config.getHeaderValue());
            }).build();
            return chain.filter(exchange.mutate().request(request).build());
        });
    }

    /**
     * 这个方法返回了配置类的 Class 对象
     * @return
     */
    @Override
    public Class<CustomAddRequestHeaderConfig> getConfigClass() {
        return configClass;
    }

    /**
     * 这个方法创建了一个新的配置对象实例
     * @return
     */
    @Override
    public CustomAddRequestHeaderConfig newConfig() {
        return BeanUtils.instantiateClass(this.configClass);
    }

    /**
     * 这个内部类定义了过滤器的配置,包括头部名称和头部值两个属性。这些属性可以在 YAML 配置中进行设置。
     */
    public static class CustomAddRequestHeaderConfig {

        private String headerName;
        private String headerValue;

        public String getHeaderName() {
            return headerName;
        }

        public void setHeaderName(String headerName) {
            this.headerName = headerName;
        }

        public String getHeaderValue() {
            return headerValue;
        }

        public void setHeaderValue(String headerValue) {
            this.headerValue = headerValue;
        }
    }
}

在YML文件中配置定义的过滤器并定义过滤器需要的两个参数

spring:
  redis:
    host: localhost
    port: 6379
    password:
  cloud:
    gateway:
      discovery:
        locator:
          lowerCaseServiceId: true
          enabled: true
      routes:
        # 认证中心
        # 指定该路由规则的ID为ruoyi-auth
        - id: ruoyi-auth
          #指定请求转发的目标服务为ruoyi-auth,采用负载均衡的方式进行转发。lb代表从服务注册发现组件(如Eureka、Consul、Nacos等)中获取服务列表
          uri: lb://ruoyi-auth
          #匹配请求路径为/auth/**的请求
          predicates:
            - Path=/auth/**
          filters:
          - CustomAddRequestHeader=customHeaderName,customHeaderValue

继承AbstractGatewayFilterFactory抽象类创建局部过滤器

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值