限流规则修改

This commit is contained in:
Wenchao Gong 2021-10-16 22:16:51 +08:00
parent 2a6479c743
commit dd46fa932f
16 changed files with 451 additions and 319 deletions

View File

@ -26,5 +26,10 @@ public class IscConstants {
/**
* Gateway 路由信息Redis Key
*/
public static final String KEY_ROUTES = "ROUTES::";
public static final String KEY_ROUTES = "ROUTES:";
/**
* Gateway 服务对应AK规则
*/
public static final String KEY_RULES = "RULES:";
}

View File

@ -1,9 +1,9 @@
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 com.ruoyi.gateway.filter.CustomerGlobalFilter;
import org.redisson.api.RedissonClient;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.annotation.Bean;
@ -56,14 +56,8 @@ public class GatewayConfig
}
@Bean
public AddRequestParamGatewayFilterFactory addRequestParamGatewayFilterFactory()
public GlobalFilter customerGlobalFilter()
{
return new AddRequestParamGatewayFilterFactory();
}
@Bean
public RemoveRequestParamGatewayFilterFactory removeRequestParamGatewayFilterFactory()
{
return new RemoveRequestParamGatewayFilterFactory();
return new CustomerGlobalFilter();
}
}

View File

@ -20,7 +20,7 @@ import reactor.core.publisher.Mono;
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository
{
public static final Codec ROUTE_CODES_INSTANCE = new TypedJsonJacksonCodec(String.class, RouteDefinition.class);
public static final String KEY_ROUTES = "ROUTES::";
public static final String KEY_ROUTES = "ROUTES:";
private final RedissonClient redissonClient;
public RedisRouteDefinitionRepository(RedissonClient redissonClient)

View File

@ -0,0 +1,170 @@
package com.ruoyi.gateway.filter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ruoyi.gateway.utils.GatewayUtils;
import com.ruoyi.gateway.utils.beans.IscRule;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
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.MultiValueMapAdapter;
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.*;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap;
/**
* 自定义全局过滤器操作如下
* 1.ak 是否存在
* 2.是否到期
* 3.是否限流
* 4.删除参数ak
* 5.添加隐藏参数
*
* @author Wenchao Gong
* @date 2021-10-15
*/
public class CustomerGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
final Route route = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
final Map<String, Object> metadata = route.getMetadata();
final ServerHttpRequest request = exchange.getRequest();
//ak 是否存在
final HttpMethod httpMethod = request.getMethod();
final String accessKeyName = String.valueOf(metadata.get(GatewayUtils.CONFIG_ACCESS_KEY_NAME_KEY));
String headerAk = GatewayUtils.getValue(null, () -> request.getHeaders().get(accessKeyName));
if (HttpMethod.GET.equals(httpMethod)) {
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(request.getQueryParams());
handleRule(headerAk, () -> queryParams.get(accessKeyName), route, request, accessKeyName);
queryParams.remove(accessKeyName);
handleHiddenParams(route, queryParams, (json, map) -> {
Iterator<Map.Entry<String, Object>> iterator = json.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> next = iterator.next();
Object value;
if (Objects.isNull(value = next.getValue()) || StrUtil.isBlank(value.toString())) {
map.add(next.getKey(), StrUtil.EMPTY);
} else {
map.add(next.getKey(), value.toString());
}
}
});
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(httpMethod)) {
final ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
final Mono<String> modifiedBody = serverRequest.bodyToMono(String.class);
modifiedBody.flatMap(body -> {
MediaType mediaType = request.getHeaders().getContentType();
if (MediaType.APPLICATION_JSON.equals(mediaType)) {
JSONObject jsonObj = JSONUtil.parseObj(body);
String ak = handleRule(headerAk, () -> Arrays.asList(jsonObj.get(accessKeyName, String.class,
true)), route, request, accessKeyName);
jsonObj.remove(accessKeyName);
handleHiddenParams(route, jsonObj, (json, map) -> {
Iterator<Map.Entry<String, Object>> iterator = json.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> next = iterator.next();
map.set(next.getKey(), next.getValue());
}
});
return Mono.just(jsonObj.toString());
} else if (MediaType.APPLICATION_FORM_URLENCODED.equals(mediaType)) {
if (StringUtils.hasText(body)) {
final Stream<String[]> stream = Arrays.stream(body.split("&")).map(param -> param.split("="));
handleRule(headerAk, () -> stream.filter(param -> param.length > 0 &&
accessKeyName.equals(param[0])).map(param -> param[1]).collect(Collectors.toList()),
route, request, accessKeyName);
final List<String[]> params = stream.filter(param -> !accessKeyName.equals(param[0])).collect(Collectors.toList());
handleHiddenParams(route, params, (json, list) -> {
Iterator<Map.Entry<String, Object>> iterator = json.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> next = iterator.next();
list.add(new String[]{next.getKey(), next.getValue().toString()});
}
});
return Mono.just(params.stream().map(param -> param[0] + '=' + param[1]).collect(Collectors.joining("&")));
}
}
return Mono.empty();
});
return GatewayUtils.modifyBody(exchange, chain, modifiedBody);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
/**
* 处理规则
*
* @param headerAk
* @param valueSupplier
* @param route
* @param request
* @param accessKeyName
* @return
*/
private String handleRule(String headerAk, Supplier<List<String>> valueSupplier, Route route,
ServerHttpRequest request, String accessKeyName) {
//获取AK
final String ak = GatewayUtils.getValue(headerAk, valueSupplier, () -> new RuntimeException("AK 不存在"));
//获取规则
final IscRule rule = GatewayUtils.getRequiredValue(() -> GatewayUtils.getRule(ak, route.getId()),
() -> new RuntimeException("AK异常"));
//是否到期
GatewayUtils.isBefore(rule, () -> new RuntimeException("AK已过期"));
//TODO 限流
//如果header中有AK,则删除
if (Objects.nonNull(headerAk)) {
request.getHeaders().remove(accessKeyName);
}
return ak;
}
/**
* 处理隐藏参数
*
* @param route
* @param result
* @param mapper
* @param <U>
*/
private <U> void handleHiddenParams(Route route, U result, BiConsumer<JSONObject, U> mapper) {
final Object obj = route.getMetadata().get(GatewayUtils.CONFIG_ADD_PARAM_KEY);
if (Objects.isNull(obj)) {
return;
}
final JSONObject json = JSONUtil.parseObj(obj.toString());
if (json.isEmpty()) {
return;
}
mapper.accept(json, result);
}
}

View File

@ -1,114 +0,0 @@
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

@ -1,104 +0,0 @@
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

@ -1,5 +1,13 @@
package com.ruoyi.gateway.utils;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.extra.spring.SpringUtil;
import com.ruoyi.gateway.utils.beans.IscRule;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.codec.TypedJsonJacksonCodec;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
@ -7,18 +15,35 @@ 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.util.StringUtils;
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;
import java.sql.Date;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
/**
* @author Wenchao Gong
* @date 2021-10-15
*/
public class GatewayUtils {
public static final String CONFIG_ACCESS_KEY_NAME_KEY = "accessKeyName";
public static final String CONFIG_ADD_PARAM_KEY = "addParam";
/**
* Gateway 服务对应AK规则
*/
public static final String KEY_RULES = "RULES:";
public static final Codec RULE_CODES_INSTANCE = new TypedJsonJacksonCodec(String.class, IscRule.class);
private static RedissonClient client = SpringUtil.getBean(RedissonClient.class);
public static Mono<Void> modifyBody(ServerWebExchange exchange, GatewayFilterChain chain, Mono<String> publisher)
{
BodyInserter bodyInserter = BodyInserters.fromPublisher(publisher, String.class);
@ -58,4 +83,82 @@ public class GatewayUtils {
}
};
}
/**
* values 中获取第一个不为空 的值
*
* @param before 获取之前的值 如果不为空直接返回
* @param valueSupplier 值列表生产者
* @return
*/
public static String getValue(String before, Supplier<List<String>> valueSupplier) {
return getValue(before, valueSupplier, null);
}
/**
* values 中获取第一个不为空 的值
*
* @param before 获取之前的值 如果不为空直接返回
* @param valueSupplier 值列表生产者
* @param errorMsgSupplier 异常信息生产者
* @param <X>
* @return
*/
public static <X extends RuntimeException> String getValue(String before, Supplier<List<String>> valueSupplier, Supplier<X> errorMsgSupplier)
{
if(StringUtils.hasText(before)) {
return before;
}
final List<String> values = valueSupplier.get();
if(CollectionUtil.isNotEmpty(values)) {
for (String value : values) {
if(StringUtils.hasText(value)) {
before = value;
break;
}
}
}
if(errorMsgSupplier != null) {
Assert.notBlank(before, errorMsgSupplier);
}
return before;
}
/**
* 获取必要值可抛出异常
* @param valueSupplier
* @param errorMsgSupplier
* @param <T>
* @param <X>
* @return
*/
public static <T, X extends RuntimeException> T getRequiredValue(Supplier<T> valueSupplier, Supplier<X> errorMsgSupplier) {
final T value = valueSupplier.get();
if(Objects.nonNull(errorMsgSupplier)) {
Assert.notNull(value, errorMsgSupplier);
}
return value;
}
/**
* 获取规则
* @param ak AK
* @param routeId 路由ID
* @return
*/
public static IscRule getRule(String ak, String routeId) {
final RMap<String, IscRule> map = client.getMap(KEY_RULES, RULE_CODES_INSTANCE);
return map.get(ak + ':' + routeId);
}
/**
* 断言是否超过结束时间
* @param rule
* @param errorMsgSupplier
* @param <X>
*/
public static <X extends RuntimeException> void isBefore(IscRule rule, Supplier<X> errorMsgSupplier) {
Assert.isTrue(Objects.nonNull(rule.getExpire()) && Date.from(Instant.now()).before(rule.getExpire()), errorMsgSupplier);
}
}

View File

@ -0,0 +1,26 @@
package com.ruoyi.gateway.utils.beans;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
/**
* 网关 规则信息(服务对应AK处理规则)
* @author Wechao Gong
* @date 2021-10-16
*/
@Getter
@Setter
public class IscRule implements Serializable
{
@JsonIgnore
private String id;
private Date expire;
private Long daysLimit;
private Long hoursLimit;
private Long minutesLimit;
private Long secondsLimit;
}

View File

@ -70,7 +70,7 @@ public interface IIscAppServiceService extends IServicePlus<IscAppService, IscAp
List<IscAppService> getAppServiceListByIds(Collection<Long> ids);
/**
* 刷新路由信息
* 刷新规则信息服务对应AK
*/
void refreshRoutes();
void refreshRules();
}

View File

@ -90,4 +90,9 @@ public interface IIscServiceService extends IServicePlus<IscService, IscServiceV
* @param bo 审核业务对象
*/
void checkAuditBO(IscAuditBo bo);
/**
* 刷新路由信息
*/
void refreshRoutes();
}

View File

@ -159,7 +159,6 @@ public class IscAppServiceApplyServiceImpl extends ServicePlusImpl<IscAppService
Assert.notNull(appService, () -> new ServiceException("申请信息不存在"));
IscAppService updateData = null;
IscRouteDefinition route = null;
IscService service = null;
if(IscConstants.AUDIT_PASS.equals(bo.getStatus())) {
service = serviceService.getById(appService.getServiceId());
@ -175,8 +174,7 @@ public class IscAppServiceApplyServiceImpl extends ServicePlusImpl<IscAppService
if(Objects.nonNull(service)) {
IscAppService dbAppService = appServiceService.getById(apply.getAppServiceId());
IscApplication application = applicationService.getById(dbAppService.getApplicationId());
route = RouteUtils.generateRoute(dbAppService, service, application.getAccessKey());
RouteUtils.saveRoute(route);
RouteUtils.saveRule(RouteUtils.generateRule(dbAppService, application.getAccessKey()));
}
}
}
@ -243,8 +241,7 @@ public class IscAppServiceApplyServiceImpl extends ServicePlusImpl<IscAppService
renewalDuration = Objects.nonNull(log) ? log.getRenewalDuration() : 1;
}
updateData.setEndTime(DateUtils.beginOfDay(DateUtils.addMonths(DateUtils.getNowDate(), renewalDuration)));
updateData.setVirtualAddr(RouteUtils.genVirtualAddrPath(appService.getApplicationId(),
appService.getServiceId(), service.getServiceAddr()));
updateData.setVirtualAddr(RouteUtils.genVirtualAddrPath(service.getServiceAddr()));
}
/**

View File

@ -20,7 +20,6 @@ import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.isc.domain.IscAppService;
import com.ruoyi.isc.domain.IscAppServiceApply;
import com.ruoyi.isc.domain.IscApplication;
import com.ruoyi.isc.domain.IscService;
import com.ruoyi.isc.domain.bo.IscAppServiceApplyBo;
import com.ruoyi.isc.domain.bo.IscAppServiceBo;
import com.ruoyi.isc.domain.vo.IscAppServiceVo;
@ -30,14 +29,13 @@ import com.ruoyi.isc.service.IIscAppServiceService;
import com.ruoyi.isc.service.IIscApplicationService;
import com.ruoyi.isc.service.IIscServiceService;
import com.ruoyi.isc.utils.RouteUtils;
import com.ruoyi.isc.utils.beans.IscRouteDefinition;
import com.ruoyi.isc.utils.beans.IscRule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
@ -60,7 +58,7 @@ public class IscAppServiceServiceImpl extends ServicePlusImpl<IscAppServiceMappe
@PostConstruct
public void init() {
refreshRoutes();
refreshRules();
}
@Override
@ -201,7 +199,7 @@ public class IscAppServiceServiceImpl extends ServicePlusImpl<IscAppServiceMappe
}
@Override
public void refreshRoutes()
public void refreshRules()
{
List<IscAppService> list = list(Wrappers.<IscAppService>lambdaQuery()
.eq(IscAppService::getEnabled, UserConstants.NORMAL)
@ -210,33 +208,23 @@ public class IscAppServiceServiceImpl extends ServicePlusImpl<IscAppServiceMappe
if(CollectionUtil.isEmpty(list)) {
return;
}
Set<Long> serviceIds = list.stream().map(IscAppService::getServiceId).collect(Collectors.toSet());
Map<Long, IscService> serviceMap = serviceService.list(Wrappers.<IscService>lambdaQuery()
.select(IscService::getServiceId, IscService::getServiceAddr, IscService::getHiddenParams, IscService::getRequestMethod)
.in(IscService::getServiceId, serviceIds)).stream()
.collect(Collectors.toMap(IscService::getServiceId, Function.identity()));
Set<Long> applicationIds = list.stream().map(IscAppService::getApplicationId).collect(Collectors.toSet());
Map<Long, String> accessKeyMap = applicationService.list(Wrappers.<IscApplication>lambdaQuery()
.select(IscApplication::getApplicationId, IscApplication::getAccessKey)
.in(IscApplication::getApplicationId, applicationIds)).stream()
.collect(Collectors.toMap(IscApplication::getApplicationId, IscApplication::getAccessKey));
List<IscRouteDefinition> routes = new ArrayList<>();
List<IscRule> routes = new ArrayList<>();
for (IscAppService appService : list)
{
IscService service = serviceMap.get(appService.getServiceId());
if(Objects.isNull(service)) {
log.error("路由初始化失败: id:[{}],服务[{}]信息不存在!", appService.getAppServiceId(), appService.getServiceId());
continue;
}
String ak = accessKeyMap.get(appService.getApplicationId());
if(StringUtils.isBlank(ak)) {
log.error("路由初始化失败: id:[{}],ak[{}]信息不存在!", appService.getAppServiceId(), appService.getApplicationId());
continue;
}
routes.add(RouteUtils.generateRoute(appService, service, ak));
routes.add(RouteUtils.generateRule(appService, ak));
}
RouteUtils.refreshRoute(routes);
RouteUtils.refreshRules(routes);
}
/**

View File

@ -25,8 +25,11 @@ import com.ruoyi.isc.domain.vo.IscServiceVo;
import com.ruoyi.isc.mapper.IscServiceMapper;
import com.ruoyi.isc.service.IIscServiceCateService;
import com.ruoyi.isc.service.IIscServiceService;
import com.ruoyi.isc.utils.RouteUtils;
import com.ruoyi.isc.utils.beans.IscRouteDefinition;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
@ -43,6 +46,11 @@ public class IscServiceServiceImpl extends ServicePlusImpl<IscServiceMapper, Isc
@Resource
private IIscServiceCateService cateService;
@PostConstruct
public void init() {
refreshRoutes();
}
@Override
public IscServiceVo queryById(Long serviceId)
{
@ -195,4 +203,18 @@ public class IscServiceServiceImpl extends ServicePlusImpl<IscServiceMapper, Isc
throw new ServiceException("审核状态异常");
}
}
@Override
public void refreshRoutes()
{
final List<IscService> serviceList = list(Wrappers.<IscService>lambdaQuery()
.select(IscService::getServiceId, IscService::getServiceAddr, IscService::getHiddenParams, IscService::getRequestMethod)
.eq(IscService::getStatus, IscConstants.AUDIT_PASS));
List<IscRouteDefinition> routes = new ArrayList<>();
for (IscService service : serviceList)
{
routes.add(RouteUtils.generateRoute(service));
}
RouteUtils.refreshRoute(routes);
}
}

View File

@ -2,15 +2,13 @@ package com.ruoyi.isc.utils;
import com.ruoyi.common.constant.IscConstants;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.isc.domain.IscAppService;
import com.ruoyi.isc.domain.IscService;
import com.ruoyi.isc.utils.beans.IscFilterDefinition;
import com.ruoyi.isc.utils.beans.IscPredicateDefinition;
import com.ruoyi.isc.utils.beans.IscRouteDefinition;
import com.ruoyi.isc.utils.beans.IscRule;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
@ -18,7 +16,10 @@ import org.redisson.codec.TypedJsonJacksonCodec;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -29,6 +30,7 @@ import java.util.stream.Collectors;
public class RouteUtils {
public static final Codec ROUTE_CODES_INSTANCE = new TypedJsonJacksonCodec(String.class, IscRouteDefinition.class);
public static final Codec RULE_CODES_INSTANCE = new TypedJsonJacksonCodec(String.class, IscRule.class);
/**
* Gateway 虚拟路径前缀
*/
@ -39,12 +41,10 @@ public class RouteUtils {
/**
* 获取虚拟URL Path 部分
*
* @param applicationId 应用ID
* @param serviceId 服务Id
* @param serviceAddr 服务地址
* @param serviceAddr 服务地址
* @return 虚拟地址Path
*/
public static String genVirtualAddrPath(Long applicationId, Long serviceId, String serviceAddr)
public static String genVirtualAddrPath(String serviceAddr)
{
return PATH_PREFIX + getPathUri(serviceAddr);
}
@ -133,15 +133,13 @@ public class RouteUtils {
/**
* 生成 路由信息
*
* @param appService 应用服务信息
* @param service 服务信息
* @param ak AK
* @return 路由信息
*/
public static IscRouteDefinition generateRoute(IscAppService appService, IscService service, String ak)
public static IscRouteDefinition generateRoute(IscService service)
{
IscRouteDefinition route = new IscRouteDefinition();
route.setId(String.valueOf(appService.getAppServiceId()));
route.setId(String.valueOf(service.getServiceId()));
final String serviceAddr = service.getServiceAddr();
route.setUri(getURI(serviceAddr.substring(0, serviceAddr.length() - getPathUri(serviceAddr).length())));
@ -150,14 +148,10 @@ public class RouteUtils {
methodPredicate.setName("Method");
methodPredicate.getArgs().put("methods", service.getRequestMethod());
IscPredicateDefinition queryPredicate = new IscPredicateDefinition();
queryPredicate.setName("Query");
queryPredicate.getArgs().put("param", ACCESS_KEY_NAME);
IscPredicateDefinition pathPredicate = new IscPredicateDefinition();
pathPredicate.setName("Path");
pathPredicate.getArgs().put("pattern", appService.getVirtualAddr());
route.setPredicates(Arrays.asList(methodPredicate, queryPredicate, pathPredicate));
pathPredicate.getArgs().put("pattern", genVirtualAddrPath(service.getServiceAddr()));
route.setPredicates(Arrays.asList(methodPredicate, pathPredicate));
//过滤器
List<IscFilterDefinition> filters = new ArrayList<>();
@ -166,26 +160,6 @@ public class RouteUtils {
stripPrefixFilter.getArgs().put("parts", "1");
filters.add(stripPrefixFilter);
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 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(addRequestParamFilter);
}
}
IscFilterDefinition retryFilter = new IscFilterDefinition();
retryFilter.setName("Retry");
retryFilter.getArgs().put("retries", "1");
@ -194,23 +168,79 @@ 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()));
metadata.put("expire", toString(appService.getEndTime()));
metadata.put("accessKeyName", ACCESS_KEY_NAME);
metadata.put("addParam", service.getHiddenParams());
return route;
}
/**
* 对象转字符串
* 生成 服务对应AK调用规则
*
* @param obj 具体属性
* @param appService
* @param accessKey
* @return
*/
public static String toString(Object obj)
public static IscRule generateRule(IscAppService appService, String accessKey)
{
return Objects.isNull(obj) ? StringUtils.EMPTY : obj instanceof Date ?
DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, (Date) obj) : String.valueOf(obj);
IscRule rule = new IscRule();
rule.setId(accessKey + ':' + appService.getServiceId());
rule.setExpire(appService.getEndTime());
rule.setDaysLimit(appService.getQuotaDays());
rule.setHoursLimit(appService.getQuotaHours());
rule.setMinutesLimit(appService.getQuotaMinutes());
rule.setSecondsLimit(appService.getQuotaSeconds());
return rule;
}
/**
* 刷新规则信息 先清空再批量新增
*
* @param rules 规则信息
* @return 是否成功
*/
public static boolean refreshRules(List<IscRule> rules)
{
Map<String, IscRule> ruleMap = rules.stream().collect(Collectors.toMap(IscRule::getId, Function.identity(), (o1, o2) -> o2));
final RMap<String, IscRule> map = client.getMap(IscConstants.KEY_RULES, RULE_CODES_INSTANCE);
map.clear();
map.putAll(ruleMap);
return true;
}
/**
* 保存规则信息
*
* @param rule 规则信息
* @return 是否成功
*/
public static boolean saveRule(IscRule rule)
{
final RMap<String, IscRule> map = client.getMap(IscConstants.KEY_RULES, RULE_CODES_INSTANCE);
map.put(rule.getId(), rule);
return true;
}
/**
* 删除规则信息
*
* @param ruleId 规则ID
* @return 是否成功
*/
public static boolean deleteRule(String ruleId)
{
final RMap<String, IscRule> map = client.getMap(IscConstants.KEY_RULES, RULE_CODES_INSTANCE);
map.remove(ruleId);
return true;
}
/**
* 更新规则信息
*
* @param rule 规则信息
* @return 是否成功
*/
public static boolean updateRule(IscRule rule)
{
return saveRule(rule);
}
}

View File

@ -1,16 +0,0 @@
package com.ruoyi.isc.utils.beans;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* @author Wechao Gong
* @date 2021/9-14
*/
@Getter
@Setter
public class GatewayRoute implements Serializable
{
}

View File

@ -0,0 +1,26 @@
package com.ruoyi.isc.utils.beans;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
/**
* 网关 规则信息(服务对应AK处理规则)
* @author Wechao Gong
* @date 2021-10-16
*/
@Getter
@Setter
public class IscRule implements Serializable
{
@JsonIgnore
private String id;
private Date expire;
private Long daysLimit;
private Long hoursLimit;
private Long minutesLimit;
private Long secondsLimit;
}