# Conflicts: # pom.xml # sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java # sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/MonitorRouteInterceptor.java # sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorInfo.java # sop-common/sop-service-common/pom.xml # sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java # sop-gateway/pom.xml # sop-gateway/src/main/resources/META-INF/gateway.propertiespull/10/MERGE
@ -1,34 +1,36 @@ |
|||||||
* [首页](/?t=1595931646391) |
* [首页](/?t=1616211903021) |
||||||
* 开发文档 |
* 开发文档 |
||||||
* [快速体验](files/10010_快速体验.md?t=1595931646394) |
* [快速体验](files/10010_快速体验.md?t=1616211903027) |
||||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1595931646412) |
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1616211903048) |
||||||
* [新增接口](files/10020_新增接口.md?t=1595931646412) |
* [新增接口](files/10020_新增接口.md?t=1616211903048) |
||||||
* [开发流程](files/10021_开发流程.md?t=1595931646412) |
* [开发流程](files/10021_开发流程.md?t=1616211903048) |
||||||
* [业务参数校验](files/10030_业务参数校验.md?t=1595931646412) |
* [用户注册](files/10022_用户注册.md?t=1616211903049) |
||||||
* [错误处理](files/10040_错误处理.md?t=1595931646412) |
* [业务参数校验](files/10030_业务参数校验.md?t=1616211903049) |
||||||
* [编写文档](files/10041_编写文档.md?t=1595931646412) |
* [错误处理](files/10040_错误处理.md?t=1616211903049) |
||||||
* [接口交互详解](files/10050_接口交互详解.md?t=1595931646412) |
* [编写文档](files/10041_编写文档.md?t=1616211903049) |
||||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1595931646413) |
* [接口交互详解](files/10050_接口交互详解.md?t=1616211903049) |
||||||
* [ISV管理](files/10085_ISV管理.md?t=1595931646413) |
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1616211903049) |
||||||
* [自定义返回结果](files/10087_自定义返回结果.md?t=1595931646413) |
* [ISV管理](files/10085_ISV管理.md?t=1616211903050) |
||||||
* [自定义过滤器](files/10088_自定义过滤器.md?t=1595931646413) |
* [自定义返回结果](files/10087_自定义返回结果.md?t=1616211903050) |
||||||
* [自定义校验token](files/10089_自定义校验token.md?t=1595931646413) |
* [自定义过滤器](files/10088_自定义过滤器.md?t=1616211903050) |
||||||
* [网关拦截器](files/10090_网关拦截器.md?t=1595931646413) |
* [自定义校验token](files/10089_自定义校验token.md?t=1616211903050) |
||||||
* [路由授权](files/10090_路由授权.md?t=1595931646413) |
* [网关拦截器](files/10090_网关拦截器.md?t=1616211903050) |
||||||
* [接口限流](files/10092_接口限流.md?t=1595931646413) |
* [路由授权](files/10091_路由授权.md?t=1616211903050) |
||||||
* [路由监控](files/10093_路由监控.md?t=1595931646413) |
* [接口限流](files/10092_接口限流.md?t=1616211903051) |
||||||
* [SDK开发](files/10095_SDK开发.md?t=1595931646413) |
* [路由监控](files/10093_路由监控.md?t=1616211903051) |
||||||
* [应用授权](files/10097_应用授权.md?t=1595931646414) |
* [SDK开发](files/10095_SDK开发.md?t=1616211903051) |
||||||
* [提供restful接口](files/10100_提供restful接口.md?t=1595931646414) |
* [应用授权](files/10097_应用授权.md?t=1616211903051) |
||||||
* [文件上传](files/10104_文件上传.md?t=1595931646414) |
* [提供restful接口](files/10100_提供restful接口.md?t=1616211903051) |
||||||
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1595931646414) |
* [文件上传](files/10104_文件上传.md?t=1616211903051) |
||||||
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1595931646414) |
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1616211903052) |
||||||
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1595931646414) |
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1616211903052) |
||||||
* [使用eureka](files/10112_使用eureka.md?t=1595931646414) |
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1616211903052) |
||||||
|
* [使用eureka](files/10112_使用eureka.md?t=1616211903052) |
||||||
|
* [超时设置](files/10113_超时设置.md?t=1616211903052) |
||||||
* 原理分析 |
* 原理分析 |
||||||
* [网关性能测试](files/90001_网关性能测试.md?t=1595931646414) |
* [网关性能测试](files/90001_网关性能测试.md?t=1616211903052) |
||||||
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1595931646415) |
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1616211903052) |
||||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1595931646415) |
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1616211903052) |
||||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1595931646415) |
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1616211903052) |
||||||
* [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1595931646415) |
* [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1616211903052) |
||||||
* [常见问题](files/90100_常见问题.md?t=1595931646415) |
* [常见问题](files/90100_常见问题.md?t=1616211903053) |
||||||
|
@ -0,0 +1,11 @@ |
|||||||
|
# 用户(ISV)注册 |
||||||
|
|
||||||
|
新增ISV有两种方式 |
||||||
|
|
||||||
|
- 方式1 |
||||||
|
|
||||||
|
启动sop-admin,在admin后台`ISV管理添加` |
||||||
|
|
||||||
|
- 方式2 |
||||||
|
|
||||||
|
启动`sop-website-server`,用户访问自主注册 |
@ -1,103 +1,39 @@ |
|||||||
# 自定义过滤器 |
# 自定义过滤器 |
||||||
|
|
||||||
## zuul过滤器 |
演示在网关追加一个header |
||||||
|
|
||||||
zuul过滤器列表如下: |
|
||||||
|
|
||||||
| 类型 | 顺序 | 过滤器 | 功能 | |
|
||||||
| ----- | ---- | ----------------------- | ---------------------------- | |
|
||||||
| pre | -1000 | PreValidateFilter (SOP自带) | 校验签名 | |
|
||||||
| pre | -998 | PreLimitFilter (SOP自带) | 限流拦截器 | |
|
||||||
| pre | -3 | ServletDetectionFilter | 标记处理 Servlet 的类型 | |
|
||||||
| pre | -2 | Servlet30WrapperFilter | 包装 HttpServletRequest 请求 | |
|
||||||
| pre | -1 | FormBodyWrapperFilter | 包装请求体 Servlet30WrapperFilter | |
|
||||||
| pre | 1 | DebugFilter | 标记调试标志 | |
|
||||||
| pre | 5 | PreDecorationFilter | 决定路由转发过滤器 | |
|
||||||
| route | 10 | RibbonRoutingFilter | serviceId 请求转发 | |
|
||||||
| route | 100 | SimpleHostRoutingFilter | url 请求转发 | |
|
||||||
| route | 500 | SendForwardFilter | forward 请求转发 | |
|
||||||
| post | 0 | SendErrorFilter | 处理有错误的请求响应 | |
|
||||||
| post | 1000 | SendResponseFilter | 处理正常的请求响应 | |
|
||||||
|
|
||||||
顺序值小的优先执行,`-3`之前是sop自带的过滤器,`-3`开始是zuul自带的过滤器。 |
|
||||||
|
|
||||||
创建自定义过滤器可以从`-500`开始(-1000 ~ -501留给SOP)。下面是一个自定义过虑器的例子: |
|
||||||
|
|
||||||
```java |
|
||||||
public class PreXXXFilter extends BaseZuulFilter { |
|
||||||
@Override |
|
||||||
protected FilterType getFilterType() { |
|
||||||
return FilterType.PRE; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected int getFilterOrder() { |
|
||||||
return -500; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected Object doRun(RequestContext requestContext) throws ZuulException { |
|
||||||
HttpServletRequest request = requestContext.getRequest(); |
|
||||||
ApiParam apiParam = ZuulContext.getApiParam(); |
|
||||||
String appKey = apiParam.fetchAppKey(); |
|
||||||
// ...业务处理 |
|
||||||
|
|
||||||
// 固定返回null |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
过滤器编写完毕后,在Config中使用: |
|
||||||
|
|
||||||
```java |
|
||||||
@Configuration |
|
||||||
public class ZuulConfig extends AlipayZuulConfiguration { |
|
||||||
|
|
||||||
... |
|
||||||
|
|
||||||
@Bean |
|
||||||
PreXXXFilter preXXXFilter() { |
|
||||||
return new PreXXXFilter(); |
|
||||||
} |
|
||||||
... |
|
||||||
|
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## spring cloud gateway |
|
||||||
|
|
||||||
跟zuul同理 |
|
||||||
|
|
||||||
```java |
```java |
||||||
public class XXXFilter implements GlobalFilter, Ordered { |
public class CustomFilter implements GlobalFilter, Ordered { |
||||||
|
|
||||||
@Override |
@Override |
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
||||||
ApiParam apiParam = (ApiParam)exchange.getAttribute(SopConstants.CACHE_API_PARAM); |
// 演示在网关追加header |
||||||
String appKey = apiParam.fetchAppKey(); |
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange); |
||||||
// ...业务处理 |
String token = apiParam.fetchAccessToken(); |
||||||
... |
ServerWebExchange serverWebExchange = ServerWebExchangeUtil.addHeaders(exchange, httpHeaders -> { |
||||||
return chain.filter(exchange); |
httpHeaders.add("token", token); |
||||||
|
}); |
||||||
|
return chain.filter(serverWebExchange); |
||||||
} |
} |
||||||
|
|
||||||
@Override |
@Override |
||||||
public int getOrder() { |
public int getOrder() { |
||||||
return -500; |
// 自定义过滤器可以从0开始 |
||||||
|
return 0; |
||||||
} |
} |
||||||
} |
} |
||||||
``` |
``` |
||||||
|
|
||||||
使用过滤器: |
使用过滤器,在sop-gateway中找到MyConfig,添加: |
||||||
|
|
||||||
```java |
```java |
||||||
@Configuration |
@Configuration |
||||||
public class GatewayConfig extends AlipayGatewayConfiguration { |
public class MyConfig { |
||||||
|
|
||||||
... |
... |
||||||
@Bean |
@Bean |
||||||
XXXFilter xxxFilter() { |
CustomFilter customFilter() { |
||||||
return new XXXFilter(); |
return new CustomFilter(); |
||||||
} |
} |
||||||
... |
... |
||||||
} |
} |
||||||
|
@ -0,0 +1,14 @@ |
|||||||
|
# 超时设置 |
||||||
|
|
||||||
|
当微服务处理业务逻辑时间过长,网关会报超时错误,默认等待时间是5秒。 |
||||||
|
|
||||||
|
可在网关指定`spring.cloud.gateway.httpclient.response-timeout`参数设置超时时间,单位毫秒 |
||||||
|
|
||||||
|
```properties |
||||||
|
# 设置响应超时10秒 |
||||||
|
spring.cloud.gateway.httpclient.response-timeout=10000 |
||||||
|
``` |
||||||
|
|
||||||
|
更多配置参见:`org.springframework.cloud.gateway.config.HttpClientProperties` |
||||||
|
|
||||||
|
测试用例参见:`com.gitee.sop.test.TimeoutTest` |
@ -1,77 +1,33 @@ |
|||||||
# 原理分析之如何路由 |
# 原理分析之如何路由 |
||||||
|
|
||||||
## zuul如何路由 |
Spring Cloud Gateway通过一系列的Filter来进行数据的传输,如下图所示: |
||||||
|
|
||||||
SOP网关默认使用zuul,当然也默认使用了zuul提供的路由功能。zuul默认使用过滤器来实现路由转发, |
 |
||||||
我们看下zuul中自带的过滤器: |
|
||||||
|
|
||||||
| 类型 | 顺序 | 过滤器 | 功能 | |
SOP网关在此基础上新增了几个Filter用来处理自己的逻辑,如:前置校验、结果返回。 |
||||||
| ----- | ---- | ----------------------- | ---------------------------- | |
|
||||||
| pre | -3 | ServletDetectionFilter | 标记处理 Servlet 的类型 | |
|
||||||
| pre | -2 | Servlet30WrapperFilter | 包装 HttpServletRequest 请求 | |
|
||||||
| pre | -1 | FormBodyWrapperFilter | 包装请求体 | |
|
||||||
| pre | 1 | DebugFilter | 标记调试标志 | |
|
||||||
| pre | 5 | PreDecorationFilter | 决定路由转发过滤器 | |
|
||||||
| route | 10 | RibbonRoutingFilter | serviceId 请求转发 | |
|
||||||
| route | 100 | SimpleHostRoutingFilter | url 请求转发 | |
|
||||||
| route | 500 | SendForwardFilter | forward 请求转发 | |
|
||||||
| post | 0 | SendErrorFilter | 处理有错误的请求响应 | |
|
||||||
| post | 1000 | SendResponseFilter | 处理正常的请求响应 | |
|
||||||
|
|
||||||
上图就是zuul提供的默认过滤器,可在org.springframework.cloud.netflix.zuul.filters下查看。 |
| 过滤器 | 类型 | Order | 功能 | |
||||||
|
| ----- | ---- | ----------------------- | ---------------------------- | |
||||||
|
|IndexFilter| `自定义` | -2147483648 | 入口过滤器,获取参数、签名校验 | |
||||||
|
|ParameterFormatterFilter | `自定义` | -2147482647 | 格式化参数 | |
||||||
|
|LimitFilter|`自定义`|-2147482447|限流| |
||||||
|
|ForwardPathFilter|系统自带|0 |设置转发的path| |
||||||
|
|RouteToRequestUrlFilter|系统自带|10000|设置转发host| |
||||||
|
|SopLoadBalancerClientFilter|`自定义`|10100|LoadBalance获取转发实例| |
||||||
|
|NettyRoutingFilter|系统自带|2147483647|获取httpclient发送请求| |
||||||
|
|ForwardRoutingFilter|系统自带|2147483647|请求分发| |
||||||
|
|GatewayModifyResponseGatewayFilter|`自定义`|-2|处理响应结果| |
||||||
|
|
||||||
zuul的过滤器顺序值小的优先执行,其中的`PreDecorationFilter`是我们重点关注的类,由它来决定路由转发去向。 |
一个完整的请求会自上而下经过这些Filter,下面讲解如何动态设置路由。 |
||||||
|
|
||||||
打开PreDecorationFilter类,看到类注释有一句话:`that determines where and how to route based on the supplied` |
## 动态设置路由 |
||||||
|
|
||||||
翻译过来就是说,决定从哪里获取路由,然后怎样去路由。 |
网关启动后会从注册中心拉取微服务实例,然后请求微服务提供的一个接口(`/sop/routes`),获取开放接口信息(被`@Open`注解的接口)。 |
||||||
|
|
||||||
PreDecorationFilter类的核心方法是run()方法。找到run方法中这一句代码: |
监听处理类在:`com.gitee.sop.bridge.route.NacosRegistryListener` |
||||||
|
|
||||||
`Route route = this.routeLocator.getMatchingRoute(requestURI);` |
获取到路由信息后,将路由信息缓存到本地,并保存到数据库,代码在:`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.load` |
||||||
|
|
||||||
这句代码很重要,表示路由从哪里获取,如果我们能够重写getMatchingRoute方法那就可以返回自己定义的路由了。 |
然后动态设置Gateway路由,代码在:`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository.refresh` |
||||||
|
|
||||||
接下来找到RouteLocator类的定义,发现是通过构造方法传进来的,那么我们就去找使用构造方法的类。(IDEA下右键构造方法--Find Usage) |
当有微服务重新启动时,网关会监听到微服务实例有变更,会重复上述步骤,确保网关存有最新的路由。 |
||||||
|
|
||||||
在org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration类中找到了定义 |
|
||||||
|
|
||||||
```java |
|
||||||
// pre filters |
|
||||||
@Bean |
|
||||||
@ConditionalOnMissingBean(PreDecorationFilter.class) |
|
||||||
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) { |
|
||||||
return new PreDecorationFilter(routeLocator, this.server.getServlet().getContextPath(), this.zuulProperties, |
|
||||||
proxyRequestHelper); |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
方法默认注入了RouteLocator类,默认注入的实现是CompositeRouteLocator类(通过打断点可以查看)。 |
|
||||||
|
|
||||||
同时方法上用了`@ConditionalOnMissingBean`注解,表示如果其它地方没有声明,则默认使用这个。 |
|
||||||
|
|
||||||
因此我们可以自己声明一个PreDecorationFilter,然后注入自定义的RouteLocator就行了。 |
|
||||||
|
|
||||||
SOP自定义的RouteLocator为:`com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator`,可自行前往查看。 |
|
||||||
|
|
||||||
然后再我们的Config中定义: |
|
||||||
|
|
||||||
```java |
|
||||||
/** |
|
||||||
* 选取路由 |
|
||||||
* @param zuulRouteRepository |
|
||||||
* @param proxyRequestHelper |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
@Bean |
|
||||||
public PreDecorationFilter preDecorationFilter(ZuulRouteRepository zuulRouteRepository, ProxyRequestHelper proxyRequestHelper) { |
|
||||||
// 自定义路由 |
|
||||||
RouteLocator routeLocator = new SopRouteLocator(zuulRouteRepository); |
|
||||||
return new PreDecorationFilter(routeLocator, |
|
||||||
this.server.getServlet().getContextPath(), |
|
||||||
this.zuulProperties, |
|
||||||
proxyRequestHelper); |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
到此,我们只需要实现RouteLocator接口,就能使用zuul默认的路由功能,非常方便。 |
|
||||||
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 187 KiB |
Before Width: | Height: | Size: 315 KiB |
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,80 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service; |
||||||
|
|
||||||
|
import com.gitee.easyopen.annotation.Api; |
||||||
|
import com.gitee.easyopen.annotation.ApiService; |
||||||
|
import com.gitee.fastmybatis.core.query.Query; |
||||||
|
import com.gitee.fastmybatis.core.query.Sort; |
||||||
|
import com.gitee.fastmybatis.core.support.PageEasyui; |
||||||
|
import com.gitee.fastmybatis.core.util.MapperUtil; |
||||||
|
import com.gitee.sop.adminserver.api.service.param.InstanceMonitorSearchParam; |
||||||
|
import com.gitee.sop.adminserver.api.service.param.MonitorErrorMsgParam; |
||||||
|
import com.gitee.sop.adminserver.api.service.param.MonitorInfoErrorSolveParam; |
||||||
|
import com.gitee.sop.adminserver.api.service.param.MonitorSearchParam; |
||||||
|
import com.gitee.sop.adminserver.bean.RouteErrorCount; |
||||||
|
import com.gitee.sop.adminserver.entity.MonitorInfoError; |
||||||
|
import com.gitee.sop.adminserver.entity.MonitorSummary; |
||||||
|
import com.gitee.sop.adminserver.mapper.MonitorInfoErrorMapper; |
||||||
|
import com.gitee.sop.adminserver.mapper.MonitorInfoMapper; |
||||||
|
import com.github.pagehelper.PageHelper; |
||||||
|
import com.github.pagehelper.PageInfo; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* 路由监控 |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@ApiService |
||||||
|
public class MonitorNewApi { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private MonitorInfoMapper monitorInfoMapper; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private MonitorInfoErrorMapper monitorInfoErrorMapper; |
||||||
|
|
||||||
|
@Api(name = "monitornew.data.page") |
||||||
|
PageInfo<Object> listMonitor(MonitorSearchParam param) { |
||||||
|
Query query = Query.build(param); |
||||||
|
query.orderby("errorCount", Sort.DESC) |
||||||
|
.orderby("avgTime", Sort.DESC); |
||||||
|
return PageHelper.offsetPage(query.getStart(), query.getLimit()) |
||||||
|
.doSelectPage(() -> monitorInfoMapper.listMonitorSummary(query)) |
||||||
|
.toPageInfo(); |
||||||
|
} |
||||||
|
|
||||||
|
@Api(name = "monitornew.routeid.data.get") |
||||||
|
List<MonitorSummary> listInstanceMonitor(InstanceMonitorSearchParam param) { |
||||||
|
Query query = Query.build(param); |
||||||
|
query.orderby("errorCount", Sort.DESC) |
||||||
|
.orderby("avgTime", Sort.DESC); |
||||||
|
return monitorInfoMapper.listInstanceMonitorInfo(query); |
||||||
|
} |
||||||
|
|
||||||
|
private Map<String, Integer> getRouteErrorCount() { |
||||||
|
List<RouteErrorCount> routeErrorCounts = monitorInfoErrorMapper.listRouteErrorCount(); |
||||||
|
return routeErrorCounts.stream() |
||||||
|
.collect(Collectors.toMap(RouteErrorCount::getRouteId, RouteErrorCount::getCount)); |
||||||
|
} |
||||||
|
|
||||||
|
@Api(name = "monitornew.error.page") |
||||||
|
PageEasyui<MonitorInfoError> listError(MonitorErrorMsgParam param) { |
||||||
|
Query query = param.toQuery() |
||||||
|
.orderby("gmt_modified", Sort.DESC); |
||||||
|
return MapperUtil.queryForEasyuiDatagrid(monitorInfoErrorMapper, query); |
||||||
|
} |
||||||
|
|
||||||
|
@Api(name = "monitornew.error.solve") |
||||||
|
void solve(MonitorInfoErrorSolveParam param) { |
||||||
|
Query query = Query.build(param); |
||||||
|
Map<String, Object> set = new HashMap<>(4); |
||||||
|
set.put("is_deleted", 1); |
||||||
|
set.put("count", 0); |
||||||
|
monitorInfoErrorMapper.updateByMap(set, query); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service; |
||||||
|
|
||||||
|
import com.gitee.easyopen.annotation.Api; |
||||||
|
import com.gitee.easyopen.annotation.ApiService; |
||||||
|
import com.gitee.sop.adminserver.api.system.param.IspPageParam; |
||||||
|
import com.gitee.sop.adminserver.api.system.param.IspResourceParam; |
||||||
|
import com.gitee.sop.adminserver.common.CopyUtil; |
||||||
|
import com.gitee.sop.adminserver.entity.IspResource; |
||||||
|
import com.gitee.sop.adminserver.mapper.IspResourceMapper; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@ApiService |
||||||
|
public class ResourceApi { |
||||||
|
|
||||||
|
public static final byte RESOURCE_TYPE_SDK = (byte) 0; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private IspResourceMapper ispResourceMapper; |
||||||
|
|
||||||
|
|
||||||
|
@Api(name = "isp.sdk.list") |
||||||
|
List<IspResource> list(IspPageParam param) { |
||||||
|
return ispResourceMapper.list(param.toQuery()); |
||||||
|
} |
||||||
|
|
||||||
|
@Api(name = "isp.sdk.add") |
||||||
|
void addSdk(IspResourceParam param) { |
||||||
|
IspResource ispResource = CopyUtil.copyBean(param, IspResource::new); |
||||||
|
ispResource.setType(RESOURCE_TYPE_SDK); |
||||||
|
ispResourceMapper.saveIgnoreNull(ispResource); |
||||||
|
} |
||||||
|
|
||||||
|
@Api(name = "isp.sdk.update") |
||||||
|
void updateSdk(IspResourceParam param) { |
||||||
|
IspResource resource = ispResourceMapper.getById(param.getId()); |
||||||
|
CopyUtil.copyProperties(param, resource); |
||||||
|
ispResourceMapper.update(resource); |
||||||
|
} |
||||||
|
|
||||||
|
@Api(name = "isp.sdk.delete") |
||||||
|
void deleteSdk(@NotNull Long id) { |
||||||
|
IspResource resource = ispResourceMapper.getById(id); |
||||||
|
if (resource != null) { |
||||||
|
ispResourceMapper.deleteById(id); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.param; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.query.annotation.Condition; |
||||||
|
import lombok.Getter; |
||||||
|
import lombok.Setter; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Getter |
||||||
|
@Setter |
||||||
|
public class InstanceMonitorSearchParam { |
||||||
|
@Condition(column = "t.route_id") |
||||||
|
private String routeId; |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.param; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.query.annotation.Condition; |
||||||
|
import com.gitee.fastmybatis.core.query.param.PageParam; |
||||||
|
import lombok.Getter; |
||||||
|
import lombok.Setter; |
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Getter |
||||||
|
@Setter |
||||||
|
public class MonitorErrorMsgParam extends PageParam { |
||||||
|
@NotBlank(message = "routeId不能为空") |
||||||
|
private String routeId; |
||||||
|
|
||||||
|
@Condition(ignoreEmptyString = true) |
||||||
|
private String instanceId; |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.param; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.query.annotation.Condition; |
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class MonitorInfoErrorSolveParam { |
||||||
|
/** 错误id,md5(error_msg), 数据库字段:error_id */ |
||||||
|
@NotBlank |
||||||
|
@Condition(index = 1) |
||||||
|
private String errorId; |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.param; |
||||||
|
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocField; |
||||||
|
import com.gitee.fastmybatis.core.query.Operator; |
||||||
|
import com.gitee.fastmybatis.core.query.annotation.Condition; |
||||||
|
import com.gitee.fastmybatis.core.query.param.PageParam; |
||||||
|
import lombok.Getter; |
||||||
|
import lombok.Setter; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Getter |
||||||
|
@Setter |
||||||
|
public class MonitorSearchParam extends PageParam { |
||||||
|
@ApiDocField(description = "服务名serviceId") |
||||||
|
@Condition(column = "service_id", operator = Operator.like, ignoreEmptyString = true) |
||||||
|
private String serviceId; |
||||||
|
|
||||||
|
@ApiDocField(description = "路由id") |
||||||
|
@Condition(column = "route_id", operator = Operator.like, ignoreEmptyString = true) |
||||||
|
private String routeId; |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.result; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class MonitorInfoErrorMsgResult { |
||||||
|
|
||||||
|
private String routeId; |
||||||
|
|
||||||
|
private String errorId; |
||||||
|
|
||||||
|
/** 错误信息, 数据库字段:error_msg */ |
||||||
|
private String errorMsg; |
||||||
|
|
||||||
|
private Integer errorStatus; |
||||||
|
|
||||||
|
private Integer count; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.system.param; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.query.param.PageParam; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class IspPageParam extends PageParam { |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.system.param; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:isp_resource |
||||||
|
* 备注:ISP资源表 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class IspResourceParam { |
||||||
|
|
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** 资源名称, 数据库字段:name */ |
||||||
|
private String name; |
||||||
|
|
||||||
|
private String version; |
||||||
|
|
||||||
|
/** 资源内容(URL), 数据库字段:content */ |
||||||
|
private String content; |
||||||
|
|
||||||
|
private String extContent; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.system.result; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:isp_resource |
||||||
|
* 备注:ISP资源表 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class IspResourceResult { |
||||||
|
|
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** 资源名称, 数据库字段:name */ |
||||||
|
private String name; |
||||||
|
|
||||||
|
private String version; |
||||||
|
|
||||||
|
/** 资源内容(URL), 数据库字段:content */ |
||||||
|
private String content; |
||||||
|
|
||||||
|
private String extContent; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package com.gitee.sop.adminserver.bean; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class RouteErrorCount { |
||||||
|
|
||||||
|
private String routeId; |
||||||
|
private Integer count; |
||||||
|
} |
@ -0,0 +1,75 @@ |
|||||||
|
package com.gitee.sop.adminserver.common; |
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.BeanUtils; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
import java.util.function.Consumer; |
||||||
|
import java.util.function.Supplier; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class CopyUtil { |
||||||
|
|
||||||
|
public static void copyProperties(Object from, Object to) { |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> T copyBean(Object from, Supplier<T> supplier) { |
||||||
|
Objects.requireNonNull(from); |
||||||
|
T to = supplier.get(); |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
return to; |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> T copyBeanNullable(Object from, Supplier<T> supplier) { |
||||||
|
if (from == null) { |
||||||
|
return supplier.get(); |
||||||
|
} |
||||||
|
T to = supplier.get(); |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
return to; |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> T copyBean(Object from, Supplier<T> supplier, Consumer<T> after) { |
||||||
|
if (from == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
T to = supplier.get(); |
||||||
|
BeanUtils.copyProperties(from, to); |
||||||
|
after.accept(to); |
||||||
|
return to; |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement) { |
||||||
|
if (fromList == null) { |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
return fromList.stream() |
||||||
|
.map(source -> { |
||||||
|
T target = toElement.get(); |
||||||
|
BeanUtils.copyProperties(source, target); |
||||||
|
return target; |
||||||
|
}) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement, Consumer<T> after) { |
||||||
|
if (fromList == null) { |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
return fromList.stream() |
||||||
|
.map(source -> { |
||||||
|
T target = toElement.get(); |
||||||
|
BeanUtils.copyProperties(source, target); |
||||||
|
after.accept(target); |
||||||
|
return target; |
||||||
|
}) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
package com.gitee.sop.adminserver.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:isp_resource |
||||||
|
* 备注:ISP资源表 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Table(name = "isp_resource") |
||||||
|
@Data |
||||||
|
public class IspResource { |
||||||
|
/** 数据库字段:id */ |
||||||
|
@Id |
||||||
|
@Column(name = "id") |
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** 资源名称, 数据库字段:name */ |
||||||
|
private String name; |
||||||
|
|
||||||
|
private String version; |
||||||
|
|
||||||
|
/** 资源内容(URL), 数据库字段:content */ |
||||||
|
private String content; |
||||||
|
|
||||||
|
private String extContent; |
||||||
|
|
||||||
|
/** 资源类型:0:SDK链接, 数据库字段:type */ |
||||||
|
private Byte type; |
||||||
|
|
||||||
|
/** 数据库字段:is_deleted */ |
||||||
|
@com.gitee.fastmybatis.core.annotation.LogicDelete |
||||||
|
private Byte isDeleted; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */ |
||||||
|
private Date gmtCreate; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
package com.gitee.sop.adminserver.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:monitor_info |
||||||
|
* 备注:接口监控信息 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Table(name = "monitor_info") |
||||||
|
@Data |
||||||
|
public class MonitorInfo { |
||||||
|
/** 数据库字段:id */ |
||||||
|
@Id |
||||||
|
@Column(name = "id") |
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** 路由id, 数据库字段:route_id */ |
||||||
|
private String routeId; |
||||||
|
|
||||||
|
/** 接口名, 数据库字段:name */ |
||||||
|
private String name; |
||||||
|
|
||||||
|
/** 版本号, 数据库字段:version */ |
||||||
|
private String version; |
||||||
|
|
||||||
|
/** 数据库字段:service_id */ |
||||||
|
private String serviceId; |
||||||
|
|
||||||
|
/** 数据库字段:instance_id */ |
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** 请求耗时最长时间, 数据库字段:max_time */ |
||||||
|
private Integer maxTime; |
||||||
|
|
||||||
|
/** 请求耗时最小时间, 数据库字段:min_time */ |
||||||
|
private Integer minTime; |
||||||
|
|
||||||
|
/** 总时长,毫秒, 数据库字段:total_time */ |
||||||
|
private Long totalTime; |
||||||
|
|
||||||
|
/** 总调用次数, 数据库字段:total_request_count */ |
||||||
|
private Long totalRequestCount; |
||||||
|
|
||||||
|
/** 成功次数, 数据库字段:success_count */ |
||||||
|
private Long successCount; |
||||||
|
|
||||||
|
/** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ |
||||||
|
private Long errorCount; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */ |
||||||
|
private Date gmtCreate; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
package com.gitee.sop.adminserver.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:monitor_info_error |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Table(name = "monitor_info_error") |
||||||
|
@Data |
||||||
|
public class MonitorInfoError { |
||||||
|
/** 数据库字段:id */ |
||||||
|
@Id |
||||||
|
@Column(name = "id") |
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** 错误id,md5Hex(instanceId + routeId + errorMsg), 数据库字段:error_id */ |
||||||
|
private String errorId; |
||||||
|
|
||||||
|
/** 实例id, 数据库字段:instance_id */ |
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** 数据库字段:route_id */ |
||||||
|
private String routeId; |
||||||
|
|
||||||
|
/** 数据库字段:error_msg */ |
||||||
|
private String errorMsg; |
||||||
|
|
||||||
|
/** http status,非200错误, 数据库字段:error_status */ |
||||||
|
private Integer errorStatus; |
||||||
|
|
||||||
|
/** 错误次数, 数据库字段:count */ |
||||||
|
private Integer count; |
||||||
|
|
||||||
|
/** 数据库字段:is_deleted */ |
||||||
|
@com.gitee.fastmybatis.core.annotation.LogicDelete |
||||||
|
private Byte isDeleted; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */ |
||||||
|
private Date gmtCreate; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
package com.gitee.sop.adminserver.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Date; |
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class MonitorSummary { |
||||||
|
|
||||||
|
private static final AtomicInteger i = new AtomicInteger(); |
||||||
|
|
||||||
|
private Integer id = i.incrementAndGet(); |
||||||
|
|
||||||
|
private String routeId; |
||||||
|
|
||||||
|
private String name; |
||||||
|
private String version; |
||||||
|
|
||||||
|
/** 数据库字段:service_id */ |
||||||
|
private String serviceId; |
||||||
|
|
||||||
|
/** 数据库字段:instance_id */ |
||||||
|
private String instanceId; |
||||||
|
|
||||||
|
/** 请求耗时最长时间, 数据库字段:max_time */ |
||||||
|
private Integer maxTime; |
||||||
|
|
||||||
|
/** 请求耗时最小时间, 数据库字段:min_time */ |
||||||
|
private Integer minTime; |
||||||
|
|
||||||
|
/** 总时长,毫秒, 数据库字段:total_time */ |
||||||
|
private Long totalTime; |
||||||
|
|
||||||
|
/** 总调用次数, 数据库字段:total_request_count */ |
||||||
|
private Long totalRequestCount; |
||||||
|
|
||||||
|
/** 成功次数, 数据库字段:success_count */ |
||||||
|
private Long successCount; |
||||||
|
|
||||||
|
/** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */ |
||||||
|
private Long errorCount; |
||||||
|
|
||||||
|
/** 未解决的错误数量 */ |
||||||
|
private Long unsolvedErrorCount; |
||||||
|
|
||||||
|
private Float avgTime; |
||||||
|
|
||||||
|
private Boolean hasChildren; |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
package com.gitee.sop.adminserver.mapper; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.mapper.CrudMapper; |
||||||
|
import com.gitee.sop.adminserver.entity.IspResource; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface IspResourceMapper extends CrudMapper<IspResource, Long> { |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
package com.gitee.sop.adminserver.mapper; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.mapper.CrudMapper; |
||||||
|
import com.gitee.sop.adminserver.bean.RouteErrorCount; |
||||||
|
import com.gitee.sop.adminserver.entity.MonitorInfoError; |
||||||
|
import org.apache.ibatis.annotations.Select; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface MonitorInfoErrorMapper extends CrudMapper<MonitorInfoError, Long> { |
||||||
|
|
||||||
|
|
||||||
|
@Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" + |
||||||
|
"WHERE is_deleted=0 \n" + |
||||||
|
"GROUP BY route_id") |
||||||
|
List<RouteErrorCount> listRouteErrorCount(); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package com.gitee.sop.adminserver.mapper; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.mapper.CrudMapper; |
||||||
|
import com.gitee.fastmybatis.core.query.Query; |
||||||
|
import com.gitee.sop.adminserver.entity.MonitorInfo; |
||||||
|
import com.gitee.sop.adminserver.entity.MonitorSummary; |
||||||
|
import org.apache.ibatis.annotations.Param; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface MonitorInfoMapper extends CrudMapper<MonitorInfo, Long> { |
||||||
|
List<MonitorSummary> listMonitorSummary(@Param("query") Query query); |
||||||
|
|
||||||
|
List<MonitorSummary> listInstanceMonitorInfo(@Param("query") Query query); |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||||
|
<!-- 注意:文件名必须跟Dao类名字一致,因为是根据文件名做关联。 --> |
||||||
|
<mapper namespace="com.gitee.sop.adminserver.mapper.MonitorInfoMapper"> |
||||||
|
|
||||||
|
|
||||||
|
<select id="listMonitorSummary" resultType="com.gitee.sop.adminserver.entity.MonitorSummary"> |
||||||
|
SELECT t.*, IFNULL(t2.unsolvedErrorCount, 0) unsolvedErrorCount |
||||||
|
FROM ( |
||||||
|
SELECT |
||||||
|
t.`service_id` serviceId, |
||||||
|
CONCAT(t.name, t.version) routeId, |
||||||
|
t.`name` name, |
||||||
|
t.`version` version, |
||||||
|
SUM(t.`max_time`) maxTime, |
||||||
|
SUM(t.`min_time`) minTime, |
||||||
|
SUM(t.`total_time`) totalTime, |
||||||
|
SUM(t.`total_request_count`) totalRequestCount, |
||||||
|
SUM(t.`success_count`) successCount, |
||||||
|
SUM(t.`error_count`) errorCount, |
||||||
|
SUM(t.`total_time`)/SUM(t.`total_request_count`) avgTime, |
||||||
|
1 hasChildren |
||||||
|
FROM |
||||||
|
`monitor_info` t |
||||||
|
<include refid="common.where"/> |
||||||
|
GROUP BY t.service_id, t.name, t.version |
||||||
|
<include refid="common.orderBy"/> |
||||||
|
) t |
||||||
|
LEFT JOIN |
||||||
|
( |
||||||
|
SELECT route_id, count(*) unsolvedErrorCount |
||||||
|
FROM monitor_info_error WHERE is_deleted=0 GROUP BY route_id |
||||||
|
) t2 on t.routeId = t2.route_id |
||||||
|
</select> |
||||||
|
|
||||||
|
<select id="listInstanceMonitorInfo" resultType="com.gitee.sop.adminserver.entity.MonitorSummary"> |
||||||
|
SELECT |
||||||
|
t.`service_id` serviceId, |
||||||
|
t.`instance_id` instanceId, |
||||||
|
t.route_id routeId, |
||||||
|
t.`name` name, |
||||||
|
t.`version` version, |
||||||
|
t.`max_time` maxTime, |
||||||
|
t.`min_time` minTime, |
||||||
|
t.`total_time` totalTime, |
||||||
|
t.`total_request_count` totalRequestCount, |
||||||
|
t.`success_count` successCount, |
||||||
|
t.`error_count` errorCount, |
||||||
|
IFNULL(t2.unsolvedErrorCount, 0) unsolvedErrorCount, |
||||||
|
t.`total_time`/t.`total_request_count` avgTime, |
||||||
|
0 hasChildren |
||||||
|
FROM |
||||||
|
`monitor_info` t |
||||||
|
LEFT JOIN |
||||||
|
( |
||||||
|
SELECT instance_id, route_id, count(*) unsolvedErrorCount |
||||||
|
FROM monitor_info_error WHERE is_deleted=0 GROUP BY instance_id, route_id |
||||||
|
) t2 on t.route_id = t2.route_id AND t.instance_id = t2.instance_id |
||||||
|
<include refid="common.where" /> |
||||||
|
<include refid="common.orderBy" /> |
||||||
|
</select> |
||||||
|
|
||||||
|
</mapper> |
@ -0,0 +1,10 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> |
||||||
|
<configuration> |
||||||
|
|
||||||
|
<plugins> |
||||||
|
<!-- com.github.pagehelper为PageHelper类所在包名 --> |
||||||
|
<plugin interceptor="com.github.pagehelper.PageInterceptor" /> |
||||||
|
</plugins> |
||||||
|
|
||||||
|
</configuration> |
@ -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.6095bfbf.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":"eac43cde","chunk-2c1f2e8f":"f092c0a0","chunk-2d0d32e7":"e7c489be","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-30c6c34f":"b288bbf5","chunk-4de1c2b6":"e74e3d03","chunk-73b2dcec":"60c5d8e9","chunk-9b31c83a":"494fc338","chunk-9f479afe":"2093f9d0","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-30c6c34f":1,"chunk-4de1c2b6":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-30c6c34f":"3b12267b","chunk-4de1c2b6":"a37cd815","chunk-73b2dcec":"ed391cc5","chunk-9b31c83a":"c4612b4a","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.f323bdd7.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.6095bfbf.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 c,r,a=n[0],f=n[1],i=n[2],d=0,h=[];d<a.length;d++)r=a[d],u[r]&&h.push(u[r][0]),u[r]=0;for(c in f)Object.prototype.hasOwnProperty.call(f,c)&&(e[c]=f[c]);l&&l(n);while(h.length)h.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],c=!0,r=1;r<t.length;r++){var a=t[r];0!==u[a]&&(c=!1)}c&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var c={},r={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-25908fca":"cc77b01c","chunk-2c1f2e8f":"f092c0a0","chunk-2d0d6219":"18813e69","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-2d238661":"23693be8","chunk-30c6c34f":"b288bbf5","chunk-4de1c2b6":"e74e3d03","chunk-73b2dcec":"60c5d8e9","chunk-9b31c83a":"494fc338","chunk-9f479afe":"2093f9d0","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(c[n])return c[n].exports;var t=c[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-30c6c34f":1,"chunk-4de1c2b6":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};r[e]?n.push(r[e]):0!==r[e]&&t[e]&&n.push(r[e]=new Promise(function(n,t){for(var c="static/css/"+({}[e]||e)+"."+{"chunk-25908fca":"a66354ec","chunk-2c1f2e8f":"0314067f","chunk-2d0d6219":"31d6cfe0","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-2d238661":"31d6cfe0","chunk-30c6c34f":"3b12267b","chunk-4de1c2b6":"a37cd815","chunk-73b2dcec":"ed391cc5","chunk-9b31c83a":"c4612b4a","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+c,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===c||d===u))return n()}var h=document.getElementsByTagName("style");for(a=0;a<h.length;a++){i=h[a],d=i.getAttribute("data-href");if(d===c||d===u)return n()}var l=document.createElement("link");l.rel="stylesheet",l.type="text/css",l.onload=n,l.onerror=function(n){var c=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+c+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=c,delete r[e],l.parentNode.removeChild(l),t(o)},l.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(l)}).then(function(){r[e]=0}));var c=u[e];if(0!==c)if(c)n.push(c[2]);else{var o=new Promise(function(n,t){c=u[e]=[n,t]});n.push(c[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(h);var t=u[e];if(0!==t){if(t){var c=n&&("load"===n.type?"missing":n.type),r=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+c+": "+r+")");o.type=c,o.request=r,t[1](o)}u[e]=void 0}};var h=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=c,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 c in e)f.d(t,c,function(n){return e[n]}.bind(null,c));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 h=0;h<i.length;h++)n(i[h]);var l=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.7297829f.js></script></body></html> |
@ -1 +0,0 @@ |
|||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d32e7"],{"5c58":function(t,e,a){"use strict";a.r(e);var l=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"app-container"},[a("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[a("el-form-item",{attrs:{label:"接口名"}},[a("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(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.loadTable}},[t._v("搜索")])],1)],1),t._v(" "),a("el-alert",{staticStyle:{"margin-bottom":"10px"},attrs:{title:"监控数据保存在网关服务器,重启网关数据会清空。",type:"info",closable:!1}}),t._v(" "),a("el-table",{attrs:{data:t.tableData,border:"","default-expand-all":!1,"row-key":"id",height:"500","empty-text":"无数据"}},[a("el-table-column",{attrs:{fixed:"",prop:"instanceId",label:"网关实例",width:"200"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.children?t._e():a("span",[t._v(t._s(e.row.instanceId))])]}}])}),t._v(" "),a("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(" "),a("el-table-column",{attrs:{prop:"serviceId",label:"serviceId",width:"170"}}),t._v(" "),a("el-table-column",{attrs:{prop:"maxTime",label:"最大耗时(ms)",width:"125"}},[a("template",{slot:"header"},[t._v("\n 最大耗时(ms)\n "),a("el-tooltip",{attrs:{effect:"dark",content:"耗时计算:签名验证成功后开始,微服务返回结果后结束",placement:"top"}},[a("i",{staticClass:"el-icon-question",staticStyle:{cursor:"pointer"}})])],1)],2),t._v(" "),a("el-table-column",{attrs:{prop:"minTime",label:"最小耗时(ms)",width:"120"}}),t._v(" "),a("el-table-column",{attrs:{prop:"avgTime",label:"平均耗时(ms)",width:"120"}}),t._v(" "),a("el-table-column",{attrs:{prop:"totalCount",label:"总调用次数",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"successCount",label:"成功次数",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"errorCount",label:"失败次数",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.errorCount>0?a("el-link",{staticStyle:{"text-decoration":"underline"},attrs:{underline:!1,type:"danger"},on:{click:function(a){return t.onShowErrorDetail(e.row)}}},[t._v("\n "+t._s(e.row.errorCount)+"\n ")]):t._e(),t._v(" "),0===e.row.errorCount?a("span",[t._v("0")]):t._e()]}}])},[a("template",{slot:"header"},[t._v("\n 失败次数\n "),a("el-tooltip",{attrs:{effect:"dark",content:"只统计微服务返回的未知错误,JSR-303验证错误算作成功",placement:"top-end"}},[a("i",{staticClass:"el-icon-question",staticStyle:{cursor:"pointer"}})])],1)],2)],1),t._v(" "),a("el-dialog",{attrs:{title:"错误详情",visible:t.logDetailVisible,width:"60%"},on:{"update:visible":function(e){t.logDetailVisible=e}}},[a("div",{staticStyle:{"overflow-x":"auto"},domProps:{innerHTML:t._s(t.errorMsgDetail)}}),t._v(" "),a("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[a("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=a("2877"),s=Object(n["a"])(i,l,o,!1,null,null,null);e["default"]=s.exports}}]); |
|
@ -0,0 +1 @@ |
|||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d6219"],{"70f0":function(t,e,o){"use strict";o.r(e);var a=function(){var t=this,e=t.$createElement,o=t._self._c||e;return o("div",{staticClass:"app-container"},[o("el-form",{attrs:{size:"mini"}},[o("el-form-item",[o("el-button",{attrs:{type:"primary",icon:"el-icon-upload"},on:{click:t.onAddSdk}},[t._v("发布SDK")])],1)],1),t._v(" "),o("el-table",{attrs:{data:t.list,border:""}},[o("el-table-column",{attrs:{prop:"name",label:"SDK",width:"120"}}),t._v(" "),o("el-table-column",{attrs:{prop:"version",label:"版本",width:"120"}}),t._v(" "),o("el-table-column",{attrs:{prop:"content",label:"下载地址"},scopedSlots:t._u([{key:"default",fn:function(e){return[o("el-link",{attrs:{type:"primary",href:e.row.content,target:"_blank"}},[t._v(t._s(e.row.content))])]}}])}),t._v(" "),o("el-table-column",{attrs:{label:"操作",width:"150",align:"center"},scopedSlots:t._u([{key:"default",fn:function(e){return[o("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(o){return t.onSdkUpdate(e.row)}}},[t._v("编辑")]),t._v(" "),o("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(o){return t.onSdkDelete(e.row)}}},[t._v("删除")])]}}])})],1),t._v(" "),o("el-dialog",{attrs:{title:t.sdkDlgTitle,visible:t.sdkDlgAddShow,"close-on-click-modal":!1},on:{"update:visible":function(e){t.sdkDlgAddShow=e},close:function(e){return t.resetForm("sdkAddForm")}}},[o("el-form",{ref:"sdkAddForm",attrs:{model:t.sdkFormAddData,rules:t.sdkFormRule,"label-width":"100px"}},[o("el-form-item",{attrs:{prop:"name",label:"选择语言"}},[o("el-select",{attrs:{placeholder:"请选择"},model:{value:t.sdkFormAddData.name,callback:function(e){t.$set(t.sdkFormAddData,"name",e)},expression:"sdkFormAddData.name"}},t._l(t.sdkConfigs,function(t){return o("el-option",{key:t.name,attrs:{label:t.name,value:t.name}})}),1)],1),t._v(" "),o("el-form-item",{attrs:{prop:"version",label:"版本"}},[o("el-input",{attrs:{maxlength:"30","show-word-limit":"",placeholder:"如:1.0"},model:{value:t.sdkFormAddData.version,callback:function(e){t.$set(t.sdkFormAddData,"version",e)},expression:"sdkFormAddData.version"}})],1),t._v(" "),o("el-form-item",{attrs:{prop:"content",label:"下载地址"}},[o("el-input",{attrs:{maxlength:"100","show-word-limit":""},model:{value:t.sdkFormAddData.content,callback:function(e){t.$set(t.sdkFormAddData,"content",e)},expression:"sdkFormAddData.content"}})],1),t._v(" "),o("el-form-item",{attrs:{prop:"extContent",label:"调用示例"}},[o("el-input",{attrs:{type:"textarea",rows:12,placeholder:"填写SDK调用示例代码,支持markdown语法"},model:{value:t.sdkFormAddData.extContent,callback:function(e){t.$set(t.sdkFormAddData,"extContent",e)},expression:"sdkFormAddData.extContent"}})],1)],1),t._v(" "),o("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[o("el-button",{on:{click:function(e){t.sdkDlgAddShow=!1}}},[t._v("取 消")]),t._v(" "),o("el-button",{attrs:{type:"primary"},on:{click:t.onSubmitForm}},[t._v("保 存")])],1)],1)],1)},n=[],d=(o("7f7f"),function(){return{id:0,name:"",version:"",content:"",extContent:""}}),s={data:function(){return{searchFormData:{},sdkDownloadConfig:[],sdkConfigs:[],sdkDlgTitle:"",sdkDlgAddShow:!1,sdkFormUpdateData:d(),sdkFormAddData:d(),sdkFormLoading:!1,sdkFormRule:{name:[{required:!0,message:"请选择语言",trigger:"blur"}],version:[{required:!0,message:"请填版本",trigger:"blur"}],content:[{required:!0,message:"请填写URL",trigger:"blur"}],extContent:[{required:!0,message:"请填写调用示例",trigger:"blur"}]},downloadUrl:"",list:[]}},created:function(){this.loadLangSelector(),this.loadTable()},methods:{loadLangSelector:function(){var t=this;this.getFile("static/sdkConfig.json?q=".concat((new Date).getTime()),function(e){t.sdkConfigs=e.langList})},loadTable:function(){var t=this;this.post("isp.sdk.list",this.searchFormData,function(e){t.list=e.data})},onSizeChange:function(t){this.searchFormData.pageSize=t,this.loadTable()},onPageIndexChange:function(t){this.searchFormData.pageIndex=t,this.loadTable()},onAddSdk:function(){this.sdkDlgTitle="添加SDK",this.sdkFormAddData=d(),this.sdkDlgAddShow=!0},onSdkUpdate:function(t){this.sdkDlgTitle="修改SDK",this.sdkFormAddData=d(),Object.assign(this.sdkFormAddData,t),this.sdkDlgAddShow=!0},onSdkDelete:function(t){var e=this;this.confirm("确认要删除【".concat(t.name,"】吗?"),function(o){e.post("isp.sdk.delete",{id:t.id},function(t){o(),e.tip("删除成功"),e.loadTable()})})},onSubmitForm:function(){var t=this;this.$refs.sdkAddForm.validate(function(e){if(e){var o=t.sdkFormAddData.id?"isp.sdk.update":"isp.sdk.add";t.post(o,t.sdkFormAddData,function(){this.sdkDlgAddShow=!1,this.loadTable()})}})}}},l=s,r=o("2877"),i=Object(r["a"])(l,a,n,!1,null,null,null);e["default"]=i.exports}}]); |
@ -0,0 +1,24 @@ |
|||||||
|
{ |
||||||
|
"langList": [ |
||||||
|
{ |
||||||
|
"name": "Java" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "C#" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "C++" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Go" |
||||||
|
},{ |
||||||
|
"name": "NodeJS" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Python" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Rust" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -1,6 +1,8 @@ |
|||||||
# just a flag |
# just a flag |
||||||
ENV = 'production' |
ENV = 'production' |
||||||
|
|
||||||
# base api |
# base api, |
||||||
VUE_APP_BASE_API = '/api' |
# 如果要前后端分离,这里填 http(s)://ip:port 末尾没有/ |
||||||
|
# 如:http://www.aaa.com, http://www.bbb.com:7777 |
||||||
|
VUE_APP_BASE_API = '' |
||||||
|
|
||||||
|
@ -0,0 +1,24 @@ |
|||||||
|
{ |
||||||
|
"langList": [ |
||||||
|
{ |
||||||
|
"name": "Java" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "C#" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "C++" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Go" |
||||||
|
},{ |
||||||
|
"name": "NodeJS" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Python" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Rust" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,283 @@ |
|||||||
|
<template> |
||||||
|
<div class="app-container"> |
||||||
|
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini" @submit.native.prevent> |
||||||
|
<el-form-item label="接口名"> |
||||||
|
<el-input v-model="searchFormData.routeId" :clearable="true" style="width: 250px;" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="serviceId"> |
||||||
|
<el-input v-model="searchFormData.serviceId" :clearable="true" style="width: 250px;" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="el-icon-search" native-type="submit" @click="loadTable">查询</el-button> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
<el-table |
||||||
|
:data="pageInfo.list" |
||||||
|
row-key="id" |
||||||
|
lazy |
||||||
|
empty-text="无数据" |
||||||
|
:load="loadInstanceMonitorInfo" |
||||||
|
> |
||||||
|
<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="200" |
||||||
|
> |
||||||
|
<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="150" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="maxTime" |
||||||
|
label="最大耗时(ms)" |
||||||
|
> |
||||||
|
<template slot="header"> |
||||||
|
最大耗时(ms) |
||||||
|
<el-tooltip content="耗时计算:签名验证成功后开始,应用返回结果后结束" placement="top"> |
||||||
|
<i class="el-icon-question" style="cursor: pointer"></i> |
||||||
|
</el-tooltip> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
prop="minTime" |
||||||
|
label="最小耗时(ms)" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="avgTime" |
||||||
|
label="平均耗时(ms)" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
{{ scope.row.avgTime.toFixed(1) }} |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
prop="totalRequestCount" |
||||||
|
label="总调用次数" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="successCount" |
||||||
|
label="成功次数" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="errorCount" |
||||||
|
label="失败次数" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="unsolvedErrorCount" |
||||||
|
label="未解决错误" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-link |
||||||
|
v-if="scope.row.unsolvedErrorCount > 0" |
||||||
|
:underline="false" |
||||||
|
type="danger" |
||||||
|
style="text-decoration: underline;" |
||||||
|
@click="onShowErrorDetail(scope.row)" |
||||||
|
> |
||||||
|
{{ scope.row.unsolvedErrorCount }} |
||||||
|
</el-link> |
||||||
|
<span v-if="scope.row.unsolvedErrorCount === 0">0</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
<el-pagination |
||||||
|
background |
||||||
|
style="margin-top: 5px" |
||||||
|
:current-page="searchFormData.pageIndex" |
||||||
|
:page-size="searchFormData.pageSize" |
||||||
|
:page-sizes="[5, 10, 20, 40]" |
||||||
|
:total="pageInfo.total" |
||||||
|
layout="total, sizes, prev, pager, next" |
||||||
|
@size-change="onSizeChange" |
||||||
|
@current-change="onPageIndexChange" |
||||||
|
/> |
||||||
|
<!-- dialog --> |
||||||
|
<el-dialog |
||||||
|
:title="errorMsgData.title" |
||||||
|
:visible.sync="logDetailVisible" |
||||||
|
:close-on-click-modal="false" |
||||||
|
width="70%" |
||||||
|
@close="onCloseErrorDlg" |
||||||
|
> |
||||||
|
<el-alert |
||||||
|
title="修复错误后请标记解决" |
||||||
|
:closable="false" |
||||||
|
class="el-alert-tip" |
||||||
|
/> |
||||||
|
<el-table |
||||||
|
:data="errorMsgData.pageInfo.rows" |
||||||
|
empty-text="无错误日志" |
||||||
|
> |
||||||
|
<el-table-column |
||||||
|
type="expand" |
||||||
|
> |
||||||
|
<template slot-scope="props"> |
||||||
|
<el-input v-model="props.row.errorMsg" type="textarea" :rows="8" readonly /> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
prop="errorMsg" |
||||||
|
label="错误内容" |
||||||
|
> |
||||||
|
<template slot-scope="props"> |
||||||
|
<span v-if="props.row.errorMsg.length > 50">{{ props.row.errorMsg.substring(0, 50) }}...</span> |
||||||
|
<span v-else>{{ props.row.errorMsg }}</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
prop="instanceId" |
||||||
|
label="实例ID" |
||||||
|
width="150px" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="count" |
||||||
|
label="报错次数" |
||||||
|
width="80px" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="gmtModified" |
||||||
|
label="报错时间" |
||||||
|
width="160px" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
label="操作" |
||||||
|
width="120" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-link type="primary" @click="onSolve(scope.row)">标记解决</el-link> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
<el-pagination |
||||||
|
background |
||||||
|
style="margin-top: 5px" |
||||||
|
:current-page="errorMsgFormData.pageIndex" |
||||||
|
:page-size="errorMsgFormData.pageSize" |
||||||
|
:page-sizes="[5, 10, 20, 40]" |
||||||
|
:total="errorMsgData.pageInfo.total" |
||||||
|
layout="total, sizes, prev, pager, next" |
||||||
|
@size-change="onErrorSizeChange" |
||||||
|
@current-change="onErrorPageIndexChange" |
||||||
|
/> |
||||||
|
<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: '', |
||||||
|
serviceId: '', |
||||||
|
pageIndex: 1, |
||||||
|
pageSize: 20 |
||||||
|
}, |
||||||
|
pageInfo: { |
||||||
|
list: [], |
||||||
|
total: 0 |
||||||
|
}, |
||||||
|
logDetailVisible: false, |
||||||
|
errorMsgFormData: { |
||||||
|
routeId: '', |
||||||
|
instanceId: '', |
||||||
|
pageIndex: 1, |
||||||
|
pageSize: 5 |
||||||
|
}, |
||||||
|
errorMsgData: { |
||||||
|
title: '', |
||||||
|
name: '', |
||||||
|
version: '', |
||||||
|
pageInfo: { |
||||||
|
rows: [], |
||||||
|
total: 0 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.loadTable() |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
loadTable: function() { |
||||||
|
this.post('monitornew.data.page', this.searchFormData, function(resp) { |
||||||
|
this.pageInfo = resp.data |
||||||
|
}) |
||||||
|
}, |
||||||
|
loadErrorData: function() { |
||||||
|
this.post('monitornew.error.page', this.errorMsgFormData, function(resp) { |
||||||
|
this.errorMsgData.pageInfo = resp.data |
||||||
|
this.logDetailVisible = true |
||||||
|
}) |
||||||
|
}, |
||||||
|
loadInstanceMonitorInfo(row, treeNode, resolve) { |
||||||
|
this.post('monitornew.routeid.data.get', { routeId: row.routeId }, resp => { |
||||||
|
const children = resp.data |
||||||
|
row.children = children |
||||||
|
resolve(children) |
||||||
|
}) |
||||||
|
}, |
||||||
|
onShowErrorDetail: function(row) { |
||||||
|
this.errorMsgData.title = `错误日志 ${row.name}(${row.version})` |
||||||
|
this.errorMsgData.name = row.name |
||||||
|
this.errorMsgData.version = row.version |
||||||
|
this.errorMsgFormData.routeId = row.routeId |
||||||
|
this.errorMsgFormData.instanceId = row.instanceId |
||||||
|
this.loadErrorData() |
||||||
|
}, |
||||||
|
onSolve: function(row) { |
||||||
|
this.confirm('确认标记为已解决吗?', function(done) { |
||||||
|
this.post('monitornew.error.solve', { routeId: row.routeId, errorId: row.errorId }, function(resp) { |
||||||
|
done() |
||||||
|
this.errorMsgFormData.pageIndex = 1 |
||||||
|
this.loadErrorData() |
||||||
|
}) |
||||||
|
}) |
||||||
|
}, |
||||||
|
onCloseErrorDlg: function() { |
||||||
|
this.loadTable() |
||||||
|
}, |
||||||
|
onErrorSizeChange: function(size) { |
||||||
|
this.errorMsgFormData.pageSize = size |
||||||
|
this.loadErrorData() |
||||||
|
}, |
||||||
|
onErrorPageIndexChange: function(pageIndex) { |
||||||
|
this.errorMsgFormData.pageIndex = pageIndex |
||||||
|
this.loadErrorData() |
||||||
|
}, |
||||||
|
onSizeChange: function(size) { |
||||||
|
this.searchFormData.pageSize = size |
||||||
|
this.loadTable() |
||||||
|
}, |
||||||
|
onPageIndexChange: function(pageIndex) { |
||||||
|
this.searchFormData.pageIndex = pageIndex |
||||||
|
this.loadTable() |
||||||
|
}, |
||||||
|
onAdd: function() { |
||||||
|
this.dialogTitle = '新增IP' |
||||||
|
this.dialogVisible = true |
||||||
|
this.dialogFormData.id = 0 |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
@ -0,0 +1,179 @@ |
|||||||
|
<template> |
||||||
|
<div class="app-container"> |
||||||
|
<el-form size="mini"> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="el-icon-upload" @click="onAddSdk">发布SDK</el-button> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
<el-table |
||||||
|
:data="list" |
||||||
|
border |
||||||
|
> |
||||||
|
<el-table-column |
||||||
|
prop="name" |
||||||
|
label="SDK" |
||||||
|
width="120" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="version" |
||||||
|
label="版本" |
||||||
|
width="120" |
||||||
|
/> |
||||||
|
<el-table-column |
||||||
|
prop="content" |
||||||
|
label="下载地址" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-link type="primary" :href="scope.row.content" target="_blank">{{ scope.row.content }}</el-link> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column |
||||||
|
label="操作" |
||||||
|
width="150" |
||||||
|
align="center" |
||||||
|
> |
||||||
|
<template slot-scope="scope"> |
||||||
|
<el-button type="text" size="mini" @click="onSdkUpdate(scope.row)">编辑</el-button> |
||||||
|
<el-button type="text" size="mini" @click="onSdkDelete(scope.row)">删除</el-button> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
<!--dialog--> |
||||||
|
<el-dialog |
||||||
|
:title="sdkDlgTitle" |
||||||
|
:visible.sync="sdkDlgAddShow" |
||||||
|
:close-on-click-modal="false" |
||||||
|
@close="resetForm('sdkAddForm')" |
||||||
|
> |
||||||
|
<el-form |
||||||
|
ref="sdkAddForm" |
||||||
|
:model="sdkFormAddData" |
||||||
|
:rules="sdkFormRule" |
||||||
|
label-width="100px" |
||||||
|
> |
||||||
|
<el-form-item prop="name" label="选择语言"> |
||||||
|
<el-select |
||||||
|
v-model="sdkFormAddData.name" |
||||||
|
placeholder="请选择" |
||||||
|
> |
||||||
|
<el-option |
||||||
|
v-for="item in sdkConfigs" |
||||||
|
:key="item.name" |
||||||
|
:label="item.name" |
||||||
|
:value="item.name" |
||||||
|
/> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item prop="version" label="版本"> |
||||||
|
<el-input v-model="sdkFormAddData.version" maxlength="30" show-word-limit placeholder="如:1.0" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item prop="content" label="下载地址"> |
||||||
|
<el-input v-model="sdkFormAddData.content" maxlength="100" show-word-limit /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item prop="extContent" label="调用示例"> |
||||||
|
<el-input v-model="sdkFormAddData.extContent" type="textarea" :rows="12" placeholder="填写SDK调用示例代码,支持markdown语法" /> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
<div slot="footer" class="dialog-footer"> |
||||||
|
<el-button @click="sdkDlgAddShow = false">取 消</el-button> |
||||||
|
<el-button type="primary" @click="onSubmitForm">保 存</el-button> |
||||||
|
</div> |
||||||
|
</el-dialog> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script> |
||||||
|
const appFormDataInit = function() { |
||||||
|
return { |
||||||
|
id: 0, |
||||||
|
name: '', |
||||||
|
version: '', |
||||||
|
content: '', |
||||||
|
extContent: '' |
||||||
|
} |
||||||
|
} |
||||||
|
export default { |
||||||
|
data() { |
||||||
|
return { |
||||||
|
searchFormData: {}, |
||||||
|
sdkDownloadConfig: [], |
||||||
|
sdkConfigs: [], |
||||||
|
sdkDlgTitle: '', |
||||||
|
sdkDlgAddShow: false, |
||||||
|
sdkFormUpdateData: appFormDataInit(), |
||||||
|
sdkFormAddData: appFormDataInit(), |
||||||
|
sdkFormLoading: false, |
||||||
|
sdkFormRule: { |
||||||
|
name: [ |
||||||
|
{ required: true, message: '请选择语言', trigger: 'blur' } |
||||||
|
], |
||||||
|
version: [ |
||||||
|
{ required: true, message: '请填版本', trigger: 'blur' } |
||||||
|
], |
||||||
|
content: [ |
||||||
|
{ required: true, message: '请填写URL', trigger: 'blur' } |
||||||
|
], |
||||||
|
extContent: [ |
||||||
|
{ required: true, message: '请填写调用示例', trigger: 'blur' } |
||||||
|
] |
||||||
|
}, |
||||||
|
downloadUrl: '', |
||||||
|
list: [] |
||||||
|
} |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.loadLangSelector() |
||||||
|
this.loadTable() |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
loadLangSelector: function() { |
||||||
|
this.getFile(`static/sdkConfig.json?q=${new Date().getTime()}`, (content) => { |
||||||
|
this.sdkConfigs = content.langList |
||||||
|
}) |
||||||
|
}, |
||||||
|
loadTable: function() { |
||||||
|
this.post('isp.sdk.list', this.searchFormData, resp => { |
||||||
|
this.list = resp.data |
||||||
|
}) |
||||||
|
}, |
||||||
|
onSizeChange: function(size) { |
||||||
|
this.searchFormData.pageSize = size |
||||||
|
this.loadTable() |
||||||
|
}, |
||||||
|
onPageIndexChange: function(pageIndex) { |
||||||
|
this.searchFormData.pageIndex = pageIndex |
||||||
|
this.loadTable() |
||||||
|
}, |
||||||
|
onAddSdk: function() { |
||||||
|
this.sdkDlgTitle = '添加SDK' |
||||||
|
this.sdkFormAddData = appFormDataInit() |
||||||
|
this.sdkDlgAddShow = true |
||||||
|
}, |
||||||
|
onSdkUpdate: function(row) { |
||||||
|
this.sdkDlgTitle = '修改SDK' |
||||||
|
this.sdkFormAddData = appFormDataInit() |
||||||
|
Object.assign(this.sdkFormAddData, row) |
||||||
|
this.sdkDlgAddShow = true |
||||||
|
}, |
||||||
|
onSdkDelete: function(row) { |
||||||
|
this.confirm(`确认要删除【${row.name}】吗?`, (done) => { |
||||||
|
this.post('isp.sdk.delete', { id: row.id }, resp => { |
||||||
|
done() |
||||||
|
this.tip('删除成功') |
||||||
|
this.loadTable() |
||||||
|
}) |
||||||
|
}) |
||||||
|
}, |
||||||
|
onSubmitForm: function() { |
||||||
|
this.$refs.sdkAddForm.validate((valid) => { |
||||||
|
if (valid) { |
||||||
|
const uri = this.sdkFormAddData.id ? 'isp.sdk.update' : 'isp.sdk.add' |
||||||
|
this.post(uri, this.sdkFormAddData, function() { |
||||||
|
this.sdkDlgAddShow = false |
||||||
|
this.loadTable() |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
@ -0,0 +1,43 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.bean; |
||||||
|
|
||||||
|
import java.util.Collection; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
/** |
||||||
|
* LRU缓存 |
||||||
|
* LinkedHashMap 本身内部有一个触发条件则自动执行的方法:删除最老元素(最近最少使用的元素) |
||||||
|
* 由于最近最少使用元素是 LinkedHashMap 内部处理 |
||||||
|
* 故我们不再需要维护 最近访问元素放在链尾,get 时直接访问/ put 时直接存储 |
||||||
|
* created by Ethan-Walker on 2019/2/16 |
||||||
|
*/ |
||||||
|
public class LRUCache<K, V> { |
||||||
|
private final Map<K, V> map; |
||||||
|
|
||||||
|
public LRUCache(int capacity) { |
||||||
|
map = new LinkedHashMap<K, V>(capacity, 0.75f, true) { |
||||||
|
@Override |
||||||
|
protected boolean removeEldestEntry(Map.Entry eldest) { |
||||||
|
// 容量大于capacity 时就删除
|
||||||
|
return size() > capacity; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
public V get(K key) { |
||||||
|
return map.get(key); |
||||||
|
} |
||||||
|
|
||||||
|
public V computeIfAbsent(K key, |
||||||
|
Function<? super K, ? extends V> mappingFunction) { |
||||||
|
return map.computeIfAbsent(key, mappingFunction); |
||||||
|
} |
||||||
|
|
||||||
|
public V put(K key, V value) { |
||||||
|
return map.put(key, value); |
||||||
|
} |
||||||
|
|
||||||
|
public Collection<V> values() { |
||||||
|
return map.values(); |
||||||
|
} |
||||||
|
} |
@ -1,46 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.gateway.codec; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys; |
|
||||||
import org.springframework.core.codec.AbstractDataBufferDecoder; |
|
||||||
import org.springframework.http.codec.HttpMessageReader; |
|
||||||
import org.springframework.util.ReflectionUtils; |
|
||||||
import org.springframework.web.reactive.function.server.HandlerStrategies; |
|
||||||
|
|
||||||
import java.lang.reflect.Method; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class MessageReaderFactory { |
|
||||||
|
|
||||||
public static final String METHOD_SET_MAX_IN_MEMORY_SIZE = "setMaxInMemorySize"; |
|
||||||
public static final String METHOD_GET_DECODER = "getDecoder"; |
|
||||||
public static final int DEFAULT_SIZE = 256 * 1024; |
|
||||||
|
|
||||||
public static List<HttpMessageReader<?>> build() { |
|
||||||
String maxInMemorySizeValueStr = EnvironmentKeys.MAX_IN_MEMORY_SIZE.getValue(); |
|
||||||
int maxInMemorySizeValue = Integer.parseInt(maxInMemorySizeValueStr); |
|
||||||
List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders(); |
|
||||||
if (DEFAULT_SIZE == maxInMemorySizeValue) { |
|
||||||
return messageReaders; |
|
||||||
} |
|
||||||
// 设置POST缓存大小
|
|
||||||
for (HttpMessageReader<?> httpMessageReader : messageReaders) { |
|
||||||
Method[] methods = ReflectionUtils.getDeclaredMethods(httpMessageReader.getClass()); |
|
||||||
for (Method method : methods) { |
|
||||||
String methodName = method.getName(); |
|
||||||
if (METHOD_SET_MAX_IN_MEMORY_SIZE.equals(methodName)) { |
|
||||||
ReflectionUtils.invokeMethod(method, httpMessageReader, maxInMemorySizeValue); |
|
||||||
} else if (METHOD_GET_DECODER.equals(methodName)) { |
|
||||||
Object decoder = ReflectionUtils.invokeMethod(method, httpMessageReader); |
|
||||||
if (decoder instanceof AbstractDataBufferDecoder) { |
|
||||||
AbstractDataBufferDecoder<?> bufferDecoder = (AbstractDataBufferDecoder<?>) decoder; |
|
||||||
bufferDecoder.setMaxInMemorySize(maxInMemorySizeValue); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return messageReaders; |
|
||||||
} |
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.gateway.controller; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController; |
|
||||||
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 ErrorLogController extends BaseErrorLogController<ServerWebExchange> { |
|
||||||
|
|
||||||
@Override |
|
||||||
protected ApiParam getApiParam(ServerWebExchange request) { |
|
||||||
Map<String, String> params = request.getRequest().getQueryParams().toSingleValueMap(); |
|
||||||
return ApiParam.build(params); |
|
||||||
} |
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
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,58 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.controller; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.SpringContext; |
||||||
|
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient; |
||||||
|
import org.springframework.beans.factory.annotation.Value; |
||||||
|
import org.springframework.cloud.client.ServiceInstance; |
||||||
|
import org.springframework.cloud.gateway.webflux.ProxyExchange; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.stereotype.Controller; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
/** |
||||||
|
* 处理restful请求 |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Controller |
||||||
|
public class RestfulController { |
||||||
|
|
||||||
|
@Value("${sop.restful.path:/rest}") |
||||||
|
private String prefix; |
||||||
|
|
||||||
|
@RequestMapping("${sop.restful.path:/rest}/**") |
||||||
|
public Mono<ResponseEntity<byte[]>> proxy(ProxyExchange<byte[]> proxy, ServerWebExchange exchange) { |
||||||
|
String path = proxy.path(); |
||||||
|
String serviceId = getServiceId(path); |
||||||
|
String targetPath = getTargetPath(serviceId, path); |
||||||
|
String rawQuery = exchange.getRequest().getURI().getRawQuery(); |
||||||
|
if (StringUtils.hasLength(rawQuery)) { |
||||||
|
targetPath = targetPath + "?" + rawQuery; |
||||||
|
} |
||||||
|
// 负载均衡
|
||||||
|
ServiceInstance serviceInstance = SpringContext.getBean(SopLoadBalancerClient.class).choose(serviceId, exchange); |
||||||
|
String uri = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + targetPath; |
||||||
|
return proxy.uri(uri).forward(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 从path中解析出serviceId |
||||||
|
* @param path 格式:/rest/<serviceId><real_path> |
||||||
|
* @return 返回serviceId |
||||||
|
*/ |
||||||
|
private String getServiceId(String path) { |
||||||
|
int length = prefix.length() + 1; |
||||||
|
path = path.substring(length); |
||||||
|
int index = path.indexOf('/'); |
||||||
|
path = path.substring(0, index); |
||||||
|
return path; |
||||||
|
} |
||||||
|
|
||||||
|
private String getTargetPath(String serviceId, String path) { |
||||||
|
int length = prefix.length() + 1; |
||||||
|
int len = length + serviceId.length(); |
||||||
|
return path.substring(len); |
||||||
|
} |
||||||
|
} |