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));
}

}

相关文章
|
前端开发
什么是 Mock 测试?掌握 Mock 测试的核心原理
Mock 的意思就是,当你很难拿到源数据时,你可以使用某些手段,去获取到跟源数据相似的假数据,拿着这些假数据,前端可以先行开发,而不需要等待后端给了数据后再开发。
|
2月前
|
存储 NoSQL Java
aspect实现mock-feign接口
该代码为一个用于Feign接口的模拟(Mock)实现类`FeignMockAspect`,通过切面编程方式对带有`@FeignClient`注解的接口提供模拟响应。在非生产环境中,根据特定配置从Redis中获取Mock数据并转换为对应类型的对象返回,以减少对外部系统的依赖和提高测试效率。使用Hutool工具类和Spring Data Redis进行数据处理与存储操作。
|
4月前
|
测试技术
详解单元测试问题之处理@Mock注解时mock对象的创建如何解决
详解单元测试问题之处理@Mock注解时mock对象的创建如何解决
43 1
|
4月前
|
前端开发
什么是 Mock 测试?
Mock 是在前后端分离开发中,用于模拟后端数据的工具,让前端能提前开发而无需等待真实接口。它的重要性在于加速协同开发,避免因数据延迟导致的阻塞。通过工具如 Apifox,可以创建请求,设定 Mock 参数和测试脚本,进行 Mock 测试以确保数据符合预期。了解 Mock.js 语法有助于更好地进行 Mock 测试。
|
6月前
|
JavaScript 数据安全/隐私保护
Mock
Mock
85 0
|
NoSQL JavaScript 前端开发
部署自己的MOCK(一)
本文适合团队内部没有MOCK服务,对mock有实际需要的小伙伴。
部署自己的MOCK(一)
|
数据可视化 前端开发 Java
Mock工具介绍,为什么使用Mock?
Mock工具介绍,为什么使用Mock?
593 0
Mockito框架里面的@Mock注解原理
一文看懂@Mock注解的底层的底层原理:@Mock注解的底层其实就是用cglib
4038 0
部署自己的MOCK(二)
本文适合团队内部没有MOCK服务,对mock有实际需要的小伙伴。
|
敏捷开发 设计模式 Java
mock打桩之EasyMock
TDD是测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,但不只使用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。
mock打桩之EasyMock