前言

例行每半年一次的工作轮换,接手了同事的网关服务
年底了工作不是很忙,看了下前人的代码,虽然都能读懂,但感觉应该可以再优雅一点
于是把网关的相关知识又翻阅了一下
官方资料

PS:这里如果按新方案调整的话,在结构上会看起来更清晰、可读性上会得到一定的提高
但学习研究是一回事,我肯定不会去直接修改前人的代码,我们还是要对运行稳定的项目持一点敬畏心,搞得不好,手一抖就是一个BUG

原方案 - 请求体修改

  1. 自定义 Filter 实现 GlobalFilter, Ordered 接口
  2. 重写 filter 方法,具体操作如下

// 这里的 exchange 是 过滤器入参 ServerWebExchange
ServerRequest sr = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
// 在 flatMap 方法里做请求体的修改
Mono modifiedBody = sr.bodyToMono(String.class).flatMap

原方案 - 响应体修改

  1. 自定义 Filter 实现 GlobalFilter, Ordered 接口
  2. 自定义 ResponseDecorator 继承 ServerHttpResponseDecorator 类,顶层是 ServerHttpResponse 接口
  3. 重写 ServerHttpResponseDecorator 的 writeWith 接口,以此实现 responseBody 的修改,加密加签
	@Override
    @SuppressWarnings(value = "unchecked")
    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        
        if (body instanceof Flux) {
            Flux<DataBuffer> fluxBody = (Flux<DataBuffer>) body;
            return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                DataBuffer join = dataBufferFactory.join(dataBuffers);
                byte[] content = new byte[join.readableByteCount()];
                join.read(content);
                // 释放掉内存
                DataBufferUtils.release(join);
                String originalResponseBody = new String(content, StandardCharsets.UTF_8);
                // 修改响应体
                String updatedResponseBody = modifyBody(originalResponseBody);
                getDelegate().getHeaders().setContentLength(updatedResponseBody.getBytes().length);
                return bufferFactory().wrap(updatedResponseBody.getBytes());
            }));
        }
        return super.writeWith(body);
    }
  1. 在 filter 方法中,添加 response 装饰器
ResponseDecorator decorator = new ResponseDecorator(exchange.getResponse());
Mono<Void> filter = chain.filter(exchange.mutate().response(decorator).build());
return filter;

新方案 - 修改客户端请求体

创建请求过滤器 RequestModifyFilter

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.Objects;

/**
 * 修改客户端请求体
 * @author weiheng
 * @date 2024-02-01
 **/
@Component
@Slf4j
public class RequestModifyFilter implements GlobalFilter, Ordered {

    @Resource
    private ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;
    @Resource
    private RequestRewriteFunction requestRewriteFunction;

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

        ServerHttpRequest request = exchange.getRequest();
        MediaType mediaType = request.getHeaders().getContentType();
        String method = request.getMethodValue();
        if (Objects.equals(HttpMethod.POST.name(), method) && MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
            // 如果是POST请求,并且请求体是 application/json 格式
            return modifyRequestBodyFilter.apply(
                            new ModifyRequestBodyGatewayFilterFactory.Config()
                                    .setRewriteFunction(byte[].class, byte[].class, requestRewriteFunction))
                    .filter(exchange, chain);
        } else {
            return filter(exchange, chain);
        }

    }

    @Override
    public int getOrder() {
        return FilterPriorityConstant.REQUEST_BODY_DECRYPT_AND_SIGNATURE_VERIFICATION;
    }
}

RequestRewriteFunction

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 修改请求体
 * @author weiheng
 * @date 2024-01-31
 **/
@Slf4j
@Component
public class RequestRewriteFunction implements RewriteFunction<byte[], byte[]> {

    /**
     * Applies this function to the given arguments.
     *
     * @param serverWebExchange the first function argument
     * @param bytes             the second function argument
     * @return the function result
     */
    @Override
    public Publisher<byte[]> apply(ServerWebExchange serverWebExchange, byte[] bytes) {

        ServerHttpRequest request = serverWebExchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        String requestId = headers.getFirst(HttpConstant.HEADER_REQUEST_ID);

        JSONObject requestBody = JSON.parseObject(bytes);
        log.info(">>>>> requestId[{}], 原始请求体[{}]", requestId, requestBody);
        try {
            // TODO 写自己的请求拦截业务,比如请求体的 【解密、验签】
            // 测试,看业务服务的 controller类请求体中是否添加了该属性 - 自测OK
            requestBody.put("traceId", RandomUtil.randomInt());
            log.info(">>>>> requestId[{}], 修改后请求体[{}]", requestId, requestBody);
            return Mono.just(requestBody.toString().getBytes());
        } catch (Exception e) {
            log.error(">>>>> requestId[{}], 修改请求体出错", requestId, e);
            throw e;
        }
    }

}

新方案 - 修改服务端响应体

创建过滤器 ResponseModifyFilter

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.Objects;

/**
 * 修改服务端响应体
 * @author weiheng
 * @date 2024-02-01
 **/
@Component
@Slf4j
public class ResponseModifyFilter implements GlobalFilter, Ordered {

    @Resource
    private ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilter;
    @Resource
    private ResponseRewriteFunction responseRewriteFunction;

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

        ServerHttpRequest request = exchange.getRequest();
        MediaType mediaType = request.getHeaders().getContentType();
        String method = request.getMethodValue();
        if (Objects.equals(HttpMethod.POST.name(), method) && MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
            // 如果是POST请求,并且请求体是 application/json 格式
            return modifyResponseBodyFilter.apply(
                            new ModifyResponseBodyGatewayFilterFactory.Config()
                                    .setRewriteFunction(byte[].class, byte[].class, responseRewriteFunction))
                    .filter(exchange, chain);
        } else {
            return filter(exchange, chain);
        }

    }

    @Override
    public int getOrder() {
        return FilterPriorityConstant.RESPONSE_BODY_ENCRYPT_AND_ADD_SIGNATURE;
    }
}

ResponseRewriteFunction

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 修改响应体
 * @author weiheng
 * @date 2024-01-31
 **/
@Slf4j
@Component
public class ResponseRewriteFunction implements RewriteFunction<byte[], byte[]> {

    /**
     * Applies this function to the given arguments.
     *
     * @param serverWebExchange the first function argument
     * @param bytes             the second function argument
     * @return the function result
     */
    @Override
    public Publisher<byte[]> apply(ServerWebExchange serverWebExchange, byte[] bytes) {

        ServerHttpRequest request = serverWebExchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        String requestId = headers.getFirst(HttpConstant.HEADER_REQUEST_ID);

        JSONObject responseBody = JSON.parseObject(bytes);
        log.info(">>>>> requestId[{}], 原始响应体[{}]", requestId, responseBody);
        try {
            // TODO 在这里写自己的业务逻辑,比如响应体的 加密、加签
            // 测试,看客户端拿到的响应体中,是否有该属性 - 亲测OK
            responseBody.put("traceResponse", RandomUtil.randomInt(1000, 9999));
            log.info(">>>>> requestId[{}], 修改后的响应体[{}]", requestId, responseBody);
            return Mono.just(responseBody.toString().getBytes());
        } catch (Exception e) {
            log.error(">>>>> requestId[{}], 修改响应体出错", requestId, e);
            throw e;
        }
    }

}

把所有过滤的优先级统一管理

自定义 FilterPriorityConstant 常量类

PS:项目中有好几个过滤器,原先都是在各自的类中定义的优先级,这里统一放到常量里进行管理
这里并未展示真实项目中的所有过滤器,仅作示例

import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;

/**
 * 过滤器优先级定义
 * @author weiheng
 * @date 2024-01-31
 **/
public class FilterPriorityConstant {

    /**
     * Useful constant for the highest precedence value.
     * @see java.lang.Integer#MIN_VALUE
     */
    public static int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

    /**
     * Useful constant for the lowest precedence value.
     * @see java.lang.Integer#MAX_VALUE
     */
    public static int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

    /** 商户权限校验 - 根据header的商户ID做接口权限校验 */
    public static int MERCHANT_AUTH_VALIDATE = 2;

    /** 请求体解密验签 */
    public static int REQUEST_BODY_DECRYPT_AND_SIGNATURE_VERIFICATION = 3;

    /** 响应体加密加签 value: -2 */
    public static int RESPONSE_BODY_ENCRYPT_AND_ADD_SIGNATURE = NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}

以上请求体和响应体的修改,亲测OK

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐