SpringCloud-Gateway整合SpingCloud-Alibaba入门简单示例
环境搭建
服务环境搭建
- Maven依赖配置
建立Maven项目结构如下:
--springcloud-alibaba-gateway
|----springcloud-provider
|----springcloud-gateway
- 父项目springcloud-alibaba-gateway的POM依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.9</version>
<relativePath/>
</parent>
<packaging>pom</packaging>
<properties>
。。。
<!-- 此处仅列出关键依赖版本,版本对应关系参考SpringCloud-Alibaba官网 -->
<spring.boot.version>2.7.9</spring.boot.version>
<spring.cloud.version>2021.0.4</spring.cloud.version>
<spring.cloud.alibaba.version>2021.0.4.0</spring.cloud.alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 应用服务springcloud-provider的POM依赖
<dependencies>
<!--springcloud-alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
- 网关服务springcloud-gateway的POM依赖
<dependencies>
<!--springcloud-alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 整合nacos需要添加负载均衡依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
- 接口代码编写
应用服务测试接口编写, 该接口将通过gateway服务进行负载均衡
package com.amano.springcloudalibabaproduct.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
@RequestMapping("/test")
public class GateWayController {
@Value("${server.port}")
private Integer port;
@GetMapping
public String testGateWay() {
return "service:" + port + " access";
}
}
应用服务启动类,添加 @EnableDiscoveryClient 注解启用服务发现
package com.amano.springcloudalibabaproduct;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class SpringcloudAlibabaProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudAlibabaProviderApplication.class, args);
}
}
网关服务同样开始服务发现
package com.amano.springcloudgateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class SpringcloudGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudGatewayApplication.class, args);
}
}
- 服务配置
应用服务的配置文件,采用bootstrap.yaml配置Nacos服务发现和配置中心的信息
server:
port: 8080
spring:
application:
name: springcloud-provider
profiles:
active: local
cloud:
nacos:
# nacos服务注册中心
discovery:
server-addr: localhost:8848
namespace: ${nacos配置的namespace,默认则不填}
group: SPRING-CLOUD-ALIBABA
# nacos配置中心
config:
group: SPRING-CLOUD-ALIBABA
name: product-config
file-extension: yaml
server-addr: localhost:8848
namespace: ${nacos配置的namespace,默认则不填}
网关服务的配置文件,同样采用bootstrap.yaml配置Nacos服务发现和配置中心的信息
server:
port: 10080
spring:
application:
name: springcloud-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: ${nacos配置的namespace,默认则不填}
group: SPRING-CLOUD-ALIBABA
config:
server-addr: localhost:8848
namespace: ${nacos配置的namespace,默认则不填}
group: SPRING-CLOUD-ALIBABA
file-extension: yaml
profiles:
active: local
网关路由信息配置到nacos配置中心, 实现网关路由的动态刷新
spring:
cloud:
gateway:
discovery:
locator:
# 允许gateway从nacos中查找微服务
enabled: true
routes:
# 确定路由唯一性的id
- id: provider_router
# lb允许才nacos中通过该名称查找到指定服务,并实现负载均衡
uri: lb://springcloud-provider
predicates:
# 路由断言
- Path=/api/provider/**
filters:
# 过滤器
- StripPrefix=2
功能测试
通过配置IDEA启动两个应用服务, 通过代码获取的端口号来验证接口是否实现负载均衡
分别启动两个springcloud-provider示例和springcloud-gateway服务
多次通过Postman访问网关服务的路由地址,查看结果
可以看到接口通过Gateway网关顺利实现了负载均衡
SpringCloud-Gateway的配置及拓展
断言
Gateway断言(Predicate)用于判断路由的匹配条件。只有满足的断言的条件,该请求才会被发到指定的服务上进行处理。
使用断言需要注意以下情况:
- 一个路由可以配置多个断言。
- 请求必须满足所有断言条件才能被转发到指定的路由。
- 当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。
- Gateway内置了11种的断言工厂,所有这些断言都与 HTTP 请求的不同属性匹配。
断言 | 格式 | 说明 | 断言工厂 |
---|---|---|---|
Path | Path=/api/provider/** | 当请求路径与参数匹配时 | PathRoutePredicateFactory |
After | - After=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] | 接收一个日期参数,判断请求日期是否晚于指定日期 | AfterRoutePredicateFactory |
Before | - Before=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] | 接收一个日期参数,判断请求日期是否早于指定日期 | BeforeRoutePredicateFactory |
Between | - Between=2021-10-20T11:47:34.255+08:00[Asia/Shanghai],2021-10-20T13:47:34.255+08:00[Asia/Shanghai] | 接收两个日期参数以逗号隔开,判断请求日期是否在指定日期之间 | BetweenRoutePredicateFactory |
Cookie | - Cookie=key,vlue | 接收两个参数,判断cookie是否具有给定的键值 | CookieRoutePredicateFactory |
Header | - Header=key,value | 接接收两个参数,判断请求header是否具有给定的键值 | HeaderRoutePredicateFactory |
Host | - Host=*.host.com,... | 接收多个URI模版变量,判断请求主机是否匹配指定的域名 | HostRoutePredicateFactory |
Method | - Method=GET,POST | 接收一个参数, 判断请求类型是否匹配 | MethodRoutePredicateFactory |
Query | - Query=key,value | 接收两个参数,判断请求param种是否包含指定key,value的参数 | QueryRoutePredicateFactory |
Remote | - Remote=192.168.0.1/16 | 接收一个参数,判断请求主机是否在指定的IP段中 | RemoteAddrRoutePredicateFactory |
Weight | - Weight=group1, 7 | 接收一个[组名,权重的]的参数,对指定组内的路由按权重进行转发 | WeightRoutePredicateFactory |
- 自定义断言
当Gateway默认提供的断言无法满足我们的需求时,我们可以自定义断言工厂, 对路由条件进行灵活拓展。
- 创建一个类继承AbstractRoutePredicateFactory类,实现 apply方法
package com.amano.springcloudgateway.predicate;
import lombok.Data;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
@Component
public class AreaGatewayFilterFactory extends AbstractRoutePredicateFactory<AreaGatewayFilterFactory.Config> {
public AreaGatewayFilterFactory() {
super(AreaGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("area");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return serverWebExchange -> {
String area = serverWebExchange.getRequest().getQueryParams().getFirst("area");
if (StringUtils.isEmpty(area)) {
return false;
}
return Objects.equals(config.getArea(), area);
};
}
@Data
public static class Config {
private String area;
}
@Override
public String name() {
return "Area";
}
}
- 修改路由配置
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: nacos_product_router
uri: lb://nacos-product
predicates:
- Path=/api/provider/**
# 使用自定义断言
- Area=Shanghai
filters:
- StripPrefix=2
- 修改测试接口, 添加参数
@GetMapping()
public String testGateWayArea(String area) {
return "service:" + port + " access:" + area;
}
- 测试
可以看到路由参数中的area不匹配配置中的参数时无法访问指定微服务的接口
而满足条件时可以正常访问
过滤器
通常情况下,出于安全方面的考虑,服务端提供的服务往往都会有一定的校验逻辑,例如用户登陆状态校验、签名校验等。SpringCloud-Gateway 提供了以下两种类型的过滤器,可以对请求和响应进行精细化控制。
过滤器类型 | 说明 |
---|---|
Pre 类型 | 这种过滤器在请求被转发到微服务之前可以对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等操作。 |
Post 类型 | 这种过滤器在微服务对请求做出响应后可以对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。 |
按照作用范围划分,SpringCloud-gateway的Filter可以分为 2 类:
- GatewayFilter(局部过滤器):应用在单个路由或者一组路由上的过滤器。
- GlobalFilter(全局过滤器): 应用在所有的路由上的过滤器。
局部过滤器
- 局部过滤器的使用方式
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: nacos_product_router
uri: lb://nacos-product
predicates:
- Path=/api/provider/**
filters:
# 截断原始路径
- StripPrefix=2
# 通过过滤器,修改返回状态
- SetStatus=201
- 在SpringCloud-Gateway中内置了31种局部过滤器, 以下列举部分常用的过滤器及其用法
过滤器工厂 | 功能 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
RequestRateLimiter | 用于对请求限流, 限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、 statuses、methods、 series |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码, 可以是数字, 也可以是字符串 |
- 自定义局部过滤器
// 自定义的过滤器类名需要满足, 过滤器名称 + GatewayFilterFactory
@Component
@Slf4j
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
public LogGatewayFilterFactory() {
super(LogGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("open");
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (config.open) {
// 过滤器逻辑处理
log.info("日志。。。。")
}
return chain.filter(exchange);
}
};
}
@Data
static class Config {
private boolean open;
}
}
全局过滤器
全局过滤器作用于所有路由,所以无需配置,通过全局路由器可以权限的统一校验,安全性验证等功能。SpringCloud-Gateway同样也内置了一些全局过滤器, 这些过滤器都实现了GlobalFilter和 Ordered接口,感兴趣的可以自行查看源码。
- 自定义全局过滤器
@Component
@Slf4j
public class TokenGlobalFilter implements GlobalFilter, Ordered {
@SneakyThrows
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (token == null || token.length() == 0 || !token.equals("token")) {
log.warn("鉴权失败");
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
// 返回失败信息
Map<String, Object> map = new HashMap<>();
map.put("code", HttpStatus.UNAUTHORIZED.value());
map.put("message", HttpStatus.UNAUTHORIZED.getReasonPhrase());
DataBuffer buffer = response.bufferFactory().wrap(new ObjectMapper().writeValueAsBytes(map));
return response.writeWith(Flux.just(buffer));
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
自定义路由匹配失败处理
在前面的测试中,当我们定义的路由规则不匹配时,Gateway会默认返回一个错误页面,这种页面通常无法满足我们的业务需求。我们通过自定义返回一个友好的提示信息。
- 创建一个类继承DefaultErrorWebExceptionHandler, 重写方法
package com.amano.springcloudgateway.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class CustomErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
public CustomErrorWebExceptionHandler(ErrorAttributes errorAttributes,
WebProperties.Resources resources,
ErrorProperties errorProperties,
ApplicationContext applicationContext) {
super(errorAttributes, resources, errorProperties, applicationContext);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
@Override
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
Map<String, Object> errorMap = getErrorAttributes(request, includeStackTrace);
int status = Integer.parseInt(errorMap.get("status").toString());
Map<String, Object> response = response(status, errorMap.get("error").toString(), errorMap);
return ServerResponse.status(status).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(response));
}
// 响应内容
public static Map<String, Object> response(int status, String errorMessage, Map<String, Object> errorMap) {
Map<String, Object> map = new HashMap<>();
map.put("code", status);
map.put("message", errorMessage);
map.put("data", errorMap);
return map;
}
}
- 覆盖Gateway默认配置
package com.amano.springcloudgateway;
import com.amano.springcloudgateway.handler.CustomErrorWebExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.Collections;
import java.util.List;
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class GateWayConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GateWayConfiguration(ServerProperties serverProperties,
ApplicationContext applicationContext,
ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean("customErrorWebExceptionHandler")
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler myErrorWebExceptionHandler(ErrorAttributes errorAttributes) {
CustomErrorWebExceptionHandler exceptionHandler = new CustomErrorWebExceptionHandler(
errorAttributes,
this.resourceProperties,
this.serverProperties.getError(),
this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}
总结
通过这个简单的实例,我们实现了springcloud-alibaba和springcloud-gateway的整合,通过路由访问nacos注册的微服务。springcloud-alibaba和gateway相关详细的文章将在后续进行更新。
转载自:https://juejin.cn/post/7206576861051928637