From b5b765238d3a7ff426681517c5323930f3193a6a Mon Sep 17 00:00:00 2001 From: Wenchao Gong Date: Fri, 15 Oct 2021 23:22:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0POST=20=E6=B7=BB=E5=8A=A0/?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=8F=82=E6=95=B0=E8=BF=87=E6=BB=A4=E5=99=A8?= =?UTF-8?q?=E3=80=81=E9=99=90=E6=B5=81=E5=99=A8=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/gateway/config/GatewayConfig.java | 24 ++++ .../gateway/exception/GatewayException.java | 11 ++ .../AddRequestParamGatewayFilterFactory.java | 114 ++++++++++++++++++ ...emoveRequestParamGatewayFilterFactory.java | 104 ++++++++++++++++ .../com/ruoyi/gateway/utils/GatewayUtils.java | 61 ++++++++++ .../java/com/ruoyi/isc/utils/RouteUtils.java | 31 +++-- ruoyi-system/pom.xml | 6 +- 7 files changed, 340 insertions(+), 11 deletions(-) create mode 100644 ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/GatewayException.java create mode 100644 ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/factory/AddRequestParamGatewayFilterFactory.java create mode 100644 ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/factory/RemoveRequestParamGatewayFilterFactory.java create mode 100644 ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/utils/GatewayUtils.java diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/GatewayConfig.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/GatewayConfig.java index 678d3cb35..5150d07d0 100644 --- a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/GatewayConfig.java +++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/config/GatewayConfig.java @@ -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()); + } } diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/GatewayException.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/GatewayException.java new file mode 100644 index 000000000..c4d4c969b --- /dev/null +++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/exception/GatewayException.java @@ -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); + } +} diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/factory/AddRequestParamGatewayFilterFactory.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/factory/AddRequestParamGatewayFilterFactory.java new file mode 100644 index 000000000..9d20f6839 --- /dev/null +++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/factory/AddRequestParamGatewayFilterFactory.java @@ -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 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 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(); + } + }; + } + +} diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/factory/RemoveRequestParamGatewayFilterFactory.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/factory/RemoveRequestParamGatewayFilterFactory.java new file mode 100644 index 000000000..403214bf9 --- /dev/null +++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/filter/factory/RemoveRequestParamGatewayFilterFactory.java @@ -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 { + + public RemoveRequestParamGatewayFilterFactory() + { + super(NameConfig.class); + } + + @Override + public List shortcutFieldOrder() + { + return Arrays.asList(NAME_KEY); + } + + @Override + public GatewayFilter apply(NameConfig config) + { + return new GatewayFilter() { + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) + { + ServerHttpRequest request = exchange.getRequest(); + HttpMethod method = request.getMethod(); + if (HttpMethod.GET.equals(method)) + { + MultiValueMap 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 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(); + } + }; + } +} diff --git a/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/utils/GatewayUtils.java b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/utils/GatewayUtils.java new file mode 100644 index 000000000..eefd3033a --- /dev/null +++ b/ruoyi-extend/ruoyi-isc-gateway/src/main/java/com/ruoyi/gateway/utils/GatewayUtils.java @@ -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 modifyBody(ServerWebExchange exchange, GatewayFilterChain chain, Mono 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 getBody() + { + return outputMessage.getBody(); + } + }; + } +} diff --git a/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/RouteUtils.java b/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/RouteUtils.java index 90f7af214..0a1b22fbe 100644 --- a/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/RouteUtils.java +++ b/ruoyi-isc/src/main/java/com/ruoyi/isc/utils/RouteUtils.java @@ -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 paramsMap = JsonUtils.parseMap(hiddenParams); Iterator> it = paramsMap.entrySet().iterator(); while (it.hasNext()) { - IscFilterDefinition addRequestParameterFilter = new IscFilterDefinition(); - Map args = addRequestParameterFilter.getArgs(); - addRequestParameterFilter.setName("AddRequestParameter"); + IscFilterDefinition addRequestParamFilter = new IscFilterDefinition(); + Map args = addRequestParamFilter.getArgs(); + addRequestParamFilter.setName("AddRequestParam"); Map.Entry 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 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())); diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml index 8e536cce2..b425e1d86 100644 --- a/ruoyi-system/pom.xml +++ b/ruoyi-system/pom.xml @@ -16,7 +16,11 @@ - + + org.jsoup + jsoup + 1.13.1 + com.ruoyi