SpringCloudGateway網關處攔截并修改請求的操作方法
SpringCloudGateway網關處攔截并修改請求

需求背景
老系統(tǒng)沒有引入Token的概念,之前的租戶Id拼接在請求上,有的是以Get,Param傳參形式;有的是以Post,Body傳參的。需要在網關層攔截請求并進行請求修改后轉發(fā)到對應服務。
舉個例子:
Get請求:
/user/getInfo?userId=1 經過網關處理后變?yōu)?/user/getInfo?userId=1&&tenantId=2333
Post請求:
/user/getInfo Body攜帶參數為:
{
userId: "1"
}
經過網關處理后變?yōu)?/p>
{
userId: "1",
tenantId: "2333"
}解決辦法
- 全局過濾器配置: 通過@Bean注解配置一個全局過濾器,用于在請求被轉發(fā)到微服務前進行處理。
- 處理GET請求: 如果是GET請求,直接修改URL并返回,不對請求體進行修改。
- 處理非GET請求: 對非GET請求,使用裝飾者模式創(chuàng)建ModifyRequestBodyServerHttpRequestDecorator對象,對請求體進行修改。
- 去掉Content-Length頭: 在修改請求體的同時,通過mutate()方法去掉請求頭中的Content-Length。
- 修改請求體的裝飾者類: 定義了一個內部類ModifyRequestBodyServerHttpRequestDecorator,繼承自ServerHttpRequestDecorator,用于實現請求體的修改。
代碼示例:
// 導入必要的類和包
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();
// 創(chuàng)建新的ServerWebExchange,該對象包含修改后的請求
ServerWebExchange modifiedExchange = exchange.mutate().request(modifiedRequest).build();
// 繼續(xù)執(zhí)行過濾器鏈
return chain.filter(modifiedExchange);
}
// 使用裝飾者模式修改請求體
ServerHttpRequest modifiedRequest = new ModifyRequestBodyServerHttpRequestDecorator(request, tenantId, exchange.getResponse().bufferFactory());
// 去掉Content-Length請求頭
modifiedRequest = modifiedRequest.mutate().header("Content-Length", (String) null).build();
// 創(chuàng)建新的ServerWebExchange,該對象包含修改后的請求
ServerWebExchange modifiedExchange = exchange.mutate().request(modifiedRequest).build();
// 繼續(xù)執(zhí)行過濾器鏈
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);
// 創(chuàng)建新的 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;
}
}
}
}解決路徑文章參考
關于裝飾者模式
裝飾者模式是一種結構型設計模式,它允許你通過將對象放入包含行為的特殊封裝類中來為原始對象添加新的行為。這種模式能夠在不修改原始對象的情況下,動態(tài)地擴展其功能。在上段代碼里,主要使用裝飾者模式去修改Body 的傳參。
主要角色:
- Component(組件): 定義一個抽象接口或抽象類,聲明對象的一些基本操作。
- ConcreteComponent(具體組件): 實現了Component接口,是被裝飾的具體對象,也是我們最終要添加新行為的對象。
- Decorator(裝飾者抽象類): 繼承了Component,并持有一個Component對象的引用,同時實現了Component定義的接口。它可以通過該引用調用Component的操作,同時可以添加、擴展或修改Component的行為。
- ConcreteDecorator(具體裝飾者): 擴展Decorator,具體實現新行為的類。
裝飾者模式的工作流程:
- 客戶端通過Component接口與ConcreteComponent對象進行交互。
- ConcreteComponent對象處理客戶端的請求。
- 客戶端可以通過Decorator接口與ConcreteDecorator對象進行交互,Decorator持有ConcreteComponent的引用。
- ConcreteDecorator在調用ConcreteComponent的操作前后,可以添加、擴展或修改行為。
給普通咖啡加點糖和牛奶
代碼示例:
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) {
// 創(chuàng)建一個簡單的咖啡
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());
}
}到此這篇關于SpringCloudGateway網關處攔截并修改請求的文章就介紹到這了,更多相關SpringCloud Gateway網關內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot下無節(jié)制和數據庫建立連接的問題及解決方法
本文介紹了無節(jié)制建立MySQL連接的危害,包括數據庫服務端資源耗盡、應用端性能劣化和監(jiān)控與運維困境,提出了系統(tǒng)性解決方案,包括連接池標準化配置、代碼規(guī)范與防御式編程、全鏈路監(jiān)控體系和架構級優(yōu)化,感興趣的朋友一起看看吧2025-03-03
maven?scope?provided和runtime的例子說明
這篇文章主要介紹了maven?scope?provided和runtime的例子說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12

