环境树立
服务环境树立
- 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相关详细的文章将在后续进行更新。