diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java index acda4d79..855a134e 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java @@ -1,6 +1,7 @@ package com.gitee.sop.gatewaycommon.bean; import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager; import com.gitee.sop.gatewaycommon.limit.LimitManager; import com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder; @@ -18,6 +19,7 @@ import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager; import com.gitee.sop.gatewaycommon.manager.LimitConfigManager; import com.gitee.sop.gatewaycommon.manager.RouteConfigManager; import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager; +import com.gitee.sop.gatewaycommon.monitor.MonitorManager; import com.gitee.sop.gatewaycommon.param.ParameterFormatter; import com.gitee.sop.gatewaycommon.result.DataNameBuilder; import com.gitee.sop.gatewaycommon.result.DefaultDataNameBuilder; @@ -157,6 +159,16 @@ public class ApiConfig { */ private TokenValidator tokenValidator = apiParam -> apiParam != null && StringUtils.isNotBlank(apiParam.fetchAccessToken()); + /** + * 路由拦截器 + */ + private List routeInterceptors = new ArrayList<>(4); + + /** + * 监控管理 + */ + private MonitorManager monitorManager = new MonitorManager(); + // -------- fields --------- /** diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/DefaultRouteInterceptorContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/DefaultRouteInterceptorContext.java new file mode 100644 index 00000000..7fc5497b --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/DefaultRouteInterceptorContext.java @@ -0,0 +1,114 @@ +package com.gitee.sop.gatewaycommon.bean; + +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; +import com.gitee.sop.gatewaycommon.param.ApiParam; + +/** + * @author tanghc + */ +public class DefaultRouteInterceptorContext implements RouteInterceptorContext { + + /** 请求参数 */ + private ApiParam apiParam; + /** 错误信息 */ + private String serviceErrorMsg; + /** 微服务返回状态 */ + private int responseStatus; + /** 开始时间 */ + private long beginTimeMillis; + /** 结束时间 */ + private long finishTimeMillis; + /** 请求上下文 */ + private Object requestContext; + /** 微服务返回结果 */ + private String serviceResult; + /** 请求包大小 */ + private long requestDataSize; + /** 返回内容大小 */ + private long responseDataSize; + + @Override + public ApiParam getApiParam() { + return apiParam; + } + + @Override + public String getServiceResult() { + return serviceResult; + } + + @Override + public int getResponseStatus() { + return responseStatus; + } + + @Override + public long getBeginTimeMillis() { + return beginTimeMillis; + } + + @Override + public long getFinishTimeMillis() { + return finishTimeMillis; + } + + @Override + public Object getRequestContext() { + return requestContext; + } + + public void setApiParam(ApiParam apiParam) { + this.apiParam = apiParam; + } + + public void setServiceResult(String serviceResult) { + this.serviceResult = serviceResult; + } + + public void setResponseStatus(int responseStatus) { + this.responseStatus = responseStatus; + } + + public void setBeginTimeMillis(long beginTimeMillis) { + this.beginTimeMillis = beginTimeMillis; + } + + public void setFinishTimeMillis(long finishTimeMillis) { + this.finishTimeMillis = finishTimeMillis; + } + + public void setRequestContext(Object requestContext) { + this.requestContext = requestContext; + } + + @Override + public String getServiceErrorMsg() { + return serviceErrorMsg; + } + + public void setServiceErrorMsg(String serviceErrorMsg) { + this.serviceErrorMsg = serviceErrorMsg; + } + + @Override + public long getRequestDataSize() { + return requestDataSize; + } + + public void setRequestDataSize(long requestDataSize) { + // spring cloud gateway get请求contentLength返回-1 + if (requestDataSize < 0) { + requestDataSize = 0; + } + this.requestDataSize = requestDataSize; + } + + @Override + public long getResponseDataSize() { + return responseDataSize; + } + + public void setResponseDataSize(long responseDataSize) { + this.responseDataSize = responseDataSize; + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java index ab9cad69..1e176a42 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java @@ -16,14 +16,6 @@ public class SopConstants { public static final String DEFAULT_SIGN_METHOD = "md5"; public static final String EMPTY_JSON = "{}"; - public static final String REDIRECT_METHOD_KEY = "r-method"; - - public static final String REDIRECT_VERSION_KEY = "r-version"; - - public static final String REDIRECT_PATH_KEY = "r-path"; - - public static final String SOP_NOT_MERGE = "sop.not-merge"; - public static final String METADATA_SERVER_CONTEXT_PATH = "server.servlet.context-path"; public static final String METADATA_SERVER_CONTEXT_PATH_COMPATIBILITY = "context-path"; @@ -58,4 +50,6 @@ public class SopConstants { public static final String METADATA_ENV_PRE_VALUE = "pre"; public static final String METADATA_ENV_GRAY_VALUE = "gray"; + public static final String CACHE_ROUTE_INTERCEPTOR_CONTEXT = "cacheRouteInterceptorContext"; + } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java index 6a7a1bba..3446be1f 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java @@ -8,8 +8,8 @@ import com.gitee.sop.gatewaycommon.gateway.common.RequestContentDataExtractor; import com.gitee.sop.gatewaycommon.gateway.common.SopServerHttpRequestDecorator; import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.FormHttpOutputMessage; -import com.gitee.sop.gatewaycommon.route.ForwardInfo; import com.gitee.sop.gatewaycommon.param.ParamNames; +import com.gitee.sop.gatewaycommon.route.ForwardInfo; import com.gitee.sop.gatewaycommon.util.RequestUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -82,15 +82,13 @@ public class ServerWebExchangeUtil { return ServerRequest.create(exchange, messageReaders); } - public static ServerWebExchange getRestfulExchange(ServerWebExchange exchange, String path) { + public static ApiParam getApiParamForRestful(ServerWebExchange exchange, String path) { int index = path.indexOf(REST_PATH); // 取"/rest"的后面部分 String newPath = path.substring(index + REST_PATH.length()); - ApiParam apiParam = new ApiParam(); - apiParam.setName(newPath); - apiParam.setVersion(""); + ApiParam apiParam = ApiParam.createRestfulApiParam(newPath); setApiParam(exchange, apiParam); - return getForwardExchange(exchange, newPath); + return apiParam; } /** @@ -100,7 +98,7 @@ public class ServerWebExchangeUtil { * @param forwardPath 重定向path * @return 返回新的ServerWebExchange,配合chain.filter(newExchange);使用 */ - public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, String forwardPath) { + private static ServerWebExchange getForwardExchange(ServerWebExchange exchange, String forwardPath) { ServerHttpRequest newRequest = exchange.getRequest() .mutate() .path(forwardPath).build(); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java index f65151a4..ae8717e7 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java @@ -4,6 +4,7 @@ import com.gitee.sop.gatewaycommon.bean.ApiConfig; import com.gitee.sop.gatewaycommon.gateway.controller.ConfigChannelController; import com.gitee.sop.gatewaycommon.gateway.controller.ErrorLogController; import com.gitee.sop.gatewaycommon.gateway.controller.GatewayController; +import com.gitee.sop.gatewaycommon.gateway.controller.GatewayMonitorController; import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFilter; import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter; import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter; @@ -64,6 +65,11 @@ public class BaseGatewayConfiguration extends AbstractConfiguration { return new ErrorLogController(); } + @Bean + public GatewayMonitorController gatewayMonitorController() { + return new GatewayMonitorController(); + } + /** * 自定义异常处理[@@]注册Bean时依赖的Bean,会从容器中直接获取,所以直接注入即可 * diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java new file mode 100644 index 00000000..910c874c --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java @@ -0,0 +1,21 @@ +package com.gitee.sop.gatewaycommon.gateway.controller; + +import com.gitee.sop.gatewaycommon.support.BaseMonitorController; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; + +import java.util.Map; + +/** + * @author tanghc + */ +@RestController +public class GatewayMonitorController extends BaseMonitorController { + + @Override + protected ApiParam getApiParam(ServerWebExchange request) { + Map params = request.getRequest().getQueryParams().toSingleValueMap(); + return ApiParam.build(params); + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java index 9af4a5e3..eff18885 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java @@ -1,11 +1,14 @@ package com.gitee.sop.gatewaycommon.gateway.filter; +import com.gitee.sop.gatewaycommon.bean.DefaultRouteInterceptorContext; +import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser; import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys; import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.route.ForwardInfo; +import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil; import com.gitee.sop.gatewaycommon.validate.Validator; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -67,7 +70,10 @@ public class IndexFilter implements WebFilter { log.error("尝试调用restful请求,但sop.restful.enable未开启"); return ServerWebExchangeUtil.forwardUnknown(exchange, chain); } - ServerWebExchange newExchange = ServerWebExchangeUtil.getRestfulExchange(exchange, path); + ApiParam apiParam = ServerWebExchangeUtil.getApiParamForRestful(exchange, path); + this.doValidate(exchange, apiParam); + ForwardInfo forwardInfo = gatewayForwardChooser.getForwardInfo(exchange); + ServerWebExchange newExchange = ServerWebExchangeUtil.getForwardExchange(exchange, forwardInfo); return chain.filter(newExchange); } if (Objects.equals(path, indexPath)) { @@ -122,12 +128,21 @@ public class IndexFilter implements WebFilter { private void doValidate(ServerWebExchange exchange, ApiParam apiParam) { try { validator.validate(apiParam); + this.afterValidate(exchange, apiParam); } catch (ApiException e) { log.error("验证失败,ip:{}, params:{}, errorMsg:{}", apiParam.fetchIp(), apiParam.toJSONString(), e.getMessage()); ServerWebExchangeUtil.setThrowable(exchange, e); } } + private void afterValidate(ServerWebExchange exchange, ApiParam param) { + RouteInterceptorUtil.runPreRoute(exchange, param, context -> { + DefaultRouteInterceptorContext defaultRouteInterceptorContext = (DefaultRouteInterceptorContext) context; + defaultRouteInterceptorContext.setRequestDataSize(exchange.getRequest().getHeaders().getContentLength()); + exchange.getAttributes().put(SopConstants.CACHE_ROUTE_INTERCEPTOR_CONTEXT, context); + }); + } + private ServerHttpRequestDecorator decorate( ServerWebExchange exchange , HttpHeaders headers diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java index 9f38866b..32408323 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java @@ -3,6 +3,7 @@ package com.gitee.sop.gatewaycommon.gateway.loadbalancer; import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext; import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.util.LoadBalanceUtil; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.Server; import org.springframework.cloud.client.ServiceInstance; @@ -14,7 +15,6 @@ import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.web.server.ServerWebExchange; import java.util.List; -import java.util.concurrent.ThreadLocalRandom; /** * 重写负载均衡处理。 @@ -58,7 +58,7 @@ public class SopLoadBalancerClient extends RibbonLoadBalancerClient implements S } private RibbonServer getRibbonServer(String serviceId, List servers) { - Server server = this.chooseRandomServer(servers); + Server server = LoadBalanceUtil.chooseByRoundRobin(serviceId, servers); if (server == null) { return null; } @@ -70,26 +70,6 @@ public class SopLoadBalancerClient extends RibbonLoadBalancerClient implements S ); } - /** - * 随机选取一台实例 - * - * @param servers 服务列表 - * @return 返回实例,没有返回null - */ - private Server chooseRandomServer(List servers) { - if (servers.isEmpty()) { - return null; - } - int serverCount = servers.size(); - // 随机选取一台实例 - int index = chooseRandomInt(serverCount); - return servers.get(index); - } - - private int chooseRandomInt(int serverCount) { - return ThreadLocalRandom.current().nextInt(serverCount); - } - private ServerIntrospector serverIntrospector(String serviceId) { ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId, ServerIntrospector.class); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java index f39fa3a1..d3e891a5 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java @@ -2,6 +2,7 @@ package com.gitee.sop.gatewaycommon.gateway.result; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; @@ -17,7 +18,6 @@ import org.springframework.web.server.ServerWebExchange; import java.util.List; import java.util.Locale; -import java.util.Map; /** @@ -29,7 +29,8 @@ public class GatewayResultExecutor extends BaseExecutorAdapter errorCodeList = exchange.getResponse().getHeaders().get(SopConstants.X_SERVICE_ERROR_CODE); if (!CollectionUtils.isEmpty(errorCodeList)) { String errorCode = errorCodeList.get(0); @@ -59,6 +60,11 @@ public class GatewayResultExecutor extends BaseExecutorAdapter { - private static final String VALIDATE_ERROR_PATH = "/sop/validateError"; - @Override public ApiParam getApiParam(ServerWebExchange exchange) { return ServerWebExchangeUtil.getApiParam(exchange); @@ -22,7 +20,7 @@ public class GatewayForwardChooser extends BaseForwardChooser public ForwardInfo getForwardInfo(ServerWebExchange exchange) { // 如果有异常,直接跳转到异常处理 if (ServerWebExchangeUtil.getThrowable(exchange) != null) { - return new ForwardInfo(VALIDATE_ERROR_PATH, ""); + return ForwardInfo.getErrorForwardInfo(); } return super.getForwardInfo(exchange); } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java index fa8c0f54..5070027d 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java @@ -5,6 +5,7 @@ import com.gitee.sop.gatewaycommon.bean.RouteDefinition; import com.gitee.sop.gatewaycommon.manager.RouteRepository; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.event.RefreshRoutesEvent; import org.springframework.cloud.gateway.route.Route; @@ -77,13 +78,27 @@ public class GatewayRouteRepository implements RouteRepository entry : routes.entrySet()) { // /food/get/?id? String pattern = entry.getKey(); - if (StringUtils.containsAny(pattern, "{") && this.pathMatcher.match(pattern, id)) { - return entry.getValue(); + if (this.pathMatcher.match(pattern, id)) { + return clone(id, entry.getValue()); } } return null; } + private GatewayTargetRoute clone(String path, GatewayTargetRoute gatewayTargetRoute) { + String prefix = "/" + gatewayTargetRoute.getServiceRouteInfo().getServiceId(); + if (path.startsWith(prefix)) { + path = path.substring(prefix.length()); + } + RouteDefinition routeDefinition = gatewayTargetRoute.getRouteDefinition(); + RouteDefinition newRouteDefinition = new RouteDefinition(); + BeanUtils.copyProperties(routeDefinition, newRouteDefinition); + newRouteDefinition.setPath(path); + return new GatewayTargetRoute(gatewayTargetRoute.getServiceRouteInfo() + , newRouteDefinition + , gatewayTargetRoute.getTargetRouteDefinition()); + } + @Override public Collection getAll() { diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/MonitorRouteInterceptor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/MonitorRouteInterceptor.java new file mode 100644 index 00000000..e2942743 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/MonitorRouteInterceptor.java @@ -0,0 +1,96 @@ +package com.gitee.sop.gatewaycommon.interceptor; + +import com.gitee.sop.gatewaycommon.bean.ApiConfig; +import com.gitee.sop.gatewaycommon.monitor.MonitorInfo; +import com.gitee.sop.gatewaycommon.monitor.MonitorManager; +import com.gitee.sop.gatewaycommon.param.ApiParam; + +import java.util.ArrayList; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * SOP默认的拦截器,用于收集监控数据 + * + * @author tanghc + */ +public class MonitorRouteInterceptor implements RouteInterceptor { + + private ThreadPoolExecutor threadPoolExecutor; + + public MonitorRouteInterceptor(int threadPoolSize) { + threadPoolExecutor = new ThreadPoolExecutor(threadPoolSize, threadPoolSize, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>()); + } + + public MonitorRouteInterceptor() { + this(4); + } + + public MonitorRouteInterceptor(ThreadPoolExecutor threadPoolExecutor) { + this.threadPoolExecutor = threadPoolExecutor; + } + + @Override + public void preRoute(RouteInterceptorContext context) { + } + + @Override + public void afterRoute(RouteInterceptorContext context) { + threadPoolExecutor.execute(() -> this.storeRequestInfo(context)); + } + + /** + * 记录接口调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数. + * 需要考虑并发情况。 + */ + protected void storeRequestInfo(RouteInterceptorContext context) { + MonitorManager monitorManager = ApiConfig.getInstance().getMonitorManager(); + ApiParam apiParam = context.getApiParam(); + String routeId = apiParam.fetchNameVersion(); + long spendTime = context.getFinishTimeMillis() - context.getBeginTimeMillis(); + // 这步操作是线程安全的,底层调用了ConcurrentHashMap.computeIfAbsent + MonitorInfo monitorInfo = monitorManager.getMonitorInfo(routeId, (k) -> this.createMonitorInfo(apiParam)); + + monitorInfo.storeMaxTime(spendTime); + monitorInfo.storeMinTime(spendTime); + monitorInfo.getTotalCount().incrementAndGet(); + monitorInfo.getTotalTime().addAndGet(spendTime); + monitorInfo.getTotalRequestDataSize().addAndGet(context.getRequestDataSize()); + monitorInfo.getTotalResponseDataSize().addAndGet(context.getResponseDataSize()); + if (context.isSuccessRequest()) { + monitorInfo.getSuccessCount().incrementAndGet(); + } else { + monitorInfo.getErrorCount().incrementAndGet(); + String errorMsg = context.getServiceErrorMsg(); + monitorInfo.addErrorMsg(errorMsg); + } + } + + private MonitorInfo createMonitorInfo(ApiParam apiParam) { + MonitorInfo monitorInfo = new MonitorInfo(); + monitorInfo.setName(apiParam.fetchName()); + monitorInfo.setVersion(apiParam.fetchVersion()); + monitorInfo.setServiceId(apiParam.fetchServiceId()); + monitorInfo.setTotalRequestDataSize(new AtomicLong()); + monitorInfo.setTotalResponseDataSize(new AtomicLong()); + monitorInfo.setTotalTime(new AtomicLong()); + monitorInfo.setMaxTime(0L); + monitorInfo.setMinTime(0L); + monitorInfo.setSuccessCount(new AtomicLong()); + monitorInfo.setTotalCount(new AtomicLong()); + monitorInfo.setErrorCount(new AtomicLong()); + monitorInfo.setErrorMsgList(new ArrayList<>(10)); + return monitorInfo; + } + + + @Override + public int getOrder() { + return -1000; + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptor.java new file mode 100644 index 00000000..a7a6fbc4 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptor.java @@ -0,0 +1,40 @@ +package com.gitee.sop.gatewaycommon.interceptor; + +/** + * 路由拦截器 + * + * @author tanghc + */ +public interface RouteInterceptor { + + /** + * 在路由转发前执行,签名校验通过后会立即执行此方法 + * + * @param context context + */ + void preRoute(RouteInterceptorContext context); + + /** + * 微服务返回结果后执行,可能返回成功,也可能返回失败结果, + * 可通过 {@link RouteInterceptorContext#getResponseStatus()} 来判断是否返回正确结果。 + * + * @param context context + */ + void afterRoute(RouteInterceptorContext context); + + /** + * 拦截器执行顺序,值小优先执行,建议从0开始,小于0留给系统使用 + * + * @return 返回顺序 + */ + int getOrder(); + + /** + * 是否匹配,返回true执行拦截器,默认true + * @param context context + * @return 返回true执行拦截器 + */ + default boolean match(RouteInterceptorContext context) { + return true; + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java new file mode 100644 index 00000000..491a7117 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java @@ -0,0 +1,85 @@ +package com.gitee.sop.gatewaycommon.interceptor; + +import com.gitee.sop.gatewaycommon.bean.SopConstants; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import org.springframework.http.HttpStatus; + +/** + * 拦截器参数 + * + * @author tanghc + */ +public interface RouteInterceptorContext { + + /** + * 返回ApiParam + * + * @return 返回ApiParam + */ + ApiParam getApiParam(); + + /** + * 获取微服务返回的内容 + * + * @return 微服务返回内容 + */ + String getServiceResult(); + + /** + * 获取微服务端的错误信息,status为200时,返回null。 + * + * @return 返回错误信息 + */ + String getServiceErrorMsg(); + + /** + * 获取微服务返回状态码 + * + * @return 返回状态码,正确为200,错误为非200 + */ + int getResponseStatus(); + + /** + * 获取路由开始时间 + * + * @return 返回开始时间 + */ + long getBeginTimeMillis(); + + /** + * 获取路由结束时间 + * + * @return 返回结束时间 + */ + long getFinishTimeMillis(); + + /** + * 获取上下文信息,zuul返回RequestContext,Gateway返回ServerWebExchange + * + * @return 返回上下文对象 + */ + Object getRequestContext(); + + /** + * 获取请求内容大小 + * + * @return 返回请求内容大小 + */ + long getRequestDataSize(); + + /** + * 获取返回结果内容大小 + * @return 返回返回结果内容大小 + */ + long getResponseDataSize(); + + /** + * 是否是成功请求,微服务主动抛出的异常也算作成功,JSR303校验失败也算作成功。 + * 只有微服务返回未知的错误算作失败。 + * @return true:成功请求 + */ + default boolean isSuccessRequest() { + int responseStatus = getResponseStatus(); + return responseStatus == HttpStatus.OK.value() || responseStatus == SopConstants.BIZ_ERROR_STATUS; + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java index 8b38406b..a91bc665 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java @@ -28,7 +28,7 @@ public interface ServerChooserContext extends ApiParamAware { default boolean isRequestGrayServer(T t) { ApiParam apiParam = getApiParam(t); - return apiParam.isGrayRequest(); + return apiParam.fetchGrayRequest(); } String getHost(T t); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java index 203f64e4..45c15f21 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java @@ -3,8 +3,10 @@ package com.gitee.sop.gatewaycommon.manager; import com.gitee.sop.gatewaycommon.bean.ApiConfig; import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.bean.BeanInitializer; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; import com.gitee.sop.gatewaycommon.bean.SpringContext; import com.gitee.sop.gatewaycommon.gateway.loadbalancer.NacosServerIntrospector; +import com.gitee.sop.gatewaycommon.interceptor.MonitorRouteInterceptor; import com.gitee.sop.gatewaycommon.limit.LimitManager; import com.gitee.sop.gatewaycommon.loadbalancer.SopPropertiesFactory; import com.gitee.sop.gatewaycommon.message.ErrorFactory; @@ -16,6 +18,7 @@ import com.gitee.sop.gatewaycommon.route.ServiceListener; import com.gitee.sop.gatewaycommon.route.ServiceRouteListener; import com.gitee.sop.gatewaycommon.secret.IsvManager; import com.gitee.sop.gatewaycommon.session.SessionManager; +import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil; import com.gitee.sop.gatewaycommon.validate.SignConfig; import com.gitee.sop.gatewaycommon.validate.Validator; import lombok.extern.slf4j.Slf4j; @@ -40,6 +43,8 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Collection; import java.util.Map; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; @@ -254,6 +259,7 @@ public class AbstractConfiguration implements ApplicationContextAware, Applicati initMessage(); initBeanInitializer(); + initRouteInterceptor(); doAfter(); } @@ -262,6 +268,13 @@ public class AbstractConfiguration implements ApplicationContextAware, Applicati beanInitializerMap.values().forEach(BeanInitializer::load); } + protected void initRouteInterceptor() { + Map routeInterceptorMap = applicationContext.getBeansOfType(RouteInterceptor.class); + Collection routeInterceptors = new ArrayList<>(routeInterceptorMap.values()); + routeInterceptors.add(new MonitorRouteInterceptor()); + RouteInterceptorUtil.addInterceptors(routeInterceptors); + } + protected void doAfter() { } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorInfo.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorInfo.java new file mode 100644 index 00000000..a05bd56a --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorInfo.java @@ -0,0 +1,88 @@ +package com.gitee.sop.gatewaycommon.monitor; + +import lombok.Data; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。 + * + * @author tanghc + */ +@Data +public class MonitorInfo { + + /** + * 接口名 + */ + private String name; + /** + * 版本号 + */ + private String version; + /** + * serviceId + */ + private String serviceId; + /** + * 总请求数据量 + */ + private AtomicLong totalRequestDataSize; + /** + * 总返回数据量 + */ + private AtomicLong totalResponseDataSize; + /** + * 请求耗时最长时间 + */ + private Long maxTime; + /** + * 请求耗时最小时间 + */ + private Long minTime; + /** + * 总时长 + */ + private AtomicLong totalTime; + /** + * 总调用次数 + */ + private AtomicLong totalCount; + /** + * 成功次数 + */ + private AtomicLong successCount; + /** + * 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败) + */ + private AtomicLong errorCount; + /** + * 错误信息 + */ + private List errorMsgList; + + public synchronized void storeMaxTime(long spendTime) { + if (spendTime > maxTime) { + maxTime = spendTime; + } + } + + public synchronized void storeMinTime(long spendTime) { + if (minTime == 0 || spendTime < minTime) { + minTime = spendTime; + } + } + + public void addErrorMsg(String errorMsg) { + if (errorMsg == null || "".equals(errorMsg)) { + return; + } + synchronized (this) { + if (errorMsgList != null && errorMsgList.size() < 10) { + errorMsgList.add(errorMsg); + } + } + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java new file mode 100644 index 00000000..a181da7e --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java @@ -0,0 +1,22 @@ +package com.gitee.sop.gatewaycommon.monitor; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * @author tanghc + */ +public class MonitorManager { + + private static Map monitorMap = new ConcurrentHashMap<>(128); + + public Map getMonitorData() { + return monitorMap; + } + + public MonitorInfo getMonitorInfo(String routeId, Function createFun) { + return monitorMap.computeIfAbsent(routeId, createFun); + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java index 1e3559b1..13d24846 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java @@ -29,12 +29,24 @@ public class ApiParam extends JSONObject implements Param { private String restName; private String restVersion; + private String serviceId; private String ip; + private boolean restful; + private boolean mergeResult = true; private boolean isGrayRequest; private transient UploadContext uploadContext; + public static ApiParam createRestfulApiParam(String path) { + ApiParam apiParam = new ApiParam(); + apiParam.setName(path); + apiParam.setVersion(""); + apiParam.setRestful(true); + apiParam.setMergeResult(false); + return apiParam; + } + public void fitNameVersion() { if (restName != null) { this.put(ParamNames.API_NAME, restName); @@ -204,12 +216,7 @@ public class ApiParam extends JSONObject implements Param { @Override public String fetchSignMethod() { - String signMethod = getString(ParamNames.SIGN_TYPE_NAME); - if (signMethod == null) { - return SopConstants.DEFAULT_SIGN_METHOD; - } else { - return signMethod; - } + return getString(ParamNames.SIGN_TYPE_NAME); } @Override @@ -251,11 +258,35 @@ public class ApiParam extends JSONObject implements Param { return ip; } - public boolean isGrayRequest() { + public boolean fetchGrayRequest() { return isGrayRequest; } public void setGrayRequest(boolean grayRequest) { isGrayRequest = grayRequest; } + + public String fetchServiceId() { + return serviceId; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + public boolean fetchMergeResult() { + return mergeResult; + } + + public void setMergeResult(boolean mergeResult) { + this.mergeResult = mergeResult; + } + + public boolean fetchRestful() { + return restful; + } + + public void setRestful(boolean restful) { + this.restful = restful; + } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java index c620c0a6..ca9dc0e8 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java @@ -3,50 +3,47 @@ package com.gitee.sop.gatewaycommon.result; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.gitee.sop.gatewaycommon.bean.ApiConfig; -import com.gitee.sop.gatewaycommon.bean.ApiContext; -import com.gitee.sop.gatewaycommon.bean.ErrorDefinition; +import com.gitee.sop.gatewaycommon.bean.DefaultRouteInterceptorContext; import com.gitee.sop.gatewaycommon.bean.Isv; -import com.gitee.sop.gatewaycommon.bean.RouteDefinition; -import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; import com.gitee.sop.gatewaycommon.bean.SopConstants; -import com.gitee.sop.gatewaycommon.bean.TargetRoute; -import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; import com.gitee.sop.gatewaycommon.message.ErrorEnum; import com.gitee.sop.gatewaycommon.message.ErrorMeta; import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.ParamNames; import com.gitee.sop.gatewaycommon.secret.IsvManager; -import com.gitee.sop.gatewaycommon.validate.alipay.AlipayConstants; +import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil; import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySignature; -import lombok.Getter; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.BooleanUtils; -import org.springframework.beans.BeanUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; +import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.Optional; /** + * 处理微服务返回结果 + * * @author tanghc */ @Slf4j public abstract class BaseExecutorAdapter implements ResultExecutor { - private static final ErrorMeta SUCCESS_META = ErrorEnum.SUCCESS.getErrorMeta(); - private static final ErrorMeta ISP_UNKNOW_ERROR_META = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta(); - private static final ErrorMeta ISP_BIZ_ERROR = ErrorEnum.BIZ_ERROR.getErrorMeta(); - private static final ErrorMeta ISV_MISSING_METHOD_META = ErrorEnum.ISV_MISSING_METHOD.getErrorMeta(); + + private static Map HTTP_STATUS_ERROR_ENUM_MAP = new HashMap<>(8); private static final String GATEWAY_CODE_NAME = "code"; private static final String GATEWAY_MSG_NAME = "msg"; private static final String ARRAY_START = "["; private static final String ARRAY_END = "]"; private static final String ROOT_JSON = "{'items':%s}".replace("'", "\""); - private static final String ERROR_METHOD = "error"; + + static { + HTTP_STATUS_ERROR_ENUM_MAP.put(HttpStatus.OK.value(), ErrorEnum.SUCCESS); + HTTP_STATUS_ERROR_ENUM_MAP.put(SopConstants.BIZ_ERROR_STATUS, ErrorEnum.BIZ_ERROR); + HTTP_STATUS_ERROR_ENUM_MAP.put(HttpStatus.NOT_FOUND.value(), ErrorEnum.ISV_INVALID_METHOD); + } /** @@ -75,65 +72,82 @@ public abstract class BaseExecutorAdapter implements ResultExecutor /** * 获取locale + * * @param t request * @return 返回locale */ protected abstract Locale getLocale(T t); + /** + * 返回拦截器上下文 + * + * @param t request + * @return 返回拦截器上下文 + */ + protected abstract RouteInterceptorContext getRouteInterceptorContext(T t); + @Override public String mergeResult(T request, String serviceResult) { + serviceResult = formatResult(serviceResult); boolean isMergeResult = this.isMergeResult(request); - if (!isMergeResult) { - return serviceResult; - } - serviceResult = wrapResult(serviceResult); int responseStatus = this.getResponseStatus(request); - JSONObject responseData; - if (responseStatus == HttpStatus.OK.value()) { - // 200正常返回 - responseData = JSON.parseObject(serviceResult); - responseData.put(GATEWAY_CODE_NAME, SUCCESS_META.getCode()); - responseData.put(GATEWAY_MSG_NAME, SUCCESS_META.getError(getLocale(request)).getMsg()); - } else if (responseStatus == SopConstants.BIZ_ERROR_STATUS) { - // 如果是业务出错 - this.storeError(request, ErrorType.BIZ); - responseData = JSON.parseObject(serviceResult); - responseData.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode()); - responseData.put(GATEWAY_MSG_NAME, ISP_BIZ_ERROR.getError(getLocale(request)).getMsg()); - } else if (responseStatus == HttpStatus.NOT_FOUND.value()) { - responseData = JSON.parseObject(serviceResult); - responseData.put(GATEWAY_CODE_NAME, ISV_MISSING_METHOD_META.getCode()); - responseData.put(GATEWAY_MSG_NAME, ISV_MISSING_METHOD_META.getError(getLocale(request)).getCode()); + this.doAfterRoute(serviceResult, responseStatus, request); + String finalResult; + if (isMergeResult) { + JSONObject responseData = this.parseServiceResult(serviceResult, responseStatus, request); + finalResult = this.merge(request, responseData); } else { - ApiParam params = this.getApiParam(request); - log.error("微服务端报错,params:{}, 微服务返回结果:{}", params, serviceResult); - this.storeError(request, ErrorType.UNKNOWN); - // 微服务端有可能返回500错误 - // {"path":"/book/getBook3","error":"Internal Server Error","message":"id不能为空","timestamp":"2019-02-13T07:41:00.495+0000","status":500} - responseData = new JSONObject(); - responseData.put(GATEWAY_CODE_NAME, ISP_UNKNOW_ERROR_META.getCode()); - responseData.put(GATEWAY_MSG_NAME, ISP_UNKNOW_ERROR_META.getError(getLocale(request)).getMsg()); + finalResult = serviceResult; } - return this.merge(request, responseData); + return finalResult; } /** - * 保存错误信息 + * 执行拦截器after操作 * - * @param request request + * @param serviceResult 微服务结果 + * @param responseStatus 微服务状态码 + * @param requestContext 微服务状态码 */ - protected void storeError(T request, ErrorType errorType) { - ApiInfo apiInfo = this.getApiInfo(request); - String errorMsg = this.getResponseErrorMessage(request); - ErrorDefinition errorDefinition = new ErrorDefinition(); - BeanUtils.copyProperties(apiInfo, errorDefinition); - errorDefinition.setErrorMsg(errorMsg); - if (errorType == ErrorType.UNKNOWN) { - ApiConfig.getInstance().getServiceErrorManager().saveUnknownError(errorDefinition); + private void doAfterRoute(String serviceResult, int responseStatus, T requestContext) { + RouteInterceptorContext routeInterceptorContext = getRouteInterceptorContext(requestContext); + if (routeInterceptorContext instanceof DefaultRouteInterceptorContext) { + DefaultRouteInterceptorContext defaultRouteInterceptorContext = (DefaultRouteInterceptorContext) routeInterceptorContext; + defaultRouteInterceptorContext.setResponseStatus(responseStatus); + defaultRouteInterceptorContext.setServiceResult(serviceResult); + defaultRouteInterceptorContext.setFinishTimeMillis(System.currentTimeMillis()); + defaultRouteInterceptorContext.setResponseDataSize(serviceResult.length()); + if (responseStatus != HttpStatus.OK.value() && responseStatus != SopConstants.BIZ_ERROR_STATUS) { + String responseErrorMessage = getResponseErrorMessage(requestContext); + if (StringUtils.isEmpty(responseErrorMessage)) { + responseErrorMessage = serviceResult; + } + defaultRouteInterceptorContext.setServiceErrorMsg(responseErrorMessage); + } } - if (errorType == ErrorType.BIZ) { - ApiConfig.getInstance().getServiceErrorManager().saveBizError(errorDefinition); + RouteInterceptorUtil.runAfterRoute(routeInterceptorContext); + } + + /** + * 将微服务的返回结果解析成JSONObject + * + * @param serviceResult 微服务返回结果 + * @param responseStatus 返回状态 + * @param request 请求 + * @return 返回JSONObject + */ + protected JSONObject parseServiceResult(String serviceResult, int responseStatus, T request) { + ErrorEnum errorEnum = HTTP_STATUS_ERROR_ENUM_MAP.get(responseStatus); + if (errorEnum == null) { + // 其它异常不应该把异常信息告诉给客户端,将微服务内容设置成空的json + serviceResult = SopConstants.EMPTY_JSON; + errorEnum = ErrorEnum.ISP_UNKNOWN_ERROR; } + ErrorMeta errorMeta = errorEnum.getErrorMeta(); + JSONObject responseData = JSON.parseObject(serviceResult); + responseData.put(GATEWAY_CODE_NAME, errorMeta.getCode()); + responseData.put(GATEWAY_MSG_NAME, errorMeta.getError(getLocale(request)).getMsg()); + return responseData; } @@ -144,53 +158,15 @@ public abstract class BaseExecutorAdapter implements ResultExecutor * @return true:需要合并 */ protected boolean isMergeResult(T request) { - // 默认全局设置 - Boolean defaultSetting = ApiContext.getApiConfig().getMergeResult(); - if (defaultSetting != null) { - return defaultSetting; - } ApiParam params = this.getApiParam(request); - TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion()); - RouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute) - .map(TargetRoute::getRouteDefinition) - .orElse(null); - return Optional.ofNullable(baseRouteDefinition) - .map(routeDefinition -> { - int mergeResult = baseRouteDefinition.getMergeResult(); - return BooleanUtils.toBoolean(mergeResult); - }) - .orElse(true); + return params.fetchMergeResult(); } - protected ApiInfo getApiInfo(T request) { - ApiParam params = this.getApiParam(request); - TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion()); - - String serviceId = Optional.ofNullable(targetRoute) - .flatMap(route -> Optional.ofNullable(route.getServiceRouteInfo())) - .map(ServiceRouteInfo::getServiceId) - .orElse(SopConstants.UNKNOWN_SERVICE); - - RouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute) - .map(TargetRoute::getRouteDefinition) - .orElse(null); - - ApiInfo apiInfo = new ApiInfo(); - apiInfo.name = params.fetchName(); - apiInfo.version = params.fetchVersion(); - apiInfo.serviceId = serviceId; - apiInfo.gatewayRouteDefinition = baseRouteDefinition; - return apiInfo; - } - - protected String wrapResult(String serviceResult) { - if (serviceResult == null) { - serviceResult = ""; - } - serviceResult = serviceResult.trim(); - if (StringUtils.isEmpty(serviceResult)) { + protected String formatResult(String serviceResult) { + if (StringUtils.isBlank(serviceResult)) { return SopConstants.EMPTY_JSON; } + // 如果直接返回数组,需要进行包装,变成:{"items": [...]} if (serviceResult.startsWith(ARRAY_START) && serviceResult.endsWith(ARRAY_END)) { return String.format(ROOT_JSON, serviceResult); } @@ -200,12 +176,11 @@ public abstract class BaseExecutorAdapter implements ResultExecutor public String merge(T exchange, JSONObject responseData) { JSONObject finalData = new JSONObject(true); ApiParam params = this.getApiParam(exchange); - String name = params.fetchName(); ApiConfig apiConfig = ApiConfig.getInstance(); // 点换成下划线 DataNameBuilder dataNameBuilder = apiConfig.getDataNameBuilder(); // alipay_goods_get_response - String responseDataNodeName = dataNameBuilder.build(name); + String responseDataNodeName = dataNameBuilder.build(params.fetchName()); finalData.put(responseDataNodeName, responseData); ResultAppender resultAppender = apiConfig.getResultAppender(); // 追加额外的结果 @@ -213,19 +188,23 @@ public abstract class BaseExecutorAdapter implements ResultExecutor resultAppender.append(finalData, params, exchange); } // 添加服务端sign + this.addResponseSign(apiConfig, params, finalData, responseDataNodeName); + return finalData.toJSONString(); + } + + private void addResponseSign(ApiConfig apiConfig, ApiParam params, JSONObject finalData, String responseDataNodeName) { if (apiConfig.isShowReturnSign() && !CollectionUtils.isEmpty(params)) { // 添加try...catch,生成sign出错不影响结果正常返回 try { String responseSignContent = this.buildResponseSignContent(responseDataNodeName, finalData); - String sign = this.createResponseSign(apiConfig, params, responseSignContent); - if (StringUtils.hasLength(sign)) { + String sign = this.createResponseSign(apiConfig.getIsvManager(), params, responseSignContent); + if (StringUtils.isNotBlank(sign)) { finalData.put(ParamNames.RESPONSE_SIGN_NAME, sign); } } catch (Exception e) { - log.error("生成平台签名失败, params: {}, serviceResult:{}", JSON.toJSONString(params), responseData, e); + log.error("生成平台签名失败, params: {}", params.toJSONString(), e); } } - return finalData.toJSONString(); } /** @@ -246,26 +225,20 @@ public abstract class BaseExecutorAdapter implements ResultExecutor return null; } - protected String getParamValue(Map apiParam, String key, String defaultValue) { - return CollectionUtils.isEmpty(apiParam) ? defaultValue : (String) apiParam.getOrDefault(key, defaultValue); - } - - /** * 这里需要使用平台的私钥生成一个sign,需要配置两套公私钥。 * - * @param apiConfig 配置 + * @param isvManager isvManager * @param params 请求参数 * @param responseSignContent 待签名内容 * @return 返回平台生成的签名 */ - protected String createResponseSign(ApiConfig apiConfig, Map params, String responseSignContent) { + protected String createResponseSign(IsvManager isvManager, ApiParam params, String responseSignContent) { if (StringUtils.isEmpty(responseSignContent)) { return null; } - IsvManager isvManager = apiConfig.getIsvManager(); // 根据appId获取秘钥 - String appKey = this.getParamValue(params, ParamNames.APP_KEY_NAME, ""); + String appKey = params.fetchAppKey(); if (StringUtils.isEmpty(appKey)) { return null; } @@ -274,30 +247,12 @@ public abstract class BaseExecutorAdapter implements ResultExecutor if (StringUtils.isEmpty(privateKeyPlatform)) { return null; } - String charset = Optional.ofNullable(params.get(ParamNames.CHARSET_NAME)) - .map(String::valueOf) - .orElse(SopConstants.UTF8); - String signType = getParamValue(params, ParamNames.SIGN_TYPE_NAME, AlipayConstants.SIGN_TYPE_RSA2); - return AlipaySignature.rsaSign(responseSignContent, privateKeyPlatform, charset, signType); - } - - @Getter - @Setter - protected static class ApiInfo { - private String name; - private String version; - private String serviceId; - private RouteDefinition gatewayRouteDefinition; + return AlipaySignature.rsaSign( + responseSignContent + , privateKeyPlatform + , params.fetchCharset() + , params.fetchSignMethod() + ); } - enum ErrorType { - /** - * 未知错误 - */ - UNKNOWN, - /** - * 业务错误 - */ - BIZ - } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java index 0b7715d5..261742d2 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java @@ -1,18 +1,14 @@ package com.gitee.sop.gatewaycommon.route; import com.gitee.sop.gatewaycommon.bean.ApiConfig; -import com.gitee.sop.gatewaycommon.bean.RouteDefinition; import com.gitee.sop.gatewaycommon.bean.ApiParamAware; import com.gitee.sop.gatewaycommon.bean.TargetRoute; import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder; import com.gitee.sop.gatewaycommon.manager.EnvGrayManager; import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; import com.gitee.sop.gatewaycommon.param.ApiParam; -import org.apache.commons.lang3.BooleanUtils; import org.springframework.beans.factory.annotation.Autowired; -import javax.annotation.PostConstruct; -import java.util.Comparator; import java.util.List; /** @@ -28,9 +24,6 @@ public abstract class BaseForwardChooser implements ForwardChooser, ApiPar ApiParam apiParam = getApiParam(t); String nameVersion = apiParam.fetchNameVersion(); TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion); - RouteDefinition routeDefinitionOrig = targetRoute.getRouteDefinition(); - String path = routeDefinitionOrig.getPath(); - String version = apiParam.fetchVersion(); String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase(); // 如果服务在灰度阶段,返回一个灰度版本号 String grayVersion = envGrayManager.getVersion(serviceId, nameVersion); @@ -40,16 +33,10 @@ public abstract class BaseForwardChooser implements ForwardChooser, ApiPar TargetRoute targetRouteDest = RouteRepositoryContext.getRouteRepository().get(newNameVersion); if (targetRouteDest != null) { apiParam.setGrayRequest(true); - if (BooleanUtils.toBoolean(routeDefinitionOrig.getCompatibleMode())) { - version = grayVersion; - } else { - // 获取灰度接口 - RouteDefinition routeDefinition = targetRouteDest.getRouteDefinition(); - path = routeDefinition.getPath(); - } + targetRoute = targetRouteDest; } } - return new ForwardInfo(path, version); + return new ForwardInfo(targetRoute); } protected boolean isGrayUser(String serviceId, ApiParam apiParam) { diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java index 00c9333d..e822712e 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java @@ -1,5 +1,6 @@ package com.gitee.sop.gatewaycommon.route; +import com.gitee.sop.gatewaycommon.bean.TargetRoute; import lombok.Data; /** @@ -7,13 +8,48 @@ import lombok.Data; */ @Data public class ForwardInfo { - private String path; - private String version; - private String domain; - public ForwardInfo(String path, String version) { - this.path = path; - this.version = version; + private TargetRoute targetRoute; + + public static ForwardInfo getErrorForwardInfo() { + return ErrorForwardInfo.errorForwardInfo; + } + + public ForwardInfo(TargetRoute targetRoute) { + this.targetRoute = targetRoute; + } + + public String getPath() { + return targetRoute.getRouteDefinition().getPath(); + } + + public String getVersion() { + return targetRoute.getRouteDefinition().getVersion(); + } + + static class ErrorForwardInfo extends ForwardInfo { + + private static final String VALIDATE_ERROR_PATH = "/sop/validateError"; + + public static ErrorForwardInfo errorForwardInfo = new ErrorForwardInfo(); + + public ErrorForwardInfo() { + this(null); + } + + public ErrorForwardInfo(TargetRoute targetRoute) { + super(targetRoute); + } + + @Override + public String getPath() { + return VALIDATE_ERROR_PATH; + } + + @Override + public String getVersion() { + return ""; + } } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/BaseMonitorController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/BaseMonitorController.java new file mode 100644 index 00000000..8a23f53a --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/BaseMonitorController.java @@ -0,0 +1,21 @@ +package com.gitee.sop.gatewaycommon.support; + +import com.gitee.sop.gatewaycommon.bean.ApiConfig; +import com.gitee.sop.gatewaycommon.monitor.MonitorManager; +import com.gitee.sop.gatewaycommon.result.ApiResult; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * 提供监控数据 + * + * @author tanghc + */ +public abstract class BaseMonitorController extends SopBaseController { + + @GetMapping("/sop/getMonitorData") + public ApiResult doExecute(T request) { + MonitorManager monitorManager = ApiConfig.getInstance().getMonitorManager(); + return execute(request, monitorManager::getMonitorData); + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/SopBaseController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/SopBaseController.java new file mode 100644 index 00000000..96346579 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/SopBaseController.java @@ -0,0 +1,45 @@ +package com.gitee.sop.gatewaycommon.support; + +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.result.ApiResult; +import com.gitee.sop.gatewaycommon.result.JsonResult; +import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner; +import org.springframework.beans.factory.annotation.Value; + +import java.util.function.Supplier; + +/** + * @author tanghc + */ +public abstract class SopBaseController { + + TaobaoSigner signer = new TaobaoSigner(); + + @Value("${sop.secret}") + private String secret; + + protected abstract ApiParam getApiParam(T t); + + public ApiResult execute(T request, Supplier supplier) { + try { + this.check(request); + JsonResult apiResult = new JsonResult(); + apiResult.setData(supplier.get()); + return apiResult; + } catch (Exception e) { + ApiResult apiResult = new ApiResult(); + apiResult.setCode("505050"); + apiResult.setMsg(e.getMessage()); + return apiResult; + } + } + + protected void check(T request) { + ApiParam apiParam = getApiParam(request); + boolean right = signer.checkSign(apiParam, secret); + if (!right) { + throw new RuntimeException("签名校验失败"); + } + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/LoadBalanceUtil.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/LoadBalanceUtil.java new file mode 100644 index 00000000..752eb870 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/LoadBalanceUtil.java @@ -0,0 +1,63 @@ +package com.gitee.sop.gatewaycommon.util; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +/** + * @author tanghc + */ +public class LoadBalanceUtil { + + /** + * key:serviceId,value:指示变量i + */ + private static Map serviceIdRoundMap = new ConcurrentHashMap<>(8); + + /** + * 轮询选择一台机器。
+ *

+ * 假设有N台服务器:S = {S1, S2, …, Sn},一个指示变量i表示上一次选择的服务器ID。变量i被初始化为N-1。 + *

+ * 参考:https://blog.csdn.net/qq_37469055/article/details/87991327 + * @param serviceId serviceId,不同的serviceId对应的服务器数量不一样,需要区分开 + * @param servers 服务器列表 + * @return 返回一台服务器实例 + */ + public static T chooseByRoundRobin(String serviceId, List servers) { + if (servers == null || servers.isEmpty()) { + return null; + } + int n = servers.size(); + int i = serviceIdRoundMap.computeIfAbsent(serviceId, (k) -> n - 1); + int j = i; + do { + j = (j + 1) % n; + i = j; + serviceIdRoundMap.put(serviceId, i); + return servers.get(i); + } while (j != i); + } + + /** + * 随机选取一台实例 + * + * @param servers 服务列表 + * @return 返回实例,没有返回null + */ + public static T chooseByRandom(List servers) { + if (servers.isEmpty()) { + return null; + } + int serverCount = servers.size(); + // 随机选取一台实例 + int index = chooseRandomInt(serverCount); + return servers.get(index); + } + + private static int chooseRandomInt(int serverCount) { + return ThreadLocalRandom.current().nextInt(serverCount); + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RouteInterceptorUtil.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RouteInterceptorUtil.java new file mode 100644 index 00000000..54af542f --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RouteInterceptorUtil.java @@ -0,0 +1,58 @@ +package com.gitee.sop.gatewaycommon.util; + +import com.gitee.sop.gatewaycommon.bean.ApiConfig; +import com.gitee.sop.gatewaycommon.bean.DefaultRouteInterceptorContext; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import lombok.extern.slf4j.Slf4j; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.function.Consumer; + +/** + * @author tanghc + */ +@Slf4j +public class RouteInterceptorUtil { + + public static void runPreRoute(Object requestContext, ApiParam param, Consumer saveContext) { + DefaultRouteInterceptorContext defaultRouteInterceptorContext = new DefaultRouteInterceptorContext(); + saveContext.accept(defaultRouteInterceptorContext); + defaultRouteInterceptorContext.setBeginTimeMillis(System.currentTimeMillis()); + defaultRouteInterceptorContext.setRequestContext(requestContext); + defaultRouteInterceptorContext.setApiParam(param); + getRouteInterceptors().forEach(routeInterceptor -> { + if (routeInterceptor.match(defaultRouteInterceptorContext)) { + routeInterceptor.preRoute(defaultRouteInterceptorContext); + } + }); + } + + public static void runAfterRoute(RouteInterceptorContext routeInterceptorContext) { + if (routeInterceptorContext == null) { + return; + } + try { + getRouteInterceptors().forEach(routeInterceptor -> { + if (routeInterceptor.match(routeInterceptorContext)) { + routeInterceptor.afterRoute(routeInterceptorContext); + } + }); + } catch (Exception e) { + log.error("执行路由拦截器异常, apiParam:{}", routeInterceptorContext.getApiParam().toJSONString()); + } + } + + public static List getRouteInterceptors() { + return ApiConfig.getInstance().getRouteInterceptors(); + } + + public static void addInterceptors(Collection interceptors) { + List routeInterceptors = getRouteInterceptors(); + routeInterceptors.addAll(interceptors); + routeInterceptors.sort(Comparator.comparing(RouteInterceptor::getOrder)); + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java index 7c5a984e..19093743 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java @@ -69,6 +69,7 @@ public class ApiValidator implements Validator { public void validate(ApiParam param) { checkIP(param); TargetRoute targetRoute = checkEnable(param); + initFields(targetRoute, param); ApiConfig apiConfig = ApiContext.getApiConfig(); if (apiConfig.isIgnoreValidate() || BooleanUtils.toBoolean(targetRoute.getRouteDefinition().getIgnoreValidate())) { @@ -127,6 +128,19 @@ public class ApiValidator implements Validator { return targetRoute; } + private void initFields(TargetRoute targetRoute, ApiParam apiParam) { + apiParam.setServiceId(targetRoute.getServiceRouteInfo().getServiceId()); + boolean mergeResult; + Boolean defaultSetting = ApiContext.getApiConfig().getMergeResult(); + if (defaultSetting != null) { + mergeResult = defaultSetting; + } else { + RouteDefinition routeDefinition = targetRoute.getRouteDefinition(); + mergeResult = routeDefinition == null || BooleanUtils.toBoolean(routeDefinition.getMergeResult()); + } + apiParam.setMergeResult(mergeResult); + } + /** * 校验上传文件内容 * diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/ValidateService.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/ValidateService.java index c5c69032..90111bd4 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/ValidateService.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/ValidateService.java @@ -1,18 +1,17 @@ package com.gitee.sop.gatewaycommon.zuul; import com.gitee.sop.gatewaycommon.bean.ApiConfig; +import com.gitee.sop.gatewaycommon.bean.DefaultRouteInterceptorContext; +import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.ParamBuilder; -import com.gitee.sop.gatewaycommon.util.RequestUtil; import com.gitee.sop.gatewaycommon.util.ResponseUtil; +import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil; import com.gitee.sop.gatewaycommon.validate.Validator; import com.netflix.zuul.context.RequestContext; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - /** * 负责签名校验 * @author tanghc @@ -29,14 +28,10 @@ public class ValidateService { /** * 校验操作 * - * @param request request - * @param response response + * @param currentContext currentContext * @param callback 校验后操作 */ - public void validate(HttpServletRequest request, HttpServletResponse response, ValidateCallback callback) { - RequestContext currentContext = RequestContext.getCurrentContext(); - currentContext.setRequest(RequestUtil.wrapRequest(request)); - currentContext.setResponse(response); + public void validate(RequestContext currentContext, ValidateCallback callback) { // 解析参数 ApiParam param = ZuulContext.getApiParam(); if (param == null) { @@ -56,18 +51,27 @@ public class ValidateService { // 验证操作,这里有负责验证签名参数 try { validator.validate(param); + this.afterValidate(currentContext, param); } catch (Exception e) { error = e; } param.fitNameVersion(); - if (error == null) { - callback.onSuccess(currentContext); - } else { - callback.onError(currentContext, param, error); + if (callback != null) { + if (error == null) { + callback.onSuccess(currentContext); + } else { + callback.onError(currentContext, param, error); + } } } - + private void afterValidate(RequestContext currentContext, ApiParam param) { + RouteInterceptorUtil.runPreRoute(currentContext, param, context -> { + DefaultRouteInterceptorContext defaultRouteInterceptorContext = (DefaultRouteInterceptorContext) context; + defaultRouteInterceptorContext.setRequestDataSize(currentContext.getRequest().getContentLengthLong()); + currentContext.set(SopConstants.CACHE_ROUTE_INTERCEPTOR_CONTEXT, context); + }); + } public interface ValidateCallback { /** diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java index 258f5f70..3f499c77 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java @@ -10,13 +10,13 @@ import com.gitee.sop.gatewaycommon.zuul.controller.ConfigChannelController; import com.gitee.sop.gatewaycommon.zuul.controller.ErrorLogController; import com.gitee.sop.gatewaycommon.zuul.controller.ZuulErrorController; import com.gitee.sop.gatewaycommon.zuul.controller.ZuulIndexController; +import com.gitee.sop.gatewaycommon.zuul.controller.ZuulMonitorController; import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter; import com.gitee.sop.gatewaycommon.zuul.filter.FormBodyWrapperFilterExt; import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter; import com.gitee.sop.gatewaycommon.zuul.filter.PreHttpServletRequestWrapperFilter; import com.gitee.sop.gatewaycommon.zuul.filter.PreLimitFilter; import com.gitee.sop.gatewaycommon.zuul.filter.PreParameterFormatterFilter; -import com.gitee.sop.gatewaycommon.zuul.filter.PreValidateFilter; import com.gitee.sop.gatewaycommon.zuul.filter.Servlet30WrapperFilterExt; import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator; import com.gitee.sop.gatewaycommon.zuul.route.ZuulForwardChooser; @@ -57,6 +57,11 @@ public class BaseZuulConfiguration extends AbstractConfiguration { return new ZuulIndexController(); } + @Bean + public ZuulMonitorController zuulMonitorController() { + return new ZuulMonitorController(); + } + @Bean @ConditionalOnMissingBean ParamBuilder paramBuilder() { @@ -120,14 +125,6 @@ public class BaseZuulConfiguration extends AbstractConfiguration { return new ZuulRouteCache(zuulRouteRepository); } - /** - * 前置校验 - */ - @Bean - PreValidateFilter preValidateFilter() { - return new PreValidateFilter(); - } - @Bean ValidateService validateService() { return new ValidateService(); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulIndexController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulIndexController.java index bc7ebd27..4556f119 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulIndexController.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulIndexController.java @@ -1,17 +1,18 @@ package com.gitee.sop.gatewaycommon.zuul.controller; -import com.gitee.sop.gatewaycommon.bean.SopConstants; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.util.RequestUtil; import com.gitee.sop.gatewaycommon.zuul.ValidateService; +import com.gitee.sop.gatewaycommon.zuul.ZuulContext; +import com.netflix.zuul.context.RequestContext; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; /** * zuul网关入口 @@ -52,26 +53,32 @@ public class ZuulIndexController { */ @RequestMapping("/") public void index(HttpServletRequest request, HttpServletResponse response) { - validateService.validate(request, response, callback); + RequestContext currentContext = RequestContext.getCurrentContext(); + currentContext.setRequest(RequestUtil.wrapRequest(request)); + currentContext.setResponse(response); + validateService.validate(currentContext, callback); } /** * restful入口 - * @param request - * @param response - * @throws ServletException - * @throws IOException + * + * @param request request + * @param response response */ @RequestMapping("/rest/**") - public void rest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + public void rest(HttpServletRequest request, HttpServletResponse response) { + RequestContext currentContext = RequestContext.getCurrentContext(); + currentContext.setRequest(RequestUtil.wrapRequest(request)); + currentContext.setResponse(response); + String url = request.getRequestURL().toString(); int index = url.indexOf(restPath); // 取/rest的后面部分 String path = url.substring(index + restPath.length()); - request.setAttribute(SopConstants.REDIRECT_METHOD_KEY, path); - request.setAttribute(SopConstants.REDIRECT_VERSION_KEY, EMPTY_VERSION); - request.setAttribute(SopConstants.SOP_NOT_MERGE, true); - request.getRequestDispatcher(this.path).forward(request, response); + ApiParam apiParam = ApiParam.createRestfulApiParam(path); + ZuulContext.setApiParam(apiParam); + + validateService.validate(currentContext, callback); } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulMonitorController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulMonitorController.java new file mode 100644 index 00000000..009902c0 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulMonitorController.java @@ -0,0 +1,21 @@ +package com.gitee.sop.gatewaycommon.zuul.controller; + +import com.gitee.sop.gatewaycommon.support.BaseMonitorController; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.util.RequestUtil; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * @author tanghc + */ +@RestController +public class ZuulMonitorController extends BaseMonitorController { + @Override + protected ApiParam getApiParam(HttpServletRequest request) { + Map params = RequestUtil.convertRequestParamsToMap(request); + return ApiParam.build(params); + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/BaseZuulFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/BaseZuulFilter.java index 5ea6fbbd..ac36a154 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/BaseZuulFilter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/BaseZuulFilter.java @@ -23,17 +23,14 @@ public abstract class BaseZuulFilter extends ZuulFilter { /** 签名验证过滤 */ public static final int PRE_VALIDATE_FILTER_ORDER = -1000; - /** 参数格式化过滤器 */ - public static final int PRE_PARAMETER_FORMATTER_FILTER_ORDER = PRE_VALIDATE_FILTER_ORDER + 1; - - /** 权限验证过滤 */ - public static final int PRE_ROUTE_PERMISSION_FILTER_ORDER = PRE_VALIDATE_FILTER_ORDER + 100; - /** 限流过滤 */ - public static final int PRE_LIMIT_FILTER_ORDER = PRE_ROUTE_PERMISSION_FILTER_ORDER + 100; + public static final int PRE_LIMIT_FILTER_ORDER = -990; + + /** 参数格式化过滤器 */ + public static final int PRE_PARAMETER_FORMATTER_FILTER_ORDER = -980; /** 灰度发布过滤器 */ - public static final int PRE_ENV_GRAY_FILTER_ORDER = PRE_LIMIT_FILTER_ORDER + 100; + public static final int PRE_ENV_GRAY_FILTER_ORDER = -970; private Integer filterOrder; diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreValidateFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreValidateFilter.java index ce35a9f0..b72a6abf 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreValidateFilter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreValidateFilter.java @@ -1,23 +1,17 @@ package com.gitee.sop.gatewaycommon.zuul.filter; -import com.gitee.sop.gatewaycommon.param.ApiParam; -import com.gitee.sop.gatewaycommon.param.ParamBuilder; -import com.gitee.sop.gatewaycommon.zuul.ZuulContext; import com.netflix.zuul.context.RequestContext; -import org.springframework.beans.factory.annotation.Autowired; /** * 校验工作转移到了 com.gitee.sop.gateway.controller.RedirectController - * + *

* 将校验工作提前,如果在zuul过滤器中校验,抛出异常将会打印非常多的日志,并且无法实现自定义返回结果。 - * + * @deprecated see {@link com.gitee.sop.gatewaycommon.zuul.ValidateService} * @author tanghc */ +@Deprecated public class PreValidateFilter extends BaseZuulFilter { - @Autowired - private ParamBuilder paramBuilder; - @Override protected FilterType getFilterType() { return FilterType.PRE; @@ -30,11 +24,6 @@ public class PreValidateFilter extends BaseZuulFilter { @Override protected Object doRun(RequestContext requestContext) { - ApiParam param = ZuulContext.getApiParam(); - if (param == null) { - param = paramBuilder.build(requestContext); - ZuulContext.setApiParam(param); - } return null; } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/param/ZuulParamBuilder.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/param/ZuulParamBuilder.java index 40dc33e8..458114b6 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/param/ZuulParamBuilder.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/param/ZuulParamBuilder.java @@ -1,6 +1,5 @@ package com.gitee.sop.gatewaycommon.zuul.param; -import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.BaseParamBuilder; import com.gitee.sop.gatewaycommon.util.RequestUtil; @@ -63,11 +62,6 @@ public class ZuulParamBuilder extends BaseParamBuilder { @Override protected void processApiParam(ApiParam apiParam, RequestContext ctx) { - HttpServletRequest request = ctx.getRequest(); - String method = (String) request.getAttribute(SopConstants.REDIRECT_METHOD_KEY); - String version = (String) request.getAttribute(SopConstants.REDIRECT_VERSION_KEY); - apiParam.setRestName(method); - apiParam.setRestVersion(version); } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java index 297767aa..416d92a3 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java @@ -2,6 +2,7 @@ package com.gitee.sop.gatewaycommon.zuul.result; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.message.Error; @@ -17,6 +18,7 @@ import lombok.extern.slf4j.Slf4j; import java.util.List; import java.util.Locale; +import java.util.function.Consumer; /** * @author tanghc @@ -24,15 +26,6 @@ import java.util.Locale; @Slf4j public class ZuulResultExecutor extends BaseExecutorAdapter implements ResultExecutorForZuul { - @Override - protected boolean isMergeResult(RequestContext request) { - Object notMerge = request.getRequest().getAttribute(SopConstants.SOP_NOT_MERGE); - if (notMerge != null) { - return false; - } - return super.isMergeResult(request); - } - @Override public int getResponseStatus(RequestContext requestContext) { List> bizHeaders = requestContext.getZuulResponseHeaders(); @@ -46,21 +39,11 @@ public class ZuulResultExecutor extends BaseExecutorAdapter> bizHeaders = requestContext.getZuulResponseHeaders(); - int index = -1; - String errorMsg = null; - for (int i = 0; i < bizHeaders.size(); i++) { - Pair header = bizHeaders.get(i); - if (SopConstants.X_SERVICE_ERROR_MESSAGE.equals(header.first())) { - errorMsg = header.second(); - index = i; - break; + return getHeader(requestContext, SopConstants.X_SERVICE_ERROR_MESSAGE, (index)->{ + if (index > -1) { + requestContext.getZuulResponseHeaders().remove(index); } - } - if (index > -1) { - requestContext.getZuulResponseHeaders().remove(index); - } - return errorMsg; + }); } @Override @@ -73,6 +56,11 @@ public class ZuulResultExecutor extends BaseExecutorAdapter after) { + List> bizHeaders = requestContext.getZuulResponseHeaders(); + int index = -1; + String value = null; + for (int i = 0; i < bizHeaders.size(); i++) { + Pair header = bizHeaders.get(i); + if (name.equals(header.first())) { + value = header.second(); + index = i; + break; + } + } + if (after != null) { + after.accept(index); + } + return value; + } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java index edb41374..34788f4e 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java @@ -45,24 +45,15 @@ public class SopRouteLocator implements RouteLocator, Ordered { /** * 这里决定使用哪个路由 * - * @param path + * @param path 当前请求路径 * @return 返回跳转的路由 */ @Override public Route getMatchingRoute(String path) { - ApiParam param = ZuulContext.getApiParam(); - String nameVersion = param.fetchNameVersion(); - ZuulTargetRoute zuulTargetRoute = zuulRouteRepository.get(nameVersion); - if (zuulTargetRoute == null) { - return null; - } - Route targetRouteDefinition = zuulTargetRoute.getTargetRouteDefinition(); ForwardInfo forwardInfo = zuulForwardChooser.getForwardInfo(RequestContext.getCurrentContext()); - String forwardPath = forwardInfo.getPath(); - targetRouteDefinition.setPath(forwardPath); - String versionInHead = forwardInfo.getVersion(); - RequestContext.getCurrentContext().addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, versionInHead); - return targetRouteDefinition; + String version = forwardInfo.getVersion(); + RequestContext.getCurrentContext().addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, version); + return (Route)forwardInfo.getTargetRoute().getTargetRouteDefinition(); } @Override diff --git a/sop-common/sop-gateway-common/src/test/java/com/gitee/sop/gatewaycommon/RoundRobinTest.java b/sop-common/sop-gateway-common/src/test/java/com/gitee/sop/gatewaycommon/RoundRobinTest.java new file mode 100644 index 00000000..97c747a3 --- /dev/null +++ b/sop-common/sop-gateway-common/src/test/java/com/gitee/sop/gatewaycommon/RoundRobinTest.java @@ -0,0 +1,62 @@ +package com.gitee.sop.gatewaycommon; + +import com.gitee.sop.gatewaycommon.util.LoadBalanceUtil; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 轮询选择一台机器。 + * + * @author tanghc + */ +public class RoundRobinTest extends TestCase { + + public void testDo() { + String serviceId = "order-service"; + List serverList = new ArrayList<>(Arrays.asList("server1", "server2", "server3")); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + } + + public void testAdd() { + String serviceId = "order-service"; + List serverList = new ArrayList<>(Arrays.asList("server1", "server2", "server3")); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + // 中途添加一个服务器 + serverList.add("server4"); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + } + + public void testRemove() { + String serviceId = "order-service"; + List serverList = new ArrayList<>(Arrays.asList("server1", "server2", "server3")); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + // 中途减少一台服务器 + serverList.remove(2); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + System.out.println(chooseRoundRobinServer(serviceId, serverList)); + } + + /** + * 轮询选择一台机器。 + * 假设有N台服务器:S = {S1, S2, …, Sn},一个指示变量i表示上一次选择的服务器ID。变量i被初始化为N-1。 + * + * @param serviceId serviceId + * @param servers 服务器列表 + * @return 返回一台服务器实例 + */ + private T chooseRoundRobinServer(String serviceId, List servers) { + return LoadBalanceUtil.chooseByRoundRobin(serviceId, servers); + } +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java new file mode 100644 index 00000000..91039ac8 --- /dev/null +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java @@ -0,0 +1,32 @@ +package com.gitee.sop.gateway.interceptor; + +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import org.springframework.stereotype.Component; + +/** + * 演示拦截器 + * + * @author tanghc + */ +@Component +public class MyRouteInterceptor implements RouteInterceptor { + + @Override + public void preRoute(RouteInterceptorContext context) { + ApiParam apiParam = context.getApiParam(); + System.out.println("请求接口:" + apiParam.fetchNameVersion()); + } + + @Override + public void afterRoute(RouteInterceptorContext context) { + System.out.println("请求成功,微服务返回结果:" + context.getServiceResult()); + } + + @Override + public int getOrder() { + return 0; + } + +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java index ed5393d2..bb536e1b 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java @@ -1,6 +1,7 @@ package com.gitee.sop.gateway.manager; import com.gitee.fastmybatis.core.query.Query; +import com.gitee.sop.gateway.entity.ConfigLimit; import com.gitee.sop.gateway.mapper.ConfigLimitMapper; import com.gitee.sop.gatewaycommon.bean.ChannelMsg; import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto; @@ -37,7 +38,7 @@ public class DbLimitConfigManager extends DefaultLimitConfigManager { } - protected void putVal(Object object) { + protected void putVal(ConfigLimit object) { ConfigLimitDto configLimitDto = new ConfigLimitDto(); MyBeanUtil.copyPropertiesIgnoreNull(object, configLimitDto); this.update(configLimitDto); diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbRouteConfigManager.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbRouteConfigManager.java index 9f87fdd9..475addd0 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbRouteConfigManager.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbRouteConfigManager.java @@ -42,7 +42,7 @@ public class DbRouteConfigManager extends DefaultRouteConfigManager { @Override public void process(ChannelMsg channelMsg) { - final RouteConfig routeConfig = channelMsg.toObject( RouteConfig.class); + final RouteConfig routeConfig = channelMsg.toObject(RouteConfig.class); switch (channelMsg.getOperation()) { case "reload": log.info("重新加载路由配置信息,routeConfigDto:{}", routeConfig);