webFilter实现mock接口

简介: 这段代码实现了一个名为 `MockFilter` 的类,继承自 `WebFilter` 接口,用于处理 HTTP 请求和响应。它通过从 Redis 缓存中获取配置信息来决定是否使用模拟数据或缓存数据来响应请求。如果开启了生产模式或关闭了模拟和缓存功能,则直接放行请求。否则,它会检查请求体并根据配置返回相应的模拟或缓存数据。同时,该过滤器支持对响应结果进行处理,并将结果存储回 Redis 中。

package com.ph.sp.gateway.filter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

@Component
@Order(3)
@Slf4j
@SuppressWarnings("all")
public class MockFilter implements WebFilter {

@Value("${mockClose:true}")
private boolean mockClose;
@Value("${cacheClose:true}")
private boolean cacheClose;
@Value("${isPrd:true}")
private boolean prd;
private final String cache = "A_CACHE_";
private final String mock = "A_MOCK_";
@Resource
private RedisTemplate<String, Map<String, String>> hashTemplate;

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    if (prd || mockClose && cacheClose) {
        return chain.filter(exchange);
    }
    ServerHttpRequest request = exchange.getRequest();
    String uri = request.getURI().getPath();
    Map<String, String> mockConfig = getRedisConfig(exchange, mock);
    Map<String, String> cacheConfig = getRedisConfig(exchange, cache);
    if (CollUtil.isEmpty(mockConfig) && CollUtil.isEmpty(cacheConfig)) {
        return chain.filter(exchange);
    }
    AtomicReference<String> requestBodyContent = new AtomicReference<>("");
    Flux<DataBuffer> body = exchange.getRequest().getBody();
    return body.doOnNext(buffer -> {
        byte[] bytes = new byte[buffer.readableByteCount()];
        buffer.read(bytes);
        DataBufferUtils.release(buffer);
        requestBodyContent.set(new String(bytes, StandardCharsets.UTF_8));
    }).then(Mono.defer(() -> diyFilter(requestBodyContent.get(), exchange, chain, mockConfig, cacheConfig)
    )).then();
}

private Mono<Void> diyFilter(String body, ServerWebExchange exchange, WebFilterChain chain,
                             Map<String, String> mockConfig, Map<String, String> cacheConfig) {
    String uri = exchange.getRequest().getURI().getPath();
    ServerHttpResponse response = exchange.getResponse();
    Pair<String, String> mockResult = check(mockConfig, body);
    if (StrUtil.isNotBlank(mockResult.getValue())) {
        return hit(mock, mockResult.getKey(), Base64Utils.decode(mockResult.getValue()), uri, response);
    }
    Pair<String, String> cacheResult = check(cacheConfig, body);
    if (StrUtil.isNotBlank(cacheResult.getValue())) {
        return hit(cache, cacheResult.getKey(), cacheResult.getValue(), uri, response);
    }
    Flux<DataBuffer> cachedFlux = Flux.defer(() -> Mono.just(exchange.getResponse().bufferFactory().wrap(body.getBytes())));
    ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
        @Override
        public HttpHeaders getHeaders() {
            HttpHeaders headers = new HttpHeaders();
            headers.putAll(exchange.getRequest().getHeaders());
            headers.remove(HttpHeaders.CONTENT_LENGTH);
            headers.setContentLength(body.getBytes().length);
            return headers;
        }

        @Override
        public Flux<DataBuffer> getBody() {
            return cachedFlux;
        }
    };
    if (StrUtil.isNotBlank(cacheResult.getKey())) {
        ServerHttpResponseDecorator mutatedResponse = decoratedResponse(exchange, cacheResult.getKey());
        return chain.filter(exchange.mutate().request(mutatedRequest).response(mutatedResponse).build());
    }
    return chain.filter(exchange.mutate().request(mutatedRequest).build());
}

private Mono<Void> hit(String type, String key, String val, String uri, ServerHttpResponse resp) {
    log.info("命中缓存数据:(key: {}, hashKey:{},用完请手动删除该条hash,谢谢)", type, type + uri, key);
    resp.getHeaders().setContentType(HeaderConstants.APPLICATION_JSON_UTF8);
    return resp.writeWith(Mono.fromSupplier(() -> resp.bufferFactory().wrap(val.getBytes(StandardCharsets.UTF_8))));
}

private ServerHttpResponseDecorator decoratedResponse(ServerWebExchange exchange, String key) {
    String path = exchange.getRequest().getURI().getPath();
    ServerHttpResponse originalResponse = exchange.getResponse();
    DataBufferFactory bufferFactory = originalResponse.bufferFactory();
    return new ServerHttpResponseDecorator(originalResponse) {
        @Override
        public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
            if (body instanceof Mono) {
                Mono<? extends DataBuffer> mono = (Mono<? extends DataBuffer>) body;
                body = mono.flux();
            }
            if (body instanceof Flux) {
                Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                return super.writeWith(fluxBody.buffer().map(dataBuffer -> {
                    DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                    DataBuffer join = dataBufferFactory.join(dataBuffer);
                    byte[] content = new byte[join.readableByteCount()];
                    join.read(content);
                    DataBufferUtils.release(join);
                    HashOperations<String, String, String> operation = hashTemplate.opsForHash();
                    operation.put(cache + path, key, new String(content, StandardCharsets.UTF_8));
                    originalResponse.getHeaders().setContentLength(content.length);
                    return bufferFactory.wrap(content);
                }));
            }
            return super.writeWith(body);
        }
    };
}

private Map<String, String> getRedisConfig(ServerWebExchange exchange, String pre) {
    String uri = exchange.getRequest().getURI().getPath();
    HashOperations<String, String, String> operation = hashTemplate.opsForHash();
    return operation.entries(pre + uri);
}

private Pair<String, String> check(Map<String, String> config, String body) {
    return config.entrySet().stream().filter(e -> StrUtil.contains(body, e.getKey())).findFirst()
            .map(e -> Pair.of(e.getKey(), e.getValue())).orElse(Pair.of(null, null));
}

}

相关文章
|
11月前
|
前端开发
什么是 Mock 测试?掌握 Mock 测试的核心原理
Mock 的意思就是,当你很难拿到源数据时,你可以使用某些手段,去获取到跟源数据相似的假数据,拿着这些假数据,前端可以先行开发,而不需要等待后端给了数据后再开发。
|
2月前
|
测试技术
详解单元测试问题之处理@Mock注解时mock对象的创建如何解决
详解单元测试问题之处理@Mock注解时mock对象的创建如何解决
24 1
|
2月前
|
前端开发
什么是 Mock 测试?
Mock 是在前后端分离开发中,用于模拟后端数据的工具,让前端能提前开发而无需等待真实接口。它的重要性在于加速协同开发,避免因数据延迟导致的阻塞。通过工具如 Apifox,可以创建请求,设定 Mock 参数和测试脚本,进行 Mock 测试以确保数据符合预期。了解 Mock.js 语法有助于更好地进行 Mock 测试。
|
4月前
|
JavaScript 数据安全/隐私保护
Mock
Mock
73 0
|
API Python
Django | 如何优雅的在某接口对其他接口的调用
Django | 如何优雅的在某接口对其他接口的调用
|
JSON 前端开发 JavaScript
你还在用静态的 Mock 数据吗
作为前端开发者,在前后端分离的时代下,不能一天到晚追着后端的同学要接口调试,我们可以自己来 Mock 现在的打包工具或者框架基本都支持或者通过第三方插件支持了 Mock 数据的能力。对于写死的静态数据而言,我们可以使用 MockJS 来生成动态的数据,这至少这样看起来更逼真,而且更方便,可以通过 Mock 的表达式来生成指定格式及数量的 JSON 数据。
203 0
|
NoSQL JavaScript 前端开发
部署自己的MOCK(一)
本文适合团队内部没有MOCK服务,对mock有实际需要的小伙伴。
部署自己的MOCK(一)
|
数据可视化 前端开发 Java
Mock工具介绍,为什么使用Mock?
Mock工具介绍,为什么使用Mock?
552 0
部署自己的MOCK(二)
本文适合团队内部没有MOCK服务,对mock有实际需要的小伙伴。
|
敏捷开发 设计模式 Java
mock打桩之EasyMock
TDD是测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,但不只使用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。
mock打桩之EasyMock