目录
前面的学习我们知道,GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理,同时,springcloud 也提供了一些内置的 filter.
比如:StripPrefix,表示给请求的 url 中去表指定的 n 个前缀路由,例如 - StripPrefix=2 那么如果你原本的请求是路由是 /user/list/get ,那么经过 StripPrefix 处理后,就会变成 /get.

如果我们需要自己去实现一个像这样的局部过滤器,该怎么实现呢?
例如 StripPrefix,他继承了 AbstractGatewayFilterFactory 这个抽象类.

这里暗含了一层意思:在 application.yml 配置文件中,可以在 filters 配置里写上这个类的前缀 StripPrefix,就表示这个类(后面的 GatewayFilterFactory 是固定写法,就表示他是一个网关过滤器).

进一步的,如果我们要自定义一个局部过滤器,例如身份认证 Token 过滤器,我们就创建一个类命名为:Token + GatewayFilter,然后继承 AbstractGatewayFilterFactory 抽象类,就表示他是一个局部过滤器.
源码如下:
- public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactory
{ - public static final String PARTS_KEY = "parts";
-
- public StripPrefixGatewayFilterFactory() {
- super(Config.class);
- }
-
- public List
shortcutFieldOrder() { - return Arrays.asList("parts");
- }
-
- public GatewayFilter apply(final Config config) {
- return new GatewayFilter() {
- public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest();
- ServerWebExchangeUtils.addOriginalRequestUrl(exchange, request.getURI());
- String path = request.getURI().getRawPath();
- String[] originalParts = StringUtils.tokenizeToStringArray(path, "/");
- StringBuilder newPath = new StringBuilder("/");
-
- for(int i = 0; i < originalParts.length; ++i) {
- if (i >= config.getParts()) {
- if (newPath.length() > 1) {
- newPath.append('/');
- }
-
- newPath.append(originalParts[i]);
- }
- }
-
- if (newPath.length() > 1 && path.endsWith("/")) {
- newPath.append('/');
- }
-
- ServerHttpRequest newRequest = request.mutate().path(newPath.toString()).build();
- exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
- return chain.filter(exchange.mutate().request(newRequest).build());
- }
-
- public String toString() {
- return GatewayToStringStyler.filterToStringCreator(StripPrefixGatewayFilterFactory.this).append("parts", config.getParts()).toString();
- }
- };
- }
-
- public static class Config {
- private int parts = 1;
-
- public Config() {
- }
-
- public int getParts() {
- return this.parts;
- }
-
- public void setParts(int parts) {
- this.parts = parts;
- }
- }
- }
如果不想给参数指定值,就可以不写 Config 中的内容.
按照上述分析,不难写出大概模样,例如我们可以模仿源码,创建包 filter.factory ,然后在这个包下定义一个 Token 局部过滤器如下:
- @Component // 表示在工厂中创建对象(不能少!)
- public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory
{ -
- public TokenGatewayFilterFactory() {
- super(Config.class);
- }
-
- /**
- * 核心方法: 处理过滤
- * @param config
- * @return
- */
- @Override
- public GatewayFilter apply(Config config) {
- return new GatewayFilter() {
- @Override
- public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // 处理过滤逻辑......
- return chain.filter(exchange);
- }
- };
- }
-
- @Override
- public List
shortcutFieldOrder() { - return super.shortcutFieldOrder();
- }
-
- public static class Config {
-
- }
-
- }
那么我们就可以在配置文件中,添加这个自定义的局部过滤器

例如,自定义一个 Token 局部过滤器,那么就可以创建一个类 filter.factory.TokenGatewayFilterFactory
在 apply 中的过滤逻辑就是,判断前端是否传入 token,如果没有就抛异常,如果有就去 redis 上看看是否存在这个 token,如果存在就放行,不存在就抛异常.
- @Slf4j
- @Component // 表示在工厂中创建对象
- public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory
{ -
- @Autowired
- private StringRedisTemplate redisTemplate;
-
- public TokenGatewayFilterFactory() {
- super(Config.class);
- }
-
- /**
- * 核心方法: 处理过滤
- * @param config
- * @return
- */
- @Override
- public GatewayFilter apply(Config config) {
- return new GatewayFilter() {
- @Override
- public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) { - //1.获取 token 信息
- //由于 header 中 key 可以重复(包括 parma 也是如此),因此获取到的是一个 List
- List
tokens = exchange.getRequest().getHeaders().get(RedisPrefix.TOKEN_KEY); - if(tokens == null) {
- throw new RuntimeException("没有 token 令牌!");
- }
- String tokenValue = tokens.get(0);
- log.info("token: {}", tokenValue);
- //2.比较 redis 上的 token 数据是否一致(redis 上存储的数据格式为: token前缀 + value)
- if(!redisTemplate.hasKey(RedisPrefix.TOKEN_KEY + tokenValue)) {
- throw new RuntimeException("token 令牌不合法!");
- }
- return chain.filter(exchange);
- }
- };
- }
-
- @Override
- public List
shortcutFieldOrder() { - return super.shortcutFieldOrder();
- }
-
- public static class Config {
-
- }
-
- }
a)例如 redis 存储的数据为

b)执行结果如下:


c)如果没有 token 数据,响应如下:


d)如果有 token,但是 token 值错误,响应如下:


如果在配置 filters 的时候,要指定一些参数,例如 isRequire(boolean类型,表示是否传),name(String 类型).
那么就可以在 Config 静态内部类中描述,然后在 shortcutFieldOrder() 方法中指定顺序,最后就可以在 apply 中拿到对应的参数,如下:
- @Slf4j
- @Component // 表示在工厂中创建对象
- public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory
{ -
- @Autowired
- private StringRedisTemplate redisTemplate;
-
- public TokenGatewayFilterFactory() {
- super(Config.class);
- }
-
- /**
- * 核心方法: 处理过滤
- * @param config
- * @return
- */
- @Override
- public GatewayFilter apply(Config config) {
- return new GatewayFilter() {
- @Override
- public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) { - log.info("config isRequire: {}", config.isRequire());
- log.info("config name: {}", config.getName());
- //1.拿到 Config 中自定义的参数 isRequire,判断是否要进行过滤
- if(config.isRequire()) {
- //2.获取 token 信息
- //由于 header 中 key 可以重复(包括 parma 也是如此),因此获取到的是一个 List
- List
tokens = exchange.getRequest().getHeaders().get(RedisPrefix.TOKEN_KEY); - if(tokens == null) {
- throw new RuntimeException("没有 token 令牌!");
- }
- String tokenValue = tokens.get(0);
- log.info("token: {}", tokenValue);
- //3.比较 redis 上的 token 数据是否一致(redis 上存储的数据格式为: token前缀 + value)
- if(!redisTemplate.hasKey(RedisPrefix.TOKEN_KEY + tokenValue)) {
- throw new RuntimeException("token 令牌不合法!");
- }
- }
- return chain.filter(exchange);
- }
- };
- }
-
- /**
- * 指定参数值填写的顺序
- * @return
- */
- @Override
- public List
shortcutFieldOrder() { - return Arrays.asList("require", "name");
- }
-
- /**
- * 提供 filter 配置中的参数值
- */
- public static class Config {
- private boolean require;
- private String name;
-
- public boolean isRequire() {
- return require;
- }
-
- public void setRequire(boolean require) {
- this.require = require;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
- }
-
- }
Ps:这里的不要命名为 isRequire ,会冲突!
在配置文件中配置 filters:

执行结果如下:


