1.pom文件
<org.springframework.boot.version>2.2.2.RELEASE</org.springframework.boot.version>
<org.springframework.boot.maven.version>2.1.0.RELEASE</org.springframework.boot.maven.version>
<org.springframework.cloud.version>Hoxton.SR1</org.springframework.cloud.version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
</dependencies>
2.配置yml文件
gateway:
matcher:
whitelist:
- /**/api-docs
- /consul-service/**
- /**/**.css
- /**/*.js
server:
port: 80
logging:
file: './log/gateway.log'
level:
root: ${LOG_LEVE:info}
spring:
application:
name: gateway
cloud:
consul:
enabled: true
host: localhost
port: 8500
discovery:
enabled: true
prefer-ip-address: true
register: true
deregister: true
hostname: localhost
health-check-path: /actuator/health
health-check-interval: 15s
health-check-critical-timeout: 30s
service-name: ${spring.application.name}
instance-id: ${spring.application.name}-${spring.cloud.client.ip-address}-${server.port}
tags: gatewayDemo
gateway:
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-headers: "*"
allow-credentials: true
allowed-methods:
- "*"
maxAge: 18000
discovery:
locator:
enabled: true
routes:
- id: api
uri: lb://api
predicates:
- Path=/api/**
filters:
default-filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: fallback
fallbackUri: forward:/circuitBreaker/fallback
resilience4j:
retry:
retry-aspect-order: 396
configs:
myConfig:
maxRetryAttempts: 5
waitDuration: 500
eventConsumerBufferSize: 10
enableExponentialBackoff: true
exponentialBackoffMultiplier: 1.1
enableRandomizedWait: false
retryExceptions:
- java.lang.Exception
ignoreExceptions:
- java.lang.IllegalStateException
instances:
retryA:
baseConfig: myConfig
circuitbreaker:
circuit-breaker-aspect-order: 397
configs:
myConfig:
ringBufferSizeInClosedState: 5
ringBufferSizeInHalfOpenState: 3
failureRateThreshold: 50
slowCallDurationThreshold: 60s
slowCallRateThreshold: 100
slidingWindowSize: 100
registerHealthIndicator: true
slidingWindowType: COUNT_BASED
minimumNumberOfCalls: 10
automaticTransitionFromOpenToHalfOpenEnabled: false
permittedNumberOfCallsInHalfOpenState: 10
waitDurationInOpenState: 60s
eventConsumerBufferSize: 10
recordExceptions:
- java.lang.Exception
ignoreExceptions:
- java.lang.IllegalStateException
instances:
backendA:
baseConfig: myConfig
minimumNumberOfCalls: 3
waitDurationInOpenState: 6s
3.配置全局异常
package cloud.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
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.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 util.JsonUtil;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static org.springframework.web.reactive.function.server.RequestPredicates.all;
@Slf4j
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
public JsonExceptionHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties,
ErrorProperties errorProperties,
ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties,
applicationContext);
}
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Throwable error = super.getError(request);
Map<String, Object> errorAttributes = super.getErrorAttributes(request, includeStackTrace);
log.error("网关出现异常了,异常为:{} ,异常类型为:{},请求为:{}", error.getMessage(), error.getCause(), JsonUtil.defaultMapper().toJson(errorAttributes));
return GlobalExceptionHandler.errorMap((Integer) errorAttributes.get("status"));
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(all(), this::renderErrorResponse);
}
@Override
protected int getHttpStatus(Map<String, Object> errorAttributes) {
return (int) errorAttributes.get("code");
}
}
package cloud.config;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfig {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public ErrorHandlerConfig(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>>
viewResolversProvider,
ServerCodecConfigurer
serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers =
viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
errorAttributes,
this.resourceProperties,
this.serverProperties.getError(),
this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}
package cloud.config;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class GlobalExceptionHandler {
private final static String SERVER_ERROR_TXT = "服务器内部错误";
private final static String ARGUMENTS_ERROR_TXT = "参数错误";
private final static String SERVICE_NOT_FOUND_TXT = "找不到服务";
private final static String BAD_REQUEST_TXT = "错误的请求";
public static Map<String,Object> errorMap(Integer status){
Map<String,Object> map=new HashMap<>();
if(status==null){
map.put("code",500);
map.put("message",SERVER_ERROR_TXT);
}else {
map.put("code",status);
switch (status) {
case 500:
map.put("message",SERVER_ERROR_TXT);
break;
case 404:
map.put("message",SERVICE_NOT_FOUND_TXT);
break;
case 400:
map.put("message",ARGUMENTS_ERROR_TXT);
break;
}
}
return map;
}
}
4.配置Resilience4j作为断路器
package cloud.config;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
@Slf4j
@Configuration
public class Resilience4jConfiguration {
@Bean
public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer(CircuitBreakerRegistry circuitBreakerRegistry) {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(circuitBreakerRegistry.getConfiguration("myConfig").get())
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(10)).build()).build());
}
}
package cloud.controller;
import entities.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/circuitBreaker")
public class FallbackController {
public static int count=1;
@RequestMapping("/fallback")
public Result fallback() {
return Result.error("服务暂时不可用");
}
}
5.各种gateway过滤器
1.输出body
package com.kittlen.cloud.filter;
import com.kittlen.cloud.dto.ReleaseMatcherByAntPathMatcher;
import entities.Result;
import com.kittlen.cloud.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import util.JsonUtil;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Component
public class MainFilter implements GlobalFilter, Ordered {
public static final String TOKEN_HEADER_TAG = "access_token";
public static final String TOKEN_PARAM = "token";
@Autowired
TokenService tokenService;
@Autowired
ReleaseMatcherByAntPathMatcher releaseMatcher;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String method = request.getMethodValue();
if (HttpMethod.POST.matches(method)) {
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
logtrace(exchange, bodyString);
exchange.getAttributes().put("POST_BODY", bodyString);
DataBufferUtils.release(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory()
.wrap(bytes);
return Mono.just(buffer);
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return chain.filter(exchange.mutate().request(mutatedRequest)
.exchange());
});
} else if (HttpMethod.GET.matches(method)) {
Map m = request.getQueryParams();
logtrace(exchange, m.toString());
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
public Mono<Void> out(ServerHttpResponse response,String msg) {
Result result = Result.error("鉴权失败");
byte[] bits = JsonUtil.toJson(result).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
private void logtrace(ServerWebExchange exchange, String param) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String path = serverHttpRequest.getURI().getPath();
String method = serverHttpRequest.getMethodValue();
String headers = serverHttpRequest.getHeaders().entrySet()
.stream()
.map(entry -> " " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
.collect(Collectors.joining("\n"));
log.info("\n" + "---------------- ---------------- ---------------->>\n" +
"HttpMethod : {}\n" +
"Uri : {}\n" +
"Param : {}\n" +
"Headers : \n" +
"{}\n" +
"\"<<---------------- ---------------- ----------------"
, method, path, param, headers);
}
public String getToken(ServerHttpRequest request) {
List<String> list = request.getHeaders().get(TOKEN_HEADER_TAG);
if (list == null || list.isEmpty()) {
list = request.getHeaders().get(TOKEN_PARAM);
}
if (list == null || list.isEmpty()) {
return "";
}
return list.get(0);
}
}
2.Headers中添加属性
package com.kittlen.cloud.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class HeadersFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest host = request.mutate().headers(headers -> {
headers.add("auth", "kittlen");
headers.remove("name");
}).build();
ServerWebExchange build = exchange.mutate().request(host).build();
return chain.filter(build);
}
@Override
public int getOrder() {
return 4;
}
}
3.白名单
package com.kittlen.cloud.filter;
import com.kittlen.cloud.dto.ReleaseMatcherByAntPathMatcher;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
public class WhiteListFilter implements GlobalFilter, Ordered {
@Autowired
ReleaseMatcherByAntPathMatcher releaseMatcher;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (releaseMatcher.release(request.getURI().getPath())) {
log.info("地址在白名单");
return chain.filter(exchange);
}
log.info("不在白名单,进行其他判断");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
package com.kittlen.cloud.config;
import com.kittlen.cloud.dto.ReleaseMatcherByAntPathMatcher;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
@ConfigurationProperties(prefix = "gateway.matcher")
public class MatcherConfig {
private List<String> whitelist;
public void setWhitelist(List<String> whitelist) {
this.whitelist = whitelist;
}
@Bean
public ReleaseMatcherByAntPathMatcher releaseMatcherByAntPathMatcher() {
return new ReleaseMatcherByAntPathMatcher(whitelist);
}
}
package com.kittlen.cloud.dto;
import org.springframework.util.AntPathMatcher;
import util.StringUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class ReleaseMatcherByAntPathMatcher {
private static AntPathMatcher antPathMatcher = new AntPathMatcher();
private List<String> urls;
public List<String> getUrls() {
return urls;
}
public void setUrls(List<String> urls) {
this.urls = urls;
}
public ReleaseMatcherByAntPathMatcher() {
}
public ReleaseMatcherByAntPathMatcher(List<String> urls) {
if (urls == null) {
this.urls = new ArrayList<>();
} else {
this.urls = urls;
}
}
public ReleaseMatcherByAntPathMatcher(List<String> urls, List<Pattern> Patterns) {
this.urls = urls;
}
public boolean release(String path) {
if (urls == null || urls.isEmpty()|| StringUtil.isEmpty(path)) {
return false;
}
if (!"/".equals(path.substring(0, 1))) {
path = "/" + path;
}
for (int i = 0; i < urls.size(); i++) {
if(antPathMatcher.match(urls.get(i), path)){
return true;
}
}
return false;
}
}