在 API 网关 spring cloud gateway 和负载均衡框架 ribbon 实战 https://www.cnblogs.com/ye-feng-yu/p/11106006.html 文章中, 主要实现网关与负载均衡等基本功能, 详见代码 https://github.com/yefengyu/spring-cloud . 本节内容将继续围绕此代码展开, 主要讲解 spring cloud gateway 自定义过滤器的功能. 本节内容的代码也会提交到 GitHub 上, 注意提交的内容.
本节主要讲解全局过滤器和局部过滤器. 注意下面的示例不能作为生产环境的代码, 只是简单的演示自定义过滤器的使用方式, 无需纠结实现的功能是否完善. 下面主要针对不同的过滤器选择几种场景进行代码演示, 不代表某个场景就必须使用全局或者局部的过滤器.
全局过滤器:
1, 限流: 每分钟只能访问 5 次服务
2, 接口用时统计
局部过滤器:
1, 简单的权限检查
2, 指定 IP 访问
1, 全局过滤器 - 限流
本节主要演示全局过滤器的用法: 实现 GlobalFilter 和 Ordered, 重写相关方法, 加入到 spring 容器管理即可, 无需配置, 全局过滤器对所有的路由都有效.
- package com.yefengyu.gateway.globalFilter;
- import io.GitHub.bucket4j.Bandwidth;
- import io.GitHub.bucket4j.Bucket;
- import io.GitHub.bucket4j.Bucket4j;
- import io.GitHub.bucket4j.Refill;
- 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;
- import java.time.Duration;
- import java.util.Map;
- import java.util.concurrent.ConcurrentHashMap;
- // 全局过滤器, 实现 GlobalFilter 接口, 和 Ordered 接口即可.
- @Component
- public class FluidControlGlobalGatewayFilter implements GlobalFilter, Ordered
- {
- int capacity = 5;// 桶的最大容量, 即能装载 Token 的最大数量
- int refillTokens = 1; // 每次 Token 补充量
- Duration duration = Duration.ofSeconds(1); // 补充 Token 的时间间隔
- private static final Map<String, Bucket> BUCKET_CACHE = new ConcurrentHashMap<>();
- private Bucket createNewBucket()
- {
- Refill refill = Refill.greedy(refillTokens, duration);
- Bandwidth limit = Bandwidth.classic(capacity, refill);
- return Bucket4j.builder().addLimit(limit).build();
- }
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
- {
- String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
- Bucket bucket = BUCKET_CACHE.computeIfAbsent(ip, k -> createNewBucket());
- System.out.println("IP:" + ip + ",has Tokens:" + bucket.getAvailableTokens());
- if (bucket.tryConsume(1))
- {
- return chain.filter(exchange);
- }
- else
- {
- exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
- return exchange.getResponse().setComplete();
- }
- }
- @Override
- public int getOrder()
- {
- return -1000;
- }
- }
注意在 pom.xml 文件中加入依赖
- <dependency>
- <groupId>com.GitHub.vladimir-bukhtoyarov</groupId>
- <artifactId>bucket4j-core</artifactId>
- <version>4.4.1</version>
- </dependency>
2, 全局过滤器 - 统计请求耗时
只需要在亲请求处理之前和之后标记时间即可. 注意此处演示的是使用配置类的形式:
- package com.yefengyu.gateway.globalFilter;
- import org.springframework.cloud.gateway.filter.GlobalFilter;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.core.annotation.Order;
- import reactor.core.publisher.Mono;
- // 全局过滤器, 使用配置类形式, 直接构造 bean, 使用注解完成 Ordered 接口功能, 统计接口调用时间
- @Configuration
- public class GlobalGatewayFilterConfig
- {
- @Bean
- @Order(-100)
- public GlobalFilter elapsedGlobalFilter()
- {
- return (exchange, chain) -> {
- // 调用请求之前统计时间
- Long startTime = System.currentTimeMillis();
- return chain.filter(exchange).then().then(Mono.fromRunnable(() -> {
- // 调用请求之后统计时间
- Long endTime = System.currentTimeMillis();
- System.out.println(
- exchange.getRequest().getURI().getRawPath() + ", cost time :" + (endTime - startTime) + "ms");
- }));
- };
- }
- }
3, 局部过滤器 - 简单的权限检查
权限检查一般把信息存储在某处, 请求到来之后进行核对, 有权限的请求将真正执行.
1, 首先编写一个工具类, 对权限做管理.
- package com.yefengyu.gateway.utitls;
- import java.util.HashMap;
- import java.util.Map;
- public final class AuthUtil
- {
- private static Map<String, String> map = new HashMap<>();
- private AuthUtil()
- {
- }
- // 程序启动的时候加载权限的信息, 比如从文件, 数据库中加载
- public static void init()
- {
- map.put("tom", "123456");
- }
- // 简单判断
- public static boolean isPermitted(String name, String password)
- {
- return map.containsKey(name) && map.get(name).equals(password);
- }
- }
我们简单的将权限信息放到 map 中保管, init 方法是初始化方法, isPermitted 是对外提供一个判断是否有权限的方法.
2, 服务启动的时候, 需要初始化权限 map, 因此主启动类进行了修改:
- package com.yefengyu.gateway;
- import com.yefengyu.gateway.utitls.AuthUtil;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.boot.context.event.ApplicationStartedEvent;
- import org.springframework.context.ApplicationListener;
- @SpringBootApplication
- public class GatewayApplication
- {
- public static void main(String[] args)
- {
- SpringApplication springApplication = new SpringApplication(GatewayApplication.class);
- springApplication.addListeners(new ApplicationListenerStarted());// 增加监听器
- springApplication.run(args);
- }
- private static class ApplicationListenerStarted
- implements ApplicationListener<ApplicationStartedEvent>
- {
- @Override
- public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent)
- {
- // 权限初始化数据
- AuthUtil.init();
- }
- }
- }
3, 编写一个局部过滤器, 需要实现 GatewayFilter, Ordered, 实现相关的方法
- package com.yefengyu.gateway.localFilter;
- import com.yefengyu.gateway.utitls.AuthUtil;
- import org.springframework.cloud.gateway.filter.GatewayFilter;
- import org.springframework.cloud.gateway.filter.GatewayFilterChain;
- import org.springframework.core.Ordered;
- import org.springframework.http.HttpStatus;
- import org.springframework.Web.server.ServerWebExchange;
- import reactor.core.publisher.Mono;
- public class AuthGatewayFilter implements GatewayFilter, Ordered
- {
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
- {
- // 获取 header 的参数
- String name = exchange.getRequest().getHeaders().getFirst("name");
- String password = exchange.getRequest().getHeaders().getFirst("password");
- boolean permitted = AuthUtil.isPermitted(name, password);// 权限比较
- if (permitted)
- {
- return chain.filter(exchange);
- }
- else
- {
- exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
- return exchange.getResponse().setComplete();
- }
- }
- @Override
- public int getOrder()
- {
- return 10;
- }
- }
4, 接着需要把上面自定义的局部过滤器加入到过滤器工厂, 并且注册到 spring 容器中.
- package com.yefengyu.gateway.localFilter;
- import org.springframework.cloud.gateway.filter.GatewayFilter;
- import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
- import org.springframework.stereotype.Component;
- @Component
- public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>
- {
- @Override
- public GatewayFilter apply(Object config)
- {
- return new AuthGatewayFilter();
- }
- }
5, 在配置文件中进行配置, 如果不配置则不启用此过滤器规则.
4, 局部过滤器 - 指定 IP 访问
我们的需求是如果在配置文件配置了一个 IP, 那么该 ip 就可以访问, 其它 IP 通通不能访问. 如果不使用该过滤器, 那么所有 IP 都可以访问服务.
这里我们看到上面的 AuthGatewayFilter 和 AuthGatewayFilterFactory 代码本来就是为了同一个过滤器规则编写的两个类, 如果过滤器规则很多, 那么类文件就很多, 其实这两个类可以合并, 并且还会提供其它的功能:
- package com.yefengyu.gateway.localFilter;
- import org.springframework.cloud.gateway.filter.GatewayFilter;
- import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
- import org.springframework.core.annotation.Order;
- import org.springframework.http.HttpStatus;
- import org.springframework.stereotype.Component;
- import java.util.Arrays;
- import java.util.List;
- @Component
- @Order(99)
- public class IPForbidGatewayFilterFactory
- extends AbstractGatewayFilterFactory<IPForbidGatewayFilterFactory.Config>
- {
- public IPForbidGatewayFilterFactory()
- {
- super(Config.class);
- }
- @Override
- public List<String> shortcutFieldOrder()
- {
- return Arrays.asList("forbidIp");
- }
- @Override
- public GatewayFilter apply(Config config)
- {
- return (exchange, chain) -> {
- String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
- if (config.getForbidIp().equals(ip))
- {
- return chain.filter(exchange);
- }
- exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
- return exchange.getResponse().setComplete();
- };
- }
- static public class Config
- {
- private String forbidIp;
- public String getForbidIp()
- {
- return forbidIp;
- }
- public void setForbidIp(String forbidIp)
- {
- this.forbidIp = forbidIp;
- }
- }
- }
Config 类定义了一个属性, 要重写 List<String> shortcutFieldOrder() 这个方法指定属性名称. 规则逻辑很简单, 判断配置文件中的 ip 是和请求来的 ip 是否相同, 相同则可以访问服务.
配置文件:
正常测试
5, 总结
全局过滤器, 对所有的路由都有效, 所有不用在配置文件中配置, 主要实现了 GlobalFilter 和 Ordered 接口, 并将过滤器注册到 spring 容器. 由于使用 java 配置类的方式也可以注册 bean, 所有也可以使用配置类的方式, Ordered 接口使用 Order 注解代替, GlobalFilter 只是个接口, 可以使用 Lambda 表达式替换.
局部过滤器, 需要在配置文件中配置, 如果配置, 则该过滤器才会生效. 主要实现 GatewayFilter, Ordered 接口, 并通过 AbstractGatewayFilterFactory 的子类注册到 spring 容器中, 当然也可以直接继承 AbstractGatewayFilterFactory, 在里面写过滤器逻辑, 还可以从配置文件中读取外部数据.
本节代码已提交: GitHub 本次提交代码内容
来源: http://www.bubuko.com/infodetail-3109616.html