淳安县建设网站,淮安建设工程协会网站查询系统,做网站要用那些软件,网站怎么做用户体验API网关的核心功能是统一流量入口#xff0c;实现路由转发#xff0c;SpringCloudGateway是API网关开发的技术之一#xff0c;此外比较流行的还有Kong和ApiSix#xff0c;这2个都是基于OpenResty技术栈。
简单的路由转发可以通过SpringCloudGateway的配置文件实现#xf…API网关的核心功能是统一流量入口实现路由转发SpringCloudGateway是API网关开发的技术之一此外比较流行的还有Kong和ApiSix这2个都是基于OpenResty技术栈。
简单的路由转发可以通过SpringCloudGateway的配置文件实现在一些业务场景种会需要动态替换路由配置中的后端服务地址单纯靠配置文件无法满足这种需求。
本文介绍一种将路由配置保存到数据库中可以根据接口请求的特定条件从数据库中动态读取后端服务地址实现灵活转发。
具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-cloud-gateway
一、概述
通过把SpringCloudGateway的相关路由配置规则保存到数据库中可以动态的灵活调整路由。在本文的实现中我们通过请求header中的特定值动态选择对应的后端服务地址。
二、项目中加入依赖
在项目的gradle中增加依赖关系。
build.gradle:
plugins {id org.springframework.boot version 3.0.2id io.spring.dependency-management version 1.1.0id java
}group cn.springcamp
version 0.0.1-SNAPSHOT
sourceCompatibility 17configurations {compileOnly {extendsFrom annotationProcessor}testCompileOnly {extendsFrom testAnnotationProcessor}
}repositories {mavenCentral()
}dependencies {implementation org.springframework.boot:spring-boot-starter-jsonimplementation org.springframework.boot:spring-boot-starter-validationimplementation org.springframework.boot:spring-boot-starter-data-r2dbcimplementation org.springframework.cloud:spring-cloud-starter-gatewayruntimeOnly com.h2database:h2runtimeOnly io.r2dbc:r2dbc-h2annotationProcessor org.projectlombok:lomboktestAnnotationProcessor org.projectlombok:lomboktestImplementation org.springframework.boot:spring-boot-starter-testtestImplementation org.junit.vintage:junit-vintage-enginetestImplementation io.projectreactor:reactor-testtestImplementation com.h2database:h2testImplementation io.r2dbc:r2dbc-h2testImplementation org.junit.vintage:junit-vintage-engine
}dependencyManagement {imports {mavenBom org.springframework.cloud:spring-cloud-dependencies:2022.0.1}
}test {useJUnitPlatform()
}由于SpringCloudGateway基于SpringWebFlux技术构建所以依赖中的数据库配置需要使用r2dbc 。
三、配置文件
示例程序首选通过配置文件对路由进行基本配置配置文件代码:
spring:r2dbc:url: r2dbc:h2:mem:///testdb?optionsDB_CLOSE_DELAY-1;DB_CLOSE_ON_EXITFALSEusername: sapassword:cloud:gateway:routes:- id: routeOnepredicates:- Path/route1/**uri: no://opfilters:- UriHostPlaceholderFilter10001- id: routeTwopredicates:- Path/route2/**uri: no://opfilters:- UriHostPlaceholderFilter10001配置文件中配置了2个路由对应的接口地址路径分别是 /route1/**和 Path/route2/**路径中的 ***表示模糊匹配只要是以 /route1/为前缀的路径都可以被访问到。
后端服务地址配置了一个无意的地址: uri: no://op因为我们的处理逻辑会通过从数据库中读取配置来动态替换后端服务地址。
三、动态路由数据存储格式
我们通过 ROUTE_FILTER_ENTITY这个数据库表来存储接口后端服务配置数据。表结构为
CREATE TABLE ROUTE_FILTER_ENTITY
(id VARCHAR(255) PRIMARY KEY,route_id VARCHAR(255), -- 路由ID对应配置文件中的 id配置项code VARCHAR(255), -- 接口请求header中的code参数的值url VARCHAR(255) -- 后端服务地址
);当客户端访问 /route1/test接口时根据配置文件的路由配置SpringCloudGateway 会命中 id: routeOne这个路由规则这个规则对应的后端服务地址是 uri: no://op并不是我们期望的真实后端服务地址。
因此我们需要读取到真实的后端服务地址并将请求转发到这个地址。跟据 routeId 和 接口请求header中的code参数的值就可以从 ROUTE_FILTER_ENTITY 表中查到对应的后端服务地址 url这个字段的值。
我们已经读取到了后端服务地址还需要将请求转发到这个地址下面介绍转发的方法。
四、后端服务动态转发
动态转发通过自定义 filter 的方式实现自定义 filter 代码如下
Component
public class UriHostPlaceholderFilter extends AbstractGatewayFilterFactoryUriHostPlaceholderFilter.Config {Autowiredprivate RouteFilterRepository routeFilterRepository;public UriHostPlaceholderFilter() {super(Config.class);}Overridepublic ListString shortcutFieldOrder() {return Collections.singletonList(order);}Overridepublic GatewayFilter apply(Config config) {return new OrderedGatewayFilter((exchange, chain) - {String code exchange.getRequest().getHeaders().getOrDefault(code, new ArrayList()).stream().findFirst().orElse();String routeId exchange.getAttribute(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR);if (StringUtils.hasText(code)) {String newurl;try {newurl routeFilterRepository.findByRouteIdAndCode(routeId, code).toFuture().get().getUrl();} catch (InterruptedException | ExecutionException e) {throw new RuntimeException(e);}if (StringUtils.hasText(exchange.getRequest().getURI().getQuery())) {newurl newurl ? exchange.getRequest().getURI().getQuery();}URI newUri null;try {newUri new URI(newurl);} catch (URISyntaxException e) {log.error(uri error, e);}exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newUri);}return chain.filter(exchange);}, config.getOrder());}DataNoArgsConstructorpublic static class Config {private int order;public Config(int order) {this.order order;}}
}通过扩展 AbstractGatewayFilterFactory 类我们自定义了 UriHostPlaceholderFilter 这个 filter 。
代码的核心逻辑在 apply 方法中。
首先通过 String code exchange.getRequest().getHeaders().getOrDefault(code, new ArrayList()).stream().findFirst().orElse()可以获取到接口请求 header 中 code 这个参数的值。
再通过 String routeId exchange.getAttribute(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR)可以获取到 routeId 。
最后通过 newurl routeFilterRepository.findByRouteIdAndCode(routeId, code).toFuture().get().getUrl()就可以从数据库中读取到配置好的后端服务地址。
拿到后端服务地址后 通过调用 exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newUri);将请求转发到对应的地址。
四、单元测试
在单元测试代码中我们预置了一条后端服务动态配置数据
insert into ROUTE_FILTER_ENTITY values(1,routeOne,alpha,http://httpbin.org/anything)然后模拟请求 /route1/test?atest这个接口根据我们的配置请求会被转发到 http://httpbin.org/anything。
执行单元测试后可以从日志中发现接口返回的数据是 http://httpbin.org/anything 这个后端服务返回的数据。
当我们希望调整后端服务地址时只需要把 ROUTE_FILTER_ENTITY 表中的这条配置数据中的 url 字段改成其它的任何服务地址即可大大增加了程序的灵活度。