4-1 Service gateway gateway use _ assertion filter current limit

Article Directory

1 Service gateway starting case

1.1 Add modulr gateway service api-gateway under parent project

1.2 The pom file introduces the gateway's jar package dependency

    <dependencies>
        <!--引入gateway网关依赖,注:不能同时引入依赖starter-web-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>

1.3 Add basic configuration to yml file

# 服务基础配置
server:
  port: 7000
spring:
  application:
    name: api-gateway
  # 网关配置
  cloud:
    gateway:
      routes: # 路由数组,支持多个路由,路由配置参照RouteDefinition
        - id: route-product # 路由标识
          uri: http://localhost:8081 # 要转发到的远程服务地址
          order: 0 # 排序,数字越小优先级越高
          predicates: #断言数组,满足条件实现转发,支持自定义断言
            - Path=/product-serv/**
          filters: #过滤数组,转发前服务处理,支持前置过滤和后置过滤,支持自定义过滤(局部过滤、全局过滤)
            - StripPrefix=1 # 转发前去掉一层访问路径

### 1.4 Add startup class ApiGateWayApplication

package cn.hzp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class);
    }
}

1.5 Start the gateway service and product service

1.6 Browser request http://localhost:7000/product-serv/product/1test verification

2 The service gateway integrates nacos to read service information

On the basis of case 1, in order to optimize the value of routing and forwarding uri http://localhost:8081, integrate nacos to read service information

2.1 pom file added to nacos dependent nacos-discovery

        <!--引入nacos依赖,实现网关注册到nacos同时从nacos读取其他服务信息-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

2.2 Add the nacos service discovery annotation @EnableDiscoveryClient to the startup class

2.3 yml file integrated nacos, modify routing and forwarding information

# 服务基础配置
server:
  port: 7000
spring:
  application:
    name: api-gateway
  # 网关配置
  cloud:
    # 增加nacos配置信息
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      # 使gateway可以读取nacos中服务信息
      discovery:
        locator:
          enabled: true
      # 路由数组,支持多个路由,路由配置参照RouteDefinition
      routes:
        - id: route-product # 路由标识
#          uri: http://localhost:8081 # 要转发到的远程服务地址
          uri: lb://service-product # lb支持负载均衡(loadBalance)
          order: 0 # 排序,数字越小优先级越高
          predicates: #断言数组,满足条件实现转发,支持自定义断言
            - Path=/product-serv/**
          filters: #过滤数组,转发前服务处理,支持前置过滤和后置过滤,支持自定义过滤(局部过滤、全局过滤)
            - StripPrefix=1 # 转发前去掉一层访问路径

2.4 Browser request http://localhost:7000/product-serv/product/1test verification

3 Service gateway assertion use

3.1 Built-in gateway assertion

These assertion classes all inherit AbstractRoutePredicateFactory and use the reference URLhttps://www.cnblogs.com/wgslucky/p/11396579.html

3.2 custom assertion

Refer to the built-in assertion classPathRoutePredicateFactory

3.2.1 Pom file introduces lombok dependency

        <!--引入lombok,方便日志输出-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

3.2.2 Add assertion class

package cn.hzp.predicates;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * 断言类名称必须是"断言的配置属性"+RoutePredicateFactory
 * 必须继承AbstractRoutePredicateFactory<配置类>
 */
@Slf4j
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
    /**
     * 构造函数
     */
    public AgeRoutePredicateFactory() {
        super(AgeRoutePredicateFactory.Config.class);
    }

    /**
     * 读取配置文件的参数信息,赋值到配置类的属性上
     */
    @Override
    public List<String> shortcutFieldOrder() {
        // 参数顺序需要和配置文件参数顺序一致
        return Arrays.asList("minAge", "maxAge");
    }

    /**
     * 断言逻辑
     */
    @Override
    public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) {
        return serverWebExchange -> {
            String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age");
            if (StringUtils.isNotEmpty(ageStr)) {
                int age = Integer.parseInt(ageStr);
                if (age >= config.getMinAge() && age <= config.getMaxAge()) {
                    return true;
                }
            }
            return false;
        };
    }

    /**
     * 配置类,接收配置文件中的参数
     */
    @Data
    public static class Config {
        private int minAge;
        private int maxAge;
    }
}

3.2.3 Add Age assertion to yml file

# 服务基础配置
server:
  port: 7000
spring:
  application:
    name: api-gateway
  # 网关配置
  cloud:
    # 增加nacos配置信息
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      # 使gateway可以读取nacos中服务信息
      discovery:
        locator:
          enabled: true
      # 路由数组,支持多个路由,路由配置参照RouteDefinition
      routes:
        - id: route-product # 路由标识
#          uri: http://localhost:8081 # 要转发到的远程服务地址
          uri: lb://service-product # lb支持负载均衡(loadBalance)
          order: 0 # 排序,数字越小优先级越高
          predicates: #断言数组,满足条件实现转发,支持自定义断言
            - Path=/product-serv/**
            - Age=18,60 #自定义断言,实现服务只能被18-60岁之间的人访问
          filters: #过滤数组,转发前服务处理,支持前置过滤和后置过滤,支持自定义过滤(局部过滤、全局过滤)
            - StripPrefix=1 # 转发前去掉一层访问路径

3.2.4 Browser test verification

  • Request http://localhost:7000/product-serv/product/1?age=61returns 404
  • Request http://localhost:7000/product-serv/product/1?age=60to return to normal

4 Service gateway filter usage

  • The pre-pre filter can verify identity and record debugging information. Such as StripPrefix
  • The post filter can modify the response standard header, collect statistics, etc. Such as SetStatus
  • Including local filters and global filters

4.1 Built-in local filter and built-in global filter

  • The built-in local filters all inherit AbstractGatewayFilterFactory, such as SetStatusGatewayFilterFactory
  • Built-in global filters inherit GlobalFilter, Ordered, such as LoadBalancerClientFilter

4.2 Custom local filter

The method is the same as the custom assertion, referring to SetStatusGatewayFilterFactory, designing a partial filter to control the output of the log

4.2.1 New local filter class LogGatewayFilterFactory

package cn.hzp.filters;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 网关gateway-自定义过滤器类
 */
@Slf4j
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
    /**
     * 构造函数
     */
    public LogGatewayFilterFactory() {
        super(LogGatewayFilterFactory.Config.class);
    }

    /**
     * 将配置文件的属性信息赋值到配置类中
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("consoleLog", "cacheLog");
    }

    /**
     * 自定义过滤逻辑
     */
    @Override
    public GatewayFilter apply(LogGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            if (config.isConsoleLog()) {
                log.info("输出控制台日志");
            }
            if (config.isCacheLog()) {
                log.info("输出缓存日志");
            }
            return chain.filter(exchange);
        };
    }

    /**
     * 配置类
     */
    @Data
    @NoArgsConstructor
    public static class Config {
        private boolean consoleLog;
        private boolean cacheLog;
    }
}

4.2.2 Add filter attribute Log to yml file

# 服务基础配置
server:
  port: 7000
spring:
  application:
    name: api-gateway
  # 网关配置
  cloud:
    # 增加nacos配置信息
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      # 使gateway可以读取nacos中服务信息
      discovery:
        locator:
          enabled: true
      # 路由数组,支持多个路由,路由配置参照RouteDefinition
      routes:
        - id: route-product # 路由标识
#          uri: http://localhost:8081 # 要转发到的远程服务地址
          uri: lb://service-product # lb支持负载均衡(loadBalance)
          order: 0 # 排序,数字越小优先级越高
          predicates: #断言数组,满足条件实现转发,支持自定义断言
            - Path=/product-serv/**
#            - Age=18,60 #自定义断言,实现服务只能被18-60岁之间的人访问
          filters: #过滤数组,转发前服务处理,支持前置过滤和后置过滤,支持自定义过滤(局部过滤、全局过滤)
            - StripPrefix=1 # 转发前去掉一层访问路径
            - Log=true, false # 自定义过滤,控制日志输出

4.2.3 Test verification

The browser requests http://localhost:7000/product-serv/product/1, the console outputs "output console log" to indicate success.

4.3 custom global filter

Design unified authentication logic, ignored by authentication center

4.3.1 New global filter class AuthGlobalFilter

package cn.hzp.filters;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    /**
     * 自定义过滤器逻辑
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 统一鉴权逻辑
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if(!StringUtils.equals("admin", token)) {
            log.info("认证失败");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    /**
     * 当前过滤器优先级,值越小优先级越高
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

4.3.2 Test verification

  • Browser request http://localhost:7000/product-serv/product/1?token=admin, return result normally
  • Browser request http://localhost:7000/product-serv/product/1?token=admins, return 401

5 Gateway integrated sentinel current limit

  • 1 Route dimension current limit, according to route id for current limit
  • 2 Custom api dimension current limit, according to api grouping for current limit

5.1 Route dimension current limiting case

5.1.1 Pom file increased dependency sentinel-spring-cloud-gateway-adapter

        <!--gateway集成sentinel实现限流-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
        </dependency>

5.1.2 Write the configuration class GatewaySentinelConfiguration

Inject SentinelGatewayFilter and SentinelGatewayBlockExceptionHandler

package cn.hzp.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import javafx.beans.property.ObjectProperty;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.*;

@Configuration
public class GatewaySentinelConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewaySentinelConfiguration(ObjectProvider<List<ViewResolver>> vewResolverProvider, ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = vewResolverProvider.getIfAvailable(Collections:: emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 初始化一个限流过滤器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter(){
        return new SentinelGatewayFilter();
    }

    /**
     * 配置初始化的限流参数
     */
    @PostConstruct
    public void initGatewayRules(){
        Set<GatewayFlowRule> rules = new HashSet<>();
        // resource为路由id,count为阈值,interval为统计时间窗口,默认1秒
        rules.add(new GatewayFlowRule("route-product")
        .setCount(1)
        .setIntervalSec(1));
        GatewayRuleManager.loadRules(rules);
    }

    /**
     * 配置异常处理器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    /**
     * 自定义限流异常返回页面
     */
    @PostConstruct
    public void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> {
            Map map = new HashMap<>();
            map.put("code", 0);
            map.put("message", "接口被限流了");
            return ServerResponse.status(HttpStatus.OK)
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .body(BodyInserters.fromObject(map));
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }

}

5.1.3 Startup test

The browser requests http://localhost:7000/product-serv/product/1?token=admin, the page is refreshed multiple times and the current limit appears, and the test is successful

5.2 Brief description of api dimension current limit

5.2.1 Modify configuration class

Add a new method custom api group in the configuration class, modify the initialization current limit method initGatewayRules

package cn.hzp.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;

import javax.annotation.PostConstruct;
import java.util.*;

@Configuration
public class GatewaySentinelConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewaySentinelConfiguration(ObjectProvider<List<ViewResolver>> vewResolverProvider, ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = vewResolverProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 初始化一个限流过滤器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    /**
     * 配置初始化的限流参数
     */
    @PostConstruct
    public void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        // 以路由id为限流维度,resource为路由id,count为阈值,interval为统计时间窗口,默认1秒
//        rules.add(new GatewayFlowRule("route-product").setCount(1).setIntervalSec(1));
        // 以api分组为限流维度
        rules.add(new GatewayFlowRule("product-api1").setCount(1).setIntervalSec(1));
        rules.add(new GatewayFlowRule("product-api2").setCount(1).setIntervalSec(1));
        GatewayRuleManager.loadRules(rules);
    }

    /**
     * 配置异常处理器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    /**
     * 自定义限流异常返回页面
     */
    @PostConstruct
    public void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> {
            Map map = new HashMap<>();
            map.put("code", 0);
            map.put("message", "接口被限流了");
            return ServerResponse.status(HttpStatus.OK)
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .body(BodyInserters.fromObject(map));
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }

    /**
     * api维度限流,自定义api分组
     */
    @PostConstruct
    public void initCustomizedApis() {
        Set<ApiDefinition> definitions = new HashSet<>();
        // api分组,apiName自定义唯一即可
        ApiDefinition api1 = new ApiDefinition("product-api1")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {
   
   {
                    add(new ApiPathPredicateItem()
                            .setPattern("/product-serv/api1/**")
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)
                    );
                }});
        ApiDefinition api2 = new ApiDefinition("product-api2")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {
   
   {
                    add(new ApiPathPredicateItem()
                            .setPattern("/product-serv/api2/demo2")
                    );
                }});
        definitions.add(api1);
        definitions.add(api2);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

}

5.2.2 Add test method to ProductController in product service


    /**
     * 测试gateway的api分组限流
     */
    @RequestMapping("/api1/demo1")
    public String api1Demo1() {
        return "/api1/demo1";
    }
    /**
     * 测试gateway的api分组限流
     */
    @RequestMapping("/api1/demo2")
    public String api1Demo2() {
        return "/api1/demo2";
    }
    /**
     * 测试gateway的api分组限流
     */
    @RequestMapping("/api2/demo1")
    public String api2Demo1() {
        return "/api2/demo1";
    }
    /**
     * 测试gateway的api分组限流
     */
    @RequestMapping("/api2/demo2")
    public String api2Demo2() {
        return "/api2/demo2";
    }

5.2.3 Test verification

  • 1 If the browser makes crazy requests http://localhost:7000/product-serv/api1/demo1, it will prompt you to be restricted
  • 2 If the browser makes crazy requests http://localhost:7000/product-serv/api1/demo2, it will prompt you to be restricted
  • 3 The browser makes crazy requests http://localhost:7000/product-serv/api2/demo1, it has been normal
    questMapping("/api1/demo2")
    public String api1Demo2() { return “/api1/demo2”; } /**


    • Test gateway’s api grouping current limit
      /
      @RequestMapping("/api2/demo1")
      public String api2Demo1() { return “/api2/demo1”; } /


      *
    • Test gateway’s api grouping current limit
      */
      @RequestMapping("/api2/demo2")
      public String api2Demo2() { return “/api2/demo2”; }


#### 5.2.3 测试验证
- 1 浏览器疯狂请求`http://localhost:7000/product-serv/api1/demo1`,会提示被限流
- 2 浏览器疯狂请求`http://localhost:7000/product-serv/api1/demo2`,会提示被限流
- 3 浏览器疯狂请求`http://localhost:7000/product-serv/api2/demo1`,一直正常
- 4 浏览器疯狂请求`http://localhost:7000/product-serv/api1/demo2`,会提示被限流

Guess you like

Origin blog.csdn.net/weixin_45544465/article/details/105939164