diff --git a/changelog.md b/changelog.md index cddbe075..0657a6a4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # changelog +## 3.1.0 + +- 新增路由监控功能 +- 新增路由拦截器 +- 优化负载均衡策略 + ## 3.0.1 - 增强国际化消息(现SpringCouldGateway支持英文国际化) diff --git a/doc/docs/_sidebar.md b/doc/docs/_sidebar.md index ad54925b..2700b59b 100644 --- a/doc/docs/_sidebar.md +++ b/doc/docs/_sidebar.md @@ -1,38 +1,39 @@ -* [首页](/?t=1580871460178) +* [首页](/?t=1582014833549) * 开发文档 - * [快速体验](files/10010_快速体验.md?t=1580871460179) - * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1580871460202) - * [新增接口](files/10020_新增接口.md?t=1580871460202) - * [开发流程](files/10021_开发流程.md?t=1580871460202) - * [业务参数校验](files/10030_业务参数校验.md?t=1580871460203) - * [错误处理](files/10040_错误处理.md?t=1580871460203) - * [编写文档](files/10041_编写文档.md?t=1580871460203) - * [接口交互详解](files/10050_接口交互详解.md?t=1580871460203) - * [easyopen支持](files/10070_easyopen支持.md?t=1580871460203) - * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1580871460203) - * [ISV管理](files/10085_ISV管理.md?t=1580871460204) - * [自定义返回结果](files/10087_自定义返回结果.md?t=1580871460204) - * [自定义过滤器](files/10088_自定义过滤器.md?t=1580871460204) - * [自定义校验token](files/10089_自定义校验token.md?t=1580871460204) - * [路由授权](files/10090_路由授权.md?t=1580871460204) - * [接口限流](files/10092_接口限流.md?t=1580871460204) - * [监控日志](files/10093_监控日志.md?t=1580871460204) - * [SDK开发](files/10095_SDK开发.md?t=1580871460204) - * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1580871460205) - * [应用授权](files/10097_应用授权.md?t=1580871460205) - * [提供restful接口](files/10100_提供restful接口.md?t=1580871460205) - * [文件上传](files/10104_文件上传.md?t=1580871460205) - * [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1580871460205) - * [预发布灰度发布](files/10110_预发布灰度发布.md?t=1580871460205) - * [动态修改请求参数](files/10111_动态修改请求参数.md?t=1580871460205) - * [使用eureka](files/10112_使用eureka.md?t=1580871460205) - * [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1580871460206) + * [快速体验](files/10010_快速体验.md?t=1582014833552) + * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1582014833568) + * [新增接口](files/10020_新增接口.md?t=1582014833568) + * [开发流程](files/10021_开发流程.md?t=1582014833568) + * [业务参数校验](files/10030_业务参数校验.md?t=1582014833568) + * [错误处理](files/10040_错误处理.md?t=1582014833568) + * [编写文档](files/10041_编写文档.md?t=1582014833569) + * [接口交互详解](files/10050_接口交互详解.md?t=1582014833569) + * [easyopen支持](files/10070_easyopen支持.md?t=1582014833569) + * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1582014833569) + * [ISV管理](files/10085_ISV管理.md?t=1582014833569) + * [自定义返回结果](files/10087_自定义返回结果.md?t=1582014833569) + * [自定义过滤器](files/10088_自定义过滤器.md?t=1582014833569) + * [自定义校验token](files/10089_自定义校验token.md?t=1582014833569) + * [网关拦截器](files/10090_网关拦截器.md?t=1582014833570) + * [路由授权](files/10090_路由授权.md?t=1582014833570) + * [接口限流](files/10092_接口限流.md?t=1582014833570) + * [监控日志](files/10093_监控日志.md?t=1582014833570) + * [SDK开发](files/10095_SDK开发.md?t=1582014833570) + * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1582014833570) + * [应用授权](files/10097_应用授权.md?t=1582014833570) + * [提供restful接口](files/10100_提供restful接口.md?t=1582014833571) + * [文件上传](files/10104_文件上传.md?t=1582014833571) + * [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1582014833571) + * [预发布灰度发布](files/10110_预发布灰度发布.md?t=1582014833571) + * [动态修改请求参数](files/10111_动态修改请求参数.md?t=1582014833571) + * [使用eureka](files/10112_使用eureka.md?t=1582014833571) + * [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1582014833572) * 原理分析 - * [网关性能测试](files/90001_网关性能测试.md?t=1580871460206) - * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1580871460206) - * [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1580871460206) - * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1580871460206) - * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1580871460207) - * [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1580871460207) - * [2.x升3.x注意事项](files/90099_2.x升3.x注意事项.md?t=1580871460207) - * [常见问题](files/90100_常见问题.md?t=1580871460207) + * [网关性能测试](files/90001_网关性能测试.md?t=1582014833572) + * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1582014833572) + * [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1582014833572) + * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1582014833572) + * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1582014833572) + * [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1582014833572) + * [2.x升3.x注意事项](files/90099_2.x升3.x注意事项.md?t=1582014833572) + * [常见问题](files/90100_常见问题.md?t=1582014833572) diff --git a/doc/docs/files/10090_网关拦截器.md b/doc/docs/files/10090_网关拦截器.md new file mode 100644 index 00000000..ee086054 --- /dev/null +++ b/doc/docs/files/10090_网关拦截器.md @@ -0,0 +1,57 @@ +# 网关拦截器 + +从3.1.0开始新增了网关拦截器,使用该拦截器可做一些数据统计,日志记录等工作。 + +使用方法如下: + +- 在sop-gateway工程下新增一个类,实现`RouteInterceptor`接口,实现接口中的方法。别忘了加`@Component` + +```java +@Component +public class MyRouteInterceptor implements RouteInterceptor { + @Override + public void preRoute(RouteInterceptorContext context) { + ApiParam apiParam = context.getApiParam(); + System.out.println("请求接口:" + apiParam.fetchNameVersion()); + } + + @Override + public void afterRoute(RouteInterceptorContext context) { + System.out.println("请求成功,微服务返回结果:" + context.getServiceResult()); + } + + @Override + public int getOrder() { + return 0; + } +} +``` + +RouteInterceptor接口方法说明: + +- `public void preRoute(RouteInterceptorContext context)` + +路由转发前执行,在签名验证通过之后会立即执行这个方法。 + +- `public void afterRoute(RouteInterceptorContext context)` + +路由转发完成后,即拿到微服务返回结果后执行这个方法 + +- `public int getOrder()` + +指定拦截执行顺序,数字小的优先执行,建议从0开始。 + +- `default boolean match(RouteInterceptorContext context)` + +是否匹配,返回true执行拦截器,默认true + +RouteInterceptorContext参数存放了各类参数信息。 + +参考类: + +- `com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor` 拦截器接口 +- `com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext` 拦截器上下文 +- `com.gitee.sop.gatewaycommon.interceptor.MonitorRouteInterceptor` 默认实现的拦截器,用于收集监控数据 + + + diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java index 72ef5e55..f20e86bb 100644 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java @@ -155,6 +155,7 @@ public class LogApi { String sign = md5Verifier.buildSign(params, secret); params.put("sign", sign); String query = QueryUtil.buildQueryString(params); + path = path.startsWith("/") ? path.substring(1) : path; String url = "http://" + ipPort + "/" + path + "?" + query; ResponseEntity entity = restTemplate.getForEntity(url, String.class); if (entity.getStatusCode() != HttpStatus.OK) { diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorApi.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorApi.java new file mode 100644 index 00000000..9fac96a3 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorApi.java @@ -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 monitorInfoList = new ArrayList<>(); + List 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 monitorInfoVOList = this.buildMonitorInfoVO(serviceInstanceVO, jsonObject.getJSONObject("data")); + List 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 monitorInfoTreeData = this.buildTreeData(monitorInfoList); + monitorResult.setMonitorInfoData(monitorInfoTreeData); + return monitorResult; + } + + private List buildTreeData(List monitorInfoList) { + AtomicInteger id = new AtomicInteger(); + List 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 comparator = Comparator + // 根据错误次数降序 + .comparing(MonitorInfoVO::getErrorCount).reversed() + // 然后根据请求耗时降序 + .thenComparing(Comparator.comparing(MonitorInfoVO::getAvgTime).reversed()); + treeData.sort(comparator); + return treeData; + } + + private MonitorInfoVO getStatistics(List 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 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 buildMonitorInfoVO(ServiceInstanceVO serviceInstanceVO, JSONObject monitorData) { + Set routeIdList = monitorData.keySet(); + List 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; + } + +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteParam.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteParam.java new file mode 100644 index 00000000..8f8db8db --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteParam.java @@ -0,0 +1,11 @@ +package com.gitee.sop.adminserver.api.service.param; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class RouteParam { + private String routeId; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorInfoVO.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorInfoVO.java new file mode 100644 index 00000000..66d56221 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorInfoVO.java @@ -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 errorMsgList; + /** + * 总请求数据量 + */ + private Long totalRequestDataSize; + /** + * 总返回数据量 + */ + private Long totalResponseDataSize; + /** + * 实例id + */ + private List children; + + public String getRouteId() { + return name + version; + } + + /** + * 平均时长,总时长/总调用次数 + * @return 返回平均时长 + */ + public long getAvgTime() { + return totalTime/totalCount; + } +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorResult.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorResult.java new file mode 100644 index 00000000..52929ad6 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorResult.java @@ -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 monitorInfoData; +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/QueryUtil.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/QueryUtil.java index 1f00e5c7..29f91563 100644 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/QueryUtil.java +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/QueryUtil.java @@ -1,7 +1,11 @@ package com.gitee.sop.adminserver.common; +import com.gitee.easyopen.verify.DefaultMd5Verifier; +import com.gitee.sop.adminserver.bean.HttpTool; + import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.util.HashMap; import java.util.Map; /** @@ -9,6 +13,8 @@ import java.util.Map; */ public class QueryUtil { + private static HttpTool httpTool = new HttpTool(); + public static String buildQueryString(Map params) throws UnsupportedEncodingException { if (params == null || params.size() == 0) { return ""; @@ -25,4 +31,16 @@ public class QueryUtil { } return query.toString(); } + + public static String requestServer(String ipPort, String path, String secret) throws Exception { + DefaultMd5Verifier md5Verifier = new DefaultMd5Verifier(); + Map 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); + } } diff --git a/sop-admin/sop-admin-server/src/main/resources/public/index.html b/sop-admin/sop-admin-server/src/main/resources/public/index.html index 9ecd2412..a633c2e0 100644 --- a/sop-admin/sop-admin-server/src/main/resources/public/index.html +++ b/sop-admin/sop-admin-server/src/main/resources/public/index.html @@ -1 +1 @@ -SOP Admin
\ No newline at end of file +SOP Admin
\ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/static/js/app.b21063c4.js b/sop-admin/sop-admin-server/src/main/resources/public/static/js/app.c6e80241.js similarity index 52% rename from sop-admin/sop-admin-server/src/main/resources/public/static/js/app.b21063c4.js rename to sop-admin/sop-admin-server/src/main/resources/public/static/js/app.c6e80241.js index 20e8b936..4dc5856a 100644 --- a/sop-admin/sop-admin-server/src/main/resources/public/static/js/app.b21063c4.js +++ b/sop-admin/sop-admin-server/src/main/resources/public/static/js/app.c6e80241.js @@ -1 +1 @@ -(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["app"],{0:function(e,t,n){e.exports=n("56d7")},"0a7b":function(e,t,n){},"0cb8":function(e,t,n){},"186a":function(e,t,n){"use strict";var a=n("dc52"),i=n.n(a);i.a},"18f0":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-link",use:"icon-link-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},2536:function(e,t,n){},"2a3d":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-password",use:"icon-password-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"30c3":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-example",use:"icon-example-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"34c8":function(e,t,n){"use strict";var a=n("88a1"),i=n.n(a);i.a},"405a":function(e,t,n){"use strict";var a=n("4a83"),i=n.n(a);i.a},"47f1":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-table",use:"icon-table-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"4a83":function(e,t,n){},"4df5":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-eye",use:"icon-eye-usage",viewBox:"0 0 128 64",content:''});r.a.add(c);t["default"]=c},"50be":function(e,t,n){"use strict";var a=n("2536"),i=n.n(a);i.a},"51ff":function(e,t,n){var a={"./dashboard.svg":"f782","./example.svg":"30c3","./eye-open.svg":"d7ec","./eye.svg":"4df5","./form.svg":"eb1b","./link.svg":"18f0","./nested.svg":"dcf8","./password.svg":"2a3d","./table.svg":"47f1","./tree.svg":"93cd","./user.svg":"b3b5"};function i(e){var t=o(e);return n(t)}function o(e){var t=a[e];if(!(t+1)){var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}return t}i.keys=function(){return Object.keys(a)},i.resolve=o,e.exports=i,i.id="51ff"},"56d7":function(e,t,n){"use strict";n.r(t);n("cadf"),n("551c"),n("f751"),n("097d");var a=n("2b0e"),i=(n("f5df"),n("5c96")),o=n.n(i),r=(n("0fae"),n("f0d9")),c=n.n(r),s=(n("b20f"),function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{attrs:{id:"app"}},[n("router-view")],1)}),u=[],l={name:"App"},d=l,m=n("2877"),h=Object(m["a"])(d,s,u,!1,null,null,null),f=h.exports,p=n("2f62"),v=(n("7f7f"),{sidebar:function(e){return e.app.sidebar},device:function(e){return e.app.device},token:function(e){return e.user.token},avatar:function(e){return e.user.avatar},name:function(e){return e.user.name}}),b=v,g=n("a78e"),w=n.n(g),x={sidebar:{opened:!w.a.get("sidebarStatus")||!!+w.a.get("sidebarStatus"),withoutAnimation:!1},device:"desktop"},y={TOGGLE_SIDEBAR:function(e){e.sidebar.opened=!e.sidebar.opened,e.sidebar.withoutAnimation=!1,e.sidebar.opened?w.a.set("sidebarStatus",1):w.a.set("sidebarStatus",0)},CLOSE_SIDEBAR:function(e,t){w.a.set("sidebarStatus",0),e.sidebar.opened=!1,e.sidebar.withoutAnimation=t},TOGGLE_DEVICE:function(e,t){e.device=t}},k={toggleSideBar:function(e){var t=e.commit;t("TOGGLE_SIDEBAR")},closeSideBar:function(e,t){var n=e.commit,a=t.withoutAnimation;n("CLOSE_SIDEBAR",a)},toggleDevice:function(e,t){var n=e.commit;n("TOGGLE_DEVICE",t)}},_={namespaced:!0,state:x,mutations:y,actions:k},C=n("83d6"),O=n.n(C),S=O.a.showSettings,z=O.a.fixedHeader,B=O.a.sidebarLogo,H={showSettings:S,fixedHeader:z,sidebarLogo:B},M={CHANGE_SETTING:function(e,t){var n=t.key,a=t.value;e.hasOwnProperty(n)&&(e[n]=a)}},T={changeSetting:function(e,t){var n=e.commit;n("CHANGE_SETTING",t)}},E={namespaced:!0,state:H,mutations:M,actions:T},L=n("bc3a"),$=n.n(L),V=n("5f87"),A=$.a.create({baseURL:"/api",withCredentials:!0,timeout:5e3});A.interceptors.request.use(function(e){return bt.getters.token&&(e.headers["X-Token"]=Object(V["a"])()),e},function(e){return console.log(e),Promise.reject(e)}),A.interceptors.response.use(function(e){var t=e.data;return 2e4!==t.code?(Object(i["Message"])({message:t.message||"error",type:"error",duration:5e3}),50008!==t.code&&50012!==t.code&&50014!==t.code||i["MessageBox"].confirm("You have been logged out, you can cancel to stay on this page, or log in again","Confirm logout",{confirmButtonText:"Re-Login",cancelButtonText:"Cancel",type:"warning"}).then(function(){bt.dispatch("user/resetToken").then(function(){location.reload()})}),Promise.reject(t.message||"error")):t},function(e){return console.log("err"+e),Object(i["Message"])({message:e.message,type:"error",duration:5e3}),Promise.reject(e)});var j=A;function I(e){return j({url:"/user/login",method:"post",data:e})}function P(e){return j({url:"/user/info",method:"get",params:{token:e}})}function N(){return j({url:"/user/logout",method:"post"})}var D=n("8c4f"),R=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"app-wrapper",class:e.classObj},["mobile"===e.device&&e.sidebar.opened?n("div",{staticClass:"drawer-bg",on:{click:e.handleClickOutside}}):e._e(),e._v(" "),n("sidebar",{staticClass:"sidebar-container"}),e._v(" "),n("div",{staticClass:"main-container"},[n("div",{class:{"fixed-header":e.fixedHeader}},[n("navbar")],1),e._v(" "),n("app-main")],1)],1)},q=[],G=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"navbar"},[n("hamburger",{staticClass:"hamburger-container",attrs:{"is-active":e.sidebar.opened},on:{toggleClick:e.toggleSideBar}}),e._v(" "),n("breadcrumb",{staticClass:"breadcrumb-container"}),e._v(" "),n("div",{staticClass:"right-menu"},[n("el-button",{staticStyle:{"margin-right":"10px"},attrs:{type:"text"},on:{click:e.doLogout}},[e._v("退出")])],1)],1)},F=[],U=n("db72"),K=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("el-breadcrumb",{staticClass:"app-breadcrumb",attrs:{separator:"/"}},[n("transition-group",{attrs:{name:"breadcrumb"}},e._l(e.levelList,function(t,a){return n("el-breadcrumb-item",{key:t.path},["noRedirect"===t.redirect||a==e.levelList.length-1?n("span",{staticClass:"no-redirect"},[e._v(e._s(t.meta.title))]):n("a",{on:{click:function(n){return n.preventDefault(),e.handleLink(t)}}},[e._v(e._s(t.meta.title))])])}),1)],1)},W=[],J=n("bd11"),X=n.n(J),Y={data:function(){return{levelList:null}},watch:{$route:function(){this.getBreadcrumb()}},created:function(){this.getBreadcrumb()},methods:{getBreadcrumb:function(){var e=this.$route.matched.filter(function(e){return e.meta&&e.meta.title}),t=e[0];this.isDashboard(t)||(e=[{path:"/dashboard",meta:{title:"Dashboard"}}].concat(e)),this.levelList=e.filter(function(e){return e.meta&&e.meta.title&&!1!==e.meta.breadcrumb})},isDashboard:function(e){var t=e&&e.name;return!!t&&t.trim().toLocaleLowerCase()==="Dashboard".toLocaleLowerCase()},pathCompile:function(e){var t=this.$route.params,n=X.a.compile(e);return n(t)},handleLink:function(e){var t=e.redirect,n=e.path;t?this.$router.push(t):this.$router.push(this.pathCompile(n))}}},Q=Y,Z=(n("34c8"),Object(m["a"])(Q,K,W,!1,null,"62cc9144",null)),ee=Z.exports,te=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticStyle:{padding:"0 15px"},on:{click:e.toggleClick}},[n("svg",{staticClass:"hamburger",class:{"is-active":e.isActive},attrs:{viewBox:"0 0 1024 1024",xmlns:"http://www.w3.org/2000/svg",width:"64",height:"64"}},[n("path",{attrs:{d:"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"}})])])},ne=[],ae={name:"Hamburger",props:{isActive:{type:Boolean,default:!1}},methods:{toggleClick:function(){this.$emit("toggleClick")}}},ie=ae,oe=(n("186a"),Object(m["a"])(ie,te,ne,!1,null,"49e15297",null)),re=oe.exports,ce={components:{Breadcrumb:ee,Hamburger:re},computed:Object(U["a"])({},Object(p["b"])(["sidebar","avatar"])),methods:{toggleSideBar:function(){this.$store.dispatch("app/toggleSideBar")},doLogout:function(){this.logout()}}},se=ce,ue=(n("405a"),Object(m["a"])(se,G,F,!1,null,"1eff5c58",null)),le=ue.exports,de=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{class:{"has-logo":e.showLogo}},[e.showLogo?n("logo",{attrs:{collapse:e.isCollapse}}):e._e(),e._v(" "),n("el-scrollbar",{attrs:{"wrap-class":"scrollbar-wrapper"}},[n("el-menu",{attrs:{"default-active":e.activeMenu,collapse:e.isCollapse,"background-color":e.variables.menuBg,"text-color":e.variables.menuText,"unique-opened":!1,"active-text-color":e.variables.menuActiveText,"collapse-transition":!1,mode:"vertical"}},e._l(e.routes,function(e){return n("sidebar-item",{key:e.path,attrs:{item:e,"base-path":e.path}})}),1)],1)],1)},me=[],he=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"sidebar-logo-container",class:{collapse:e.collapse}},[n("transition",{attrs:{name:"sidebarLogoFade"}},[e.collapse?n("router-link",{key:"collapse",staticClass:"sidebar-logo-link",attrs:{to:"/"}},[e.logo?n("img",{staticClass:"sidebar-logo",attrs:{src:e.logo}}):n("h1",{staticClass:"sidebar-title"},[e._v(e._s(e.title)+" ")])]):n("router-link",{key:"expand",staticClass:"sidebar-logo-link",attrs:{to:"/"}},[e.logo?n("img",{staticClass:"sidebar-logo",attrs:{src:e.logo}}):e._e(),e._v(" "),n("h1",{staticClass:"sidebar-title"},[e._v(e._s(e.title)+" ")])])],1)],1)},fe=[],pe={name:"SidebarLogo",props:{collapse:{type:Boolean,required:!0}},data:function(){return{title:"SOP Admin",logo:"https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png"}}},ve=pe,be=(n("7c27"),Object(m["a"])(ve,he,fe,!1,null,"b905289c",null)),ge=be.exports,we=function(){var e=this,t=e.$createElement,n=e._self._c||t;return e.item.hidden?e._e():n("div",{staticClass:"menu-wrapper"},[!e.hasOneShowingChild(e.item.children,e.item)||e.onlyOneChild.children&&!e.onlyOneChild.noShowingChildren||e.item.alwaysShow?n("el-submenu",{ref:"subMenu",attrs:{index:e.resolvePath(e.item.path),"popper-append-to-body":""}},[n("template",{slot:"title"},[e.item.meta?n("item",{attrs:{icon:e.item.meta&&e.item.meta.icon,title:e.item.meta.title}}):e._e()],1),e._v(" "),e._l(e.item.children,function(t){return n("sidebar-item",{key:t.path,staticClass:"nest-menu",attrs:{"is-nest":!0,item:t,"base-path":e.resolvePath(t.path)}})})],2):[e.onlyOneChild.meta?n("app-link",{attrs:{to:e.resolvePath(e.onlyOneChild.path)}},[n("el-menu-item",{class:{"submenu-title-noDropdown":!e.isNest},attrs:{index:e.resolvePath(e.onlyOneChild.path)}},[n("item",{attrs:{icon:e.onlyOneChild.meta.icon||e.item.meta&&e.item.meta.icon,title:e.onlyOneChild.meta.title}})],1)],1):e._e()]],2)},xe=[],ye=n("df7c"),ke=n.n(ye);function _e(e){return/^(https?:|mailto:|tel:)/.test(e)}var Ce,Oe,Se={name:"MenuItem",functional:!0,props:{icon:{type:String,default:""},title:{type:String,default:""}},render:function(e,t){var n=t.props,a=n.icon,i=n.title,o=[];return a&&o.push(e("svg-icon",{attrs:{"icon-class":a}})),i&&o.push(e("span",{slot:"title"},[i])),o}},ze=Se,Be=Object(m["a"])(ze,Ce,Oe,!1,null,null,null),He=Be.exports,Me=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("component",e._b({},"component",e.linkProps(e.to),!1),[e._t("default")],2)},Te=[],Ee={props:{to:{type:String,required:!0}},methods:{linkProps:function(e){return _e(e)?{is:"a",href:e,target:"_blank",rel:"noopener"}:{is:"router-link",to:e}}}},Le=Ee,$e=Object(m["a"])(Le,Me,Te,!1,null,null,null),Ve=$e.exports,Ae={computed:{device:function(){return this.$store.state.app.device}},mounted:function(){this.fixBugIniOS()},methods:{fixBugIniOS:function(){var e=this,t=this.$refs.subMenu;if(t){var n=t.handleMouseleave;t.handleMouseleave=function(t){"mobile"!==e.device&&n(t)}}}}},je={name:"SidebarItem",components:{Item:He,AppLink:Ve},mixins:[Ae],props:{item:{type:Object,required:!0},isNest:{type:Boolean,default:!1},basePath:{type:String,default:""}},data:function(){return this.onlyOneChild=null,{}},methods:{hasOneShowingChild:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],n=arguments.length>1?arguments[1]:void 0,a=t.filter(function(t){return!t.hidden&&(e.onlyOneChild=t,!0)});return 1===a.length||0===a.length&&(this.onlyOneChild=Object(U["a"])({},n,{path:"",noShowingChildren:!0}),!0)},resolvePath:function(e){return _e(e)?e:_e(this.basePath)?this.basePath:ke.a.resolve(this.basePath,e)}}},Ie=je,Pe=Object(m["a"])(Ie,we,xe,!1,null,null,null),Ne=Pe.exports,De=n("cf1e"),Re=n.n(De),qe={components:{SidebarItem:Ne,Logo:ge},computed:Object(U["a"])({},Object(p["b"])(["sidebar"]),{routes:function(){return this.$router.options.routes},activeMenu:function(){var e=this.$route,t=e.meta,n=e.path;return t.activeMenu?t.activeMenu:n},showLogo:function(){return this.$store.state.settings.sidebarLogo},variables:function(){return Re.a},isCollapse:function(){return!this.sidebar.opened}})},Ge=qe,Fe=Object(m["a"])(Ge,de,me,!1,null,null,null),Ue=Fe.exports,Ke=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("section",{staticClass:"app-main"},[n("transition",{attrs:{name:"fade-transform",mode:"out-in"}},[n("router-view",{key:e.key})],1)],1)},We=[],Je={name:"AppMain",computed:{key:function(){return this.$route.fullPath}}},Xe=Je,Ye=(n("50be"),Object(m["a"])(Xe,Ke,We,!1,null,"43c24f68",null)),Qe=Ye.exports,Ze=document,et=Ze.body,tt=992,nt={watch:{$route:function(e){"mobile"===this.device&&this.sidebar.opened&&bt.dispatch("app/closeSideBar",{withoutAnimation:!1})}},beforeMount:function(){window.addEventListener("resize",this.$_resizeHandler)},beforeDestroy:function(){window.removeEventListener("resize",this.$_resizeHandler)},mounted:function(){var e=this.$_isMobile();e&&(bt.dispatch("app/toggleDevice","mobile"),bt.dispatch("app/closeSideBar",{withoutAnimation:!0}))},methods:{$_isMobile:function(){var e=et.getBoundingClientRect();return e.width-1'});r.a.add(c);t["default"]=c},"9f2b":function(e,t,n){"use strict";var a=n("bf90"),i=n.n(a);i.a},b20f:function(e,t,n){e.exports={menuText:"#bfcbd9",menuActiveText:"#409EFF",subMenuActiveText:"#f4f4f5",menuBg:"#304156",menuHover:"#263445",subMenuBg:"#1f2d3d",subMenuHover:"#001528",sideBarWidth:"210px"}},b3b5:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-user",use:"icon-user-usage",viewBox:"0 0 130 130",content:''});r.a.add(c);t["default"]=c},bf90:function(e,t,n){},cf1e:function(e,t,n){e.exports={menuText:"#bfcbd9",menuActiveText:"#409EFF",subMenuActiveText:"#f4f4f5",menuBg:"#304156",menuHover:"#263445",subMenuBg:"#1f2d3d",subMenuHover:"#001528",sideBarWidth:"210px"}},d7ec:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-eye-open",use:"icon-eye-open-usage",viewBox:"0 0 1024 1024",content:''});r.a.add(c);t["default"]=c},dc52:function(e,t,n){},dcf8:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-nested",use:"icon-nested-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},eb1b:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-form",use:"icon-form-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},f782:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-dashboard",use:"icon-dashboard-usage",viewBox:"0 0 128 100",content:''});r.a.add(c);t["default"]=c}},[[0,"runtime","chunk-elementUI","chunk-libs"]]]); \ No newline at end of file +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["app"],{0:function(e,t,n){e.exports=n("56d7")},"0a7b":function(e,t,n){},"0cb8":function(e,t,n){},"186a":function(e,t,n){"use strict";var a=n("dc52"),i=n.n(a);i.a},"18f0":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-link",use:"icon-link-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},2536:function(e,t,n){},"2a3d":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-password",use:"icon-password-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"30c3":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-example",use:"icon-example-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"34c8":function(e,t,n){"use strict";var a=n("88a1"),i=n.n(a);i.a},"405a":function(e,t,n){"use strict";var a=n("4a83"),i=n.n(a);i.a},"47f1":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-table",use:"icon-table-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},"4a83":function(e,t,n){},"4df5":function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-eye",use:"icon-eye-usage",viewBox:"0 0 128 64",content:''});r.a.add(c);t["default"]=c},"50be":function(e,t,n){"use strict";var a=n("2536"),i=n.n(a);i.a},"51ff":function(e,t,n){var a={"./dashboard.svg":"f782","./example.svg":"30c3","./eye-open.svg":"d7ec","./eye.svg":"4df5","./form.svg":"eb1b","./link.svg":"18f0","./nested.svg":"dcf8","./password.svg":"2a3d","./table.svg":"47f1","./tree.svg":"93cd","./user.svg":"b3b5"};function i(e){var t=o(e);return n(t)}function o(e){var t=a[e];if(!(t+1)){var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}return t}i.keys=function(){return Object.keys(a)},i.resolve=o,e.exports=i,i.id="51ff"},"56d7":function(e,t,n){"use strict";n.r(t);n("cadf"),n("551c"),n("f751"),n("097d");var a=n("2b0e"),i=(n("f5df"),n("5c96")),o=n.n(i),r=(n("0fae"),n("f0d9")),c=n.n(r),s=(n("b20f"),function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{attrs:{id:"app"}},[n("router-view")],1)}),u=[],l={name:"App"},d=l,m=n("2877"),h=Object(m["a"])(d,s,u,!1,null,null,null),f=h.exports,p=n("2f62"),v=(n("7f7f"),{sidebar:function(e){return e.app.sidebar},device:function(e){return e.app.device},token:function(e){return e.user.token},avatar:function(e){return e.user.avatar},name:function(e){return e.user.name}}),b=v,g=n("a78e"),w=n.n(g),x={sidebar:{opened:!w.a.get("sidebarStatus")||!!+w.a.get("sidebarStatus"),withoutAnimation:!1},device:"desktop"},y={TOGGLE_SIDEBAR:function(e){e.sidebar.opened=!e.sidebar.opened,e.sidebar.withoutAnimation=!1,e.sidebar.opened?w.a.set("sidebarStatus",1):w.a.set("sidebarStatus",0)},CLOSE_SIDEBAR:function(e,t){w.a.set("sidebarStatus",0),e.sidebar.opened=!1,e.sidebar.withoutAnimation=t},TOGGLE_DEVICE:function(e,t){e.device=t}},k={toggleSideBar:function(e){var t=e.commit;t("TOGGLE_SIDEBAR")},closeSideBar:function(e,t){var n=e.commit,a=t.withoutAnimation;n("CLOSE_SIDEBAR",a)},toggleDevice:function(e,t){var n=e.commit;n("TOGGLE_DEVICE",t)}},_={namespaced:!0,state:x,mutations:y,actions:k},C=n("83d6"),O=n.n(C),S=O.a.showSettings,z=O.a.fixedHeader,B=O.a.sidebarLogo,M={showSettings:S,fixedHeader:z,sidebarLogo:B},H={CHANGE_SETTING:function(e,t){var n=t.key,a=t.value;e.hasOwnProperty(n)&&(e[n]=a)}},T={changeSetting:function(e,t){var n=e.commit;n("CHANGE_SETTING",t)}},E={namespaced:!0,state:M,mutations:H,actions:T},L=n("bc3a"),$=n.n(L),V=n("5f87"),A=$.a.create({baseURL:"/api",withCredentials:!0,timeout:5e3});A.interceptors.request.use(function(e){return bt.getters.token&&(e.headers["X-Token"]=Object(V["a"])()),e},function(e){return console.log(e),Promise.reject(e)}),A.interceptors.response.use(function(e){var t=e.data;return 2e4!==t.code?(Object(i["Message"])({message:t.message||"error",type:"error",duration:5e3}),50008!==t.code&&50012!==t.code&&50014!==t.code||i["MessageBox"].confirm("You have been logged out, you can cancel to stay on this page, or log in again","Confirm logout",{confirmButtonText:"Re-Login",cancelButtonText:"Cancel",type:"warning"}).then(function(){bt.dispatch("user/resetToken").then(function(){location.reload()})}),Promise.reject(t.message||"error")):t},function(e){return console.log("err"+e),Object(i["Message"])({message:e.message,type:"error",duration:5e3}),Promise.reject(e)});var j=A;function I(e){return j({url:"/user/login",method:"post",data:e})}function P(e){return j({url:"/user/info",method:"get",params:{token:e}})}function N(){return j({url:"/user/logout",method:"post"})}var D=n("8c4f"),R=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"app-wrapper",class:e.classObj},["mobile"===e.device&&e.sidebar.opened?n("div",{staticClass:"drawer-bg",on:{click:e.handleClickOutside}}):e._e(),e._v(" "),n("sidebar",{staticClass:"sidebar-container"}),e._v(" "),n("div",{staticClass:"main-container"},[n("div",{class:{"fixed-header":e.fixedHeader}},[n("navbar")],1),e._v(" "),n("app-main")],1)],1)},q=[],G=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"navbar"},[n("hamburger",{staticClass:"hamburger-container",attrs:{"is-active":e.sidebar.opened},on:{toggleClick:e.toggleSideBar}}),e._v(" "),n("breadcrumb",{staticClass:"breadcrumb-container"}),e._v(" "),n("div",{staticClass:"right-menu"},[n("el-button",{staticStyle:{"margin-right":"10px"},attrs:{type:"text"},on:{click:e.doLogout}},[e._v("退出")])],1)],1)},F=[],U=n("db72"),K=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("el-breadcrumb",{staticClass:"app-breadcrumb",attrs:{separator:"/"}},[n("transition-group",{attrs:{name:"breadcrumb"}},e._l(e.levelList,function(t,a){return n("el-breadcrumb-item",{key:t.path},["noRedirect"===t.redirect||a==e.levelList.length-1?n("span",{staticClass:"no-redirect"},[e._v(e._s(t.meta.title))]):n("a",{on:{click:function(n){return n.preventDefault(),e.handleLink(t)}}},[e._v(e._s(t.meta.title))])])}),1)],1)},W=[],J=n("bd11"),X=n.n(J),Y={data:function(){return{levelList:null}},watch:{$route:function(){this.getBreadcrumb()}},created:function(){this.getBreadcrumb()},methods:{getBreadcrumb:function(){var e=this.$route.matched.filter(function(e){return e.meta&&e.meta.title}),t=e[0];this.isDashboard(t)||(e=[{path:"/dashboard",meta:{title:"Dashboard"}}].concat(e)),this.levelList=e.filter(function(e){return e.meta&&e.meta.title&&!1!==e.meta.breadcrumb})},isDashboard:function(e){var t=e&&e.name;return!!t&&t.trim().toLocaleLowerCase()==="Dashboard".toLocaleLowerCase()},pathCompile:function(e){var t=this.$route.params,n=X.a.compile(e);return n(t)},handleLink:function(e){var t=e.redirect,n=e.path;t?this.$router.push(t):this.$router.push(this.pathCompile(n))}}},Q=Y,Z=(n("34c8"),Object(m["a"])(Q,K,W,!1,null,"62cc9144",null)),ee=Z.exports,te=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticStyle:{padding:"0 15px"},on:{click:e.toggleClick}},[n("svg",{staticClass:"hamburger",class:{"is-active":e.isActive},attrs:{viewBox:"0 0 1024 1024",xmlns:"http://www.w3.org/2000/svg",width:"64",height:"64"}},[n("path",{attrs:{d:"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"}})])])},ne=[],ae={name:"Hamburger",props:{isActive:{type:Boolean,default:!1}},methods:{toggleClick:function(){this.$emit("toggleClick")}}},ie=ae,oe=(n("186a"),Object(m["a"])(ie,te,ne,!1,null,"49e15297",null)),re=oe.exports,ce={components:{Breadcrumb:ee,Hamburger:re},computed:Object(U["a"])({},Object(p["b"])(["sidebar","avatar"])),methods:{toggleSideBar:function(){this.$store.dispatch("app/toggleSideBar")},doLogout:function(){this.logout()}}},se=ce,ue=(n("405a"),Object(m["a"])(se,G,F,!1,null,"1eff5c58",null)),le=ue.exports,de=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{class:{"has-logo":e.showLogo}},[e.showLogo?n("logo",{attrs:{collapse:e.isCollapse}}):e._e(),e._v(" "),n("el-scrollbar",{attrs:{"wrap-class":"scrollbar-wrapper"}},[n("el-menu",{attrs:{"default-active":e.activeMenu,collapse:e.isCollapse,"background-color":e.variables.menuBg,"text-color":e.variables.menuText,"unique-opened":!1,"active-text-color":e.variables.menuActiveText,"collapse-transition":!1,mode:"vertical"}},e._l(e.routes,function(e){return n("sidebar-item",{key:e.path,attrs:{item:e,"base-path":e.path}})}),1)],1)],1)},me=[],he=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"sidebar-logo-container",class:{collapse:e.collapse}},[n("transition",{attrs:{name:"sidebarLogoFade"}},[e.collapse?n("router-link",{key:"collapse",staticClass:"sidebar-logo-link",attrs:{to:"/"}},[e.logo?n("img",{staticClass:"sidebar-logo",attrs:{src:e.logo}}):n("h1",{staticClass:"sidebar-title"},[e._v(e._s(e.title)+" ")])]):n("router-link",{key:"expand",staticClass:"sidebar-logo-link",attrs:{to:"/"}},[e.logo?n("img",{staticClass:"sidebar-logo",attrs:{src:e.logo}}):e._e(),e._v(" "),n("h1",{staticClass:"sidebar-title"},[e._v(e._s(e.title)+" ")])])],1)],1)},fe=[],pe={name:"SidebarLogo",props:{collapse:{type:Boolean,required:!0}},data:function(){return{title:"SOP Admin",logo:"https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png"}}},ve=pe,be=(n("7c27"),Object(m["a"])(ve,he,fe,!1,null,"b905289c",null)),ge=be.exports,we=function(){var e=this,t=e.$createElement,n=e._self._c||t;return e.item.hidden?e._e():n("div",{staticClass:"menu-wrapper"},[!e.hasOneShowingChild(e.item.children,e.item)||e.onlyOneChild.children&&!e.onlyOneChild.noShowingChildren||e.item.alwaysShow?n("el-submenu",{ref:"subMenu",attrs:{index:e.resolvePath(e.item.path),"popper-append-to-body":""}},[n("template",{slot:"title"},[e.item.meta?n("item",{attrs:{icon:e.item.meta&&e.item.meta.icon,title:e.item.meta.title}}):e._e()],1),e._v(" "),e._l(e.item.children,function(t){return n("sidebar-item",{key:t.path,staticClass:"nest-menu",attrs:{"is-nest":!0,item:t,"base-path":e.resolvePath(t.path)}})})],2):[e.onlyOneChild.meta?n("app-link",{attrs:{to:e.resolvePath(e.onlyOneChild.path)}},[n("el-menu-item",{class:{"submenu-title-noDropdown":!e.isNest},attrs:{index:e.resolvePath(e.onlyOneChild.path)}},[n("item",{attrs:{icon:e.onlyOneChild.meta.icon||e.item.meta&&e.item.meta.icon,title:e.onlyOneChild.meta.title}})],1)],1):e._e()]],2)},xe=[],ye=n("df7c"),ke=n.n(ye);function _e(e){return/^(https?:|mailto:|tel:)/.test(e)}var Ce,Oe,Se={name:"MenuItem",functional:!0,props:{icon:{type:String,default:""},title:{type:String,default:""}},render:function(e,t){var n=t.props,a=n.icon,i=n.title,o=[];return a&&o.push(e("svg-icon",{attrs:{"icon-class":a}})),i&&o.push(e("span",{slot:"title"},[i])),o}},ze=Se,Be=Object(m["a"])(ze,Ce,Oe,!1,null,null,null),Me=Be.exports,He=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("component",e._b({},"component",e.linkProps(e.to),!1),[e._t("default")],2)},Te=[],Ee={props:{to:{type:String,required:!0}},methods:{linkProps:function(e){return _e(e)?{is:"a",href:e,target:"_blank",rel:"noopener"}:{is:"router-link",to:e}}}},Le=Ee,$e=Object(m["a"])(Le,He,Te,!1,null,null,null),Ve=$e.exports,Ae={computed:{device:function(){return this.$store.state.app.device}},mounted:function(){this.fixBugIniOS()},methods:{fixBugIniOS:function(){var e=this,t=this.$refs.subMenu;if(t){var n=t.handleMouseleave;t.handleMouseleave=function(t){"mobile"!==e.device&&n(t)}}}}},je={name:"SidebarItem",components:{Item:Me,AppLink:Ve},mixins:[Ae],props:{item:{type:Object,required:!0},isNest:{type:Boolean,default:!1},basePath:{type:String,default:""}},data:function(){return this.onlyOneChild=null,{}},methods:{hasOneShowingChild:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],n=arguments.length>1?arguments[1]:void 0,a=t.filter(function(t){return!t.hidden&&(e.onlyOneChild=t,!0)});return 1===a.length||0===a.length&&(this.onlyOneChild=Object(U["a"])({},n,{path:"",noShowingChildren:!0}),!0)},resolvePath:function(e){return _e(e)?e:_e(this.basePath)?this.basePath:ke.a.resolve(this.basePath,e)}}},Ie=je,Pe=Object(m["a"])(Ie,we,xe,!1,null,null,null),Ne=Pe.exports,De=n("cf1e"),Re=n.n(De),qe={components:{SidebarItem:Ne,Logo:ge},computed:Object(U["a"])({},Object(p["b"])(["sidebar"]),{routes:function(){return this.$router.options.routes},activeMenu:function(){var e=this.$route,t=e.meta,n=e.path;return t.activeMenu?t.activeMenu:n},showLogo:function(){return this.$store.state.settings.sidebarLogo},variables:function(){return Re.a},isCollapse:function(){return!this.sidebar.opened}})},Ge=qe,Fe=Object(m["a"])(Ge,de,me,!1,null,null,null),Ue=Fe.exports,Ke=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("section",{staticClass:"app-main"},[n("transition",{attrs:{name:"fade-transform",mode:"out-in"}},[n("router-view",{key:e.key})],1)],1)},We=[],Je={name:"AppMain",computed:{key:function(){return this.$route.fullPath}}},Xe=Je,Ye=(n("50be"),Object(m["a"])(Xe,Ke,We,!1,null,"43c24f68",null)),Qe=Ye.exports,Ze=document,et=Ze.body,tt=992,nt={watch:{$route:function(e){"mobile"===this.device&&this.sidebar.opened&&bt.dispatch("app/closeSideBar",{withoutAnimation:!1})}},beforeMount:function(){window.addEventListener("resize",this.$_resizeHandler)},beforeDestroy:function(){window.removeEventListener("resize",this.$_resizeHandler)},mounted:function(){var e=this.$_isMobile();e&&(bt.dispatch("app/toggleDevice","mobile"),bt.dispatch("app/closeSideBar",{withoutAnimation:!0}))},methods:{$_isMobile:function(){var e=et.getBoundingClientRect();return e.width-1'});r.a.add(c);t["default"]=c},"9f2b":function(e,t,n){"use strict";var a=n("bf90"),i=n.n(a);i.a},b20f:function(e,t,n){e.exports={menuText:"#bfcbd9",menuActiveText:"#409EFF",subMenuActiveText:"#f4f4f5",menuBg:"#304156",menuHover:"#263445",subMenuBg:"#1f2d3d",subMenuHover:"#001528",sideBarWidth:"210px"}},b3b5:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-user",use:"icon-user-usage",viewBox:"0 0 130 130",content:''});r.a.add(c);t["default"]=c},bf90:function(e,t,n){},cf1e:function(e,t,n){e.exports={menuText:"#bfcbd9",menuActiveText:"#409EFF",subMenuActiveText:"#f4f4f5",menuBg:"#304156",menuHover:"#263445",subMenuBg:"#1f2d3d",subMenuHover:"#001528",sideBarWidth:"210px"}},d7ec:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-eye-open",use:"icon-eye-open-usage",viewBox:"0 0 1024 1024",content:''});r.a.add(c);t["default"]=c},dc52:function(e,t,n){},dcf8:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-nested",use:"icon-nested-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},eb1b:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-form",use:"icon-form-usage",viewBox:"0 0 128 128",content:''});r.a.add(c);t["default"]=c},f782:function(e,t,n){"use strict";n.r(t);var a=n("e017"),i=n.n(a),o=n("21a1"),r=n.n(o),c=new i.a({id:"icon-dashboard",use:"icon-dashboard-usage",viewBox:"0 0 128 100",content:''});r.a.add(c);t["default"]=c}},[[0,"runtime","chunk-elementUI","chunk-libs"]]]); \ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d32e7.213708f2.js b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d32e7.213708f2.js new file mode 100644 index 00000000..d8de8452 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d32e7.213708f2.js @@ -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("
"):"无内容",this.logDetailVisible=!0}}},i=r,n=l("2877"),s=Object(n["a"])(i,a,o,!1,null,null,null);e["default"]=s.exports}}]); \ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-6f78c9fe.3ac83b41.js b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-6f78c9fe.3ac83b41.js deleted file mode 100644 index c8511867..00000000 --- a/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-6f78c9fe.3ac83b41.js +++ /dev/null @@ -1 +0,0 @@ -(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-6f78c9fe"],{"02f4":function(t,e,n){var r=n("4588"),i=n("be13");t.exports=function(t){return function(e,n){var o,a,l=String(i(e)),c=r(n),s=l.length;return c<0||c>=s?t?"":void 0:(o=l.charCodeAt(c),o<55296||o>56319||c+1===s||(a=l.charCodeAt(c+1))<56320||a>57343?t?l.charAt(c):o:t?l.slice(c,c+2):a-56320+(o-55296<<10)+65536)}}},"0390":function(t,e,n){"use strict";var r=n("02f4")(!0);t.exports=function(t,e,n){return e+(n?r(t,e).length:1)}},"0a49":function(t,e,n){var r=n("9b43"),i=n("626a"),o=n("4bf8"),a=n("9def"),l=n("cd1c");t.exports=function(t,e){var n=1==t,c=2==t,s=3==t,u=4==t,f=6==t,d=5==t||f,v=e||l;return function(e,l,p){for(var g,h,b=o(e),m=i(b),D=r(l,p,3),x=a(m.length),y=0,_=n?v(e,x):c?v(e,0):void 0;x>y;y++)if((d||y in m)&&(g=m[y],h=D(g,y,b),t))if(n)_[y]=h;else if(h)switch(t){case 3:return!0;case 5:return g;case 6:return y;case 2:_.push(g)}else if(u)return!1;return f?-1:s||u?u:_}}},"0bfb":function(t,e,n){"use strict";var r=n("cb7c");t.exports=function(){var t=r(this),e="";return t.global&&(e+="g"),t.ignoreCase&&(e+="i"),t.multiline&&(e+="m"),t.unicode&&(e+="u"),t.sticky&&(e+="y"),e}},1169:function(t,e,n){var r=n("2d95");t.exports=Array.isArray||function(t){return"Array"==r(t)}},"20d6":function(t,e,n){"use strict";var r=n("5ca1"),i=n("0a49")(6),o="findIndex",a=!0;o in[]&&Array(1)[o](function(){a=!1}),r(r.P+r.F*a,"Array",{findIndex:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),n("9c6c")(o)},"214f":function(t,e,n){"use strict";n("b0c5");var r=n("2aba"),i=n("32e9"),o=n("79e5"),a=n("be13"),l=n("2b4c"),c=n("520a"),s=l("species"),u=!o(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")}),f=function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2===n.length&&"a"===n[0]&&"b"===n[1]}();t.exports=function(t,e,n){var d=l(t),v=!o(function(){var e={};return e[d]=function(){return 7},7!=""[t](e)}),p=v?!o(function(){var e=!1,n=/a/;return n.exec=function(){return e=!0,null},"split"===t&&(n.constructor={},n.constructor[s]=function(){return n}),n[d](""),!e}):void 0;if(!v||!p||"replace"===t&&!u||"split"===t&&!f){var g=/./[d],h=n(a,d,""[t],function(t,e,n,r,i){return e.exec===c?v&&!i?{done:!0,value:g.call(e,n,r)}:{done:!0,value:t.call(n,e,r)}:{done:!1}}),b=h[0],m=h[1];r(String.prototype,t,b),i(RegExp.prototype,d,2==e?function(t,e){return m.call(t,this,e)}:function(t){return m.call(t,this)})}}},"520a":function(t,e,n){"use strict";var r=n("0bfb"),i=RegExp.prototype.exec,o=String.prototype.replace,a=i,l="lastIndex",c=function(){var t=/a/,e=/b*/g;return i.call(t,"a"),i.call(e,"a"),0!==t[l]||0!==e[l]}(),s=void 0!==/()??/.exec("")[1],u=c||s;u&&(a=function(t){var e,n,a,u,f=this;return s&&(n=new RegExp("^"+f.source+"$(?!\\s)",r.call(f))),c&&(e=f[l]),a=i.call(f,t),c&&a&&(f[l]=f.global?a.index+a[0].length:e),s&&a&&a.length>1&&o.call(a[0],n,function(){for(u=1;u]*>)/g,v=/\$([$&`']|\d\d?)/g,p=function(t){return void 0===t?t:String(t)};n("214f")("replace",2,function(t,e,n,g){return[function(r,i){var o=t(this),a=void 0==r?void 0:r[e];return void 0!==a?a.call(r,o,i):n.call(String(o),r,i)},function(t,e){var i=g(n,t,this,e);if(i.done)return i.value;var f=r(t),d=String(this),v="function"===typeof e;v||(e=String(e));var b=f.global;if(b){var m=f.unicode;f.lastIndex=0}var D=[];while(1){var x=c(f,d);if(null===x)break;if(D.push(x),!b)break;var y=String(x[0]);""===y&&(f.lastIndex=l(d,o(f.lastIndex),m))}for(var _="",w=0,I=0;I=w&&(_+=d.slice(w,k)+L,w=k+S.length)}return _+d.slice(w)}];function h(t,e,r,o,a,l){var c=r+t.length,s=o.length,u=v;return void 0!==a&&(a=i(a),u=d),n.call(l,u,function(n,i){var l;switch(i.charAt(0)){case"$":return"$";case"&":return t;case"`":return e.slice(0,r);case"'":return e.slice(c);case"<":l=a[i.slice(1,-1)];break;default:var u=+i;if(0===u)return n;if(u>s){var d=f(u/10);return 0===d?n:d<=s?void 0===o[d-1]?i.charAt(1):o[d-1]+i.charAt(1):n}l=o[u-1]}return void 0===l?"":l})}})},b0c5:function(t,e,n){"use strict";var r=n("520a");n("5ca1")({target:"RegExp",proto:!0,forced:r!==/./.exec},{exec:r})},cd1c:function(t,e,n){var r=n("e853");t.exports=function(t,e){return new(r(t))(e)}},e75e:function(t,e,n){"use strict";n.r(e);var r=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"app-container"},[n("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[n("el-form-item",[n("el-button",{attrs:{type:"primary",icon:"el-icon-plus"},on:{click:t.onAddServer}},[t._v("添加监控服务器")])],1)],1),t._v(" "),n("el-table",{staticStyle:{width:"100%","margin-bottom":"20px"},attrs:{data:t.tableData,border:"","default-expand-all":!0,"row-key":"treeId","empty-text":"请添加监控服务器"}},[n("el-table-column",{attrs:{prop:"monitorName",label:"网关实例",width:"300"},scopedSlots:t._u([{key:"default",fn:function(e){return[0===e.row.parentId?n("span",[t._v(t._s(e.row.monitorName))]):t._e()]}}])}),t._v(" "),n("el-table-column",{attrs:{prop:"serviceId",label:"serviceId",width:"200"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0?n("span",[t._v(t._s(e.row.serviceId))]):t._e()]}}])}),t._v(" "),n("el-table-column",{attrs:{prop:"name",label:"接口名 (版本号)",width:"200"},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(" "),n("el-table-column",{attrs:{prop:"count",label:"出错次数",width:"100"}}),t._v(" "),n("el-table-column",{attrs:{prop:"errorMsg",label:"报错信息",width:"300"},scopedSlots:t._u([{key:"default",fn:function(e){return e.row.parentId>0?[n("div",{staticStyle:{display:"inline-block"},domProps:{innerHTML:t._s(t.showErrorMsg(e.row))}}),t._v(" "),n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return t.onShowErrorDetail(e.row)}}},[t._v("详情")])]:void 0}}],null,!0)}),t._v(" "),n("el-table-column",{attrs:{label:"操作",width:"180"},scopedSlots:t._u([{key:"default",fn:function(e){return[0===e.row.parentId&&e.row.children?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return t.onClearLog(e.row)}}},[t._v("清空日志")]):t._e(),t._v(" "),0===e.row.parentId?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return t.onDelete(e.row)}}},[t._v("删除实例")]):t._e()]}}])})],1),t._v(" "),n("el-dialog",{attrs:{title:"选择服务器实例",visible:t.logDialogInstanceVisible,"close-on-click-modal":!1},on:{"update:visible":function(e){t.logDialogInstanceVisible=e}}},[n("el-form",{ref:"logDialogForm",attrs:{model:t.logDialogFormData,rules:t.rulesLog,"label-width":"150px",size:"mini"}},[n("el-form-item",[n("p",{staticStyle:{color:"#878787"}},[t._v("只能选择网关实例,其它实例不支持")])]),t._v(" "),n("el-form-item",{attrs:{prop:"instanceData",label:"服务器实例"}},[n("el-select",{staticStyle:{width:"400px"},attrs:{"value-key":"id"},model:{value:t.logDialogFormData.instanceData,callback:function(e){t.$set(t.logDialogFormData,"instanceData",e)},expression:"logDialogFormData.instanceData"}},t._l(t.serviceData,function(e){return n("el-option",{key:e.id,attrs:{label:e.serviceId+"("+e.ipPort+")",value:e,disabled:t.isOptionDisabled(e)}},[n("span",{staticStyle:{float:"left"}},[t._v(t._s(e.serviceId)+" "),t.isOptionDisabled(e)?n("span",[t._v("(已添加)")]):t._e()]),t._v(" "),n("span",{staticStyle:{float:"right",color:"#8492a6","font-size":"13px"}},[t._v(t._s(e.ipPort))])])}),1)],1)],1),t._v(" "),n("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[n("el-button",{on:{click:function(e){t.logDialogInstanceVisible=!1}}},[t._v("取 消")]),t._v(" "),n("el-button",{attrs:{type:"primary"},on:{click:t.onLogDialogSave}},[t._v("保 存")])],1)],1),t._v(" "),n("el-dialog",{attrs:{title:"错误详情",visible:t.logDetailVisible,width:"60%"},on:{"update:visible":function(e){t.logDetailVisible=e}}},[n("div",{staticStyle:{"overflow-x":"auto"},domProps:{innerHTML:t._s(t.errorMsgDetail)}}),t._v(" "),n("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[n("el-button",{attrs:{type:"primary"},on:{click:function(e){t.logDetailVisible=!1}}},[t._v("关 闭")])],1)])],1)},i=[],o=(n("a481"),n("ac6a"),n("20d6"),{data:function(){return{searchFormData:{},tableData:[],serviceData:[],addedInstanceList:[],logDialogFormData:{instanceData:null},logDialogInstanceVisible:!1,logDetailVisible:!1,rulesLog:{instanceData:[{required:!0,message:"不能为空",trigger:"blur"}]},errorMsgDetail:""}},created:function(){this.loadServiceInstance(),this.loadTable()},methods:{loadServiceInstance:function(){this.post("service.instance.list",{},function(t){this.serviceData=t.data.filter(function(t){return t.instanceId&&t.instanceId.length>0})}),this.post("monitor.instance.list",{},function(t){this.addedInstanceList=t.data})},loadTable:function(){this.post("monitor.log.list",{},function(t){this.tableData=this.buildTreeData(t.data)})},isOptionDisabled:function(t){var e=t.ipPort,n=this.addedInstanceList.findIndex(function(t,n,r){return t===e});return n>-1},buildTreeData:function(t){return t.forEach(function(e){var n=e.parentId;0===n||t.forEach(function(t){if(t.treeId===n){var r=t.children;r||(r=[]),r.push(e),t.children=r}})}),t=t.filter(function(t){return 0===t.parentId}),t},showErrorMsg:function(t){var e=t.errorMsg.replace(/\/g,"");return e.substring(0,30)+"..."},onAddServer:function(){this.logDialogInstanceVisible=!0},onDelete:function(t){this.confirm("确定要删除实例【"+t.monitorName+"】吗?",function(e){this.post("monitor.instance.del",{id:t.rawId},function(t){e(),this.tip("删除成功"),this.loadTable()})})},onClearLog:function(t){this.confirm("确定要清空日志吗?",function(e){this.post("monitor.log.clear",{id:t.rawId},function(t){e(),this.tip("清空成功"),this.loadTable()})})},onShowErrorDetail:function(t){this.errorMsgDetail=t.errorMsg,this.logDetailVisible=!0},onLogDialogSave:function(){var t=this;this.$refs["logDialogForm"].validate(function(e){if(e){var n=t.logDialogFormData.instanceData;t.post("monitor.instance.add",n,function(t){this.logDialogInstanceVisible=!1,this.loadTable()})}})}}}),a=o,l=n("2877"),c=Object(l["a"])(a,r,i,!1,null,null,null);e["default"]=c.exports},e853:function(t,e,n){var r=n("d3f4"),i=n("1169"),o=n("2b4c")("species");t.exports=function(t){var e;return i(t)&&(e=t.constructor,"function"!=typeof e||e!==Array&&!i(e.prototype)||(e=void 0),r(e)&&(e=e[o],null===e&&(e=void 0))),void 0===e?Array:e}}}]); \ No newline at end of file diff --git a/sop-admin/sop-admin-vue/src/router/index.js b/sop-admin/sop-admin-vue/src/router/index.js index e88328d3..5476d797 100644 --- a/sop-admin/sop-admin-vue/src/router/index.js +++ b/sop-admin/sop-admin-vue/src/router/index.js @@ -1,7 +1,7 @@ import Vue from 'vue' import Router from 'vue-router' -Vue.use(Router) +Vue.use(Router); /* Layout */ import Layout from '@/layout' @@ -73,18 +73,18 @@ export const constantRoutes = [ component: () => import('@/views/service/route'), meta: { title: '路由管理' } }, + { + path: 'monitor', + name: 'Monitor', + component: () => import('@/views/service/monitor'), + meta: { title: '路由监控' } + }, { path: 'limit', name: 'Limit', component: () => import('@/views/service/limit'), meta: { title: '限流管理' } }, - { - path: 'log', - name: 'Log', - component: () => import('@/views/service/log'), - meta: { title: '监控日志' } - }, { path: 'blacklist', name: 'Blacklist', @@ -123,19 +123,19 @@ export const constantRoutes = [ }, // 404 page must be placed at the end !!! { path: '*', redirect: '/404', hidden: true } -] +]; const createRouter = () => new Router({ // mode: 'history', // require service support scrollBehavior: () => ({ y: 0 }), routes: constantRoutes -}) +}); -const router = createRouter() +const router = createRouter(); // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 export function resetRouter() { - const newRouter = createRouter() + const newRouter = createRouter(); router.matcher = newRouter.matcher // reset router } diff --git a/sop-admin/sop-admin-vue/src/views/service/monitor.vue b/sop-admin/sop-admin-vue/src/views/service/monitor.vue new file mode 100644 index 00000000..363981fe --- /dev/null +++ b/sop-admin/sop-admin-vue/src/views/service/monitor.vue @@ -0,0 +1,154 @@ + + + diff --git a/sop-auth/pom.xml b/sop-auth/pom.xml index 908e6bb1..d70d96f1 100644 --- a/sop-auth/pom.xml +++ b/sop-auth/pom.xml @@ -26,7 +26,7 @@ com.gitee.sop sop-service-common - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT diff --git a/sop-common/pom.xml b/sop-common/pom.xml index 87f7582c..96e55498 100644 --- a/sop-common/pom.xml +++ b/sop-common/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.gitee.sop sop-common - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT pom diff --git a/sop-common/sop-bridge-gateway/pom.xml b/sop-common/sop-bridge-gateway/pom.xml index 2595ecfc..4cfc4cf4 100644 --- a/sop-common/sop-bridge-gateway/pom.xml +++ b/sop-common/sop-bridge-gateway/pom.xml @@ -5,11 +5,11 @@ sop-common com.gitee.sop - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT ../pom.xml 4.0.0 - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT sop-bridge-gateway @@ -17,7 +17,7 @@ com.gitee.sop sop-gateway-common - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT diff --git a/sop-common/sop-bridge-zuul/pom.xml b/sop-common/sop-bridge-zuul/pom.xml index a0171904..3f55c6f0 100644 --- a/sop-common/sop-bridge-zuul/pom.xml +++ b/sop-common/sop-bridge-zuul/pom.xml @@ -5,11 +5,11 @@ sop-common com.gitee.sop - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT ../pom.xml 4.0.0 - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT sop-bridge-zuul @@ -17,7 +17,7 @@ com.gitee.sop sop-gateway-common - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT diff --git a/sop-common/sop-bridge-zuul/src/main/resources/sop-bridge.properties b/sop-common/sop-bridge-zuul/src/main/resources/sop-bridge.properties index 5f7ad097..da679b4d 100644 --- a/sop-common/sop-bridge-zuul/src/main/resources/sop-bridge.properties +++ b/sop-common/sop-bridge-zuul/src/main/resources/sop-bridge.properties @@ -29,7 +29,7 @@ ribbon.ReadTimeout=5000 # 设置为true(默认false),则所有请求都重试,默认只支持get请求重试 # 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性 ribbon.OkToRetryOnAllOperations=false -hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000 +hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=13000 # 不用改 mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create diff --git a/sop-common/sop-gateway-common/pom.xml b/sop-common/sop-gateway-common/pom.xml index 1ecfe1d0..5ce5de36 100644 --- a/sop-common/sop-gateway-common/pom.xml +++ b/sop-common/sop-gateway-common/pom.xml @@ -5,11 +5,11 @@ com.gitee.sop sop-common - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT ../pom.xml sop-gateway-common - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT jar sop-gateway-common diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java index acda4d79..855a134e 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java @@ -1,6 +1,7 @@ package com.gitee.sop.gatewaycommon.bean; import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager; import com.gitee.sop.gatewaycommon.limit.LimitManager; import com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder; @@ -18,6 +19,7 @@ import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager; import com.gitee.sop.gatewaycommon.manager.LimitConfigManager; import com.gitee.sop.gatewaycommon.manager.RouteConfigManager; import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager; +import com.gitee.sop.gatewaycommon.monitor.MonitorManager; import com.gitee.sop.gatewaycommon.param.ParameterFormatter; import com.gitee.sop.gatewaycommon.result.DataNameBuilder; import com.gitee.sop.gatewaycommon.result.DefaultDataNameBuilder; @@ -157,6 +159,16 @@ public class ApiConfig { */ private TokenValidator tokenValidator = apiParam -> apiParam != null && StringUtils.isNotBlank(apiParam.fetchAccessToken()); + /** + * 路由拦截器 + */ + private List routeInterceptors = new ArrayList<>(4); + + /** + * 监控管理 + */ + private MonitorManager monitorManager = new MonitorManager(); + // -------- fields --------- /** diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/DefaultRouteInterceptorContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/DefaultRouteInterceptorContext.java new file mode 100644 index 00000000..7fc5497b --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/DefaultRouteInterceptorContext.java @@ -0,0 +1,114 @@ +package com.gitee.sop.gatewaycommon.bean; + +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; +import com.gitee.sop.gatewaycommon.param.ApiParam; + +/** + * @author tanghc + */ +public class DefaultRouteInterceptorContext implements RouteInterceptorContext { + + /** 请求参数 */ + private ApiParam apiParam; + /** 错误信息 */ + private String serviceErrorMsg; + /** 微服务返回状态 */ + private int responseStatus; + /** 开始时间 */ + private long beginTimeMillis; + /** 结束时间 */ + private long finishTimeMillis; + /** 请求上下文 */ + private Object requestContext; + /** 微服务返回结果 */ + private String serviceResult; + /** 请求包大小 */ + private long requestDataSize; + /** 返回内容大小 */ + private long responseDataSize; + + @Override + public ApiParam getApiParam() { + return apiParam; + } + + @Override + public String getServiceResult() { + return serviceResult; + } + + @Override + public int getResponseStatus() { + return responseStatus; + } + + @Override + public long getBeginTimeMillis() { + return beginTimeMillis; + } + + @Override + public long getFinishTimeMillis() { + return finishTimeMillis; + } + + @Override + public Object getRequestContext() { + return requestContext; + } + + public void setApiParam(ApiParam apiParam) { + this.apiParam = apiParam; + } + + public void setServiceResult(String serviceResult) { + this.serviceResult = serviceResult; + } + + public void setResponseStatus(int responseStatus) { + this.responseStatus = responseStatus; + } + + public void setBeginTimeMillis(long beginTimeMillis) { + this.beginTimeMillis = beginTimeMillis; + } + + public void setFinishTimeMillis(long finishTimeMillis) { + this.finishTimeMillis = finishTimeMillis; + } + + public void setRequestContext(Object requestContext) { + this.requestContext = requestContext; + } + + @Override + public String getServiceErrorMsg() { + return serviceErrorMsg; + } + + public void setServiceErrorMsg(String serviceErrorMsg) { + this.serviceErrorMsg = serviceErrorMsg; + } + + @Override + public long getRequestDataSize() { + return requestDataSize; + } + + public void setRequestDataSize(long requestDataSize) { + // spring cloud gateway get请求contentLength返回-1 + if (requestDataSize < 0) { + requestDataSize = 0; + } + this.requestDataSize = requestDataSize; + } + + @Override + public long getResponseDataSize() { + return responseDataSize; + } + + public void setResponseDataSize(long responseDataSize) { + this.responseDataSize = responseDataSize; + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java index ab9cad69..1e176a42 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java @@ -16,14 +16,6 @@ public class SopConstants { public static final String DEFAULT_SIGN_METHOD = "md5"; public static final String EMPTY_JSON = "{}"; - public static final String REDIRECT_METHOD_KEY = "r-method"; - - public static final String REDIRECT_VERSION_KEY = "r-version"; - - public static final String REDIRECT_PATH_KEY = "r-path"; - - public static final String SOP_NOT_MERGE = "sop.not-merge"; - public static final String METADATA_SERVER_CONTEXT_PATH = "server.servlet.context-path"; public static final String METADATA_SERVER_CONTEXT_PATH_COMPATIBILITY = "context-path"; @@ -58,4 +50,6 @@ public class SopConstants { public static final String METADATA_ENV_PRE_VALUE = "pre"; public static final String METADATA_ENV_GRAY_VALUE = "gray"; + public static final String CACHE_ROUTE_INTERCEPTOR_CONTEXT = "cacheRouteInterceptorContext"; + } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java index 6a7a1bba..3446be1f 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java @@ -8,8 +8,8 @@ import com.gitee.sop.gatewaycommon.gateway.common.RequestContentDataExtractor; import com.gitee.sop.gatewaycommon.gateway.common.SopServerHttpRequestDecorator; import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.FormHttpOutputMessage; -import com.gitee.sop.gatewaycommon.route.ForwardInfo; import com.gitee.sop.gatewaycommon.param.ParamNames; +import com.gitee.sop.gatewaycommon.route.ForwardInfo; import com.gitee.sop.gatewaycommon.util.RequestUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -82,15 +82,13 @@ public class ServerWebExchangeUtil { return ServerRequest.create(exchange, messageReaders); } - public static ServerWebExchange getRestfulExchange(ServerWebExchange exchange, String path) { + public static ApiParam getApiParamForRestful(ServerWebExchange exchange, String path) { int index = path.indexOf(REST_PATH); // 取"/rest"的后面部分 String newPath = path.substring(index + REST_PATH.length()); - ApiParam apiParam = new ApiParam(); - apiParam.setName(newPath); - apiParam.setVersion(""); + ApiParam apiParam = ApiParam.createRestfulApiParam(newPath); setApiParam(exchange, apiParam); - return getForwardExchange(exchange, newPath); + return apiParam; } /** @@ -100,7 +98,7 @@ public class ServerWebExchangeUtil { * @param forwardPath 重定向path * @return 返回新的ServerWebExchange,配合chain.filter(newExchange);使用 */ - public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, String forwardPath) { + private static ServerWebExchange getForwardExchange(ServerWebExchange exchange, String forwardPath) { ServerHttpRequest newRequest = exchange.getRequest() .mutate() .path(forwardPath).build(); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java index f65151a4..ae8717e7 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java @@ -4,6 +4,7 @@ import com.gitee.sop.gatewaycommon.bean.ApiConfig; import com.gitee.sop.gatewaycommon.gateway.controller.ConfigChannelController; import com.gitee.sop.gatewaycommon.gateway.controller.ErrorLogController; import com.gitee.sop.gatewaycommon.gateway.controller.GatewayController; +import com.gitee.sop.gatewaycommon.gateway.controller.GatewayMonitorController; import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFilter; import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter; import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter; @@ -64,6 +65,11 @@ public class BaseGatewayConfiguration extends AbstractConfiguration { return new ErrorLogController(); } + @Bean + public GatewayMonitorController gatewayMonitorController() { + return new GatewayMonitorController(); + } + /** * 自定义异常处理[@@]注册Bean时依赖的Bean,会从容器中直接获取,所以直接注入即可 * diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java new file mode 100644 index 00000000..910c874c --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java @@ -0,0 +1,21 @@ +package com.gitee.sop.gatewaycommon.gateway.controller; + +import com.gitee.sop.gatewaycommon.support.BaseMonitorController; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; + +import java.util.Map; + +/** + * @author tanghc + */ +@RestController +public class GatewayMonitorController extends BaseMonitorController { + + @Override + protected ApiParam getApiParam(ServerWebExchange request) { + Map params = request.getRequest().getQueryParams().toSingleValueMap(); + return ApiParam.build(params); + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java index 9af4a5e3..eff18885 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java @@ -1,11 +1,14 @@ package com.gitee.sop.gatewaycommon.gateway.filter; +import com.gitee.sop.gatewaycommon.bean.DefaultRouteInterceptorContext; +import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser; import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys; import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.route.ForwardInfo; +import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil; import com.gitee.sop.gatewaycommon.validate.Validator; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -67,7 +70,10 @@ public class IndexFilter implements WebFilter { log.error("尝试调用restful请求,但sop.restful.enable未开启"); return ServerWebExchangeUtil.forwardUnknown(exchange, chain); } - ServerWebExchange newExchange = ServerWebExchangeUtil.getRestfulExchange(exchange, path); + ApiParam apiParam = ServerWebExchangeUtil.getApiParamForRestful(exchange, path); + this.doValidate(exchange, apiParam); + ForwardInfo forwardInfo = gatewayForwardChooser.getForwardInfo(exchange); + ServerWebExchange newExchange = ServerWebExchangeUtil.getForwardExchange(exchange, forwardInfo); return chain.filter(newExchange); } if (Objects.equals(path, indexPath)) { @@ -122,12 +128,21 @@ public class IndexFilter implements WebFilter { private void doValidate(ServerWebExchange exchange, ApiParam apiParam) { try { validator.validate(apiParam); + this.afterValidate(exchange, apiParam); } catch (ApiException e) { log.error("验证失败,ip:{}, params:{}, errorMsg:{}", apiParam.fetchIp(), apiParam.toJSONString(), e.getMessage()); ServerWebExchangeUtil.setThrowable(exchange, e); } } + private void afterValidate(ServerWebExchange exchange, ApiParam param) { + RouteInterceptorUtil.runPreRoute(exchange, param, context -> { + DefaultRouteInterceptorContext defaultRouteInterceptorContext = (DefaultRouteInterceptorContext) context; + defaultRouteInterceptorContext.setRequestDataSize(exchange.getRequest().getHeaders().getContentLength()); + exchange.getAttributes().put(SopConstants.CACHE_ROUTE_INTERCEPTOR_CONTEXT, context); + }); + } + private ServerHttpRequestDecorator decorate( ServerWebExchange exchange , HttpHeaders headers diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java index 9f38866b..32408323 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java @@ -3,6 +3,7 @@ package com.gitee.sop.gatewaycommon.gateway.loadbalancer; import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext; import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.util.LoadBalanceUtil; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.Server; import org.springframework.cloud.client.ServiceInstance; @@ -14,7 +15,6 @@ import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.web.server.ServerWebExchange; import java.util.List; -import java.util.concurrent.ThreadLocalRandom; /** * 重写负载均衡处理。 @@ -58,7 +58,7 @@ public class SopLoadBalancerClient extends RibbonLoadBalancerClient implements S } private RibbonServer getRibbonServer(String serviceId, List servers) { - Server server = this.chooseRandomServer(servers); + Server server = LoadBalanceUtil.chooseByRoundRobin(serviceId, servers); if (server == null) { return null; } @@ -70,26 +70,6 @@ public class SopLoadBalancerClient extends RibbonLoadBalancerClient implements S ); } - /** - * 随机选取一台实例 - * - * @param servers 服务列表 - * @return 返回实例,没有返回null - */ - private Server chooseRandomServer(List servers) { - if (servers.isEmpty()) { - return null; - } - int serverCount = servers.size(); - // 随机选取一台实例 - int index = chooseRandomInt(serverCount); - return servers.get(index); - } - - private int chooseRandomInt(int serverCount) { - return ThreadLocalRandom.current().nextInt(serverCount); - } - private ServerIntrospector serverIntrospector(String serviceId) { ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId, ServerIntrospector.class); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java index f39fa3a1..d3e891a5 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java @@ -2,6 +2,7 @@ package com.gitee.sop.gatewaycommon.gateway.result; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; @@ -17,7 +18,6 @@ import org.springframework.web.server.ServerWebExchange; import java.util.List; import java.util.Locale; -import java.util.Map; /** @@ -29,7 +29,8 @@ public class GatewayResultExecutor extends BaseExecutorAdapter errorCodeList = exchange.getResponse().getHeaders().get(SopConstants.X_SERVICE_ERROR_CODE); if (!CollectionUtils.isEmpty(errorCodeList)) { String errorCode = errorCodeList.get(0); @@ -59,6 +60,11 @@ public class GatewayResultExecutor extends BaseExecutorAdapter { - private static final String VALIDATE_ERROR_PATH = "/sop/validateError"; - @Override public ApiParam getApiParam(ServerWebExchange exchange) { return ServerWebExchangeUtil.getApiParam(exchange); @@ -22,7 +20,7 @@ public class GatewayForwardChooser extends BaseForwardChooser public ForwardInfo getForwardInfo(ServerWebExchange exchange) { // 如果有异常,直接跳转到异常处理 if (ServerWebExchangeUtil.getThrowable(exchange) != null) { - return new ForwardInfo(VALIDATE_ERROR_PATH, ""); + return ForwardInfo.getErrorForwardInfo(); } return super.getForwardInfo(exchange); } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java index fa8c0f54..5070027d 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java @@ -5,6 +5,7 @@ import com.gitee.sop.gatewaycommon.bean.RouteDefinition; import com.gitee.sop.gatewaycommon.manager.RouteRepository; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.event.RefreshRoutesEvent; import org.springframework.cloud.gateway.route.Route; @@ -77,13 +78,27 @@ public class GatewayRouteRepository implements RouteRepository entry : routes.entrySet()) { // /food/get/?id? String pattern = entry.getKey(); - if (StringUtils.containsAny(pattern, "{") && this.pathMatcher.match(pattern, id)) { - return entry.getValue(); + if (this.pathMatcher.match(pattern, id)) { + return clone(id, entry.getValue()); } } return null; } + private GatewayTargetRoute clone(String path, GatewayTargetRoute gatewayTargetRoute) { + String prefix = "/" + gatewayTargetRoute.getServiceRouteInfo().getServiceId(); + if (path.startsWith(prefix)) { + path = path.substring(prefix.length()); + } + RouteDefinition routeDefinition = gatewayTargetRoute.getRouteDefinition(); + RouteDefinition newRouteDefinition = new RouteDefinition(); + BeanUtils.copyProperties(routeDefinition, newRouteDefinition); + newRouteDefinition.setPath(path); + return new GatewayTargetRoute(gatewayTargetRoute.getServiceRouteInfo() + , newRouteDefinition + , gatewayTargetRoute.getTargetRouteDefinition()); + } + @Override public Collection getAll() { diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/MonitorRouteInterceptor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/MonitorRouteInterceptor.java new file mode 100644 index 00000000..e2942743 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/MonitorRouteInterceptor.java @@ -0,0 +1,96 @@ +package com.gitee.sop.gatewaycommon.interceptor; + +import com.gitee.sop.gatewaycommon.bean.ApiConfig; +import com.gitee.sop.gatewaycommon.monitor.MonitorInfo; +import com.gitee.sop.gatewaycommon.monitor.MonitorManager; +import com.gitee.sop.gatewaycommon.param.ApiParam; + +import java.util.ArrayList; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * SOP默认的拦截器,用于收集监控数据 + * + * @author tanghc + */ +public class MonitorRouteInterceptor implements RouteInterceptor { + + private ThreadPoolExecutor threadPoolExecutor; + + public MonitorRouteInterceptor(int threadPoolSize) { + threadPoolExecutor = new ThreadPoolExecutor(threadPoolSize, threadPoolSize, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>()); + } + + public MonitorRouteInterceptor() { + this(4); + } + + public MonitorRouteInterceptor(ThreadPoolExecutor threadPoolExecutor) { + this.threadPoolExecutor = threadPoolExecutor; + } + + @Override + public void preRoute(RouteInterceptorContext context) { + } + + @Override + public void afterRoute(RouteInterceptorContext context) { + threadPoolExecutor.execute(() -> this.storeRequestInfo(context)); + } + + /** + * 记录接口调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数. + * 需要考虑并发情况。 + */ + protected void storeRequestInfo(RouteInterceptorContext context) { + MonitorManager monitorManager = ApiConfig.getInstance().getMonitorManager(); + ApiParam apiParam = context.getApiParam(); + String routeId = apiParam.fetchNameVersion(); + long spendTime = context.getFinishTimeMillis() - context.getBeginTimeMillis(); + // 这步操作是线程安全的,底层调用了ConcurrentHashMap.computeIfAbsent + MonitorInfo monitorInfo = monitorManager.getMonitorInfo(routeId, (k) -> this.createMonitorInfo(apiParam)); + + monitorInfo.storeMaxTime(spendTime); + monitorInfo.storeMinTime(spendTime); + monitorInfo.getTotalCount().incrementAndGet(); + monitorInfo.getTotalTime().addAndGet(spendTime); + monitorInfo.getTotalRequestDataSize().addAndGet(context.getRequestDataSize()); + monitorInfo.getTotalResponseDataSize().addAndGet(context.getResponseDataSize()); + if (context.isSuccessRequest()) { + monitorInfo.getSuccessCount().incrementAndGet(); + } else { + monitorInfo.getErrorCount().incrementAndGet(); + String errorMsg = context.getServiceErrorMsg(); + monitorInfo.addErrorMsg(errorMsg); + } + } + + private MonitorInfo createMonitorInfo(ApiParam apiParam) { + MonitorInfo monitorInfo = new MonitorInfo(); + monitorInfo.setName(apiParam.fetchName()); + monitorInfo.setVersion(apiParam.fetchVersion()); + monitorInfo.setServiceId(apiParam.fetchServiceId()); + monitorInfo.setTotalRequestDataSize(new AtomicLong()); + monitorInfo.setTotalResponseDataSize(new AtomicLong()); + monitorInfo.setTotalTime(new AtomicLong()); + monitorInfo.setMaxTime(0L); + monitorInfo.setMinTime(0L); + monitorInfo.setSuccessCount(new AtomicLong()); + monitorInfo.setTotalCount(new AtomicLong()); + monitorInfo.setErrorCount(new AtomicLong()); + monitorInfo.setErrorMsgList(new ArrayList<>(10)); + return monitorInfo; + } + + + @Override + public int getOrder() { + return -1000; + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptor.java new file mode 100644 index 00000000..a7a6fbc4 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptor.java @@ -0,0 +1,40 @@ +package com.gitee.sop.gatewaycommon.interceptor; + +/** + * 路由拦截器 + * + * @author tanghc + */ +public interface RouteInterceptor { + + /** + * 在路由转发前执行,签名校验通过后会立即执行此方法 + * + * @param context context + */ + void preRoute(RouteInterceptorContext context); + + /** + * 微服务返回结果后执行,可能返回成功,也可能返回失败结果, + * 可通过 {@link RouteInterceptorContext#getResponseStatus()} 来判断是否返回正确结果。 + * + * @param context context + */ + void afterRoute(RouteInterceptorContext context); + + /** + * 拦截器执行顺序,值小优先执行,建议从0开始,小于0留给系统使用 + * + * @return 返回顺序 + */ + int getOrder(); + + /** + * 是否匹配,返回true执行拦截器,默认true + * @param context context + * @return 返回true执行拦截器 + */ + default boolean match(RouteInterceptorContext context) { + return true; + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java new file mode 100644 index 00000000..491a7117 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/RouteInterceptorContext.java @@ -0,0 +1,85 @@ +package com.gitee.sop.gatewaycommon.interceptor; + +import com.gitee.sop.gatewaycommon.bean.SopConstants; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import org.springframework.http.HttpStatus; + +/** + * 拦截器参数 + * + * @author tanghc + */ +public interface RouteInterceptorContext { + + /** + * 返回ApiParam + * + * @return 返回ApiParam + */ + ApiParam getApiParam(); + + /** + * 获取微服务返回的内容 + * + * @return 微服务返回内容 + */ + String getServiceResult(); + + /** + * 获取微服务端的错误信息,status为200时,返回null。 + * + * @return 返回错误信息 + */ + String getServiceErrorMsg(); + + /** + * 获取微服务返回状态码 + * + * @return 返回状态码,正确为200,错误为非200 + */ + int getResponseStatus(); + + /** + * 获取路由开始时间 + * + * @return 返回开始时间 + */ + long getBeginTimeMillis(); + + /** + * 获取路由结束时间 + * + * @return 返回结束时间 + */ + long getFinishTimeMillis(); + + /** + * 获取上下文信息,zuul返回RequestContext,Gateway返回ServerWebExchange + * + * @return 返回上下文对象 + */ + Object getRequestContext(); + + /** + * 获取请求内容大小 + * + * @return 返回请求内容大小 + */ + long getRequestDataSize(); + + /** + * 获取返回结果内容大小 + * @return 返回返回结果内容大小 + */ + long getResponseDataSize(); + + /** + * 是否是成功请求,微服务主动抛出的异常也算作成功,JSR303校验失败也算作成功。 + * 只有微服务返回未知的错误算作失败。 + * @return true:成功请求 + */ + default boolean isSuccessRequest() { + int responseStatus = getResponseStatus(); + return responseStatus == HttpStatus.OK.value() || responseStatus == SopConstants.BIZ_ERROR_STATUS; + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java index 8b38406b..a91bc665 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java @@ -28,7 +28,7 @@ public interface ServerChooserContext extends ApiParamAware { default boolean isRequestGrayServer(T t) { ApiParam apiParam = getApiParam(t); - return apiParam.isGrayRequest(); + return apiParam.fetchGrayRequest(); } String getHost(T t); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java index 203f64e4..45c15f21 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java @@ -3,8 +3,10 @@ package com.gitee.sop.gatewaycommon.manager; import com.gitee.sop.gatewaycommon.bean.ApiConfig; import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.bean.BeanInitializer; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; import com.gitee.sop.gatewaycommon.bean.SpringContext; import com.gitee.sop.gatewaycommon.gateway.loadbalancer.NacosServerIntrospector; +import com.gitee.sop.gatewaycommon.interceptor.MonitorRouteInterceptor; import com.gitee.sop.gatewaycommon.limit.LimitManager; import com.gitee.sop.gatewaycommon.loadbalancer.SopPropertiesFactory; import com.gitee.sop.gatewaycommon.message.ErrorFactory; @@ -16,6 +18,7 @@ import com.gitee.sop.gatewaycommon.route.ServiceListener; import com.gitee.sop.gatewaycommon.route.ServiceRouteListener; import com.gitee.sop.gatewaycommon.secret.IsvManager; import com.gitee.sop.gatewaycommon.session.SessionManager; +import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil; import com.gitee.sop.gatewaycommon.validate.SignConfig; import com.gitee.sop.gatewaycommon.validate.Validator; import lombok.extern.slf4j.Slf4j; @@ -40,6 +43,8 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Collection; import java.util.Map; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; @@ -254,6 +259,7 @@ public class AbstractConfiguration implements ApplicationContextAware, Applicati initMessage(); initBeanInitializer(); + initRouteInterceptor(); doAfter(); } @@ -262,6 +268,13 @@ public class AbstractConfiguration implements ApplicationContextAware, Applicati beanInitializerMap.values().forEach(BeanInitializer::load); } + protected void initRouteInterceptor() { + Map routeInterceptorMap = applicationContext.getBeansOfType(RouteInterceptor.class); + Collection routeInterceptors = new ArrayList<>(routeInterceptorMap.values()); + routeInterceptors.add(new MonitorRouteInterceptor()); + RouteInterceptorUtil.addInterceptors(routeInterceptors); + } + protected void doAfter() { } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorInfo.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorInfo.java new file mode 100644 index 00000000..a05bd56a --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorInfo.java @@ -0,0 +1,88 @@ +package com.gitee.sop.gatewaycommon.monitor; + +import lombok.Data; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 每个接口 总调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数,错误查看。 + * + * @author tanghc + */ +@Data +public class MonitorInfo { + + /** + * 接口名 + */ + private String name; + /** + * 版本号 + */ + private String version; + /** + * serviceId + */ + private String serviceId; + /** + * 总请求数据量 + */ + private AtomicLong totalRequestDataSize; + /** + * 总返回数据量 + */ + private AtomicLong totalResponseDataSize; + /** + * 请求耗时最长时间 + */ + private Long maxTime; + /** + * 请求耗时最小时间 + */ + private Long minTime; + /** + * 总时长 + */ + private AtomicLong totalTime; + /** + * 总调用次数 + */ + private AtomicLong totalCount; + /** + * 成功次数 + */ + private AtomicLong successCount; + /** + * 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败) + */ + private AtomicLong errorCount; + /** + * 错误信息 + */ + private List errorMsgList; + + public synchronized void storeMaxTime(long spendTime) { + if (spendTime > maxTime) { + maxTime = spendTime; + } + } + + public synchronized void storeMinTime(long spendTime) { + if (minTime == 0 || spendTime < minTime) { + minTime = spendTime; + } + } + + public void addErrorMsg(String errorMsg) { + if (errorMsg == null || "".equals(errorMsg)) { + return; + } + synchronized (this) { + if (errorMsgList != null && errorMsgList.size() < 10) { + errorMsgList.add(errorMsg); + } + } + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java new file mode 100644 index 00000000..a181da7e --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorManager.java @@ -0,0 +1,22 @@ +package com.gitee.sop.gatewaycommon.monitor; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * @author tanghc + */ +public class MonitorManager { + + private static Map monitorMap = new ConcurrentHashMap<>(128); + + public Map getMonitorData() { + return monitorMap; + } + + public MonitorInfo getMonitorInfo(String routeId, Function createFun) { + return monitorMap.computeIfAbsent(routeId, createFun); + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java index 1e3559b1..13d24846 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java @@ -29,12 +29,24 @@ public class ApiParam extends JSONObject implements Param { private String restName; private String restVersion; + private String serviceId; private String ip; + private boolean restful; + private boolean mergeResult = true; private boolean isGrayRequest; private transient UploadContext uploadContext; + public static ApiParam createRestfulApiParam(String path) { + ApiParam apiParam = new ApiParam(); + apiParam.setName(path); + apiParam.setVersion(""); + apiParam.setRestful(true); + apiParam.setMergeResult(false); + return apiParam; + } + public void fitNameVersion() { if (restName != null) { this.put(ParamNames.API_NAME, restName); @@ -204,12 +216,7 @@ public class ApiParam extends JSONObject implements Param { @Override public String fetchSignMethod() { - String signMethod = getString(ParamNames.SIGN_TYPE_NAME); - if (signMethod == null) { - return SopConstants.DEFAULT_SIGN_METHOD; - } else { - return signMethod; - } + return getString(ParamNames.SIGN_TYPE_NAME); } @Override @@ -251,11 +258,35 @@ public class ApiParam extends JSONObject implements Param { return ip; } - public boolean isGrayRequest() { + public boolean fetchGrayRequest() { return isGrayRequest; } public void setGrayRequest(boolean grayRequest) { isGrayRequest = grayRequest; } + + public String fetchServiceId() { + return serviceId; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + public boolean fetchMergeResult() { + return mergeResult; + } + + public void setMergeResult(boolean mergeResult) { + this.mergeResult = mergeResult; + } + + public boolean fetchRestful() { + return restful; + } + + public void setRestful(boolean restful) { + this.restful = restful; + } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java index 1c41e20a..ca9dc0e8 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java @@ -3,50 +3,47 @@ package com.gitee.sop.gatewaycommon.result; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.gitee.sop.gatewaycommon.bean.ApiConfig; -import com.gitee.sop.gatewaycommon.bean.ApiContext; -import com.gitee.sop.gatewaycommon.bean.ErrorDefinition; +import com.gitee.sop.gatewaycommon.bean.DefaultRouteInterceptorContext; import com.gitee.sop.gatewaycommon.bean.Isv; -import com.gitee.sop.gatewaycommon.bean.RouteDefinition; -import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; import com.gitee.sop.gatewaycommon.bean.SopConstants; -import com.gitee.sop.gatewaycommon.bean.TargetRoute; -import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; +import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; import com.gitee.sop.gatewaycommon.message.ErrorEnum; import com.gitee.sop.gatewaycommon.message.ErrorMeta; import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.ParamNames; import com.gitee.sop.gatewaycommon.secret.IsvManager; -import com.gitee.sop.gatewaycommon.validate.alipay.AlipayConstants; +import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil; import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySignature; -import lombok.Getter; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.BooleanUtils; -import org.springframework.beans.BeanUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; +import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.Optional; /** + * 处理微服务返回结果 + * * @author tanghc */ @Slf4j public abstract class BaseExecutorAdapter implements ResultExecutor { - private static final ErrorMeta SUCCESS_META = ErrorEnum.SUCCESS.getErrorMeta(); - private static final ErrorMeta ISP_UNKNOW_ERROR_META = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta(); - private static final ErrorMeta ISP_BIZ_ERROR = ErrorEnum.BIZ_ERROR.getErrorMeta(); - private static final ErrorMeta ISV_MISSING_METHOD_META = ErrorEnum.ISV_MISSING_METHOD.getErrorMeta(); + + private static Map HTTP_STATUS_ERROR_ENUM_MAP = new HashMap<>(8); private static final String GATEWAY_CODE_NAME = "code"; private static final String GATEWAY_MSG_NAME = "msg"; private static final String ARRAY_START = "["; private static final String ARRAY_END = "]"; private static final String ROOT_JSON = "{'items':%s}".replace("'", "\""); - private static final String ERROR_METHOD = "error"; + + static { + HTTP_STATUS_ERROR_ENUM_MAP.put(HttpStatus.OK.value(), ErrorEnum.SUCCESS); + HTTP_STATUS_ERROR_ENUM_MAP.put(SopConstants.BIZ_ERROR_STATUS, ErrorEnum.BIZ_ERROR); + HTTP_STATUS_ERROR_ENUM_MAP.put(HttpStatus.NOT_FOUND.value(), ErrorEnum.ISV_INVALID_METHOD); + } /** @@ -75,65 +72,82 @@ public abstract class BaseExecutorAdapter implements ResultExecutor /** * 获取locale + * * @param t request * @return 返回locale */ protected abstract Locale getLocale(T t); + /** + * 返回拦截器上下文 + * + * @param t request + * @return 返回拦截器上下文 + */ + protected abstract RouteInterceptorContext getRouteInterceptorContext(T t); + @Override public String mergeResult(T request, String serviceResult) { + serviceResult = formatResult(serviceResult); boolean isMergeResult = this.isMergeResult(request); - if (!isMergeResult) { - return serviceResult; - } - serviceResult = wrapResult(serviceResult); int responseStatus = this.getResponseStatus(request); - JSONObject responseData; - if (responseStatus == HttpStatus.OK.value()) { - // 200正常返回 - responseData = JSON.parseObject(serviceResult); - responseData.put(GATEWAY_CODE_NAME, SUCCESS_META.getCode()); - responseData.put(GATEWAY_MSG_NAME, SUCCESS_META.getError(getLocale(request)).getMsg()); - } else if (responseStatus == SopConstants.BIZ_ERROR_STATUS) { - // 如果是业务出错 - this.storeError(request, ErrorType.BIZ); - responseData = JSON.parseObject(serviceResult); - responseData.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode()); - responseData.put(GATEWAY_MSG_NAME, ISP_BIZ_ERROR.getError(getLocale(request)).getMsg()); - } else if (responseStatus == HttpStatus.NOT_FOUND.value()) { - responseData = JSON.parseObject(serviceResult); - responseData.put(GATEWAY_CODE_NAME, ISV_MISSING_METHOD_META.getCode()); - responseData.put(GATEWAY_MSG_NAME, ISV_MISSING_METHOD_META.getError(getLocale(request)).getCode()); + this.doAfterRoute(serviceResult, responseStatus, request); + String finalResult; + if (isMergeResult) { + JSONObject responseData = this.parseServiceResult(serviceResult, responseStatus, request); + finalResult = this.merge(request, responseData); } else { - ApiParam params = this.getApiParam(request); - log.error("微服务端报错,params:{}, 微服务返回结果:{}", params, serviceResult); - this.storeError(request, ErrorType.UNKNOWN); - // 微服务端有可能返回500错误 - // {"path":"/book/getBook3","error":"Internal Server Error","message":"id不能为空","timestamp":"2019-02-13T07:41:00.495+0000","status":500} - responseData = new JSONObject(); - responseData.put(GATEWAY_CODE_NAME, ISP_UNKNOW_ERROR_META.getCode()); - responseData.put(GATEWAY_MSG_NAME, ISP_UNKNOW_ERROR_META.getError(getLocale(request)).getMsg()); + finalResult = serviceResult; } - return this.merge(request, responseData); + return finalResult; } /** - * 保存错误信息 + * 执行拦截器after操作 * - * @param request request + * @param serviceResult 微服务结果 + * @param responseStatus 微服务状态码 + * @param requestContext 微服务状态码 */ - protected void storeError(T request, ErrorType errorType) { - ApiInfo apiInfo = this.getApiInfo(request); - String errorMsg = this.getResponseErrorMessage(request); - ErrorDefinition errorDefinition = new ErrorDefinition(); - BeanUtils.copyProperties(apiInfo, errorDefinition); - errorDefinition.setErrorMsg(errorMsg); - if (errorType == ErrorType.UNKNOWN) { - ApiConfig.getInstance().getServiceErrorManager().saveUnknownError(errorDefinition); + private void doAfterRoute(String serviceResult, int responseStatus, T requestContext) { + RouteInterceptorContext routeInterceptorContext = getRouteInterceptorContext(requestContext); + if (routeInterceptorContext instanceof DefaultRouteInterceptorContext) { + DefaultRouteInterceptorContext defaultRouteInterceptorContext = (DefaultRouteInterceptorContext) routeInterceptorContext; + defaultRouteInterceptorContext.setResponseStatus(responseStatus); + defaultRouteInterceptorContext.setServiceResult(serviceResult); + defaultRouteInterceptorContext.setFinishTimeMillis(System.currentTimeMillis()); + defaultRouteInterceptorContext.setResponseDataSize(serviceResult.length()); + if (responseStatus != HttpStatus.OK.value() && responseStatus != SopConstants.BIZ_ERROR_STATUS) { + String responseErrorMessage = getResponseErrorMessage(requestContext); + if (StringUtils.isEmpty(responseErrorMessage)) { + responseErrorMessage = serviceResult; + } + defaultRouteInterceptorContext.setServiceErrorMsg(responseErrorMessage); + } } - if (errorType == ErrorType.BIZ) { - ApiConfig.getInstance().getServiceErrorManager().saveBizError(errorDefinition); + RouteInterceptorUtil.runAfterRoute(routeInterceptorContext); + } + + /** + * 将微服务的返回结果解析成JSONObject + * + * @param serviceResult 微服务返回结果 + * @param responseStatus 返回状态 + * @param request 请求 + * @return 返回JSONObject + */ + protected JSONObject parseServiceResult(String serviceResult, int responseStatus, T request) { + ErrorEnum errorEnum = HTTP_STATUS_ERROR_ENUM_MAP.get(responseStatus); + if (errorEnum == null) { + // 其它异常不应该把异常信息告诉给客户端,将微服务内容设置成空的json + serviceResult = SopConstants.EMPTY_JSON; + errorEnum = ErrorEnum.ISP_UNKNOWN_ERROR; } + ErrorMeta errorMeta = errorEnum.getErrorMeta(); + JSONObject responseData = JSON.parseObject(serviceResult); + responseData.put(GATEWAY_CODE_NAME, errorMeta.getCode()); + responseData.put(GATEWAY_MSG_NAME, errorMeta.getError(getLocale(request)).getMsg()); + return responseData; } @@ -144,53 +158,15 @@ public abstract class BaseExecutorAdapter implements ResultExecutor * @return true:需要合并 */ protected boolean isMergeResult(T request) { - // 默认全局设置 - Boolean defaultSetting = ApiContext.getApiConfig().getMergeResult(); - if (defaultSetting != null) { - return defaultSetting; - } ApiParam params = this.getApiParam(request); - TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion()); - RouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute) - .map(TargetRoute::getRouteDefinition) - .orElse(null); - return Optional.ofNullable(baseRouteDefinition) - .map(routeDefinition -> { - int mergeResult = baseRouteDefinition.getMergeResult(); - return BooleanUtils.toBoolean(mergeResult); - }) - .orElse(true); + return params.fetchMergeResult(); } - protected ApiInfo getApiInfo(T request) { - ApiParam params = this.getApiParam(request); - TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion()); - - String serviceId = Optional.ofNullable(targetRoute) - .flatMap(route -> Optional.ofNullable(route.getServiceRouteInfo())) - .map(ServiceRouteInfo::getServiceId) - .orElse(SopConstants.UNKNOWN_SERVICE); - - RouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute) - .map(TargetRoute::getRouteDefinition) - .orElse(null); - - ApiInfo apiInfo = new ApiInfo(); - apiInfo.name = params.fetchName(); - apiInfo.version = params.fetchVersion(); - apiInfo.serviceId = serviceId; - apiInfo.gatewayRouteDefinition = baseRouteDefinition; - return apiInfo; - } - - protected String wrapResult(String serviceResult) { - if (serviceResult == null) { - serviceResult = ""; - } - serviceResult = serviceResult.trim(); - if (StringUtils.isEmpty(serviceResult)) { + protected String formatResult(String serviceResult) { + if (StringUtils.isBlank(serviceResult)) { return SopConstants.EMPTY_JSON; } + // 如果直接返回数组,需要进行包装,变成:{"items": [...]} if (serviceResult.startsWith(ARRAY_START) && serviceResult.endsWith(ARRAY_END)) { return String.format(ROOT_JSON, serviceResult); } @@ -200,15 +176,11 @@ public abstract class BaseExecutorAdapter implements ResultExecutor public String merge(T exchange, JSONObject responseData) { JSONObject finalData = new JSONObject(true); ApiParam params = this.getApiParam(exchange); - if (params == null) { - return responseData.toJSONString(); - } - String name = params.fetchName(); ApiConfig apiConfig = ApiConfig.getInstance(); // 点换成下划线 DataNameBuilder dataNameBuilder = apiConfig.getDataNameBuilder(); // alipay_goods_get_response - String responseDataNodeName = dataNameBuilder.build(name); + String responseDataNodeName = dataNameBuilder.build(params.fetchName()); finalData.put(responseDataNodeName, responseData); ResultAppender resultAppender = apiConfig.getResultAppender(); // 追加额外的结果 @@ -216,19 +188,23 @@ public abstract class BaseExecutorAdapter implements ResultExecutor resultAppender.append(finalData, params, exchange); } // 添加服务端sign + this.addResponseSign(apiConfig, params, finalData, responseDataNodeName); + return finalData.toJSONString(); + } + + private void addResponseSign(ApiConfig apiConfig, ApiParam params, JSONObject finalData, String responseDataNodeName) { if (apiConfig.isShowReturnSign() && !CollectionUtils.isEmpty(params)) { // 添加try...catch,生成sign出错不影响结果正常返回 try { String responseSignContent = this.buildResponseSignContent(responseDataNodeName, finalData); - String sign = this.createResponseSign(apiConfig, params, responseSignContent); - if (StringUtils.hasLength(sign)) { + String sign = this.createResponseSign(apiConfig.getIsvManager(), params, responseSignContent); + if (StringUtils.isNotBlank(sign)) { finalData.put(ParamNames.RESPONSE_SIGN_NAME, sign); } } catch (Exception e) { - log.error("生成平台签名失败, params: {}, serviceResult:{}", JSON.toJSONString(params), responseData, e); + log.error("生成平台签名失败, params: {}", params.toJSONString(), e); } } - return finalData.toJSONString(); } /** @@ -249,26 +225,20 @@ public abstract class BaseExecutorAdapter implements ResultExecutor return null; } - protected String getParamValue(Map apiParam, String key, String defaultValue) { - return CollectionUtils.isEmpty(apiParam) ? defaultValue : (String) apiParam.getOrDefault(key, defaultValue); - } - - /** * 这里需要使用平台的私钥生成一个sign,需要配置两套公私钥。 * - * @param apiConfig 配置 + * @param isvManager isvManager * @param params 请求参数 * @param responseSignContent 待签名内容 * @return 返回平台生成的签名 */ - protected String createResponseSign(ApiConfig apiConfig, Map params, String responseSignContent) { + protected String createResponseSign(IsvManager isvManager, ApiParam params, String responseSignContent) { if (StringUtils.isEmpty(responseSignContent)) { return null; } - IsvManager isvManager = apiConfig.getIsvManager(); // 根据appId获取秘钥 - String appKey = this.getParamValue(params, ParamNames.APP_KEY_NAME, ""); + String appKey = params.fetchAppKey(); if (StringUtils.isEmpty(appKey)) { return null; } @@ -277,30 +247,12 @@ public abstract class BaseExecutorAdapter implements ResultExecutor if (StringUtils.isEmpty(privateKeyPlatform)) { return null; } - String charset = Optional.ofNullable(params.get(ParamNames.CHARSET_NAME)) - .map(String::valueOf) - .orElse(SopConstants.UTF8); - String signType = getParamValue(params, ParamNames.SIGN_TYPE_NAME, AlipayConstants.SIGN_TYPE_RSA2); - return AlipaySignature.rsaSign(responseSignContent, privateKeyPlatform, charset, signType); - } - - @Getter - @Setter - protected static class ApiInfo { - private String name; - private String version; - private String serviceId; - private RouteDefinition gatewayRouteDefinition; + return AlipaySignature.rsaSign( + responseSignContent + , privateKeyPlatform + , params.fetchCharset() + , params.fetchSignMethod() + ); } - enum ErrorType { - /** - * 未知错误 - */ - UNKNOWN, - /** - * 业务错误 - */ - BIZ - } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java index 0b7715d5..261742d2 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java @@ -1,18 +1,14 @@ package com.gitee.sop.gatewaycommon.route; import com.gitee.sop.gatewaycommon.bean.ApiConfig; -import com.gitee.sop.gatewaycommon.bean.RouteDefinition; import com.gitee.sop.gatewaycommon.bean.ApiParamAware; import com.gitee.sop.gatewaycommon.bean.TargetRoute; import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder; import com.gitee.sop.gatewaycommon.manager.EnvGrayManager; import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; import com.gitee.sop.gatewaycommon.param.ApiParam; -import org.apache.commons.lang3.BooleanUtils; import org.springframework.beans.factory.annotation.Autowired; -import javax.annotation.PostConstruct; -import java.util.Comparator; import java.util.List; /** @@ -28,9 +24,6 @@ public abstract class BaseForwardChooser implements ForwardChooser, ApiPar ApiParam apiParam = getApiParam(t); String nameVersion = apiParam.fetchNameVersion(); TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion); - RouteDefinition routeDefinitionOrig = targetRoute.getRouteDefinition(); - String path = routeDefinitionOrig.getPath(); - String version = apiParam.fetchVersion(); String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase(); // 如果服务在灰度阶段,返回一个灰度版本号 String grayVersion = envGrayManager.getVersion(serviceId, nameVersion); @@ -40,16 +33,10 @@ public abstract class BaseForwardChooser implements ForwardChooser, ApiPar TargetRoute targetRouteDest = RouteRepositoryContext.getRouteRepository().get(newNameVersion); if (targetRouteDest != null) { apiParam.setGrayRequest(true); - if (BooleanUtils.toBoolean(routeDefinitionOrig.getCompatibleMode())) { - version = grayVersion; - } else { - // 获取灰度接口 - RouteDefinition routeDefinition = targetRouteDest.getRouteDefinition(); - path = routeDefinition.getPath(); - } + targetRoute = targetRouteDest; } } - return new ForwardInfo(path, version); + return new ForwardInfo(targetRoute); } protected boolean isGrayUser(String serviceId, ApiParam apiParam) { diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java index 00c9333d..e822712e 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java @@ -1,5 +1,6 @@ package com.gitee.sop.gatewaycommon.route; +import com.gitee.sop.gatewaycommon.bean.TargetRoute; import lombok.Data; /** @@ -7,13 +8,48 @@ import lombok.Data; */ @Data public class ForwardInfo { - private String path; - private String version; - private String domain; - public ForwardInfo(String path, String version) { - this.path = path; - this.version = version; + private TargetRoute targetRoute; + + public static ForwardInfo getErrorForwardInfo() { + return ErrorForwardInfo.errorForwardInfo; + } + + public ForwardInfo(TargetRoute targetRoute) { + this.targetRoute = targetRoute; + } + + public String getPath() { + return targetRoute.getRouteDefinition().getPath(); + } + + public String getVersion() { + return targetRoute.getRouteDefinition().getVersion(); + } + + static class ErrorForwardInfo extends ForwardInfo { + + private static final String VALIDATE_ERROR_PATH = "/sop/validateError"; + + public static ErrorForwardInfo errorForwardInfo = new ErrorForwardInfo(); + + public ErrorForwardInfo() { + this(null); + } + + public ErrorForwardInfo(TargetRoute targetRoute) { + super(targetRoute); + } + + @Override + public String getPath() { + return VALIDATE_ERROR_PATH; + } + + @Override + public String getVersion() { + return ""; + } } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/BaseMonitorController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/BaseMonitorController.java new file mode 100644 index 00000000..8a23f53a --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/BaseMonitorController.java @@ -0,0 +1,21 @@ +package com.gitee.sop.gatewaycommon.support; + +import com.gitee.sop.gatewaycommon.bean.ApiConfig; +import com.gitee.sop.gatewaycommon.monitor.MonitorManager; +import com.gitee.sop.gatewaycommon.result.ApiResult; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * 提供监控数据 + * + * @author tanghc + */ +public abstract class BaseMonitorController extends SopBaseController { + + @GetMapping("/sop/getMonitorData") + public ApiResult doExecute(T request) { + MonitorManager monitorManager = ApiConfig.getInstance().getMonitorManager(); + return execute(request, monitorManager::getMonitorData); + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/SopBaseController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/SopBaseController.java new file mode 100644 index 00000000..96346579 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/support/SopBaseController.java @@ -0,0 +1,45 @@ +package com.gitee.sop.gatewaycommon.support; + +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.result.ApiResult; +import com.gitee.sop.gatewaycommon.result.JsonResult; +import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner; +import org.springframework.beans.factory.annotation.Value; + +import java.util.function.Supplier; + +/** + * @author tanghc + */ +public abstract class SopBaseController { + + TaobaoSigner signer = new TaobaoSigner(); + + @Value("${sop.secret}") + private String secret; + + protected abstract ApiParam getApiParam(T t); + + public ApiResult execute(T request, Supplier supplier) { + try { + this.check(request); + JsonResult apiResult = new JsonResult(); + apiResult.setData(supplier.get()); + return apiResult; + } catch (Exception e) { + ApiResult apiResult = new ApiResult(); + apiResult.setCode("505050"); + apiResult.setMsg(e.getMessage()); + return apiResult; + } + } + + protected void check(T request) { + ApiParam apiParam = getApiParam(request); + boolean right = signer.checkSign(apiParam, secret); + if (!right) { + throw new RuntimeException("签名校验失败"); + } + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/LoadBalanceUtil.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/LoadBalanceUtil.java new file mode 100644 index 00000000..752eb870 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/LoadBalanceUtil.java @@ -0,0 +1,63 @@ +package com.gitee.sop.gatewaycommon.util; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +/** + * @author tanghc + */ +public class LoadBalanceUtil { + + /** + * key:serviceId,value:指示变量i + */ + private static Map serviceIdRoundMap = new ConcurrentHashMap<>(8); + + /** + * 轮询选择一台机器。
+ *

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

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

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