添加POST 添加/删除参数过滤器、限流器配置

This commit is contained in:
Wenchao Gong 2021-10-15 23:22:08 +08:00
parent 98722a6b22
commit b5b765238d
7 changed files with 340 additions and 11 deletions

View File

@ -1,15 +1,21 @@
package com.ruoyi.gateway.config;
import com.ruoyi.gateway.config.provider.RedisRouteDefinitionRepository;
import com.ruoyi.gateway.filter.factory.AddRequestParamGatewayFilterFactory;
import com.ruoyi.gateway.filter.factory.RemoveRequestParamGatewayFilterFactory;
import org.redisson.api.RedissonClient;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
@ -52,4 +58,22 @@ public class GatewayConfig
{
return new RedisRouteDefinitionRepository(redissonClient);
}
@Bean
public AddRequestParamGatewayFilterFactory addRequestParamGatewayFilterFactory()
{
return new AddRequestParamGatewayFilterFactory();
}
@Bean
public RemoveRequestParamGatewayFilterFactory removeRequestParamGatewayFilterFactory()
{
return new RemoveRequestParamGatewayFilterFactory();
}
@Bean("routeIdKeyResolver")
public KeyResolver routeIdKeyResolver()
{
return exchange -> Mono.just(((Route) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR)).getId());
}
}

View File

@ -0,0 +1,11 @@
package com.ruoyi.gateway.exception;
/**
* @author Wenchao Gong
* @date 2021-10-04
*/
public class GatewayException extends RuntimeException {
public GatewayException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,114 @@
package com.ruoyi.gateway.filter.factory;
import cn.hutool.json.JSONObject;
import com.ruoyi.gateway.exception.GatewayException;
import com.ruoyi.gateway.utils.GatewayUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
/**
* @author Wenchao Gong
* @date 2021-10-04
*/
public class AddRequestParamGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config)
{
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
final ServerHttpRequest request = exchange.getRequest();
HttpMethod method = request.getMethod();
if (HttpMethod.GET.equals(method))
{
URI uri = request.getURI();
StringBuilder query = new StringBuilder();
String originalQuery = uri.getRawQuery();
if (StringUtils.hasText(originalQuery))
{
query.append(originalQuery);
if (originalQuery.charAt(originalQuery.length() - 1) != '&')
{
query.append('&');
}
}
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
// TODO urlencode?
query.append(config.getName());
query.append('=');
query.append(value);
try
{
URI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build(false).toUri();
return chain.filter(exchange.mutate().request(request.mutate().uri(newUri).build()).build());
} catch (RuntimeException ex)
{
throw new IllegalStateException("Invalid URI query: \"" + query.toString() + "\"");
}
} else if (HttpMethod.POST.equals(method))
{
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
.flatMap(body -> {
MediaType mediaType = request.getHeaders().getContentType();
if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType))
{
JSONObject jsonObject = new JSONObject(body);
jsonObject.set(config.getName(), config.getValue());
return Mono.just(jsonObject.toString());
} else if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType))
{
StringBuilder newBody = new StringBuilder();
if (org.springframework.util.StringUtils.hasText(body))
{
newBody.append(body);
if (body.charAt(body.length() - 1) != '&')
{
newBody.append('&');
}
}
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
newBody.append(config.getName());
newBody.append('=');
newBody.append(value);
return Mono.just(newBody.toString());
}
return Mono.empty();
});
return GatewayUtils.modifyBody(exchange, chain, modifiedBody);
} else
{
throw new GatewayException(String.format("Method %s Not Supported!", method.name()));
}
}
@Override
public String toString()
{
return filterToStringCreator(AddRequestParamGatewayFilterFactory.this)
.append(config.getName(), config.getValue()).toString();
}
};
}
}

View File

@ -0,0 +1,104 @@
package com.ruoyi.gateway.filter.factory;
import cn.hutool.json.JSONObject;
import com.ruoyi.gateway.exception.GatewayException;
import com.ruoyi.gateway.utils.GatewayUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap;
/**
* @author Wenchao Gong
* @date 2021-10-15
*/
public class RemoveRequestParamGatewayFilterFactory extends AbstractGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
public RemoveRequestParamGatewayFilterFactory()
{
super(NameConfig.class);
}
@Override
public List<String> shortcutFieldOrder()
{
return Arrays.asList(NAME_KEY);
}
@Override
public GatewayFilter apply(NameConfig config)
{
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
ServerHttpRequest request = exchange.getRequest();
HttpMethod method = request.getMethod();
if (HttpMethod.GET.equals(method))
{
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(request.getQueryParams());
queryParams.remove(config.getName());
URI newUri = UriComponentsBuilder.fromUri(request.getURI())
.replaceQueryParams(unmodifiableMultiValueMap(queryParams)).build().toUri();
ServerHttpRequest updatedRequest = exchange.getRequest().mutate().uri(newUri).build();
return chain.filter(exchange.mutate().request(updatedRequest).build());
} else if (HttpMethod.POST.equals(method))
{
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
.flatMap(body -> {
MediaType mediaType = request.getHeaders().getContentType();
if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType))
{
JSONObject jsonObject = new JSONObject(body);
jsonObject.remove(config.getName());
return Mono.just(jsonObject.toString());
} else if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType))
{
if (StringUtils.hasText(body))
{
return Mono.just(Arrays.stream(body.split("&"))
.filter(str -> config.getName().equals(str.split("=")[0]))
.collect(Collectors.joining("&")));
}
return Mono.just(body);
}
return Mono.empty();
});
return GatewayUtils.modifyBody(exchange, chain, modifiedBody);
} else
{
throw new GatewayException(String.format("Method %s Not Supported!", method.name()));
}
}
@Override
public String toString()
{
return filterToStringCreator(RemoveRequestParamGatewayFilterFactory.this)
.append("name", config.getName()).toString();
}
};
}
}

View File

@ -0,0 +1,61 @@
package com.ruoyi.gateway.utils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Wenchao Gong
* @date 2021-10-15
*/
public class GatewayUtils {
public static Mono<Void> modifyBody(ServerWebExchange exchange, GatewayFilterChain chain, Mono<String> publisher)
{
BodyInserter bodyInserter = BodyInserters.fromPublisher(publisher, String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
return chain.filter(exchange.mutate().request(decorator).build());
}));
}
public static ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage)
{
return new ServerHttpRequestDecorator(exchange.getRequest()) {
public HttpHeaders getHeaders()
{
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0L)
{
httpHeaders.setContentLength(contentLength);
} else
{
httpHeaders.set("Transfer-Encoding", "chunked");
}
return httpHeaders;
}
public Flux<DataBuffer> getBody()
{
return outputMessage.getBody();
}
};
}
}

View File

@ -33,6 +33,7 @@ public class RouteUtils {
* Gateway 虚拟路径前缀
*/
public static final String PATH_PREFIX = "/proxy";
public static final String ACCESS_KEY_NAME = "ak";
private static RedissonClient client = SpringUtils.getBean(RedissonClient.class);
/**
@ -151,7 +152,7 @@ public class RouteUtils {
IscPredicateDefinition queryPredicate = new IscPredicateDefinition();
queryPredicate.setName("Query");
queryPredicate.getArgs().put("param", "ak");
queryPredicate.getArgs().put("param", ACCESS_KEY_NAME);
IscPredicateDefinition pathPredicate = new IscPredicateDefinition();
pathPredicate.setName("Path");
@ -165,23 +166,34 @@ public class RouteUtils {
stripPrefixFilter.getArgs().put("parts", "1");
filters.add(stripPrefixFilter);
IscFilterDefinition removeRequestParameterFilter = new IscFilterDefinition();
removeRequestParameterFilter.setName("RemoveRequestParameter");
removeRequestParameterFilter.getArgs().put("name", "ak");
filters.add(removeRequestParameterFilter);
final Long quotaSeconds = appService.getQuotaSeconds();
if (Objects.nonNull(quotaSeconds))
{
IscFilterDefinition requestRateLimiterFilter = new IscFilterDefinition();
requestRateLimiterFilter.setName("RequestRateLimiter");
requestRateLimiterFilter.getArgs().put("redis-rate-limiter.replenishRate", String.valueOf(quotaSeconds));
requestRateLimiterFilter.getArgs().put("redis-rate-limiter.burstCapacity", String.valueOf(quotaSeconds << 1));
requestRateLimiterFilter.getArgs().put("key-resolver", "#{@routeIdKeyResolver}");
filters.add(requestRateLimiterFilter);
}
IscFilterDefinition removeRequestParamFilter = new IscFilterDefinition();
removeRequestParamFilter.setName("RemoveRequestParam");
removeRequestParamFilter.getArgs().put("name", ACCESS_KEY_NAME);
filters.add(removeRequestParamFilter);
String hiddenParams = service.getHiddenParams();
if(StringUtils.isNotBlank(hiddenParams)) {
Map<String, Object> paramsMap = JsonUtils.parseMap(hiddenParams);
Iterator<Map.Entry<String, Object>> it = paramsMap.entrySet().iterator();
while (it.hasNext()) {
IscFilterDefinition addRequestParameterFilter = new IscFilterDefinition();
Map<String, String> args = addRequestParameterFilter.getArgs();
addRequestParameterFilter.setName("AddRequestParameter");
IscFilterDefinition addRequestParamFilter = new IscFilterDefinition();
Map<String, String> args = addRequestParamFilter.getArgs();
addRequestParamFilter.setName("AddRequestParam");
Map.Entry<String, Object> param = it.next();
args.put("name", param.getKey());
args.put("value", String.valueOf(param.getValue()));
filters.add(addRequestParameterFilter);
filters.add(addRequestParamFilter);
}
}
@ -193,7 +205,6 @@ public class RouteUtils {
//其他信息
Map<String, Object> metadata = route.getMetadata();
metadata.put("secondsLimit", toString(appService.getQuotaSeconds()));
metadata.put("minutesLimit", toString(appService.getQuotaMinutes()));
metadata.put("hoursLimit", toString(appService.getQuotaHours()));
metadata.put("daysLimit", toString(appService.getQuotaDays()));

View File

@ -16,7 +16,11 @@
</description>
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>