SpringCloudGateway网关处拦截并修改请求
需求背景
老系统没有引入Token的概念,之前的租户Id拼接在请求上,有的是以Get,Param传参形式;有的是以Post,Body传参的。需要在网关层拦截请求并进行请求修改后转发到对应服务。
举个例子:
Get请求:
/user/getInfo?userId=1
经过网关处理后变为 /user/getInfo?userId=1&&tenantId=2333
Post请求:
/user/getInfo
Body携带参数为:
json
{
userId: "1"
}
经过网关处理后变为
json
{
userId: "1",
tenantId: "2333"
}
解决办法
- 全局过滤器配置: 通过
@Bean
注解配置一个全局过滤器,用于在请求被转发到微服务前进行处理。 - 处理GET请求: 如果是GET请求,直接修改URL并返回,不对请求体进行修改。
- 处理非GET请求: 对非GET请求,使用装饰者模式创建
ModifyRequestBodyServerHttpRequestDecorator
对象,对请求体进行修改。 - 去掉Content-Length头: 在修改请求体的同时,通过
mutate()
方法去掉请求头中的Content-Length
。 - 修改请求体的装饰者类: 定义了一个内部类
ModifyRequestBodyServerHttpRequestDecorator
,继承自ServerHttpRequestDecorator
,用于实现请求体的修改。
代码示例:
java
// 导入必要的类和包
package com.***.gateway.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Configuration
@Slf4j
public class GatewayConfig {
// 配置全局过滤器
@Bean
public GlobalFilter customGlobalFilter() {
return (exchange, chain) -> {
// 获取原始请求对象
ServerHttpRequest request = exchange.getRequest();
// 构建URI组件构建器,用于修改请求URL
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(request.getURI());
// 初始化租户ID
String tenantId = "";
// 检查请求头中是否包含 "TenantId",如果有则获取其值
if (request.getHeaders().containsKey("TenantId")) {
tenantId = request.getHeaders().get("TenantId").get(0);
uriBuilder.queryParam("tenantId", tenantId);
}
// 如果请求是GET请求,则直接返回
if (request.getMethodValue().equals("GET")) {
log.info("请求是Get请求,url is {}", uriBuilder.build().toUri());
ServerHttpRequest modifiedRequest = request.mutate().uri(uriBuilder.build().toUri()).build();
// 创建新的ServerWebExchange,该对象包含修改后的请求
ServerWebExchange modifiedExchange = exchange.mutate().request(modifiedRequest).build();
// 继续执行过滤器链
return chain.filter(modifiedExchange);
}
// 使用装饰者模式修改请求体
ServerHttpRequest modifiedRequest = new ModifyRequestBodyServerHttpRequestDecorator(request, tenantId, exchange.getResponse().bufferFactory());
// 去掉Content-Length请求头
modifiedRequest = modifiedRequest.mutate().header("Content-Length", (String) null).build();
// 创建新的ServerWebExchange,该对象包含修改后的请求
ServerWebExchange modifiedExchange = exchange.mutate().request(modifiedRequest).build();
// 继续执行过滤器链
return chain.filter(modifiedExchange);
};
}
// 定义修改请求体的装饰者类
private static class ModifyRequestBodyServerHttpRequestDecorator extends ServerHttpRequestDecorator {
private final String tenantId;
private final DataBufferFactory bufferFactory;
private final ObjectMapper objectMapper = new ObjectMapper();
// 构造方法,传入原始请求、tenantId和数据缓冲工厂
ModifyRequestBodyServerHttpRequestDecorator(ServerHttpRequest delegate, String tenantId, DataBufferFactory bufferFactory) {
super(delegate);
this.tenantId = tenantId;
this.bufferFactory = bufferFactory;
}
// 重写获取请求体的方法,对请求体进行修改
@NotNull
@Override
public Flux<DataBuffer> getBody() {
return super.getBody().map(dataBuffer -> {
// 读取原始请求体数据
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
String body = new String(bytes, StandardCharsets.UTF_8);
// 修改请求体内容
String newBody = modifyJsonBody(body);
// 创建新的 DataBuffer
byte[] newData = newBody.getBytes(StandardCharsets.UTF_8);
return bufferFactory.wrap(newData);
});
}
// 对 JSON 请求体进行修改,添加 tenantId 字段
private String modifyJsonBody(String originalBody) {
try {
JsonNode jsonNode = objectMapper.readTree(originalBody);
((ObjectNode) jsonNode).put("tenantId", tenantId);
return objectMapper.writeValueAsString(jsonNode);
} catch (IOException e) {
log.error("Error modifying JSON body", e);
return originalBody;
}
}
}
}
解决路径文章参考
关于装饰者模式
装饰者模式是一种结构型设计模式,它允许你通过将对象放入包含行为的特殊封装类中来为原始对象添加新的行为。这种模式能够在不修改原始对象的情况下,动态地扩展其功能。在上段代码里,主要使用装饰者模式去修改Body 的传参。
主要角色:
- Component(组件): 定义一个抽象接口或抽象类,声明对象的一些基本操作。
- ConcreteComponent(具体组件): 实现了Component接口,是被装饰的具体对象,也是我们最终要添加新行为的对象。
- Decorator(装饰者抽象类): 继承了Component,并持有一个Component对象的引用,同时实现了Component定义的接口。它可以通过该引用调用Component的操作,同时可以添加、扩展或修改Component的行为。
- ConcreteDecorator(具体装饰者): 扩展Decorator,具体实现新行为的类。
装饰者模式的工作流程:
- 客户端通过Component接口与ConcreteComponent对象进行交互。
- ConcreteComponent对象处理客户端的请求。
- 客户端可以通过Decorator接口与ConcreteDecorator对象进行交互,Decorator持有ConcreteComponent的引用。
- ConcreteDecorator在调用ConcreteComponent的操作前后,可以添加、扩展或修改行为。
给普通咖啡加点糖和牛奶
代码示例:
java
public class DecoratorPatternExample {
// Component(组件)
interface Coffee {
String getDescription();
double cost();
}
// ConcreteComponent(具体组件)
static class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double cost() {
return 1.0;
}
}
// Decorator(装饰者抽象类)
abstract static class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double cost() {
return decoratedCoffee.cost();
}
}
// ConcreteDecorator(具体装饰者)
static class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", with Milk";
}
@Override
public double cost() {
return super.cost() + 0.5;
}
}
// ConcreteDecorator(具体装饰者)
static class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", with Sugar";
}
@Override
public double cost() {
return super.cost() + 0.2;
}
}
public static void main(String[] args) {
// 创建一个简单的咖啡
Coffee simpleCoffee = new SimpleCoffee();
System.out.println("Cost: " + simpleCoffee.cost() + ", Description: " + simpleCoffee.getDescription());
// 使用装饰者模式添加牛奶和糖
Coffee milkSugarCoffee = new MilkDecorator(new SugarDecorator(simpleCoffee));
System.out.println("Cost: " + milkSugarCoffee.cost() + ", Description: " + milkSugarCoffee.getDescription());
}
}