pull/3/head
tanghc 4 years ago
parent 1850d6d567
commit 0c4dd04bc7
  1. 5
      doc/docs/files/10089_自定义校验token.md
  2. 21
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/RouteApi.java
  3. 1
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  4. 32
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteStatusUpdateParam.java
  5. 2
      sop-admin/sop-admin-server/src/main/resources/public/index.html
  6. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/css/app.6095bfbf.css
  7. 0
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-30c6c34f.3b12267b.css
  8. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-9b31c83a.c4612b4a.css
  9. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/app.f323bdd7.js
  10. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-25908fca.02b977ea.js
  11. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-25908fca.66819987.js
  12. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d32e7.213708f2.js
  13. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d32e7.e7c489be.js
  14. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-30c6c34f.b288bbf5.js
  15. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-4bdcb5ea.cf292569.js
  16. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9b31c83a.494fc338.js
  17. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9b31c83a.52bc6b2c.js
  18. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.2093f9d0.js
  19. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.3e73ea9f.js
  20. 3
      sop-admin/sop-admin-vue/src/styles/index.scss
  21. 12
      sop-admin/sop-admin-vue/src/utils/global.js
  22. 35
      sop-admin/sop-admin-vue/src/views/isv/index.vue
  23. 454
      sop-admin/sop-admin-vue/src/views/service/limit.vue
  24. 16
      sop-admin/sop-admin-vue/src/views/service/monitor.vue
  25. 335
      sop-admin/sop-admin-vue/src/views/service/route.vue
  26. 14
      sop-admin/sop-admin-vue/src/views/service/serviceList.vue
  27. 6
      sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1007_TokenController.java
  28. 23
      sop-gateway/src/main/java/com/gitee/sop/gateway/config/MyConfig.java
  29. 2
      sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java

@ -15,11 +15,10 @@ boolean needToken() default false;
@ApiOperation(value="传递token", notes = "传递token") @ApiOperation(value="传递token", notes = "传递token")
@Open(value = "story.get.token", needToken = true/* 设置true,网关会校验token是否存在 */) @Open(value = "story.get.token", needToken = true/* 设置true,网关会校验token是否存在 */)
@RequestMapping("token") @RequestMapping("token")
public StoryResult token(StoryParam story) { public StoryResult token(StoryParam story, HttpServletRequest request) {
OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext();
String appAuthToken = openContext.getAppAuthToken();
StoryResult result = new StoryResult(); StoryResult result = new StoryResult();
result.setName("appAuthToken:" + appAuthToken); result.setName("appAuthToken:" + openContext.getAppAuthToken());
return result; return result;
} }
``` ```

@ -13,6 +13,7 @@ import com.gitee.sop.adminserver.api.service.param.RouteAddParam;
import com.gitee.sop.adminserver.api.service.param.RouteDeleteParam; import com.gitee.sop.adminserver.api.service.param.RouteDeleteParam;
import com.gitee.sop.adminserver.api.service.param.RoutePermissionParam; import com.gitee.sop.adminserver.api.service.param.RoutePermissionParam;
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam; import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
import com.gitee.sop.adminserver.api.service.param.RouteStatusUpdateParam;
import com.gitee.sop.adminserver.api.service.param.RouteUpdateParam; import com.gitee.sop.adminserver.api.service.param.RouteUpdateParam;
import com.gitee.sop.adminserver.api.service.result.RouteVO; import com.gitee.sop.adminserver.api.service.result.RouteVO;
import com.gitee.sop.adminserver.bean.RouteConfigDto; import com.gitee.sop.adminserver.bean.RouteConfigDto;
@ -123,6 +124,26 @@ public class RouteApi {
this.updateRouteConfig(param); this.updateRouteConfig(param);
} }
@Api(name = "route.status.update")
@ApiDocMethod(description = "修改路由状态")
void updateRouteStatus(RouteStatusUpdateParam param) {
String routeId = param.getId();
ConfigRouteBase configRouteBase = configRouteBaseMapper.getByColumn("route_id", routeId);
boolean doSave = configRouteBase == null;
if (doSave) {
configRouteBase = new ConfigRouteBase();
configRouteBase.setRouteId(routeId);
}
configRouteBase.setStatus(param.getStatus());
int i = doSave ? configRouteBaseMapper.save(configRouteBase)
: configRouteBaseMapper.update(configRouteBase);
if (i > 0) {
this.sendMsg(configRouteBase);
}
}
@Api(name = "route.del") @Api(name = "route.del")
@ApiDocMethod(description = "删除路由") @ApiDocMethod(description = "删除路由")
void delRoute(RouteDeleteParam param) { void delRoute(RouteDeleteParam param) {

@ -75,6 +75,7 @@ public class ServiceApi {
return serviceId.contains(param.getServiceId()); return serviceId.contains(param.getServiceId());
} }
}) })
.sorted()
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

@ -0,0 +1,32 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @author tanghc
*/
@Getter
@Setter
public class RouteStatusUpdateParam {
/**
* 路由的Id
*/
@NotBlank(message = "id不能为空")
@ApiDocField(description = "路由id")
private String id;
/**
* 状态
*/
@NotNull
@ApiDocField(description = "状态,0:审核,1:启用,2:禁用")
private Byte status;
}

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

@ -1 +1 @@
.custom-tree-node{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;font-size:14px;padding-right:8px}.el-input.is-disabled .el-input__inner,.el-radio__input.is-disabled+span.el-radio__label{color:#909399}.limit-tip[data-v-08125a7c]{cursor:pointer;margin-left:10px} .custom-tree-node{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;font-size:14px;padding-right:8px}.el-input.is-disabled .el-input__inner,.el-radio__input.is-disabled+span.el-radio__label{color:#909399}.roles-content{cursor:pointer;color:#20a0ff}

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

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

@ -63,3 +63,6 @@ div:focus {
.app-container { .app-container {
padding: 20px; padding: 20px;
} }
.cell .el-button {padding: 0;}
span.tip {color: #909399;font-size: 12px;}

@ -94,6 +94,18 @@ Object.assign(Vue.prototype, {
} }
}).catch(function() {}) }).catch(function() {})
}, },
downloadText(filename, text) {
const element = document.createElement('a')
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text))
element.setAttribute('download', filename)
element.style.display = 'none'
document.body.appendChild(element)
element.click()
document.body.removeChild(element);
},
/** /**
* 重置表单 * 重置表单
* @param formName 表单元素的ref * @param formName 表单元素的ref

@ -15,14 +15,9 @@
fit fit
highlight-current-row highlight-current-row
> >
<el-table-column
prop="id"
label="ID"
width="80"
/>
<el-table-column <el-table-column
prop="appKey" prop="appKey"
label="appId" label="AppId"
width="250" width="250"
/> />
<el-table-column <el-table-column
@ -37,11 +32,11 @@
<el-table-column <el-table-column
prop="roleList" prop="roleList"
label="角色" label="角色"
width="100" width="150"
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
> >
<template slot-scope="scope"> <template slot-scope="scope">
<div v-html="roleRender(scope.row)"></div> <span v-html="roleRender(scope.row)"></span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -59,24 +54,20 @@
label="添加时间" label="添加时间"
width="160" width="160"
/> />
<el-table-column
prop="gmtModified"
label="修改时间"
width="160"
/>
<el-table-column <el-table-column
prop="remark" prop="remark"
label="备注" label="备注"
width="120" width="200"
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
/> />
<el-table-column <el-table-column
label="操作" label="操作"
width="150" width="200"
> >
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button> <el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
<el-button type="text" size="mini" @click="onKeysUpdate(scope.row)">秘钥管理</el-button> <el-button type="text" size="mini" @click="onKeysUpdate(scope.row)">秘钥管理</el-button>
<el-button type="text" size="mini" @click="onExportKeys(scope.row)">导出秘钥</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -281,6 +272,20 @@ export default {
onKeysUpdate: function(row) { onKeysUpdate: function(row) {
this.$router.push({ path: `keys?appKey=${row.appKey}` }) this.$router.push({ path: `keys?appKey=${row.appKey}` })
}, },
onExportKeys: function(row) {
this.post('isv.keys.get', { appKey: row.appKey }, function(resp) {
const data = resp.data
const appId = data.appKey
const privateKeyIsv = data.privateKeyIsv
const publicKeyPlatform = data.publicKeyPlatform
let content = `AppId:${appId}\n\n开发者私钥:\n${privateKeyIsv}\n\n`
if (publicKeyPlatform) {
content = content + `平台公钥:\n${publicKeyPlatform}`
}
const filename = `${appId}.txt`
this.downloadText(filename, content)
})
},
onSizeChange: function(size) { onSizeChange: function(size) {
this.searchFormData.pageSize = size this.searchFormData.pageSize = size
this.loadTable() this.loadTable()

@ -1,229 +1,215 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-container> <div v-if="tabsData.length === 0">
<el-aside style="min-height: 300px;width: 250px;"> 无服务
<el-input v-model="filterText" prefix-icon="el-icon-search" placeholder="搜索服务..." style="margin-bottom:20px;" size="mini" clearable /> </div>
<el-tree <div v-else>
ref="tree2" <el-tabs v-model="tabsActive" type="card" @tab-click="selectTab">
:data="treeData" <el-tab-pane v-for="tabName in tabsData" :key="tabName" :label="tabName" :name="tabName" />
:props="defaultProps" </el-tabs>
:filter-node-method="filterNode" <el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
:highlight-current="true" <el-form-item label="路由ID">
:expand-on-click-node="false" <el-input v-model="searchFormData.routeId" placeholder="接口名,支持模糊查询" clearable />
empty-text="无数据" </el-form-item>
node-key="id" <el-form-item label="AppId">
class="filter-tree" <el-input v-model="searchFormData.appKey" placeholder="AppId,支持模糊查询" clearable />
default-expand-all </el-form-item>
@node-click="onNodeClick" <el-form-item label="IP">
<el-input v-model="searchFormData.limitIp" placeholder="ip,支持模糊查询" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="onSearchTable">查询</el-button>
</el-form-item>
</el-form>
<el-button type="primary" size="mini" icon="el-icon-plus" style="margin-bottom: 10px;" @click="onAdd">新增限流</el-button>
<el-table
:data="pageInfo.list"
border
>
<el-table-column
prop="limitKey"
label="限流维度"
width="400"
> >
<span slot-scope="{ node, data }" class="custom-tree-node"> <template slot-scope="scope">
<div> <div v-html="limitRender(scope.row)"></div>
<el-tooltip v-show="data.custom" content="自定义服务" class="item" effect="light" placement="left"> </template>
<i class="el-icon-warning-outline"></i> </el-table-column>
</el-tooltip> <el-table-column
<span v-if="data.label.length < serviceTextLimitSize">{{ data.label }}</span> prop="limitType"
<span v-else> label="限流策略"
<el-tooltip :content="data.label" class="item" effect="light" placement="right"> width="120"
<span>{{ data.label.substring(0, serviceTextLimitSize) + '...' }}</span>
</el-tooltip>
</span>
</div>
<span>
<el-button
v-if="data.custom === 1"
type="text"
size="mini"
icon="el-icon-delete"
title="删除服务"
@click.stop="() => onDelService(data)"/>
</span>
</span>
</el-tree>
</el-aside>
<el-main style="padding-top:0">
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
<el-form-item label="路由ID">
<el-input v-model="searchFormData.routeId" placeholder="接口名,支持模糊查询" clearable />
</el-form-item>
<el-form-item label="AppId">
<el-input v-model="searchFormData.appKey" placeholder="AppId,支持模糊查询" clearable />
</el-form-item>
<el-form-item label="IP">
<el-input v-model="searchFormData.limitIp" placeholder="ip,支持模糊查询" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="onSearchTable">查询</el-button>
</el-form-item>
</el-form>
<el-button type="primary" size="mini" icon="el-icon-plus" style="margin-bottom: 10px;" @click="onAdd">新增限流</el-button>
<el-table
:data="pageInfo.list"
border
> >
<el-table-column <template slot="header" slot-scope>
prop="limitKey" 限流策略
label="限流维度" <el-popover
width="400" ref="popover"
> placement="top"
<template slot-scope="scope"> title="限流策略"
<div v-html="limitRender(scope.row)"></div> width="500"
</template> trigger="hover">
</el-table-column> <div>
<el-table-column <p>窗口策略每秒处理固定数量的请求超出请求数量返回错误信息</p>
prop="limitType" <p>令牌桶策略每秒放置固定数量的令牌数每个请求进来后先去拿令牌拿到了令牌才能继续拿不到则等候令牌重新生成了再拿</p>
label="限流策略" </div>
width="120" </el-popover>
> <i v-popover:popover class="el-icon-question" style="cursor: pointer"></i>
<template slot="header" slot-scope> </template>
限流策略 <i class="el-icon-question" style="cursor: pointer" @click="onLimitTypeTipClick"></i> <template slot-scope="scope">
</template> <span v-if="scope.row.limitType === 1">窗口策略</span>
<template slot-scope="scope"> <span v-if="scope.row.limitType === 2">令牌桶策略</span>
<span v-if="scope.row.limitType === 1">窗口策略</span> </template>
<span v-if="scope.row.limitType === 2">令牌桶策略</span> </el-table-column>
</template> <el-table-column
</el-table-column> prop="info"
<el-table-column label="限流信息"
prop="info" width="250"
label="限流信息" >
width="250" <template slot-scope="scope">
> <span v-html="infoRender(scope.row)"></span>
<template slot-scope="scope"> </template>
<span v-html="infoRender(scope.row)"></span> </el-table-column>
</template> <el-table-column
</el-table-column> prop="limitStatus"
<el-table-column label="状态"
prop="limitStatus" width="80"
label="状态" >
width="80" <template slot-scope="scope">
> <span v-if="scope.row.limitStatus === 1" style="color:#67C23A">已开启</span>
<template slot-scope="scope"> <span v-if="scope.row.limitStatus === 0" style="color:#909399">已关闭</span>
<span v-if="scope.row.limitStatus === 1" style="color:#67C23A">已开启</span> </template>
<span v-if="scope.row.limitStatus === 0" style="color:#909399">已关闭</span> </el-table-column>
</template> <el-table-column
</el-table-column> prop="orderIndex"
<el-table-column label="排序"
prop="orderIndex" width="80"
label="排序" />
width="80" <el-table-column
/> prop="remark"
<el-table-column label="备注"
prop="remark" width="150"
label="备注" :show-overflow-tooltip="true"
width="150" />
:show-overflow-tooltip="true" <el-table-column
/> prop="gmtCreate"
<el-table-column label="创建时间"
prop="gmtCreate" width="160"
label="创建时间"
width="160"
/>
<el-table-column
prop="gmtModified"
label="修改时间"
width="160"
/>
<el-table-column
label="操作"
fixed="right"
width="80"
>
<template slot-scope="scope">
<el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
background
style="margin-top: 5px"
:current-page="searchFormData.pageIndex"
:page-size="searchFormData.pageSize"
:page-sizes="[5, 10, 20, 40]"
:total="pageInfo.total"
layout="total, sizes, prev, pager, next"
@size-change="onSizeChange"
@current-change="onPageIndexChange"
/> />
<!-- dialog --> <el-table-column
<el-dialog prop="gmtModified"
:title="dlgTitle" label="修改时间"
:visible.sync="limitDialogVisible" width="160"
:close-on-click-modal="false" />
@close="onLimitDialogClose" <el-table-column
label="操作"
fixed="right"
width="80"
> >
<el-form <template slot-scope="scope">
ref="limitDialogForm" <el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
:model="limitDialogFormData" </template>
:rules="rulesLimit" </el-table-column>
label-width="150px" </el-table>
size="mini" <el-pagination
background
style="margin-top: 5px"
:current-page="searchFormData.pageIndex"
:page-size="searchFormData.pageSize"
:page-sizes="[5, 10, 20, 40]"
:total="pageInfo.total"
layout="total, sizes, prev, pager, next"
@size-change="onSizeChange"
@current-change="onPageIndexChange"
/>
</div>
<!-- dialog -->
<el-dialog
:title="dlgTitle"
:visible.sync="limitDialogVisible"
:close-on-click-modal="false"
@close="onLimitDialogClose"
>
<el-form
ref="limitDialogForm"
:model="limitDialogFormData"
:rules="rulesLimit"
label-width="150px"
size="mini"
>
<el-form-item label="限流维度" prop="typeKey">
<el-checkbox-group v-model="limitDialogFormData.typeKey">
<el-checkbox v-model="limitDialogFormData.typeKey[0]" :label="1" name="typeKey">路由ID</el-checkbox>
<el-checkbox v-model="limitDialogFormData.typeKey[1]" :label="2" name="typeKey">AppId</el-checkbox>
<el-checkbox v-model="limitDialogFormData.typeKey[2]" :label="3" name="typeKey">IP</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item v-show="checkTypeKey(1)" prop="routeId" label="路由ID" :rules="checkTypeKey(1) ? rulesLimit.routeId : []">
<el-select v-model="limitDialogFormData.routeId" filterable placeholder="可筛选" style="width: 300px;">
<el-option
v-for="item in routeList"
:key="item.id"
:label="item.id"
:value="item.id"
>
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.version }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item v-show="checkTypeKey(2)" prop="appKey" label="AppId" :rules="checkTypeKey(2) ? rulesLimit.appKey : []">
<el-input v-model="limitDialogFormData.appKey" placeholder="需要限流的AppId" />
</el-form-item>
<el-form-item v-show="checkTypeKey(3)" label="限流IP" prop="limitIp" :rules="checkTypeKey(3) ? rulesLimit.ip : []">
<el-input v-model="limitDialogFormData.limitIp" type="textarea" :rows="2" placeholder="多个用英文逗号隔开" />
</el-form-item>
<el-form-item label="限流策略">
<el-radio-group v-model="limitDialogFormData.limitType">
<el-radio :label="1">窗口策略</el-radio>
<el-radio :label="2">令牌桶策略</el-radio>
</el-radio-group>
<el-popover
ref="popover"
placement="top"
title="限流策略"
width="500"
trigger="hover">
<div>
<p>窗口策略每秒处理固定数量的请求超出请求数量返回错误信息</p>
<p>令牌桶策略每秒放置固定数量的令牌数每个请求进来后先去拿令牌拿到了令牌才能继续拿不到则等候令牌重新生成了再拿</p>
</div>
</el-popover>
<i v-popover:popover class="el-icon-question" style="cursor: pointer"></i>
</el-form-item>
<el-form-item label="开启状态">
<el-switch
v-model="limitDialogFormData.limitStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
> >
<el-form-item label="限流维度" prop="typeKey"> </el-switch>
<el-checkbox-group v-model="limitDialogFormData.typeKey"> </el-form-item>
<el-checkbox v-model="limitDialogFormData.typeKey[0]" :label="1" name="typeKey">路由ID</el-checkbox> <el-form-item label="排序" prop="orderIndex">
<el-checkbox v-model="limitDialogFormData.typeKey[1]" :label="2" name="typeKey">AppId</el-checkbox> <el-input-number v-model="limitDialogFormData.orderIndex" controls-position="right" :min="0" />
<el-checkbox v-model="limitDialogFormData.typeKey[2]" :label="3" name="typeKey">IP</el-checkbox> <span class="tip" style="margin-left: 10px">值小优先执行</span>
</el-checkbox-group> </el-form-item>
</el-form-item> <el-form-item v-show="isWindowType()" label="请求数" prop="execCountPerSecond" :rules="isWindowType() ? rulesLimit.execCountPerSecond : []">
<el-form-item v-show="checkTypeKey(1)" prop="routeId" label="路由ID" :rules="checkTypeKey(1) ? rulesLimit.routeId : []"> <el-input-number v-model="limitDialogFormData.durationSeconds" controls-position="right" :min="1" /> 秒可处理
<el-select v-model="limitDialogFormData.routeId" filterable placeholder="可筛选" style="width: 300px;"> <el-input-number v-model="limitDialogFormData.execCountPerSecond" controls-position="right" :min="1" /> 个请求
<el-option </el-form-item>
v-for="item in routeList" <el-form-item v-show="isTokenType()" label="令牌桶容量" prop="tokenBucketCount" :rules="isTokenType() ? rulesLimit.tokenBucketCount : []">
:key="item.id" <el-input-number v-model="limitDialogFormData.tokenBucketCount" controls-position="right" :min="1" />
:label="item.id" </el-form-item>
:value="item.id" <el-form-item label="备注" prop="remark">
> <el-input v-model="limitDialogFormData.remark" type="textarea" :rows="2" />
<span style="float: left">{{ item.name }}</span> </el-form-item>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.version }}</span> </el-form>
</el-option> <div slot="footer" class="dialog-footer">
</el-select> <el-button @click="limitDialogVisible = false"> </el-button>
</el-form-item> <el-button type="primary" @click="onLimitDialogSave"> </el-button>
<el-form-item v-show="checkTypeKey(2)" prop="appKey" label="AppId" :rules="checkTypeKey(2) ? rulesLimit.appKey : []"> </div>
<el-input v-model="limitDialogFormData.appKey" placeholder="需要限流的AppId" /> </el-dialog>
</el-form-item>
<el-form-item v-show="checkTypeKey(3)" label="限流IP" prop="limitIp" :rules="checkTypeKey(3) ? rulesLimit.ip : []">
<el-input v-model="limitDialogFormData.limitIp" type="textarea" :rows="2" placeholder="多个用英文逗号隔开" />
</el-form-item>
<el-form-item label="限流策略">
<el-radio-group v-model="limitDialogFormData.limitType">
<el-radio :label="1">窗口策略</el-radio>
<el-radio :label="2">令牌桶策略</el-radio>
</el-radio-group>
<i class="el-icon-question limit-tip" @click="onLimitTypeTipClick"></i>
</el-form-item>
<el-form-item label="开启状态">
<el-switch
v-model="limitDialogFormData.limitStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
>
</el-switch>
</el-form-item>
<el-form-item label="排序" prop="orderIndex">
<el-input-number v-model="limitDialogFormData.orderIndex" controls-position="right" :min="0" />
<el-tooltip class="item" content="值小优先执行" placement="top">
<i class="el-icon-question limit-tip"></i>
</el-tooltip>
</el-form-item>
<el-form-item v-show="isWindowType()" label="请求数" prop="execCountPerSecond" :rules="isWindowType() ? rulesLimit.execCountPerSecond : []">
<el-input-number v-model="limitDialogFormData.durationSeconds" controls-position="right" :min="1" /> 秒可处理
<el-input-number v-model="limitDialogFormData.execCountPerSecond" controls-position="right" :min="1" /> 个请求
</el-form-item>
<el-form-item v-show="isTokenType()" label="令牌桶容量" prop="tokenBucketCount" :rules="isTokenType() ? rulesLimit.tokenBucketCount : []">
<el-input-number v-model="limitDialogFormData.tokenBucketCount" controls-position="right" :min="1" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="limitDialogFormData.remark" type="textarea" :rows="2" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="limitDialogVisible = false"> </el-button>
<el-button type="primary" @click="onLimitDialogSave"> </el-button>
</div>
</el-dialog>
</el-main>
</el-container>
</div> </div>
</template> </template>
<style> <style>
@ -242,6 +228,8 @@
export default { export default {
data() { data() {
return { return {
tabsData: [],
tabsActive: '',
serviceTextLimitSize: 20, serviceTextLimitSize: 20,
filterText: '', filterText: '',
treeData: [], treeData: [],
@ -320,9 +308,20 @@ export default {
} }
}, },
created() { created() {
this.loadTree() this.loadTabs()
}, },
methods: { methods: {
loadTabs() {
this.post('registry.service.list', {}, function(resp) {
this.tabsData = resp.data
this.$nextTick(() => {
if (this.tabsData.length > 0) {
this.tabsActive = this.tabsData[0]
this.loadLimitData()
}
})
})
},
// //
loadTree: function() { loadTree: function() {
this.post('registry.service.list', {}, function(resp) { this.post('registry.service.list', {}, function(resp) {
@ -344,6 +343,15 @@ export default {
this.loadRouteList(this.serviceId) this.loadRouteList(this.serviceId)
} }
}, },
selectTab() {
this.loadLimitData()
},
loadLimitData() {
this.serviceId = this.tabsActive
this.searchFormData.serviceId = this.serviceId
this.loadTable()
this.loadRouteList(this.serviceId)
},
/** /**
* 数组转成树状结构 * 数组转成树状结构
* @param data 数据结构 [{ * @param data 数据结构 [{
@ -506,9 +514,3 @@ export default {
} }
} }
</script> </script>
<style scoped>
.limit-tip {
cursor: pointer;
margin-left: 10px;
}
</style>

@ -54,11 +54,9 @@
> >
<template slot="header"> <template slot="header">
最大耗时(ms) 最大耗时(ms)
<i <el-tooltip effect="dark" content="耗时计算:签名验证成功后开始,微服务返回结果后结束" placement="top">
class="el-icon-question" <i class="el-icon-question" style="cursor: pointer"></i>
style="cursor: pointer" </el-tooltip>
@click="$alert('耗时计算:签名验证成功后开始,微服务返回结果后结束')"
></i>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -88,11 +86,9 @@
> >
<template slot="header"> <template slot="header">
失败次数 失败次数
<i <el-tooltip effect="dark" content="只统计微服务返回的未知错误,JSR-303验证错误算作成功" placement="top-end">
class="el-icon-question" <i class="el-icon-question" style="cursor: pointer"></i>
style="cursor: pointer" </el-tooltip>
@click="$alert('只统计微服务返回的未知错误,JSR-303验证错误算作成功')"
></i>
</template> </template>
<template slot-scope="scope"> <template slot-scope="scope">
<el-link <el-link

@ -1,176 +1,121 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-container> <div v-if="tabsData.length === 0">
<el-aside style="min-height: 300px;width: 250px;"> 无服务
<el-button </div>
type="primary" <div v-else>
plain <el-tabs v-model="tabsActive" type="card" @tab-click="selectTab">
size="mini" <el-tab-pane v-for="tabName in tabsData" :key="tabName" :label="tabName" :name="tabName" />
icon="el-icon-plus" </el-tabs>
style="display: none;" <el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini" @submit.native.prevent>
@click.stop="addService" <el-form-item label="路由名称">
<el-input v-model="searchFormData.id" :clearable="true" placeholder="输入接口名或版本号" />
</el-form-item>
<el-form-item>
<el-checkbox v-model="searchFormData.permission">授权接口</el-checkbox>
</el-form-item>
<el-form-item>
<el-checkbox v-model="searchFormData.needToken">需要token</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" native-type="submit" @click="onSearchTable">查询</el-button>
</el-form-item>
</el-form>
<el-button
v-show="isCustomService"
type="primary"
size="mini"
icon="el-icon-plus"
@click.stop="addRoute"
>
新建路由
</el-button>
<el-table
:data="pageInfo.rows"
border
highlight-current-row
style="margin-top: 10px;"
>
<el-table-column
prop="name"
label="接口名 (版本号)"
> >
新建服务 <template slot-scope="scope">
</el-button> {{ getNameVersion(scope.row) }}
<el-input </template>
v-model="filterText" </el-table-column>
prefix-icon="el-icon-search" <el-table-column
placeholder="搜索服务..." prop="roles"
style="margin-bottom:10px;margin-top:10px;" label="访问权限"
size="mini" width="150"
clearable :show-overflow-tooltip="true"
/>
<el-tree
ref="serviceTree"
:data="treeData"
:props="defaultProps"
:filter-node-method="filterNode"
:highlight-current="true"
:expand-on-click-node="false"
empty-text="无数据"
node-key="serviceId"
class="filter-tree"
default-expand-all
@node-click="onNodeClick"
> >
<span slot-scope="{ node, data }" class="custom-tree-node"> <template slot-scope="scope">
<div> <span v-if="!scope.row.permission">
<el-tooltip v-show="data.custom" content="自定义服务" class="item" effect="light" placement="left"> 公开
<i class="el-icon-warning-outline"></i>
</el-tooltip>
<span v-if="data.label.length < serviceTextLimitSize">{{ data.label }}</span>
<span v-else>
<el-tooltip :content="data.label" class="item" effect="light" placement="right">
<span>{{ data.label.substring(0, serviceTextLimitSize) + '...' }}</span>
</el-tooltip>
</span>
</div>
<span>
<el-button
v-if="data.custom === 1"
type="text"
size="mini"
icon="el-icon-delete"
title="删除服务"
@click.stop="() => onDelService(data)"
/>
</span> </span>
</span> <span v-else class="roles-content" @click="onTableAuth(scope.row)" v-html="roleRender(scope.row)"></span>
</el-tree> </template>
</el-aside> </el-table-column>
<el-main style="padding-top:0"> <el-table-column
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini"> prop="ignoreValidate"
<el-form-item label="路由名称"> label="签名校验"
<el-input v-model="searchFormData.id" :clearable="true" placeholder="输入接口名或版本号" /> width="120"
</el-form-item> >
<el-form-item> <template slot-scope="scope">
<el-checkbox v-model="searchFormData.permission">授权接口</el-checkbox> <span v-if="scope.row.ignoreValidate === 0">校验</span>
</el-form-item> <span v-if="scope.row.ignoreValidate === 1" style="color:#E6A23C">不校验</span>
<el-form-item> </template>
<el-checkbox v-model="searchFormData.needToken">需要token</el-checkbox> </el-table-column>
</el-form-item> <el-table-column
<el-form-item> prop="mergeResult"
<el-button type="primary" icon="el-icon-search" @click="onSearchTable">查询</el-button> label="统一格式输出"
</el-form-item> width="120"
</el-form>
<el-button
v-show="isCustomService"
type="primary"
size="mini"
icon="el-icon-plus"
@click.stop="addRoute"
> >
新建路由 <template slot-scope="scope">
</el-button> <span v-if="scope.row.mergeResult === 1"></span>
<el-table <span v-if="scope.row.mergeResult === 0" style="color:#E6A23C"></span>
:data="pageInfo.rows" </template>
border </el-table-column>
highlight-current-row <el-table-column
style="margin-top: 10px;" prop="needToken"
label="需要token"
width="120"
> >
<el-table-column <template slot-scope="scope">
prop="name" <span v-if="scope.row.needToken === 1" style="font-weight: bold;color: #303133;"></span>
label="接口名 (版本号)" <span v-if="scope.row.needToken === 0"></span>
width="350" </template>
> </el-table-column>
<template slot-scope="scope"> <el-table-column
{{ scope.row.name + (scope.row.version ? ' (' + scope.row.version + ')' : '') }} prop="status"
</template> label="状态"
</el-table-column> width="80"
<el-table-column >
prop="roles" <template slot-scope="scope">
label="访问权限" <el-switch
width="150" v-model="scope.row.status"
> active-color="#13ce66"
<template slot-scope="scope"> inactive-color="#ff4949"
<span v-html="roleRender(scope.row)"></span> :active-value="1"
</template> :inactive-value="2"
</el-table-column> @change="onChangeStatus(scope.row)"
<el-table-column />
prop="ignoreValidate" </template>
label="签名校验" </el-table-column>
width="80" </el-table>
> <el-pagination
<template slot-scope="scope"> background
<span v-if="scope.row.ignoreValidate === 0">校验</span> style="margin-top: 5px"
<span v-if="scope.row.ignoreValidate === 1" style="color:#E6A23C">不校验</span> :current-page="searchFormData.pageIndex"
</template> :page-size="searchFormData.pageSize"
</el-table-column> :page-sizes="[10, 20, 40]"
<el-table-column :total="pageInfo.total"
prop="mergeResult" layout="total, sizes, prev, pager, next"
label="统一格式输出" @size-change="onSizeChange"
width="120" @current-change="onPageIndexChange"
> />
<template slot-scope="scope"> </div>
<span v-if="scope.row.mergeResult === 1"></span>
<span v-if="scope.row.mergeResult === 0" style="color:#E6A23C"></span>
</template>
</el-table-column>
<el-table-column
prop="needToken"
label="需要token"
width="100"
>
<template slot-scope="scope">
<span v-if="scope.row.needToken === 1" style="font-weight: bold;color: #303133;"></span>
<span v-if="scope.row.needToken === 0"></span>
</template>
</el-table-column>
<el-table-column
prop="status"
label="状态"
width="80"
>
<template slot-scope="scope">
<span v-if="scope.row.status === 0" style="color:#E6A23C">待审核</span>
<span v-if="scope.row.status === 1" style="color:#67C23A">已启用</span>
<span v-if="scope.row.status === 2" style="color:#F56C6C">已禁用</span>
</template>
</el-table-column>
<el-table-column
label="操作"
width="100"
>
<template slot-scope="scope">
<el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
<el-button v-if="scope.row.permission" type="text" size="mini" @click="onTableAuth(scope.row)">授权</el-button>
<el-button v-if="scope.row.custom" type="text" size="mini" @click="onTableDel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
background
style="margin-top: 5px"
:current-page="searchFormData.pageIndex"
:page-size="searchFormData.pageSize"
:page-sizes="[10, 20, 40]"
:total="pageInfo.total"
layout="total, sizes, prev, pager, next"
@size-change="onSizeChange"
@current-change="onPageIndexChange"
/>
</el-main>
</el-container>
<!-- route dialog --> <!-- route dialog -->
<el-dialog <el-dialog
:title="routeDialogTitle" :title="routeDialogTitle"
@ -270,11 +215,14 @@
} }
.el-input.is-disabled .el-input__inner {color: #909399;} .el-input.is-disabled .el-input__inner {color: #909399;}
.el-radio__input.is-disabled+span.el-radio__label {color: #909399;} .el-radio__input.is-disabled+span.el-radio__label {color: #909399;}
.roles-content { cursor: pointer;color: #20a0ff }
</style> </style>
<script> <script>
export default { export default {
data() { data() {
return { return {
tabsData: [],
tabsActive: '',
serviceTextLimitSize: 20, serviceTextLimitSize: 20,
filterText: '', filterText: '',
treeData: [], treeData: [],
@ -352,10 +300,21 @@ export default {
} }
}, },
created() { created() {
this.loadTree() this.loadTabs()
this.loadRouteRole() this.loadRouteRole()
}, },
methods: { methods: {
loadTabs() {
this.post('registry.service.list', {}, function(resp) {
this.tabsData = resp.data
this.$nextTick(() => {
if (this.tabsData.length > 0) {
this.tabsActive = this.tabsData[0]
this.loadRouteData()
}
})
})
},
// //
loadTree: function() { loadTree: function() {
this.post('registry.service.list', {}, function(resp) { this.post('registry.service.list', {}, function(resp) {
@ -383,6 +342,14 @@ export default {
this.loadTable() this.loadTable()
} }
}, },
selectTab() {
this.loadRouteData()
},
loadRouteData() {
this.serviceId = this.tabsActive
this.searchFormData.serviceId = this.serviceId
this.loadTable()
},
/** /**
* 数组转成树状结构 * 数组转成树状结构
* @param data 数据结构 [{ * @param data 数据结构 [{
@ -421,6 +388,9 @@ export default {
result.push(root) result.push(root)
return result return result
}, },
getNameVersion(row) {
return row.name + (row.version ? ' (' + row.version + ')' : '')
},
// table // table
loadTable: function(param) { loadTable: function(param) {
if (!this.searchFormData.serviceId) { if (!this.searchFormData.serviceId) {
@ -469,6 +439,30 @@ export default {
}) })
}) })
}, },
// element-ui switch
// https://blog.csdn.net/Gomeer/article/details/103697593
onChangeStatus: function(row) {
const newStatus = row.status
const oldStatus = newStatus === 1 ? 2 : 1
//
row.status = oldStatus
const nameVersion = this.getNameVersion(row)
const msg = oldStatus === 1 ? `确认要禁用 ${nameVersion} 吗?` : `确认要启用 ${nameVersion} 吗?`
this.confirm(msg, function(done) {
const data = {
id: row.id,
status: newStatus
}
// 'route.role.update', this.authDialogFormData
this.post('route.status.update', data, function() {
done()
row.status = newStatus
})
}, (done) => {
row.status = oldStatus
done()
})
},
onCloseRouteDialog: function() { onCloseRouteDialog: function() {
this.resetForm('routeDialogFormRef') this.resetForm('routeDialogFormRef')
}, },
@ -495,20 +489,17 @@ export default {
}) })
}, },
roleRender: function(row) { roleRender: function(row) {
if (!row.permission) {
return '(公开)'
}
const html = [] const html = []
const roles = row.roles const roles = row.roles
for (let i = 0; i < roles.length; i++) { for (let i = 0; i < roles.length; i++) {
html.push(roles[i].description) html.push(roles[i].description)
} }
return html.length > 0 ? html.join(', ') : '<span class="x-red">未授权</span>' return html.length > 0 ? html.join(', ') : '点击授权'
}, },
onRouteDialogSave: function() { onRouteDialogSave: function() {
this.$refs.routeDialogFormRef.validate((valid) => { this.$refs.routeDialogFormRef.validate((valid) => {
if (valid) { if (valid) {
const uri = this.routeDialogFormData.id ? 'route.update' : 'route.add' const uri = this.routeDialogFormData.id ? 'route.status.update' : 'route.add'
this.routeDialogFormData.serviceId = this.serviceId this.routeDialogFormData.serviceId = this.serviceId
this.post(uri, this.routeDialogFormData, function() { this.post(uri, this.routeDialogFormData, function() {
this.routeDialogVisible = false this.routeDialogVisible = false

@ -106,7 +106,7 @@
<el-tabs v-model="tabsActiveName" type="card"> <el-tabs v-model="tabsActiveName" type="card">
<el-tab-pane label="灰度用户" name="first"> <el-tab-pane label="灰度用户" name="first">
<el-alert <el-alert
title="可以是appId或IP地址,多个用英文逗号隔开" title="可以是AppId或IP地址,多个用英文逗号隔开"
type="info" type="info"
:closable="false" :closable="false"
style="margin-bottom: 20px;" style="margin-bottom: 20px;"
@ -114,13 +114,18 @@
<el-form-item prop="userKeyContent"> <el-form-item prop="userKeyContent">
<el-input <el-input
v-model="grayForm.userKeyContent" v-model="grayForm.userKeyContent"
placeholder="可以是appId或IP地址,多个用英文逗号隔开" placeholder="可以是AppId或IP地址,多个用英文逗号隔开"
type="textarea" type="textarea"
:rows="6" :rows="6"
/> />
</el-form-item> </el-form-item>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="接口配置" name="second"> <el-tab-pane label="接口配置" name="second">
<el-alert
title="灰度接口:接口名相同,版本号不同"
type="info"
:closable="false"
/>
<el-form-item> <el-form-item>
<el-button type="text" @click="addNameVersion">新增灰度接口</el-button> <el-button type="text" @click="addNameVersion">新增灰度接口</el-button>
</el-form-item> </el-form-item>
@ -138,7 +143,8 @@
老接口 老接口
<el-select <el-select
v-model="grayRouteConfig.oldRouteId" v-model="grayRouteConfig.oldRouteId"
style="margin-right: 10px;" filterable
style="margin-right: 10px;width: 250px"
@change="onChangeOldRoute(grayRouteConfig)" @change="onChangeOldRoute(grayRouteConfig)"
> >
<el-option <el-option
@ -159,7 +165,9 @@
灰度接口 灰度接口
<el-select <el-select
v-model="grayRouteConfig.newVersion" v-model="grayRouteConfig.newVersion"
filterable
no-data-text="无数据" no-data-text="无数据"
style="width: 250px"
> >
<el-option <el-option
v-for="routeNew in getGraySelectData(grayRouteConfig.oldRouteId)" v-for="routeNew in getGraySelectData(grayRouteConfig.oldRouteId)"

@ -1,7 +1,9 @@
package com.gitee.sop.storyweb.controller; package com.gitee.sop.storyweb.controller;
import com.gitee.sop.servercommon.annotation.Open; import com.gitee.sop.servercommon.annotation.Open;
import com.gitee.sop.servercommon.bean.OpenContext;
import com.gitee.sop.servercommon.bean.ParamNames; import com.gitee.sop.servercommon.bean.ParamNames;
import com.gitee.sop.servercommon.bean.ServiceContext;
import com.gitee.sop.storyweb.controller.param.StoryParam; import com.gitee.sop.storyweb.controller.param.StoryParam;
import com.gitee.sop.storyweb.controller.result.StoryResult; import com.gitee.sop.storyweb.controller.result.StoryResult;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
@ -24,9 +26,9 @@ public class Example1007_TokenController {
@Open(value = "story.get.token", needToken = true/* 设置true,网关会校验token是否存在 */) @Open(value = "story.get.token", needToken = true/* 设置true,网关会校验token是否存在 */)
@RequestMapping("token") @RequestMapping("token")
public StoryResult token(StoryParam story, HttpServletRequest request) { public StoryResult token(StoryParam story, HttpServletRequest request) {
String appAuthToken = request.getParameter(ParamNames.APP_AUTH_TOKEN_NAME); OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext();
StoryResult result = new StoryResult(); StoryResult result = new StoryResult();
result.setName("appAuthToken:" + appAuthToken); result.setName("appAuthToken:" + openContext.getAppAuthToken());
return result; return result;
} }
} }

@ -0,0 +1,23 @@
package com.gitee.sop.gateway.config;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class MyConfig {
@PostConstruct
public void after() {
ApiConfig.getInstance().setTokenValidator(apiParam -> {
// 获取客户端传递过来的token
String token = apiParam.fetchAccessToken();
return !StringUtils.isBlank(token);
// TODO: 校验token有效性,可以从redis中读取
// 返回true表示这个token真实、有效
});
}
}

@ -102,7 +102,7 @@ public class AllInOneTest extends TestBase {
.version("1.0") .version("1.0")
.ignoreSign(true) .ignoreSign(true)
.bizContent(new BizContent().add("id", "222").add("name", "忽略222")) .bizContent(new BizContent().add("id", "222").add("name", "忽略222"))
.httpMethod(HttpTool.HTTPMethod.GET); .httpMethod(HttpTool.HTTPMethod.POST);
client.execute(requestBuilder); client.execute(requestBuilder);
} }

Loading…
Cancel
Save