Spring Cloud / Alibaba 微服务架构 | 2021年11月更文挑战(30)

283 阅读2分钟

这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战

上篇文章我们自定义了一个局部过滤器,用来校验Http请求头部中的Token,本篇文章我们将自定义一个全局过滤器,用来缓存HTTP请求某一类型的body。

自定义全局过滤器

之所以要自定义一个全局过滤器缓存HTTP请求某一类型的body的原因是因为我们将来会通过我们的网关去访问鉴权微服务,也就是e-commerce-authority-center。我们的SpringCloud Gateway是基于Spring WebFlux实现的,Spring WebFlux是由Spring框架5.0中引入的新的响应式的Web框架,它与SpringMVC不同。

通过authority我们可以去完成登录、注册、获取Token的过程,这些过程也会去通过网关里的Filter去实现。但是要登录到系统需要提供用户名密码,而且这个请求需要是POST请求,但是我们在Filter里是拿不到POST请求的数据的,所以我们需要有一个过滤器将用户的请求的数据缓存下来,这样的话,在我们另一个过滤器里去实现登录注册的时候才能拿到请求的数据,所以这篇文章里实现的过滤器是为之后的过滤器做准备。

1、创建对应的包和类

在e-commerce-gateway子模块下的com.sheep.ecommerce.filter包下创建一个类GlobalCacheRequestBodyFilter,即用来缓存HTTP请求某一类型的body的全局过滤器。

再在com.sheep.ecommerce包下创建一个constant包,包下创建GatewayConstant,存储网关常量。

2、编写 GatewayConstant

/**
 * <h1>网关常量定义</h1>
 * */
public class GatewayConstant {

    /** 登录 uri */
    public static final String LOGIN_URI = "/e-commerce/login";

    /** 注册 uri */
    public static final String REGISTER_URI = "/e-commerce/register";

    /** 去授权中心拿到登录 token 的 uri 格式化接口 */
    public static final String AUTHORITY_CENTER_TOKEN_URL_FORMAT =
            "http://%s:%s/ecommerce-authority-center/authority/token";

    /** 去授权中心注册并拿到 token 的 uri 格式化接口 */
    public static final String AUTHORITY_CENTER_REGISTER_URL_FORMAT =
            "http://%s:%s/ecommerce-authority-center/authority/register";
}

3、编写全局过滤器 GlobalCacheRequestBodyFilter

因为这是一个全局过滤器,所以需要去实现GlobalFilter和Ordered接口,记得要打上@Component注解。

代码如下:

/**
 * <h1>缓存请求 body 的全局过滤器</h1>
 * Spring WebFlux
 * */
@Slf4j
@Component
@SuppressWarnings("all")
public class GlobalCacheRequestBodyFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        boolean isloginOrRegister =
                exchange.getRequest().getURI().getPath().contains(GatewayConstant.LOGIN_URI)
                || exchange.getRequest().getURI().getPath().contains(GatewayConstant.REGISTER_URI);

        if (null == exchange.getRequest().getHeaders().getContentType()
                || !isloginOrRegister) {
            return chain.filter(exchange);
        }

        // DataBufferUtils.join 拿到请求中的数据 --> DataBuffer
        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {

            // 确保数据缓冲区不被释放, 必须要 DataBufferUtils.retain
            DataBufferUtils.retain(dataBuffer);
            // defer、just 都是去创建数据源, 得到当前数据的副本
            Flux<DataBuffer> cachedFlux = Flux.defer(() ->
                    Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
            // 重新包装 ServerHttpRequest, 重写 getBody 方法, 能够返回请求数据
            ServerHttpRequest mutatedRequest =
                    new ServerHttpRequestDecorator(exchange.getRequest()) {
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return cachedFlux;
                        }
                    };
            // 将包装之后的 ServerHttpRequest 向下继续传递
            return chain.filter(exchange.mutate().request(mutatedRequest).build());
        });
    }

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

相应注释也在代码之中了,这边就不再重复描述了。