环境:Springboot2.3.10.RELEASE + SpringCloud Hoxton.SR11 + SpringCloud Alibaba2.2.5.RELEASE + Redis
依赖
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
- 工程机构
- service-consumer子模块接口
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Resource
private RestTemplate restTemplate ;
@Resource
private HttpServletRequest request ;
@GetMapping("/get")
public Object invoke(String serviceName) {
try {
TimeUnit.SECONDS.sleep(3) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
return restTemplate.getForObject("http://service-producer/discovery/get?serviceName=" + serviceName, Object.class);
}
}
- service-producer子模块接口
@RestController
@RequestMapping("discovery")
public class DiscoveryController {
@NacosInjected
private NamingService namingService;
@Resource
private DiscoveryClient discoverClient ;
@GetMapping(value = "/get")
public Object get(@RequestParam String serviceName) throws Exception {
Map<String, Object> res = new HashMap<>() ;
res.put("services", discoverClient.getServices()) ;
res.put("instances", discoverClient.getInstances(serviceName)) ;
res.put("port", 9000) ;
return res ;
}
}
服务发现配置(访问中直接服务名访问)
方式一(不推荐):
spring:
cloud:
gateway:
discovery:
locator:
# 如:http://localhost:9999/service-consumer/consumer/get?serviceName=service-producer
enabled: true
lower-case-service-id: true
通过上面的配置,我们可以直接通过服务名(spring.application.name)来访问服务。实际在生产环境应该不会这样用吧,所以这样的功能应该是关闭的。
访问:
方式二:
spring:
cloud:
gateway:
routes:
- id: gw001
#当前如果配置的路由被匹配,这里的uri就是被转发的地址
uri: lb://service-consumer
# 这里的name(Path)是GatewayPredicate接口实现类对应的前缀。如:PathRoutePredicateFactory => Path
predicates:
#下面两种写法都OK。XXX=xxx 这种写法与谓词工厂中的shortcutFieldOrder方法是对应的Pattern是List集合,那么多个配置就用逗号分割。
- Path=/api/**, /api-a/**, /api-b/**
filters:
#截取请求路径的第一段(http://xxx:port/api/demo) 这里会自动的截取掉/api
#最后的转发地址为: http://service-consumer/demo
- StripPrefix=1
访问:
这里可以分别通过/api, /api-a, /api-b三个前缀访问。
整合Hystrix
这里需要引入Hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
spring:
cloud:
gateway:
routes:
- id: gw001
#当前如果配置的路由被匹配,这里的uri就是被转发的地址
uri: lb://service-consumer
# 这里的name(Path)是GatewayPredicate接口实现类对应的前缀。如:PathRoutePredicateFactory => Path
predicates:
#下面两种写法都OK。XXX=xxx 这种写法与谓词工厂中的shortcutFieldOrder方法是对应的Pattern是List集合,那么多个配置就用逗号分割。
- Path=/api/**, /api-a/**, /api-b/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: gatewayfallback
fallbackUri: forward:/gateway/error
注意:Hystrix Filter的name必须填写,名称随意。fallbackUri属性是在降级的时候调用的接口。
@RestController
@RequestMapping("/gateway")
public class DefaultController {
@RequestMapping("/error")
public Object error() {
Map<String, Object> result = new HashMap<>() ;
result.put("code", -1) ;
result.put("message", "服务不可用,请稍候再试") ;
result.put("data", null) ;
return result ;
}
}
hysrix默认的熔断时间是1s,所以在service-consumer接口中通过睡眠3s来模拟
访问:
hysrix相关的配置还是按照常规的配置即可。
自定义过滤器
- 全局过滤器
@Component
public class GlobalExecutionTimeFilter implements GlobalFilter {
private static Logger logger = LoggerFactory.getLogger(GlobalExecutionTimeFilter.class) ;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long start = System.currentTimeMillis() ;
Mono<Void> result = chain.filter(exchange) ;
logger.info("本次任务执行时间:{}", (System.currentTimeMillis() - start) + "毫秒") ;
return result ;
}
}
只需要实现GlobalFilter并且注册成Bean即可。全局过滤器会自动的应用到所有的路由上并且还不同配置任何东西。
访问:
- 自定义路由过滤器
该过滤器必须配置到具体的路由才能生效。
下面自定义一个验证token的过滤器。
@Component
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.TokenConfig> {
public static final String ENABLE_KEY = "enable";
public static final String EXCLUDE_KEY = "exclude" ;
public TokenGatewayFilterFactory() {
super(TokenConfig.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(ENABLE_KEY, EXCLUDE_KEY);
}
@Override
public GatewayFilter apply(TokenConfig config) {
return (exchange, chain) -> {
if (!config.isEnable()) {
return chain.filter(exchange) ;
}
ServerHttpRequest request = exchange.getRequest() ;
ServerHttpResponse response = exchange.getResponse() ;
String token = request.getHeaders().getFirst("access-token") ;
if (token == null || "".equals(token)) {
token = request.getQueryParams().getFirst("access-token") ;
}
if (token == null || "".equals(token)) {
DataBuffer data = setErrorInfo(response, "请检查Token是否填写");
return response.writeWith(Mono.just(data)) ;
}
return chain.filter(exchange) ;
};
}
private DataBuffer setErrorInfo(ServerHttpResponse response, String msg) {
//设置headers
HttpHeaders httpHeaders = response.getHeaders();
httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
//设置body
String body = "{\"code\":-1, \"message\": \"" + msg + "\"}";
DataBuffer data = response.bufferFactory().wrap(body.getBytes());
return data;
}
public static class TokenConfig {
private boolean enable ;
private String exclude ;
public String getExclude() {
return exclude;
}
public void setExclude(String exclude) {
this.exclude = exclude;
}
public boolean isEnable() {
return enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
}
}
配置:
spring:
cloud:
gateway:
routes:
- id: gw001
#当前如果配置的路由被匹配,这里的uri就是被转发的地址
uri: lb://service-consumer
# 这里的name(Path)是GatewayPredicate接口实现类对应的前缀。如:PathRoutePredicateFactory => Path
predicates:
#下面两种写法都OK。XXX=xxx 这种写法与谓词工厂中的shortcutFieldOrder方法是对应的Pattern是List集合,那么多个配置就用逗号分割。
- Path=/api/**, /api-a/**, /api-b/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: gatewayfallback
fallbackUri: forward:/gateway/error
- name: Token
args:
enable: true
访问:
如果请求header中不包含access-token
限流
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
spring:
cloud:
gateway:
routes:
- id: gw001
#当前如果配置的路由被匹配,这里的uri就是被转发的地址
uri: lb://service-consumer
# 这里的name(Path)是GatewayPredicate接口实现类对应的前缀。如:PathRoutePredicateFactory => Path
predicates:
#下面两种写法都OK。XXX=xxx 这种写法与谓词工厂中的shortcutFieldOrder方法是对应的Pattern是List集合,那么多个配置就用逗号分割。
- Path=/api/**, /api-a/**, /api-b/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
keyResolver: '#{@redisKeyResolver}' #SpringEL表达式
redis-rate-limiter.replenishRate: 1 #补充率,每秒产生多少令牌
redis-rate-limiter.burstCapacity: 2 #令牌桶容量
@Component
public class RedisKeyResolver implements KeyResolver {
// 限流策略,根据IP等信息进行限流,同一IP每秒访问次数
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
String address = exchange.getRequest().getRemoteAddress().getHostString() ;
return Mono.just(address) ;
}
}
访问:
当访问过快就会返回429状态码,太多的请求。
完毕!!!
给个关注+转发呗谢谢
关注公众:Springboot实战案例锦集
SpringCloud Alibaba 之 Nacos 服务
Spring Cloud Gateway应用详解2内置过滤器