# Conflicts: # sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.javaeureka
commit
ef03f9c82f
@ -1,38 +1,39 @@ |
|||||||
* [首页](/?t=1580871460178) |
* [首页](/?t=1582014833549) |
||||||
* 开发文档 |
* 开发文档 |
||||||
* [快速体验](files/10010_快速体验.md?t=1580871460179) |
* [快速体验](files/10010_快速体验.md?t=1582014833552) |
||||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1580871460202) |
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1582014833568) |
||||||
* [新增接口](files/10020_新增接口.md?t=1580871460202) |
* [新增接口](files/10020_新增接口.md?t=1582014833568) |
||||||
* [开发流程](files/10021_开发流程.md?t=1580871460202) |
* [开发流程](files/10021_开发流程.md?t=1582014833568) |
||||||
* [业务参数校验](files/10030_业务参数校验.md?t=1580871460203) |
* [业务参数校验](files/10030_业务参数校验.md?t=1582014833568) |
||||||
* [错误处理](files/10040_错误处理.md?t=1580871460203) |
* [错误处理](files/10040_错误处理.md?t=1582014833568) |
||||||
* [编写文档](files/10041_编写文档.md?t=1580871460203) |
* [编写文档](files/10041_编写文档.md?t=1582014833569) |
||||||
* [接口交互详解](files/10050_接口交互详解.md?t=1580871460203) |
* [接口交互详解](files/10050_接口交互详解.md?t=1582014833569) |
||||||
* [easyopen支持](files/10070_easyopen支持.md?t=1580871460203) |
* [easyopen支持](files/10070_easyopen支持.md?t=1582014833569) |
||||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1580871460203) |
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1582014833569) |
||||||
* [ISV管理](files/10085_ISV管理.md?t=1580871460204) |
* [ISV管理](files/10085_ISV管理.md?t=1582014833569) |
||||||
* [自定义返回结果](files/10087_自定义返回结果.md?t=1580871460204) |
* [自定义返回结果](files/10087_自定义返回结果.md?t=1582014833569) |
||||||
* [自定义过滤器](files/10088_自定义过滤器.md?t=1580871460204) |
* [自定义过滤器](files/10088_自定义过滤器.md?t=1582014833569) |
||||||
* [自定义校验token](files/10089_自定义校验token.md?t=1580871460204) |
* [自定义校验token](files/10089_自定义校验token.md?t=1582014833569) |
||||||
* [路由授权](files/10090_路由授权.md?t=1580871460204) |
* [网关拦截器](files/10090_网关拦截器.md?t=1582014833570) |
||||||
* [接口限流](files/10092_接口限流.md?t=1580871460204) |
* [路由授权](files/10090_路由授权.md?t=1582014833570) |
||||||
* [监控日志](files/10093_监控日志.md?t=1580871460204) |
* [接口限流](files/10092_接口限流.md?t=1582014833570) |
||||||
* [SDK开发](files/10095_SDK开发.md?t=1580871460204) |
* [监控日志](files/10093_监控日志.md?t=1582014833570) |
||||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1580871460205) |
* [SDK开发](files/10095_SDK开发.md?t=1582014833570) |
||||||
* [应用授权](files/10097_应用授权.md?t=1580871460205) |
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1582014833570) |
||||||
* [提供restful接口](files/10100_提供restful接口.md?t=1580871460205) |
* [应用授权](files/10097_应用授权.md?t=1582014833570) |
||||||
* [文件上传](files/10104_文件上传.md?t=1580871460205) |
* [提供restful接口](files/10100_提供restful接口.md?t=1582014833571) |
||||||
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1580871460205) |
* [文件上传](files/10104_文件上传.md?t=1582014833571) |
||||||
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1580871460205) |
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1582014833571) |
||||||
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1580871460205) |
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1582014833571) |
||||||
* [使用eureka](files/10112_使用eureka.md?t=1580871460205) |
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1582014833571) |
||||||
* [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1580871460206) |
* [使用eureka](files/10112_使用eureka.md?t=1582014833571) |
||||||
|
* [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1582014833572) |
||||||
* 原理分析 |
* 原理分析 |
||||||
* [网关性能测试](files/90001_网关性能测试.md?t=1580871460206) |
* [网关性能测试](files/90001_网关性能测试.md?t=1582014833572) |
||||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1580871460206) |
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1582014833572) |
||||||
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1580871460206) |
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1582014833572) |
||||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1580871460206) |
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1582014833572) |
||||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1580871460207) |
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1582014833572) |
||||||
* [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1580871460207) |
* [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1582014833572) |
||||||
* [2.x升3.x注意事项](files/90099_2.x升3.x注意事项.md?t=1580871460207) |
* [2.x升3.x注意事项](files/90099_2.x升3.x注意事项.md?t=1582014833572) |
||||||
* [常见问题](files/90100_常见问题.md?t=1580871460207) |
* [常见问题](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` 默认实现的拦截器,用于收集监控数据 |
||||||
|
|
||||||
|
|
||||||
|
|
@ -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 +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> |
File diff suppressed because one or more lines are too long
@ -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}}]); |
File diff suppressed because one or more lines are too long
@ -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> |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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返回RequestContext,Gateway返回ServerWebExchange |
||||||
|
* |
||||||
|
* @return 返回上下文对象 |
||||||
|
*/ |
||||||
|
Object getRequestContext(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取请求内容大小 |
||||||
|
* |
||||||
|
* @return 返回请求内容大小 |
||||||
|
*/ |
||||||
|
long getRequestDataSize(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取返回结果内容大小 |
||||||
|
* @return 返回返回结果内容大小 |
||||||
|
*/ |
||||||
|
long getResponseDataSize(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 是否是成功请求,微服务主动抛出的异常也算作成功,JSR303校验失败也算作成功。 |
||||||
|
* 只有微服务返回未知的错误算作失败。 |
||||||
|
* @return true:成功请求 |
||||||
|
*/ |
||||||
|
default boolean isSuccessRequest() { |
||||||
|
int responseStatus = getResponseStatus(); |
||||||
|
return responseStatus == HttpStatus.OK.value() || responseStatus == SopConstants.BIZ_ERROR_STATUS; |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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 { |
||||||
|
|
||||||
|
/** |
||||||
|
* key:serviceId,value:指示变量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)); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue