mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
新增路由监控功能
This commit is contained in:
@@ -155,6 +155,7 @@ public class LogApi {
|
|||||||
String sign = md5Verifier.buildSign(params, secret);
|
String sign = md5Verifier.buildSign(params, secret);
|
||||||
params.put("sign", sign);
|
params.put("sign", sign);
|
||||||
String query = QueryUtil.buildQueryString(params);
|
String query = QueryUtil.buildQueryString(params);
|
||||||
|
path = path.startsWith("/") ? path.substring(1) : path;
|
||||||
String url = "http://" + ipPort + "/" + path + "?" + query;
|
String url = "http://" + ipPort + "/" + path + "?" + query;
|
||||||
ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
|
ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
|
||||||
if (entity.getStatusCode() != HttpStatus.OK) {
|
if (entity.getStatusCode() != HttpStatus.OK) {
|
||||||
|
@@ -0,0 +1,163 @@
|
|||||||
|
package com.gitee.sop.adminserver.api.service;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.gitee.easyopen.annotation.Api;
|
||||||
|
import com.gitee.easyopen.annotation.ApiService;
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDoc;
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
|
||||||
|
import com.gitee.easyopen.exception.ApiException;
|
||||||
|
import com.gitee.sop.adminserver.api.service.param.RouteParam;
|
||||||
|
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
|
||||||
|
import com.gitee.sop.adminserver.api.service.param.ServiceSearchParam;
|
||||||
|
import com.gitee.sop.adminserver.api.service.result.MonitorInfoVO;
|
||||||
|
import com.gitee.sop.adminserver.api.service.result.MonitorResult;
|
||||||
|
import com.gitee.sop.adminserver.api.service.result.ServiceInstanceVO;
|
||||||
|
import com.gitee.sop.adminserver.common.QueryUtil;
|
||||||
|
import com.gitee.sop.adminserver.service.ServerService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@ApiService
|
||||||
|
@ApiDoc("服务管理-监控")
|
||||||
|
@Slf4j
|
||||||
|
public class MonitorApi {
|
||||||
|
|
||||||
|
private static final String GATEWAY_MONITOR_PATH = "/sop/getMonitorData";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ServerService serverService;
|
||||||
|
|
||||||
|
@Value("${sop.secret}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@Api(name = "monitor.data.list")
|
||||||
|
@ApiDocMethod(description = "获取监控数据")
|
||||||
|
public MonitorResult listMonitorData(RouteParam param) {
|
||||||
|
ServiceSearchParam serviceSearchParam = new ServiceSearchParam();
|
||||||
|
serviceSearchParam.setServiceId("sop-gateway");
|
||||||
|
String searchRouteId = param.getRouteId();
|
||||||
|
List<MonitorInfoVO> monitorInfoList = new ArrayList<>();
|
||||||
|
List<ServiceInstanceVO> serviceInstanceVOS = serverService.listService(serviceSearchParam);
|
||||||
|
for (ServiceInstanceVO serviceInstanceVO : serviceInstanceVOS) {
|
||||||
|
if (StringUtils.isBlank(serviceInstanceVO.getInstanceId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String ipPort = serviceInstanceVO.getIpPort();
|
||||||
|
try {
|
||||||
|
String data = QueryUtil.requestServer(ipPort, GATEWAY_MONITOR_PATH, secret);
|
||||||
|
JSONObject jsonObject = JSON.parseObject(data);
|
||||||
|
List<MonitorInfoVO> monitorInfoVOList = this.buildMonitorInfoVO(serviceInstanceVO, jsonObject.getJSONObject("data"));
|
||||||
|
List<MonitorInfoVO> newList = monitorInfoVOList.stream()
|
||||||
|
.filter(monitorInfoVO -> StringUtils.isBlank(searchRouteId)
|
||||||
|
|| StringUtils.containsIgnoreCase(monitorInfoVO.getRouteId(), searchRouteId))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
monitorInfoList.addAll(newList);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("请求服务失败, ipPort:{}, path:{}", ipPort, GATEWAY_MONITOR_PATH, e);
|
||||||
|
throw new ApiException("请求数据失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MonitorResult monitorResult = new MonitorResult();
|
||||||
|
List<MonitorInfoVO> monitorInfoTreeData = this.buildTreeData(monitorInfoList);
|
||||||
|
monitorResult.setMonitorInfoData(monitorInfoTreeData);
|
||||||
|
return monitorResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MonitorInfoVO> buildTreeData(List<MonitorInfoVO> monitorInfoList) {
|
||||||
|
AtomicInteger id = new AtomicInteger();
|
||||||
|
List<MonitorInfoVO> treeData = new ArrayList<>(8);
|
||||||
|
monitorInfoList.stream()
|
||||||
|
.collect(Collectors.groupingBy(MonitorInfoVO::getRouteId))
|
||||||
|
.forEach((routeId, items) -> {
|
||||||
|
MonitorInfoVO monitorInfoVOTotal = getStatistics(items, id);
|
||||||
|
monitorInfoVOTotal.setId(id.incrementAndGet());
|
||||||
|
treeData.add(monitorInfoVOTotal);
|
||||||
|
});
|
||||||
|
|
||||||
|
Comparator<MonitorInfoVO> comparator = Comparator
|
||||||
|
// 根据错误次数降序
|
||||||
|
.comparing(MonitorInfoVO::getErrorCount).reversed()
|
||||||
|
// 然后根据请求耗时降序
|
||||||
|
.thenComparing(Comparator.comparing(MonitorInfoVO::getAvgTime).reversed());
|
||||||
|
treeData.sort(comparator);
|
||||||
|
return treeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MonitorInfoVO getStatistics(List<MonitorInfoVO> children, AtomicInteger id) {
|
||||||
|
long totalRequestDataSize = 0;
|
||||||
|
long totalResponseDataSize = 0;
|
||||||
|
long maxTime = 0;
|
||||||
|
long minTime = 0;
|
||||||
|
long totalTime = 0;
|
||||||
|
long totalCount = 0;
|
||||||
|
long successCount = 0;
|
||||||
|
long errorCount = 0;
|
||||||
|
List<String> errorMsgList = new ArrayList<>();
|
||||||
|
|
||||||
|
String name = null,version = null, serviceId = null;
|
||||||
|
|
||||||
|
for (MonitorInfoVO child : children) {
|
||||||
|
name = child.getName();
|
||||||
|
version = child.getVersion();
|
||||||
|
serviceId = child.getServiceId();
|
||||||
|
|
||||||
|
child.setId(id.incrementAndGet());
|
||||||
|
totalRequestDataSize += child.getTotalRequestDataSize();
|
||||||
|
totalResponseDataSize += child.getTotalResponseDataSize();
|
||||||
|
if (minTime == 0 || child.getMinTime() < minTime) {
|
||||||
|
minTime = child.getMinTime();
|
||||||
|
}
|
||||||
|
if (child.getMaxTime() > maxTime) {
|
||||||
|
maxTime = child.getMaxTime();
|
||||||
|
}
|
||||||
|
totalTime += child.getTotalTime();
|
||||||
|
totalCount += child.getTotalCount();
|
||||||
|
successCount += child.getSuccessCount();
|
||||||
|
errorCount += child.getErrorCount();
|
||||||
|
errorMsgList.addAll(child.getErrorMsgList());
|
||||||
|
}
|
||||||
|
|
||||||
|
MonitorInfoVO total = new MonitorInfoVO();
|
||||||
|
total.setName(name);
|
||||||
|
total.setVersion(version);
|
||||||
|
total.setServiceId(serviceId);
|
||||||
|
total.setErrorCount(errorCount);
|
||||||
|
total.setMaxTime(maxTime);
|
||||||
|
total.setMinTime(minTime);
|
||||||
|
total.setSuccessCount(successCount);
|
||||||
|
total.setTotalCount(totalCount);
|
||||||
|
total.setTotalRequestDataSize(totalRequestDataSize);
|
||||||
|
total.setTotalResponseDataSize(totalResponseDataSize);
|
||||||
|
total.setTotalTime(totalTime);
|
||||||
|
total.setChildren(children);
|
||||||
|
total.setErrorMsgList(errorMsgList);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MonitorInfoVO> buildMonitorInfoVO(ServiceInstanceVO serviceInstanceVO, JSONObject monitorData) {
|
||||||
|
Set<String> routeIdList = monitorData.keySet();
|
||||||
|
List<MonitorInfoVO> ret = new ArrayList<>(routeIdList.size());
|
||||||
|
routeIdList.forEach(routeId -> {
|
||||||
|
JSONObject monitorInfo = monitorData.getJSONObject(routeId);
|
||||||
|
MonitorInfoVO monitorInfoVO = monitorInfo.toJavaObject(MonitorInfoVO.class);
|
||||||
|
monitorInfoVO.setInstanceId(serviceInstanceVO.getIpPort());
|
||||||
|
ret.add(monitorInfoVO);
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
package com.gitee.sop.adminserver.api.service.param;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RouteParam {
|
||||||
|
private String routeId;
|
||||||
|
}
|
@@ -0,0 +1,83 @@
|
|||||||
|
package com.gitee.sop.adminserver.api.service.result;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。
|
||||||
|
*
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class MonitorInfoVO {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private String instanceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口名
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* 版本号
|
||||||
|
*/
|
||||||
|
private String version;
|
||||||
|
/**
|
||||||
|
* serviceId
|
||||||
|
*/
|
||||||
|
private String serviceId;
|
||||||
|
/**
|
||||||
|
* 请求耗时最长时间
|
||||||
|
*/
|
||||||
|
private Long maxTime;
|
||||||
|
/**
|
||||||
|
* 请求耗时最小时间
|
||||||
|
*/
|
||||||
|
private Long minTime;
|
||||||
|
/**
|
||||||
|
* 总时长
|
||||||
|
*/
|
||||||
|
private Long totalTime;
|
||||||
|
/**
|
||||||
|
* 总调用次数
|
||||||
|
*/
|
||||||
|
private Long totalCount;
|
||||||
|
/**
|
||||||
|
* 成功次数
|
||||||
|
*/
|
||||||
|
private Long successCount;
|
||||||
|
/**
|
||||||
|
* 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败)
|
||||||
|
*/
|
||||||
|
private Long errorCount;
|
||||||
|
/**
|
||||||
|
* 错误信息
|
||||||
|
*/
|
||||||
|
private List<String> errorMsgList;
|
||||||
|
/**
|
||||||
|
* 总请求数据量
|
||||||
|
*/
|
||||||
|
private Long totalRequestDataSize;
|
||||||
|
/**
|
||||||
|
* 总返回数据量
|
||||||
|
*/
|
||||||
|
private Long totalResponseDataSize;
|
||||||
|
/**
|
||||||
|
* 实例id
|
||||||
|
*/
|
||||||
|
private List<MonitorInfoVO> children;
|
||||||
|
|
||||||
|
public String getRouteId() {
|
||||||
|
return name + version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平均时长,总时长/总调用次数
|
||||||
|
* @return 返回平均时长
|
||||||
|
*/
|
||||||
|
public long getAvgTime() {
|
||||||
|
return totalTime/totalCount;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,13 @@
|
|||||||
|
package com.gitee.sop.adminserver.api.service.result;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class MonitorResult {
|
||||||
|
private List<MonitorInfoVO> monitorInfoData;
|
||||||
|
}
|
@@ -1,7 +1,11 @@
|
|||||||
package com.gitee.sop.adminserver.common;
|
package com.gitee.sop.adminserver.common;
|
||||||
|
|
||||||
|
import com.gitee.easyopen.verify.DefaultMd5Verifier;
|
||||||
|
import com.gitee.sop.adminserver.bean.HttpTool;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,6 +13,8 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class QueryUtil {
|
public class QueryUtil {
|
||||||
|
|
||||||
|
private static HttpTool httpTool = new HttpTool();
|
||||||
|
|
||||||
public static String buildQueryString(Map<String, ?> params) throws UnsupportedEncodingException {
|
public static String buildQueryString(Map<String, ?> params) throws UnsupportedEncodingException {
|
||||||
if (params == null || params.size() == 0) {
|
if (params == null || params.size() == 0) {
|
||||||
return "";
|
return "";
|
||||||
@@ -25,4 +31,16 @@ public class QueryUtil {
|
|||||||
}
|
}
|
||||||
return query.toString();
|
return query.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String requestServer(String ipPort, String path, String secret) throws Exception {
|
||||||
|
DefaultMd5Verifier md5Verifier = new DefaultMd5Verifier();
|
||||||
|
Map<String, Object> params = new HashMap<>(16);
|
||||||
|
params.put("time", System.currentTimeMillis());
|
||||||
|
String sign = md5Verifier.buildSign(params, secret);
|
||||||
|
params.put("sign", sign);
|
||||||
|
String query = QueryUtil.buildQueryString(params);
|
||||||
|
path = path.startsWith("/") ? path.substring(1) : path;
|
||||||
|
String url = "http://" + ipPort + "/" + path + "?" + query;
|
||||||
|
return httpTool.get(url, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.c6dfb7ee.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],d=0,l=[];d<a.length;d++)c=a[d],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);h&&h(n);while(l.length)l.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-25908fca":"66819987","chunk-2c1f2e8f":"f092c0a0","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-4de1c2b6":"e74e3d03","chunk-626b7094":"97d3a892","chunk-6f78c9fe":"3ac83b41","chunk-73b2dcec":"60c5d8e9","chunk-9b31c83a":"52bc6b2c","chunk-9f479afe":"b0599ada","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-25908fca":1,"chunk-2c1f2e8f":1,"chunk-4de1c2b6":1,"chunk-626b7094":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-25908fca":"a66354ec","chunk-2c1f2e8f":"0314067f","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-4de1c2b6":"a37cd815","chunk-626b7094":"e41ad972","chunk-6f78c9fe":"31d6cfe0","chunk-73b2dcec":"ed391cc5","chunk-9b31c83a":"3b12267b","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===r||d===u))return n()}var l=document.getElementsByTagName("style");for(a=0;a<l.length;a++){i=l[a],d=i.getAttribute("data-href");if(d===r||d===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],h.parentNode.removeChild(h),t(o)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=a(e),i=function(n){d.onerror=d.onload=null,clearTimeout(l);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var l=0;l<i.length;l++)n(i[l]);var h=d;t()})([]);</script><script src=static/js/chunk-elementUI.298ac98c.js></script><script src=static/js/chunk-libs.75deb05f.js></script><script src=static/js/app.b21063c4.js></script></body></html>
|
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.c6dfb7ee.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],d=0,l=[];d<a.length;d++)c=a[d],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);h&&h(n);while(l.length)l.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-25908fca":"66819987","chunk-2c1f2e8f":"f092c0a0","chunk-2d0d32e7":"213708f2","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-4de1c2b6":"e74e3d03","chunk-626b7094":"97d3a892","chunk-73b2dcec":"60c5d8e9","chunk-9b31c83a":"52bc6b2c","chunk-9f479afe":"b0599ada","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-25908fca":1,"chunk-2c1f2e8f":1,"chunk-4de1c2b6":1,"chunk-626b7094":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-25908fca":"a66354ec","chunk-2c1f2e8f":"0314067f","chunk-2d0d32e7":"31d6cfe0","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-4de1c2b6":"a37cd815","chunk-626b7094":"e41ad972","chunk-73b2dcec":"ed391cc5","chunk-9b31c83a":"3b12267b","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===r||d===u))return n()}var l=document.getElementsByTagName("style");for(a=0;a<l.length;a++){i=l[a],d=i.getAttribute("data-href");if(d===r||d===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],h.parentNode.removeChild(h),t(o)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=a(e),i=function(n){d.onerror=d.onload=null,clearTimeout(l);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var l=0;l<i.length;l++)n(i[l]);var h=d;t()})([]);</script><script src=static/js/chunk-elementUI.298ac98c.js></script><script src=static/js/chunk-libs.75deb05f.js></script><script src=static/js/app.c6e80241.js></script></body></html>
|
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
@@ -1,7 +1,7 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Router from 'vue-router'
|
import Router from 'vue-router'
|
||||||
|
|
||||||
Vue.use(Router)
|
Vue.use(Router);
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
import Layout from '@/layout'
|
import Layout from '@/layout'
|
||||||
@@ -73,18 +73,18 @@ export const constantRoutes = [
|
|||||||
component: () => import('@/views/service/route'),
|
component: () => import('@/views/service/route'),
|
||||||
meta: { title: '路由管理' }
|
meta: { title: '路由管理' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'monitor',
|
||||||
|
name: 'Monitor',
|
||||||
|
component: () => import('@/views/service/monitor'),
|
||||||
|
meta: { title: '路由监控' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'limit',
|
path: 'limit',
|
||||||
name: 'Limit',
|
name: 'Limit',
|
||||||
component: () => import('@/views/service/limit'),
|
component: () => import('@/views/service/limit'),
|
||||||
meta: { title: '限流管理' }
|
meta: { title: '限流管理' }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'log',
|
|
||||||
name: 'Log',
|
|
||||||
component: () => import('@/views/service/log'),
|
|
||||||
meta: { title: '监控日志' }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'blacklist',
|
path: 'blacklist',
|
||||||
name: 'Blacklist',
|
name: 'Blacklist',
|
||||||
@@ -123,19 +123,19 @@ export const constantRoutes = [
|
|||||||
},
|
},
|
||||||
// 404 page must be placed at the end !!!
|
// 404 page must be placed at the end !!!
|
||||||
{ path: '*', redirect: '/404', hidden: true }
|
{ path: '*', redirect: '/404', hidden: true }
|
||||||
]
|
];
|
||||||
|
|
||||||
const createRouter = () => new Router({
|
const createRouter = () => new Router({
|
||||||
// mode: 'history', // require service support
|
// mode: 'history', // require service support
|
||||||
scrollBehavior: () => ({ y: 0 }),
|
scrollBehavior: () => ({ y: 0 }),
|
||||||
routes: constantRoutes
|
routes: constantRoutes
|
||||||
})
|
});
|
||||||
|
|
||||||
const router = createRouter()
|
const router = createRouter();
|
||||||
|
|
||||||
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
|
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
|
||||||
export function resetRouter() {
|
export function resetRouter() {
|
||||||
const newRouter = createRouter()
|
const newRouter = createRouter();
|
||||||
router.matcher = newRouter.matcher // reset router
|
router.matcher = newRouter.matcher // reset router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
154
sop-admin/sop-admin-vue/src/views/service/monitor.vue
Normal file
154
sop-admin/sop-admin-vue/src/views/service/monitor.vue
Normal file
@@ -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>
|
Reference in New Issue
Block a user