Article Directory
-
- 1 Service gateway starting case
-
- 1.1 Add modulr gateway service api-gateway under parent project
- 1.2 The pom file introduces the gateway's jar package dependency
- 1.3 Add basic configuration to yml file
- 1.5 Start the gateway service and product service
- 1.6 Browser request `http://localhost:7000/product-serv/product/1` test verification
- 2 The service gateway integrates nacos to read service information
-
- 2.1 pom file added to nacos dependent nacos-discovery
- 2.2 Add the nacos service discovery annotation @EnableDiscoveryClient to the startup class
- 2.3 yml file integrated nacos, modify routing and forwarding information
- 2.4 Browser request `http://localhost:7000/product-serv/product/1` test verification
- 3 Service gateway assertion use
- 4 Service gateway filter usage
- 5 Gateway integrated sentinel current limit
1 Service gateway starting case
1.1 Add modulr gateway service api-gateway under parent project
1.2 The pom file introduces the gateway's jar package dependency
<dependencies>
<!--引入gateway网关依赖,注:不能同时引入依赖starter-web-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
1.3 Add basic configuration to yml file
# 服务基础配置
server:
port: 7000
spring:
application:
name: api-gateway
# 网关配置
cloud:
gateway:
routes: # 路由数组,支持多个路由,路由配置参照RouteDefinition
- id: route-product # 路由标识
uri: http://localhost:8081 # 要转发到的远程服务地址
order: 0 # 排序,数字越小优先级越高
predicates: #断言数组,满足条件实现转发,支持自定义断言
- Path=/product-serv/**
filters: #过滤数组,转发前服务处理,支持前置过滤和后置过滤,支持自定义过滤(局部过滤、全局过滤)
- StripPrefix=1 # 转发前去掉一层访问路径
### 1.4 Add startup class ApiGateWayApplication
package cn.hzp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class);
}
}
1.5 Start the gateway service and product service
1.6 Browser request http://localhost:7000/product-serv/product/1
test verification
2 The service gateway integrates nacos to read service information
On the basis of case 1, in order to optimize the value of routing and forwarding uri http://localhost:8081
, integrate nacos to read service information
2.1 pom file added to nacos dependent nacos-discovery
<!--引入nacos依赖,实现网关注册到nacos同时从nacos读取其他服务信息-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.2 Add the nacos service discovery annotation @EnableDiscoveryClient to the startup class
2.3 yml file integrated nacos, modify routing and forwarding information
# 服务基础配置
server:
port: 7000
spring:
application:
name: api-gateway
# 网关配置
cloud:
# 增加nacos配置信息
nacos:
discovery:
server-addr: localhost:8848
gateway:
# 使gateway可以读取nacos中服务信息
discovery:
locator:
enabled: true
# 路由数组,支持多个路由,路由配置参照RouteDefinition
routes:
- id: route-product # 路由标识
# uri: http://localhost:8081 # 要转发到的远程服务地址
uri: lb://service-product # lb支持负载均衡(loadBalance)
order: 0 # 排序,数字越小优先级越高
predicates: #断言数组,满足条件实现转发,支持自定义断言
- Path=/product-serv/**
filters: #过滤数组,转发前服务处理,支持前置过滤和后置过滤,支持自定义过滤(局部过滤、全局过滤)
- StripPrefix=1 # 转发前去掉一层访问路径
2.4 Browser request http://localhost:7000/product-serv/product/1
test verification
3 Service gateway assertion use
3.1 Built-in gateway assertion
These assertion classes all inherit AbstractRoutePredicateFactory and use the reference URLhttps://www.cnblogs.com/wgslucky/p/11396579.html
3.2 custom assertion
Refer to the built-in assertion classPathRoutePredicateFactory
3.2.1 Pom file introduces lombok dependency
<!--引入lombok,方便日志输出-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
3.2.2 Add assertion class
package cn.hzp.predicates;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* 断言类名称必须是"断言的配置属性"+RoutePredicateFactory
* 必须继承AbstractRoutePredicateFactory<配置类>
*/
@Slf4j
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
/**
* 构造函数
*/
public AgeRoutePredicateFactory() {
super(AgeRoutePredicateFactory.Config.class);
}
/**
* 读取配置文件的参数信息,赋值到配置类的属性上
*/
@Override
public List<String> shortcutFieldOrder() {
// 参数顺序需要和配置文件参数顺序一致
return Arrays.asList("minAge", "maxAge");
}
/**
* 断言逻辑
*/
@Override
public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) {
return serverWebExchange -> {
String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age");
if (StringUtils.isNotEmpty(ageStr)) {
int age = Integer.parseInt(ageStr);
if (age >= config.getMinAge() && age <= config.getMaxAge()) {
return true;
}
}
return false;
};
}
/**
* 配置类,接收配置文件中的参数
*/
@Data
public static class Config {
private int minAge;
private int maxAge;
}
}
3.2.3 Add Age assertion to yml file
# 服务基础配置
server:
port: 7000
spring:
application:
name: api-gateway
# 网关配置
cloud:
# 增加nacos配置信息
nacos:
discovery:
server-addr: localhost:8848
gateway:
# 使gateway可以读取nacos中服务信息
discovery:
locator:
enabled: true
# 路由数组,支持多个路由,路由配置参照RouteDefinition
routes:
- id: route-product # 路由标识
# uri: http://localhost:8081 # 要转发到的远程服务地址
uri: lb://service-product # lb支持负载均衡(loadBalance)
order: 0 # 排序,数字越小优先级越高
predicates: #断言数组,满足条件实现转发,支持自定义断言
- Path=/product-serv/**
- Age=18,60 #自定义断言,实现服务只能被18-60岁之间的人访问
filters: #过滤数组,转发前服务处理,支持前置过滤和后置过滤,支持自定义过滤(局部过滤、全局过滤)
- StripPrefix=1 # 转发前去掉一层访问路径
3.2.4 Browser test verification
- Request
http://localhost:7000/product-serv/product/1?age=61
returns 404 - Request
http://localhost:7000/product-serv/product/1?age=60
to return to normal
4 Service gateway filter usage
- The pre-pre filter can verify identity and record debugging information. Such as StripPrefix
- The post filter can modify the response standard header, collect statistics, etc. Such as SetStatus
- Including local filters and global filters
4.1 Built-in local filter and built-in global filter
- The built-in local filters all inherit AbstractGatewayFilterFactory, such as SetStatusGatewayFilterFactory
- Built-in global filters inherit GlobalFilter, Ordered, such as LoadBalancerClientFilter
4.2 Custom local filter
The method is the same as the custom assertion, referring to SetStatusGatewayFilterFactory, designing a partial filter to control the output of the log
4.2.1 New local filter class LogGatewayFilterFactory
package cn.hzp.filters;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 网关gateway-自定义过滤器类
*/
@Slf4j
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
/**
* 构造函数
*/
public LogGatewayFilterFactory() {
super(LogGatewayFilterFactory.Config.class);
}
/**
* 将配置文件的属性信息赋值到配置类中
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("consoleLog", "cacheLog");
}
/**
* 自定义过滤逻辑
*/
@Override
public GatewayFilter apply(LogGatewayFilterFactory.Config config) {
return (exchange, chain) -> {
if (config.isConsoleLog()) {
log.info("输出控制台日志");
}
if (config.isCacheLog()) {
log.info("输出缓存日志");
}
return chain.filter(exchange);
};
}
/**
* 配置类
*/
@Data
@NoArgsConstructor
public static class Config {
private boolean consoleLog;
private boolean cacheLog;
}
}
4.2.2 Add filter attribute Log to yml file
# 服务基础配置
server:
port: 7000
spring:
application:
name: api-gateway
# 网关配置
cloud:
# 增加nacos配置信息
nacos:
discovery:
server-addr: localhost:8848
gateway:
# 使gateway可以读取nacos中服务信息
discovery:
locator:
enabled: true
# 路由数组,支持多个路由,路由配置参照RouteDefinition
routes:
- id: route-product # 路由标识
# uri: http://localhost:8081 # 要转发到的远程服务地址
uri: lb://service-product # lb支持负载均衡(loadBalance)
order: 0 # 排序,数字越小优先级越高
predicates: #断言数组,满足条件实现转发,支持自定义断言
- Path=/product-serv/**
# - Age=18,60 #自定义断言,实现服务只能被18-60岁之间的人访问
filters: #过滤数组,转发前服务处理,支持前置过滤和后置过滤,支持自定义过滤(局部过滤、全局过滤)
- StripPrefix=1 # 转发前去掉一层访问路径
- Log=true, false # 自定义过滤,控制日志输出
4.2.3 Test verification
The browser requests http://localhost:7000/product-serv/product/1
, the console outputs "output console log" to indicate success.
4.3 custom global filter
Design unified authentication logic, ignored by authentication center
4.3.1 New global filter class AuthGlobalFilter
package cn.hzp.filters;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {
/**
* 自定义过滤器逻辑
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 统一鉴权逻辑
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(!StringUtils.equals("admin", token)) {
log.info("认证失败");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 当前过滤器优先级,值越小优先级越高
*/
@Override
public int getOrder() {
return 0;
}
}
4.3.2 Test verification
- Browser request
http://localhost:7000/product-serv/product/1?token=admin
, return result normally - Browser request
http://localhost:7000/product-serv/product/1?token=admins
, return 401
5 Gateway integrated sentinel current limit
- 1 Route dimension current limit, according to route id for current limit
- 2 Custom api dimension current limit, according to api grouping for current limit
5.1 Route dimension current limiting case
5.1.1 Pom file increased dependency sentinel-spring-cloud-gateway-adapter
<!--gateway集成sentinel实现限流-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
5.1.2 Write the configuration class GatewaySentinelConfiguration
Inject SentinelGatewayFilter and SentinelGatewayBlockExceptionHandler
package cn.hzp.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import javafx.beans.property.ObjectProperty;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
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.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewaySentinelConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewaySentinelConfiguration(ObjectProvider<List<ViewResolver>> vewResolverProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = vewResolverProvider.getIfAvailable(Collections:: emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 初始化一个限流过滤器
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter(){
return new SentinelGatewayFilter();
}
/**
* 配置初始化的限流参数
*/
@PostConstruct
public void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
// resource为路由id,count为阈值,interval为统计时间窗口,默认1秒
rules.add(new GatewayFlowRule("route-product")
.setCount(1)
.setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
/**
* 配置异常处理器
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 自定义限流异常返回页面
*/
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> {
Map map = new HashMap<>();
map.put("code", 0);
map.put("message", "接口被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(map));
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
5.1.3 Startup test
The browser requests http://localhost:7000/product-serv/product/1?token=admin
, the page is refreshed multiple times and the current limit appears, and the test is successful
5.2 Brief description of api dimension current limit
5.2.1 Modify configuration class
Add a new method custom api group in the configuration class, modify the initialization current limit method initGatewayRules
package cn.hzp.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
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.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewaySentinelConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewaySentinelConfiguration(ObjectProvider<List<ViewResolver>> vewResolverProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = vewResolverProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 初始化一个限流过滤器
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
/**
* 配置初始化的限流参数
*/
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
// 以路由id为限流维度,resource为路由id,count为阈值,interval为统计时间窗口,默认1秒
// rules.add(new GatewayFlowRule("route-product").setCount(1).setIntervalSec(1));
// 以api分组为限流维度
rules.add(new GatewayFlowRule("product-api1").setCount(1).setIntervalSec(1));
rules.add(new GatewayFlowRule("product-api2").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
/**
* 配置异常处理器
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 自定义限流异常返回页面
*/
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> {
Map map = new HashMap<>();
map.put("code", 0);
map.put("message", "接口被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(map));
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
/**
* api维度限流,自定义api分组
*/
@PostConstruct
public void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
// api分组,apiName自定义唯一即可
ApiDefinition api1 = new ApiDefinition("product-api1")
.setPredicateItems(new HashSet<ApiPredicateItem>() {
{
add(new ApiPathPredicateItem()
.setPattern("/product-serv/api1/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)
);
}});
ApiDefinition api2 = new ApiDefinition("product-api2")
.setPredicateItems(new HashSet<ApiPredicateItem>() {
{
add(new ApiPathPredicateItem()
.setPattern("/product-serv/api2/demo2")
);
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
5.2.2 Add test method to ProductController in product service
/**
* 测试gateway的api分组限流
*/
@RequestMapping("/api1/demo1")
public String api1Demo1() {
return "/api1/demo1";
}
/**
* 测试gateway的api分组限流
*/
@RequestMapping("/api1/demo2")
public String api1Demo2() {
return "/api1/demo2";
}
/**
* 测试gateway的api分组限流
*/
@RequestMapping("/api2/demo1")
public String api2Demo1() {
return "/api2/demo1";
}
/**
* 测试gateway的api分组限流
*/
@RequestMapping("/api2/demo2")
public String api2Demo2() {
return "/api2/demo2";
}
5.2.3 Test verification
- 1 If the browser makes crazy requests
http://localhost:7000/product-serv/api1/demo1
, it will prompt you to be restricted - 2 If the browser makes crazy requests
http://localhost:7000/product-serv/api1/demo2
, it will prompt you to be restricted - 3 The browser makes crazy requests
http://localhost:7000/product-serv/api2/demo1
, it has been normal
questMapping("/api1/demo2")
public String api1Demo2() { return “/api1/demo2”; } /**
- Test gateway’s api grouping current limit
/
@RequestMapping("/api2/demo1")
public String api2Demo1() { return “/api2/demo1”; } /
* - Test gateway’s api grouping current limit
*/
@RequestMapping("/api2/demo2")
public String api2Demo2() { return “/api2/demo2”; }
- Test gateway’s api grouping current limit
#### 5.2.3 测试验证
- 1 浏览器疯狂请求`http://localhost:7000/product-serv/api1/demo1`,会提示被限流
- 2 浏览器疯狂请求`http://localhost:7000/product-serv/api1/demo2`,会提示被限流
- 3 浏览器疯狂请求`http://localhost:7000/product-serv/api2/demo1`,一直正常
- 4 浏览器疯狂请求`http://localhost:7000/product-serv/api1/demo2`,会提示被限流