Merge branch 'master' into eureka

# Conflicts:
#	sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java
eureka
tanghc 5 years ago
commit ef03f9c82f
  1. 6
      changelog.md
  2. 73
      doc/docs/_sidebar.md
  3. 57
      doc/docs/files/10090_网关拦截器.md
  4. 1
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java
  5. 163
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorApi.java
  6. 11
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteParam.java
  7. 83
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorInfoVO.java
  8. 13
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorResult.java
  9. 18
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/QueryUtil.java
  10. 2
      sop-admin/sop-admin-server/src/main/resources/public/index.html
  11. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/app.c6e80241.js
  12. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d32e7.213708f2.js
  13. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-6f78c9fe.3ac83b41.js
  14. 22
      sop-admin/sop-admin-vue/src/router/index.js
  15. 154
      sop-admin/sop-admin-vue/src/views/service/monitor.vue
  16. 2
      sop-auth/pom.xml
  17. 2
      sop-common/pom.xml
  18. 6
      sop-common/sop-bridge-gateway/pom.xml
  19. 6
      sop-common/sop-bridge-zuul/pom.xml
  20. 2
      sop-common/sop-bridge-zuul/src/main/resources/sop-bridge.properties
  21. 4
      sop-common/sop-gateway-common/pom.xml
  22. 12
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java
  23. 114
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/DefaultRouteInterceptorContext.java
  24. 10
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java
  25. 12
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java
  26. 6
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java
  27. 21
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java
  28. 17
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java
  29. 24
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java
  30. 10
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java
  31. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayForwardChooser.java
  32. 19
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java
  33. 96
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/MonitorRouteInterceptor.java
  34. 40
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptor.java
  35. 85
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java
  36. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java
  37. 13
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java
  38. 88
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorInfo.java
  39. 22
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java
  40. 45
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java
  41. 238
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java
  42. 17
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java
  43. 48
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java
  44. 21
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/BaseMonitorController.java
  45. 45
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/SopBaseController.java
  46. 63
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/LoadBalanceUtil.java
  47. 58
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RouteInterceptorUtil.java
  48. 14
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java
  49. 34
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/ValidateService.java
  50. 15
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java
  51. 33
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulIndexController.java
  52. 21
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulMonitorController.java
  53. 13
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/BaseZuulFilter.java
  54. 17
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreValidateFilter.java
  55. 6
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/param/ZuulParamBuilder.java
  56. 52
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java
  57. 17
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java
  58. 62
      sop-common/sop-gateway-common/src/test/java/com/gitee/sop/gatewaycommon/RoundRobinTest.java
  59. 4
      sop-common/sop-service-common/pom.xml
  60. 2
      sop-example/sop-book/sop-book-web/pom.xml
  61. 2
      sop-example/sop-easyopen/pom.xml
  62. 2
      sop-example/sop-springmvc/pom.xml
  63. 2
      sop-example/sop-story/sop-story-web/pom.xml
  64. 2
      sop-gateway/pom.xml
  65. 32
      sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java
  66. 3
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java
  67. 2
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbRouteConfigManager.java
  68. 2
      sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java
  69. 2
      sop-website/pom.xml

@ -1,5 +1,11 @@
# changelog
## 3.1.0
- 新增路由监控功能
- 新增路由拦截器
- 优化负载均衡策略
## 3.0.1
- 增强国际化消息(现SpringCouldGateway支持英文国际化)

@ -1,38 +1,39 @@
* [首页](/?t=1580871460178)
* [首页](/?t=1582014833549)
* 开发文档
* [快速体验](files/10010_快速体验.md?t=1580871460179)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1580871460202)
* [新增接口](files/10020_新增接口.md?t=1580871460202)
* [开发流程](files/10021_开发流程.md?t=1580871460202)
* [业务参数校验](files/10030_业务参数校验.md?t=1580871460203)
* [错误处理](files/10040_错误处理.md?t=1580871460203)
* [编写文档](files/10041_编写文档.md?t=1580871460203)
* [接口交互详解](files/10050_接口交互详解.md?t=1580871460203)
* [easyopen支持](files/10070_easyopen支持.md?t=1580871460203)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1580871460203)
* [ISV管理](files/10085_ISV管理.md?t=1580871460204)
* [自定义返回结果](files/10087_自定义返回结果.md?t=1580871460204)
* [自定义过滤器](files/10088_自定义过滤器.md?t=1580871460204)
* [自定义校验token](files/10089_自定义校验token.md?t=1580871460204)
* [路由授权](files/10090_路由授权.md?t=1580871460204)
* [接口限流](files/10092_接口限流.md?t=1580871460204)
* [监控日志](files/10093_监控日志.md?t=1580871460204)
* [SDK开发](files/10095_SDK开发.md?t=1580871460204)
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1580871460205)
* [应用授权](files/10097_应用授权.md?t=1580871460205)
* [提供restful接口](files/10100_提供restful接口.md?t=1580871460205)
* [文件上传](files/10104_文件上传.md?t=1580871460205)
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1580871460205)
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1580871460205)
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1580871460205)
* [使用eureka](files/10112_使用eureka.md?t=1580871460205)
* [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1580871460206)
* [快速体验](files/10010_快速体验.md?t=1582014833552)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1582014833568)
* [新增接口](files/10020_新增接口.md?t=1582014833568)
* [开发流程](files/10021_开发流程.md?t=1582014833568)
* [业务参数校验](files/10030_业务参数校验.md?t=1582014833568)
* [错误处理](files/10040_错误处理.md?t=1582014833568)
* [编写文档](files/10041_编写文档.md?t=1582014833569)
* [接口交互详解](files/10050_接口交互详解.md?t=1582014833569)
* [easyopen支持](files/10070_easyopen支持.md?t=1582014833569)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1582014833569)
* [ISV管理](files/10085_ISV管理.md?t=1582014833569)
* [自定义返回结果](files/10087_自定义返回结果.md?t=1582014833569)
* [自定义过滤器](files/10088_自定义过滤器.md?t=1582014833569)
* [自定义校验token](files/10089_自定义校验token.md?t=1582014833569)
* [网关拦截器](files/10090_网关拦截器.md?t=1582014833570)
* [路由授权](files/10090_路由授权.md?t=1582014833570)
* [接口限流](files/10092_接口限流.md?t=1582014833570)
* [监控日志](files/10093_监控日志.md?t=1582014833570)
* [SDK开发](files/10095_SDK开发.md?t=1582014833570)
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1582014833570)
* [应用授权](files/10097_应用授权.md?t=1582014833570)
* [提供restful接口](files/10100_提供restful接口.md?t=1582014833571)
* [文件上传](files/10104_文件上传.md?t=1582014833571)
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1582014833571)
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1582014833571)
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1582014833571)
* [使用eureka](files/10112_使用eureka.md?t=1582014833571)
* [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1582014833572)
* 原理分析
* [网关性能测试](files/90001_网关性能测试.md?t=1580871460206)
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1580871460206)
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1580871460206)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1580871460206)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1580871460207)
* [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1580871460207)
* [2.x升3.x注意事项](files/90099_2.x升3.x注意事项.md?t=1580871460207)
* [常见问题](files/90100_常见问题.md?t=1580871460207)
* [网关性能测试](files/90001_网关性能测试.md?t=1582014833572)
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1582014833572)
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1582014833572)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1582014833572)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1582014833572)
* [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1582014833572)
* [2.x升3.x注意事项](files/90099_2.x升3.x注意事项.md?t=1582014833572)
* [常见问题](files/90100_常见问题.md?t=1582014833572)

@ -0,0 +1,57 @@
# 网关拦截器
从3.1.0开始新增了网关拦截器,使用该拦截器可做一些数据统计,日志记录等工作。
使用方法如下:
- 在sop-gateway工程下新增一个类,实现`RouteInterceptor`接口,实现接口中的方法。别忘了加`@Component`
```java
@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;
}
}
```
RouteInterceptor接口方法说明:
- `public void preRoute(RouteInterceptorContext context)`
路由转发前执行,在签名验证通过之后会立即执行这个方法。
- `public void afterRoute(RouteInterceptorContext context)`
路由转发完成后,即拿到微服务返回结果后执行这个方法
- `public int getOrder()`
指定拦截执行顺序,数字小的优先执行,建议从0开始。
- `default boolean match(RouteInterceptorContext context)`
是否匹配,返回true执行拦截器,默认true
RouteInterceptorContext参数存放了各类参数信息。
参考类:
- `com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor` 拦截器接口
- `com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext` 拦截器上下文
- `com.gitee.sop.gatewaycommon.interceptor.MonitorRouteInterceptor` 默认实现的拦截器,用于收集监控数据

@ -155,6 +155,7 @@ public class LogApi {
String sign = md5Verifier.buildSign(params, secret);
params.put("sign", sign);
String query = QueryUtil.buildQueryString(params);
path = path.startsWith("/") ? path.substring(1) : path;
String url = "http://" + ipPort + "/" + path + "?" + query;
ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
if (entity.getStatusCode() != HttpStatus.OK) {

@ -0,0 +1,163 @@
package com.gitee.sop.adminserver.api.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.exception.ApiException;
import com.gitee.sop.adminserver.api.service.param.RouteParam;
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
import com.gitee.sop.adminserver.api.service.param.ServiceSearchParam;
import com.gitee.sop.adminserver.api.service.result.MonitorInfoVO;
import com.gitee.sop.adminserver.api.service.result.MonitorResult;
import com.gitee.sop.adminserver.api.service.result.ServiceInstanceVO;
import com.gitee.sop.adminserver.common.QueryUtil;
import com.gitee.sop.adminserver.service.ServerService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* @author tanghc
*/
@ApiService
@ApiDoc("服务管理-监控")
@Slf4j
public class MonitorApi {
private static final String GATEWAY_MONITOR_PATH = "/sop/getMonitorData";
@Autowired
private ServerService serverService;
@Value("${sop.secret}")
private String secret;
@Api(name = "monitor.data.list")
@ApiDocMethod(description = "获取监控数据")
public MonitorResult listMonitorData(RouteParam param) {
ServiceSearchParam serviceSearchParam = new ServiceSearchParam();
serviceSearchParam.setServiceId("sop-gateway");
String searchRouteId = param.getRouteId();
List<MonitorInfoVO> monitorInfoList = new ArrayList<>();
List<ServiceInstanceVO> serviceInstanceVOS = serverService.listService(serviceSearchParam);
for (ServiceInstanceVO serviceInstanceVO : serviceInstanceVOS) {
if (StringUtils.isBlank(serviceInstanceVO.getInstanceId())) {
continue;
}
String ipPort = serviceInstanceVO.getIpPort();
try {
String data = QueryUtil.requestServer(ipPort, GATEWAY_MONITOR_PATH, secret);
JSONObject jsonObject = JSON.parseObject(data);
List<MonitorInfoVO> monitorInfoVOList = this.buildMonitorInfoVO(serviceInstanceVO, jsonObject.getJSONObject("data"));
List<MonitorInfoVO> newList = monitorInfoVOList.stream()
.filter(monitorInfoVO -> StringUtils.isBlank(searchRouteId)
|| StringUtils.containsIgnoreCase(monitorInfoVO.getRouteId(), searchRouteId))
.collect(Collectors.toList());
monitorInfoList.addAll(newList);
} catch (Exception e) {
log.error("请求服务失败, ipPort:{}, path:{}", ipPort, GATEWAY_MONITOR_PATH, e);
throw new ApiException("请求数据失败");
}
}
MonitorResult monitorResult = new MonitorResult();
List<MonitorInfoVO> monitorInfoTreeData = this.buildTreeData(monitorInfoList);
monitorResult.setMonitorInfoData(monitorInfoTreeData);
return monitorResult;
}
private List<MonitorInfoVO> buildTreeData(List<MonitorInfoVO> monitorInfoList) {
AtomicInteger id = new AtomicInteger();
List<MonitorInfoVO> treeData = new ArrayList<>(8);
monitorInfoList.stream()
.collect(Collectors.groupingBy(MonitorInfoVO::getRouteId))
.forEach((routeId, items) -> {
MonitorInfoVO monitorInfoVOTotal = getStatistics(items, id);
monitorInfoVOTotal.setId(id.incrementAndGet());
treeData.add(monitorInfoVOTotal);
});
Comparator<MonitorInfoVO> comparator = Comparator
// 根据错误次数降序
.comparing(MonitorInfoVO::getErrorCount).reversed()
// 然后根据请求耗时降序
.thenComparing(Comparator.comparing(MonitorInfoVO::getAvgTime).reversed());
treeData.sort(comparator);
return treeData;
}
private MonitorInfoVO getStatistics(List<MonitorInfoVO> children, AtomicInteger id) {
long totalRequestDataSize = 0;
long totalResponseDataSize = 0;
long maxTime = 0;
long minTime = 0;
long totalTime = 0;
long totalCount = 0;
long successCount = 0;
long errorCount = 0;
List<String> errorMsgList = new ArrayList<>();
String name = null,version = null, serviceId = null;
for (MonitorInfoVO child : children) {
name = child.getName();
version = child.getVersion();
serviceId = child.getServiceId();
child.setId(id.incrementAndGet());
totalRequestDataSize += child.getTotalRequestDataSize();
totalResponseDataSize += child.getTotalResponseDataSize();
if (minTime == 0 || child.getMinTime() < minTime) {
minTime = child.getMinTime();
}
if (child.getMaxTime() > maxTime) {
maxTime = child.getMaxTime();
}
totalTime += child.getTotalTime();
totalCount += child.getTotalCount();
successCount += child.getSuccessCount();
errorCount += child.getErrorCount();
errorMsgList.addAll(child.getErrorMsgList());
}
MonitorInfoVO total = new MonitorInfoVO();
total.setName(name);
total.setVersion(version);
total.setServiceId(serviceId);
total.setErrorCount(errorCount);
total.setMaxTime(maxTime);
total.setMinTime(minTime);
total.setSuccessCount(successCount);
total.setTotalCount(totalCount);
total.setTotalRequestDataSize(totalRequestDataSize);
total.setTotalResponseDataSize(totalResponseDataSize);
total.setTotalTime(totalTime);
total.setChildren(children);
total.setErrorMsgList(errorMsgList);
return total;
}
private List<MonitorInfoVO> buildMonitorInfoVO(ServiceInstanceVO serviceInstanceVO, JSONObject monitorData) {
Set<String> routeIdList = monitorData.keySet();
List<MonitorInfoVO> ret = new ArrayList<>(routeIdList.size());
routeIdList.forEach(routeId -> {
JSONObject monitorInfo = monitorData.getJSONObject(routeId);
MonitorInfoVO monitorInfoVO = monitorInfo.toJavaObject(MonitorInfoVO.class);
monitorInfoVO.setInstanceId(serviceInstanceVO.getIpPort());
ret.add(monitorInfoVO);
});
return ret;
}
}

@ -0,0 +1,11 @@
package com.gitee.sop.adminserver.api.service.param;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class RouteParam {
private String routeId;
}

@ -0,0 +1,83 @@
package com.gitee.sop.adminserver.api.service.result;
import lombok.Data;
import java.util.List;
/**
* 每个接口 总调用流量最大时间最小时间总时长平均时长调用次数成功次数失败次数错误查看
*
* @author tanghc
*/
@Data
public class MonitorInfoVO {
private Integer id;
private String instanceId;
/**
* 接口名
*/
private String name;
/**
* 版本号
*/
private String version;
/**
* serviceId
*/
private String serviceId;
/**
* 请求耗时最长时间
*/
private Long maxTime;
/**
* 请求耗时最小时间
*/
private Long minTime;
/**
* 总时长
*/
private Long totalTime;
/**
* 总调用次数
*/
private Long totalCount;
/**
* 成功次数
*/
private Long successCount;
/**
* 失败次数业务主动抛出的异常算作成功如参数校验未知的错误算失败
*/
private Long errorCount;
/**
* 错误信息
*/
private List<String> errorMsgList;
/**
* 总请求数据量
*/
private Long totalRequestDataSize;
/**
* 总返回数据量
*/
private Long totalResponseDataSize;
/**
* 实例id
*/
private List<MonitorInfoVO> children;
public String getRouteId() {
return name + version;
}
/**
* 平均时长总时长/总调用次数
* @return 返回平均时长
*/
public long getAvgTime() {
return totalTime/totalCount;
}
}

@ -0,0 +1,13 @@
package com.gitee.sop.adminserver.api.service.result;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class MonitorResult {
private List<MonitorInfoVO> monitorInfoData;
}

@ -1,7 +1,11 @@
package com.gitee.sop.adminserver.common;
import com.gitee.easyopen.verify.DefaultMd5Verifier;
import com.gitee.sop.adminserver.bean.HttpTool;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
/**
@ -9,6 +13,8 @@ import java.util.Map;
*/
public class QueryUtil {
private static HttpTool httpTool = new HttpTool();
public static String buildQueryString(Map<String, ?> params) throws UnsupportedEncodingException {
if (params == null || params.size() == 0) {
return "";
@ -25,4 +31,16 @@ public class QueryUtil {
}
return query.toString();
}
public static String requestServer(String ipPort, String path, String secret) throws Exception {
DefaultMd5Verifier md5Verifier = new DefaultMd5Verifier();
Map<String, Object> params = new HashMap<>(16);
params.put("time", System.currentTimeMillis());
String sign = md5Verifier.buildSign(params, secret);
params.put("sign", sign);
String query = QueryUtil.buildQueryString(params);
path = path.startsWith("/") ? path.substring(1) : path;
String url = "http://" + ipPort + "/" + path + "?" + query;
return httpTool.get(url, null);
}
}

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.c6dfb7ee.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],d=0,l=[];d<a.length;d++)c=a[d],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);h&&h(n);while(l.length)l.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-25908fca":"66819987","chunk-2c1f2e8f":"f092c0a0","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-4de1c2b6":"e74e3d03","chunk-626b7094":"97d3a892","chunk-6f78c9fe":"3ac83b41","chunk-73b2dcec":"60c5d8e9","chunk-9b31c83a":"52bc6b2c","chunk-9f479afe":"b0599ada","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-25908fca":1,"chunk-2c1f2e8f":1,"chunk-4de1c2b6":1,"chunk-626b7094":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-25908fca":"a66354ec","chunk-2c1f2e8f":"0314067f","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-4de1c2b6":"a37cd815","chunk-626b7094":"e41ad972","chunk-6f78c9fe":"31d6cfe0","chunk-73b2dcec":"ed391cc5","chunk-9b31c83a":"3b12267b","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===r||d===u))return n()}var l=document.getElementsByTagName("style");for(a=0;a<l.length;a++){i=l[a],d=i.getAttribute("data-href");if(d===r||d===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],h.parentNode.removeChild(h),t(o)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=a(e),i=function(n){d.onerror=d.onload=null,clearTimeout(l);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var l=0;l<i.length;l++)n(i[l]);var h=d;t()})([]);</script><script src=static/js/chunk-elementUI.298ac98c.js></script><script src=static/js/chunk-libs.75deb05f.js></script><script src=static/js/app.b21063c4.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.c6dfb7ee.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],d=0,l=[];d<a.length;d++)c=a[d],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);h&&h(n);while(l.length)l.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-25908fca":"66819987","chunk-2c1f2e8f":"f092c0a0","chunk-2d0d32e7":"213708f2","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-4de1c2b6":"e74e3d03","chunk-626b7094":"97d3a892","chunk-73b2dcec":"60c5d8e9","chunk-9b31c83a":"52bc6b2c","chunk-9f479afe":"b0599ada","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-25908fca":1,"chunk-2c1f2e8f":1,"chunk-4de1c2b6":1,"chunk-626b7094":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-25908fca":"a66354ec","chunk-2c1f2e8f":"0314067f","chunk-2d0d32e7":"31d6cfe0","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-4de1c2b6":"a37cd815","chunk-626b7094":"e41ad972","chunk-73b2dcec":"ed391cc5","chunk-9b31c83a":"3b12267b","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===r||d===u))return n()}var l=document.getElementsByTagName("style");for(a=0;a<l.length;a++){i=l[a],d=i.getAttribute("data-href");if(d===r||d===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],h.parentNode.removeChild(h),t(o)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=a(e),i=function(n){d.onerror=d.onload=null,clearTimeout(l);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var l=0;l<i.length;l++)n(i[l]);var h=d;t()})([]);</script><script src=static/js/chunk-elementUI.298ac98c.js></script><script src=static/js/chunk-libs.75deb05f.js></script><script src=static/js/app.c6e80241.js></script></body></html>

@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d32e7"],{"5c58":function(t,e,l){"use strict";l.r(e);var a=function(){var t=this,e=t.$createElement,l=t._self._c||e;return l("div",{staticClass:"app-container"},[l("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[l("el-form-item",{attrs:{label:"接口名"}},[l("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"输入接口名或版本号"},model:{value:t.searchFormData.routeId,callback:function(e){t.$set(t.searchFormData,"routeId",e)},expression:"searchFormData.routeId"}})],1),t._v(" "),l("el-form-item",[l("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.loadTable}},[t._v("搜索")])],1)],1),t._v(" "),l("el-alert",{staticStyle:{"margin-bottom":"10px"},attrs:{title:"监控数据保存在网关服务器,重启网关数据会清空。",type:"info",closable:!1}}),t._v(" "),l("el-table",{attrs:{data:t.tableData,border:"","default-expand-all":!1,"row-key":"id",height:"500","empty-text":"无数据"}},[l("el-table-column",{attrs:{fixed:"",prop:"instanceId",label:"网关实例",width:"200"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.children?t._e():l("span",[t._v(t._s(e.row.instanceId))])]}}])}),t._v(" "),l("el-table-column",{attrs:{fixed:"",prop:"name",label:"接口名 (版本号)",width:"280"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v("\n "+t._s(e.row.name+(e.row.version?" ("+e.row.version+")":""))+"\n ")]}}])}),t._v(" "),l("el-table-column",{attrs:{prop:"serviceId",label:"serviceId",width:"170"}}),t._v(" "),l("el-table-column",{attrs:{prop:"maxTime",label:"最大耗时(ms)",width:"125"}},[l("template",{slot:"header"},[t._v("\n 最大耗时(ms)\n "),l("i",{staticClass:"el-icon-question",staticStyle:{cursor:"pointer"},on:{click:function(e){return t.$alert("耗时计算:签名验证成功后开始,微服务返回结果后结束")}}})])],2),t._v(" "),l("el-table-column",{attrs:{prop:"minTime",label:"最小耗时(ms)",width:"120"}}),t._v(" "),l("el-table-column",{attrs:{prop:"avgTime",label:"平均耗时(ms)",width:"120"}}),t._v(" "),l("el-table-column",{attrs:{prop:"totalCount",label:"总调用次数",width:"100"}}),t._v(" "),l("el-table-column",{attrs:{prop:"successCount",label:"成功次数",width:"100"}}),t._v(" "),l("el-table-column",{attrs:{prop:"errorCount",label:"失败次数",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.errorCount>0?l("el-link",{staticStyle:{"text-decoration":"underline"},attrs:{underline:!1,type:"danger"},on:{click:function(l){return t.onShowErrorDetail(e.row)}}},[t._v("\n "+t._s(e.row.errorCount)+"\n ")]):t._e(),t._v(" "),0===e.row.errorCount?l("span",[t._v("0")]):t._e()]}}])},[l("template",{slot:"header"},[t._v("\n 失败次数\n "),l("i",{staticClass:"el-icon-question",staticStyle:{cursor:"pointer"},on:{click:function(e){return t.$alert("只统计微服务返回的未知错误,JSR-303验证错误算作成功")}}})])],2)],1),t._v(" "),l("el-dialog",{attrs:{title:"错误详情",visible:t.logDetailVisible,width:"60%"},on:{"update:visible":function(e){t.logDetailVisible=e}}},[l("div",{staticStyle:{"overflow-x":"auto"},domProps:{innerHTML:t._s(t.errorMsgDetail)}}),t._v(" "),l("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[l("el-button",{attrs:{type:"primary"},on:{click:function(e){t.logDetailVisible=!1}}},[t._v("关 闭")])],1)])],1)},o=[],r={data:function(){return{searchFormData:{routeId:""},tableData:[],logDetailVisible:!1,errorMsgDetail:""}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("monitor.data.list",this.searchFormData,function(t){var e=t.data;this.tableData=e.monitorInfoData})},onShowErrorDetail:function(t){var e=t.errorMsgList;this.errorMsgDetail=e.length>0?e.join("<br>"):"无内容",this.logDetailVisible=!0}}},i=r,n=l("2877"),s=Object(n["a"])(i,a,o,!1,null,null,null);e["default"]=s.exports}}]);

@ -1,7 +1,7 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
Vue.use(Router);
/* Layout */
import Layout from '@/layout'
@ -73,18 +73,18 @@ export const constantRoutes = [
component: () => import('@/views/service/route'),
meta: { title: '路由管理' }
},
{
path: 'monitor',
name: 'Monitor',
component: () => import('@/views/service/monitor'),
meta: { title: '路由监控' }
},
{
path: 'limit',
name: 'Limit',
component: () => import('@/views/service/limit'),
meta: { title: '限流管理' }
},
{
path: 'log',
name: 'Log',
component: () => import('@/views/service/log'),
meta: { title: '监控日志' }
},
{
path: 'blacklist',
name: 'Blacklist',
@ -123,19 +123,19 @@ export const constantRoutes = [
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
];
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
});
const router = createRouter()
const router = createRouter();
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
const newRouter = createRouter();
router.matcher = newRouter.matcher // reset router
}

@ -0,0 +1,154 @@
<template>
<div class="app-container">
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
<el-form-item label="接口名">
<el-input v-model="searchFormData.routeId" :clearable="true" placeholder="输入接口名或版本号" style="width: 250px;" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="loadTable">搜索</el-button>
</el-form-item>
</el-form>
<el-alert
title="监控数据保存在网关服务器,重启网关数据会清空。"
type="info"
:closable="false"
style="margin-bottom: 10px"
/>
<el-table
:data="tableData"
border
:default-expand-all="false"
row-key="id"
height="500"
empty-text="无数据"
>
<el-table-column
fixed
prop="instanceId"
label="网关实例"
width="200"
>
<template slot-scope="scope">
<span v-if="!scope.row.children">{{ scope.row.instanceId }}</span>
</template>
</el-table-column>
<el-table-column
fixed
prop="name"
label="接口名 (版本号)"
width="280"
>
<template slot-scope="scope">
{{ scope.row.name + (scope.row.version ? ' (' + scope.row.version + ')' : '') }}
</template>
</el-table-column>
<el-table-column
prop="serviceId"
label="serviceId"
width="170"
/>
<el-table-column
prop="maxTime"
label="最大耗时(ms)"
width="125"
>
<template slot="header">
最大耗时(ms)
<i
class="el-icon-question"
style="cursor: pointer"
@click="$alert('耗时计算:签名验证成功后开始,微服务返回结果后结束')"
></i>
</template>
</el-table-column>
<el-table-column
prop="minTime"
label="最小耗时(ms)"
width="120"
/>
<el-table-column
prop="avgTime"
label="平均耗时(ms)"
width="120"
/>
<el-table-column
prop="totalCount"
label="总调用次数"
width="100"
/>
<el-table-column
prop="successCount"
label="成功次数"
width="100"
/>
<el-table-column
prop="errorCount"
label="失败次数"
width="100"
>
<template slot="header">
失败次数
<i
class="el-icon-question"
style="cursor: pointer"
@click="$alert('只统计微服务返回的未知错误,JSR-303验证错误算作成功')"
></i>
</template>
<template slot-scope="scope">
<el-link
v-if="scope.row.errorCount > 0"
:underline="false"
type="danger"
style="text-decoration: underline;"
@click="onShowErrorDetail(scope.row)"
>
{{ scope.row.errorCount }}
</el-link>
<span v-if="scope.row.errorCount === 0">0</span>
</template>
</el-table-column>
</el-table>
<!-- dialog -->
<el-dialog
title="错误详情"
:visible.sync="logDetailVisible"
width="60%"
>
<div style="overflow-x: auto" v-html="errorMsgDetail"></div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="logDetailVisible = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
searchFormData: {
routeId: ''
},
tableData: [],
logDetailVisible: false,
errorMsgDetail: ''
}
},
created() {
this.loadTable()
},
methods: {
loadTable: function() {
this.post('monitor.data.list', this.searchFormData, function(resp) {
const data = resp.data
this.tableData = data.monitorInfoData
})
},
onShowErrorDetail: function(row) {
const errorMsgList = row.errorMsgList
this.errorMsgDetail = errorMsgList.length > 0 ? errorMsgList.join('<br>') : '无内容'
this.logDetailVisible = true
}
}
}
</script>

@ -26,7 +26,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
</dependency>
<!-- sop相关配置 end-->

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>

@ -5,11 +5,11 @@
<parent>
<artifactId>sop-common</artifactId>
<groupId>com.gitee.sop</groupId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
<artifactId>sop-bridge-gateway</artifactId>
@ -17,7 +17,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
</dependency>
<dependency>

@ -5,11 +5,11 @@
<parent>
<artifactId>sop-common</artifactId>
<groupId>com.gitee.sop</groupId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
<artifactId>sop-bridge-zuul</artifactId>
@ -17,7 +17,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
</dependency>
<dependency>

@ -29,7 +29,7 @@ ribbon.ReadTimeout=5000
# 设置为true(默认false),则所有请求都重试,默认只支持get请求重试
# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性
ribbon.OkToRetryOnAllOperations=false
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=13000
# 不用改
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create

@ -5,11 +5,11 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sop-gateway-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sop-gateway-common</name>

@ -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<RouteInterceptor> routeInterceptors = new ArrayList<>(4);
/**
* 监控管理
*/
private MonitorManager monitorManager = new MonitorManager();
// -------- fields ---------
/**

@ -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;
}
}

@ -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";
}

@ -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();

@ -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会从容器中直接获取所以直接注入即可
*

@ -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<ServerWebExchange> {
@Override
protected ApiParam getApiParam(ServerWebExchange request) {
Map<String, String> params = request.getRequest().getQueryParams().toSingleValueMap();
return ApiParam.build(params);
}
}

@ -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

@ -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<Server> 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<Server> 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);

@ -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<ServerWebExchange
@Override
public int getResponseStatus(ServerWebExchange exchange) {
int responseStatus = HttpStatus.OK.value();
HttpStatus statusCode = exchange.getResponse().getStatusCode();
int responseStatus = statusCode.value();
List<String> 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<ServerWebExchange
return exchange.getLocaleContext().getLocale();
}
@Override
protected RouteInterceptorContext getRouteInterceptorContext(ServerWebExchange exchange) {
return (RouteInterceptorContext) exchange.getAttributes().get(SopConstants.CACHE_ROUTE_INTERCEPTOR_CONTEXT);
}
@Override
public String buildErrorResult(ServerWebExchange exchange, Throwable ex) {
Locale locale = getLocale(exchange);

@ -11,8 +11,6 @@ import org.springframework.web.server.ServerWebExchange;
*/
public class GatewayForwardChooser extends BaseForwardChooser<ServerWebExchange> {
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<ServerWebExchange>
public ForwardInfo getForwardInfo(ServerWebExchange exchange) {
// 如果有异常,直接跳转到异常处理
if (ServerWebExchangeUtil.getThrowable(exchange) != null) {
return new ForwardInfo(VALIDATE_ERROR_PATH, "");
return ForwardInfo.getErrorForwardInfo();
}
return super.getForwardInfo(exchange);
}

@ -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<GatewayTargetRout
for (Map.Entry<String, GatewayTargetRoute> 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<GatewayTargetRoute> getAll() {

@ -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;
}
}

@ -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;
}
}

@ -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返回RequestContextGateway返回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;
}
}

@ -28,7 +28,7 @@ public interface ServerChooserContext<T> extends ApiParamAware<T> {
default boolean isRequestGrayServer(T t) {
ApiParam apiParam = getApiParam(t);
return apiParam.isGrayRequest();
return apiParam.fetchGrayRequest();
}
String getHost(T t);

@ -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<String, RouteInterceptor> routeInterceptorMap = applicationContext.getBeansOfType(RouteInterceptor.class);
Collection<RouteInterceptor> routeInterceptors = new ArrayList<>(routeInterceptorMap.values());
routeInterceptors.add(new MonitorRouteInterceptor());
RouteInterceptorUtil.addInterceptors(routeInterceptors);
}
protected void doAfter() {
}

@ -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<String> 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);
}
}
}
}

@ -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<String, MonitorInfo> monitorMap = new ConcurrentHashMap<>(128);
public Map<String, MonitorInfo> getMonitorData() {
return monitorMap;
}
public MonitorInfo getMonitorInfo(String routeId, Function<String, MonitorInfo> createFun) {
return monitorMap.computeIfAbsent(routeId, createFun);
}
}

@ -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;
}
}

@ -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<T, R> implements ResultExecutor<T, R> {
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<Integer, ErrorEnum> 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<T, R> implements ResultExecutor<T, R>
/**
* 获取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<T, R> implements ResultExecutor<T, R>
* @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,15 +176,11 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
public String merge(T exchange, JSONObject responseData) {
JSONObject finalData = new JSONObject(true);
ApiParam params = this.getApiParam(exchange);
if (params == null) {
return responseData.toJSONString();
}
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();
// 追加额外的结果
@ -216,19 +188,23 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
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();
}
/**
@ -249,26 +225,20 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
return null;
}
protected String getParamValue(Map<String, Object> 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<String, Object> 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;
}
@ -277,30 +247,12 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
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
}
}

@ -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<T> implements ForwardChooser<T>, 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<T> implements ForwardChooser<T>, 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) {

@ -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 "";
}
}
}

@ -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<T> extends SopBaseController<T> {
@GetMapping("/sop/getMonitorData")
public ApiResult doExecute(T request) {
MonitorManager monitorManager = ApiConfig.getInstance().getMonitorManager();
return execute(request, monitorManager::getMonitorData);
}
}

@ -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<T> {
TaobaoSigner signer = new TaobaoSigner();
@Value("${sop.secret}")
private String secret;
protected abstract ApiParam getApiParam(T t);
public ApiResult execute(T request, Supplier<Object> 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("签名校验失败");
}
}
}

@ -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 {
/**
* keyserviceIdvalue指示变量i
*/
private static Map<String, Integer> serviceIdRoundMap = new ConcurrentHashMap<>(8);
/**
* 轮询选择一台机器<br>
* <p>
* 假设有N台服务器S = {S1, S2, , Sn}一个指示变量i表示上一次选择的服务器ID变量i被初始化为N-1
* </p>
* 参考https://blog.csdn.net/qq_37469055/article/details/87991327
* @param serviceId serviceId不同的serviceId对应的服务器数量不一样需要区分开
* @param servers 服务器列表
* @return 返回一台服务器实例
*/
public static <T> T chooseByRoundRobin(String serviceId, List<T> 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> T chooseByRandom(List<T> 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);
}
}

@ -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<RouteInterceptorContext> 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<RouteInterceptor> getRouteInterceptors() {
return ApiConfig.getInstance().getRouteInterceptors();
}
public static void addInterceptors(Collection<RouteInterceptor> interceptors) {
List<RouteInterceptor> routeInterceptors = getRouteInterceptors();
routeInterceptors.addAll(interceptors);
routeInterceptors.sort(Comparator.comparing(RouteInterceptor::getOrder));
}
}

@ -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);
}
/**
* 校验上传文件内容
*

@ -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 {
/**

@ -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<RequestContext> 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();

@ -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);
}
}

@ -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<HttpServletRequest> {
@Override
protected ApiParam getApiParam(HttpServletRequest request) {
Map<String, String> params = RequestUtil.convertRequestParamsToMap(request);
return ApiParam.build(params);
}
}

@ -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;

@ -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
*
* <p>
* 将校验工作提前如果在zuul过滤器中校验抛出异常将会打印非常多的日志并且无法实现自定义返回结果
*
* @deprecated see {@link com.gitee.sop.gatewaycommon.zuul.ValidateService}
* @author tanghc
*/
@Deprecated
public class PreValidateFilter extends BaseZuulFilter {
@Autowired
private ParamBuilder<RequestContext> 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;
}

@ -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<RequestContext> {
@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);
}
}

@ -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<RequestContext, String> 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<Pair<String, String>> bizHeaders = requestContext.getZuulResponseHeaders();
@ -46,21 +39,11 @@ public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, Stri
@Override
public String getResponseErrorMessage(RequestContext requestContext) {
List<Pair<String, String>> bizHeaders = requestContext.getZuulResponseHeaders();
int index = -1;
String errorMsg = null;
for (int i = 0; i < bizHeaders.size(); i++) {
Pair<String, String> 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<RequestContext, Stri
return requestContext.getRequest().getLocale();
}
@Override
protected RouteInterceptorContext getRouteInterceptorContext(RequestContext requestContext) {
return (RouteInterceptorContext) requestContext.get(SopConstants.CACHE_ROUTE_INTERCEPTOR_CONTEXT);
}
@Override
public String buildErrorResult(RequestContext requestContext, Throwable throwable) {
Locale locale = getLocale(requestContext);
@ -99,4 +87,22 @@ public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, Stri
}
return error;
}
private String getHeader(RequestContext requestContext, String name, Consumer<Integer> after) {
List<Pair<String, String>> bizHeaders = requestContext.getZuulResponseHeaders();
int index = -1;
String value = null;
for (int i = 0; i < bizHeaders.size(); i++) {
Pair<String, String> header = bizHeaders.get(i);
if (name.equals(header.first())) {
value = header.second();
index = i;
break;
}
}
if (after != null) {
after.accept(index);
}
return value;
}
}

@ -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

@ -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<String> 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<String> 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<String> 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> T chooseRoundRobinServer(String serviceId, List<T> servers) {
return LoadBalanceUtil.chooseByRoundRobin(serviceId, servers);
}
}

@ -6,11 +6,11 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sop-service-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sop-service-common</name>

@ -28,7 +28,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.gitee.sop</groupId>

@ -29,7 +29,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
</dependency>
<!-- 使用nacos注册中心

@ -20,7 +20,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
</dependency>
<!-- nacos -->
<dependency>

@ -28,7 +28,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.gitee.sop</groupId>

@ -34,7 +34,7 @@
<groupId>com.gitee.sop</groupId>
<artifactId>sop-bridge-gateway</artifactId>
<!--<artifactId>sop-bridge-zuul</artifactId>-->
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
</dependency>
<dependency>

@ -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;
}
}

@ -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);

@ -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);

@ -333,7 +333,7 @@ public class AllInOneTest extends TestBase {
Map<String, String> header = new HashMap<>(4);
header.put("Accept-Language", "en-US");
Client.RequestBuilder requestBuilder = new Client.RequestBuilder()
.method("alipay.story.get__")
.method("alipay.story.get9")
.version("1.0")
.header(header)
.bizContent(new BizContent().add("id", "1").add("name", "葫芦娃"))

@ -35,7 +35,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>3.0.1-SNAPSHOT</version>
<version>3.1.0-SNAPSHOT</version>
</dependency>
<dependency>

Loading…
Cancel
Save