SpringCloudGateway网关处拦截并修改请求

2023-12-22 08:44

本文主要是介绍SpringCloudGateway网关处拦截并修改请求,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

SpringCloudGateway网关处拦截并修改请求

img

需求背景

老系统没有引入Token的概念,之前的租户Id拼接在请求上,有的是以Get,Param传参形式;有的是以Post,Body传参的。需要在网关层拦截请求并进行请求修改后转发到对应服务。

举个例子:

Get请求:

/user/getInfo?userId=1 经过网关处理后变为 /user/getInfo?userId=1&&tenantId=2333

Post请求:

/user/getInfo Body携带参数为:

{userId: "1"
}

经过网关处理后变为

{userId: "1",tenantId: "2333"
}

解决办法

  1. 全局过滤器配置: 通过@Bean注解配置一个全局过滤器,用于在请求被转发到微服务前进行处理。
  2. 处理GET请求: 如果是GET请求,直接修改URL并返回,不对请求体进行修改。
  3. 处理非GET请求: 对非GET请求,使用装饰者模式创建ModifyRequestBodyServerHttpRequestDecorator对象,对请求体进行修改。
  4. 去掉Content-Length头: 在修改请求体的同时,通过mutate()方法去掉请求头中的Content-Length
  5. 修改请求体的装饰者类: 定义了一个内部类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 {// 配置全局过滤器@Beanpublic GlobalFilter customGlobalFilter() {return (exchange, chain) -> {// 获取原始请求对象ServerHttpRequest request = exchange.getRequest();// 构建URI组件构建器,用于修改请求URLUriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(request.getURI());// 初始化租户IDString 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@Overridepublic 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);// 创建新的 DataBufferbyte[] 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;}}}
}

解决路径文章参考

http://t.csdnimg.cn/9kos5

http://t.csdnimg.cn/Aklwh

关于装饰者模式

装饰者模式是一种结构型设计模式,它允许你通过将对象放入包含行为的特殊封装类中来为原始对象添加新的行为。这种模式能够在不修改原始对象的情况下,动态地扩展其功能。在上段代码里,主要使用装饰者模式去修改Body 的传参。

主要角色:

  1. Component(组件): 定义一个抽象接口或抽象类,声明对象的一些基本操作。
  2. ConcreteComponent(具体组件): 实现了Component接口,是被装饰的具体对象,也是我们最终要添加新行为的对象。
  3. Decorator(装饰者抽象类): 继承了Component,并持有一个Component对象的引用,同时实现了Component定义的接口。它可以通过该引用调用Component的操作,同时可以添加、扩展或修改Component的行为。
  4. ConcreteDecorator(具体装饰者): 扩展Decorator,具体实现新行为的类。

装饰者模式的工作流程:

  1. 客户端通过Component接口与ConcreteComponent对象进行交互。
  2. ConcreteComponent对象处理客户端的请求。
  3. 客户端可以通过Decorator接口与ConcreteDecorator对象进行交互,Decorator持有ConcreteComponent的引用。
  4. ConcreteDecorator在调用ConcreteComponent的操作前后,可以添加、扩展或修改行为。

给普通咖啡加点糖和牛奶

代码示例:

public class DecoratorPatternExample {// Component(组件)interface Coffee {String getDescription();double cost();}// ConcreteComponent(具体组件)static class SimpleCoffee implements Coffee {@Overridepublic String getDescription() {return "Simple Coffee";}@Overridepublic double cost() {return 1.0;}}// Decorator(装饰者抽象类)abstract static class CoffeeDecorator implements Coffee {protected Coffee decoratedCoffee;public CoffeeDecorator(Coffee coffee) {this.decoratedCoffee = coffee;}@Overridepublic String getDescription() {return decoratedCoffee.getDescription();}@Overridepublic double cost() {return decoratedCoffee.cost();}}// ConcreteDecorator(具体装饰者)static class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getDescription() {return super.getDescription() + ", with Milk";}@Overridepublic double cost() {return super.cost() + 0.5;}}// ConcreteDecorator(具体装饰者)static class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getDescription() {return super.getDescription() + ", with Sugar";}@Overridepublic 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());}
}

这篇关于SpringCloudGateway网关处拦截并修改请求的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/523317

相关文章

Docker镜像修改hosts及dockerfile修改hosts文件的实现方式

《Docker镜像修改hosts及dockerfile修改hosts文件的实现方式》:本文主要介绍Docker镜像修改hosts及dockerfile修改hosts文件的实现方式,具有很好的参考价... 目录docker镜像修改hosts及dockerfile修改hosts文件准备 dockerfile 文

Java利用docx4j+Freemarker生成word文档

《Java利用docx4j+Freemarker生成word文档》这篇文章主要为大家详细介绍了Java如何利用docx4j+Freemarker生成word文档,文中的示例代码讲解详细,感兴趣的小伙伴... 目录技术方案maven依赖创建模板文件实现代码技术方案Java 1.8 + docx4j + Fr

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J