路由管理优化

1.x
tanghc 6 years ago
parent a5b2d9c51b
commit 5d436081a5
  1. 40
      sop-admin/sop-admin-front/assets/css/xadmin.css
  2. 24
      sop-admin/sop-admin-front/assets/js/ApiUtil.js
  3. 24
      sop-admin/sop-admin-front/assets/js/common.js
  4. 32
      sop-admin/sop-admin-front/assets/js/index.js
  5. 76
      sop-admin/sop-admin-front/assets/js/lib.js
  6. 1
      sop-admin/sop-admin-front/assets/js/xadmin.js
  7. 2
      sop-admin/sop-admin-front/assets/lib/layui/lay/modules/mobile.js
  8. 63
      sop-admin/sop-admin-front/index.html
  9. 55
      sop-admin/sop-admin-front/pages/service/routeManager.html
  10. 95
      sop-admin/sop-admin-front/pages/service/routeManager.js
  11. 78
      sop-admin/sop-admin-front/pages/service/serviceList.html
  12. 0
      sop-admin/sop-admin-front/pages/service/serviceList.js
  13. 10
      sop-admin/sop-admin-front/pages/todo.html
  14. 194
      sop-admin/sop-admin-front/pages/welcome.html
  15. 16
      sop-admin/sop-admin-server/pom.xml
  16. 13
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/RouteListApi.java
  17. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/config/ConfigListApi.java
  18. 6
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/demo/GoodsApi.java
  19. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/demo/param/GoodsParam.java
  20. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/demo/result/Goods.java
  21. 57
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/RouteListApi.java
  22. 57
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  23. 13
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteSearchParam.java
  24. 17
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/ServiceSearchParam.java
  25. 22
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/ServiceInfo.java
  26. 34
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/SystemApi.java
  27. 17
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/GatewayFilterDefinition.java
  28. 42
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/GatewayPredicateDefinition.java
  29. 25
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/GatewayRouteDefinition.java
  30. 27
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ServiceRouteInfo.java
  31. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/SopAdminConstants.java
  32. 97
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ZookeeperContext.java
  33. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/config/WebConfig.java
  34. 38
      sop-admin/sop-admin-server/src/main/resources/application-dev.properties
  35. 27
      sop-admin/sop-admin-server/src/main/resources/application-dev.yml
  36. 1
      sop-admin/sop-admin-server/src/main/resources/application.properties
  37. 3
      sop-admin/sop-admin-server/src/main/resources/application.yml
  38. 5
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/BaseRouteDefinition.java
  39. 2
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java
  40. 11
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/NameVersionRoutePredicateFactory.java
  41. 23
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteManager.java
  42. 3
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java
  43. 18
      sop-gateway/src/test/java/com/gitee/sop/gateway/CuratorTest.java
  44. 13
      sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceZookeeperApiMetaManager.java
  45. 5
      sop-service-common/src/main/java/com/gitee/sop/servercommon/route/GatewayRouteDefinition.java
  46. 11
      sop-service-common/src/main/java/com/gitee/sop/servercommon/route/ServiceRouteInfo.java
  47. 1
      sop-story/sop-story-web/src/main/resources/bootstrap.yml

@ -101,7 +101,7 @@ body{
width:100%; width:100%;
} }
.login hr { .login hr {
background: #fff url() 0 0 no-repeat; background: #fff 0 0 no-repeat;
} }
.login hr.hr15 { .login hr.hr15 {
height: 15px; height: 15px;
@ -126,7 +126,7 @@ body{
z-index: 99; z-index: 99;
border-bottom: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5;
line-height: 39px; line-height: 39px;
height: 39px; height: 37px;
overflow: hidden; overflow: hidden;
} }
xblock{ xblock{
@ -158,6 +158,9 @@ xblock{
.x-red{ .x-red{
color: red; color: red;
} }
.x-green{
color: green;
}
.x-a{ .x-a{
color: #1AA093; color: #1AA093;
} }
@ -184,7 +187,7 @@ xblock{
} }
.page a{ .page a{
display: inline-block; display: inline-block;
background: #fff url() 0 0 no-repeat; background: #fff 0 0 no-repeat;
color: #888; color: #888;
padding: 10px; padding: 10px;
min-width: 15px; min-width: 15px;
@ -199,7 +202,7 @@ xblock{
} }
.page span.current{ .page span.current{
display: inline-block; display: inline-block;
background: #009688 url() 0 0 no-repeat; background: #009688 0 0 no-repeat;
color: #fff; color: #fff;
padding: 10px; padding: 10px;
min-width: 15px; min-width: 15px;
@ -211,7 +214,7 @@ xblock{
text-align: center; text-align: center;
} }
.page .pagination li.active span{ .page .pagination li.active span{
background: #009688 url() 0 0 no-repeat; background: #009688 0 0 no-repeat;
color: #fff; color: #fff;
border: 1px solid #009688; border: 1px solid #009688;
@ -244,7 +247,7 @@ xblock{
} }
.container .left_open i{ .container .left_open i{
display: block; display: block;
background: rgba(255,255,255,0.1) url() 0 0 no-repeat; background: rgba(255,255,255,0.1) 0 0 no-repeat;
color: #fff; color: #fff;
width: 32px; width: 32px;
height: 32px; height: 32px;
@ -255,7 +258,7 @@ xblock{
cursor: pointer; cursor: pointer;
} }
.container .left_open i:hover{ .container .left_open i:hover{
background: rgba(255,255,255,0.3) url() 0 0 no-repeat; background: rgba(255,255,255,0.3) 0 0 no-repeat;
} }
.container .left{ .container .left{
@ -289,7 +292,7 @@ xblock{
bottom: 42px; bottom: 42px;
left: 0; left: 0;
z-index: 2; z-index: 2;
padding-top: 10px; /*padding-top: 1px;*/
background-color: #EEEEEE; background-color: #EEEEEE;
width: 220px; width: 220px;
max-width: 220px; max-width: 220px;
@ -347,19 +350,26 @@ xblock{
} }
.left-nav #nav li .sub-menu li a i{ .left-nav #nav li .sub-menu li a i{
font-size: 12px; font-size: 12px;
margin-left: 8px;
} }
.left-nav #nav li a i{ .left-nav #nav li a i{
padding-right: 10px; /*padding-right: 10px;*/
line-height: 14px; line-height: 14px;
} }
.left-nav #nav li .nav_right{ .left-nav #nav li .nav_right{
float: right; float: right;
font-size: 16px; font-size: 16px;
} }
.left-nav #nav li.active {
background: #ddd;
}
.left-nav #nav li.active a {
color: #148cf1;
}
.x-slide_left { .x-slide_left {
width: 17px; width: 17px;
height: 61px; height: 61px;
background: url(../images/icon.png) 0 0 no-repeat;
position: absolute; position: absolute;
top: 200px; top: 200px;
left: 221px; left: 221px;
@ -390,13 +400,13 @@ xblock{
.page-content .tab{ .page-content .tab{
height: 100%; height: 100%;
width: 100%; width: 100%;
background: #EFEEF0 url() 0 0 no-repeat; background: #EFEEF0 0 0 no-repeat;
margin: 0px; margin: 0px;
} }
.page-content .layui-tab-title{ .page-content .layui-tab-title{
/*padding-top: 5px;*/ /*padding-top: 5px;*/
height: 35px; height: 35px;
background: #EFEEF0 url() 0 0 no-repeat; background: #EFEEF0 0 0 no-repeat;
position: relative; position: relative;
z-index: 100; z-index: 100;
} }
@ -416,7 +426,7 @@ xblock{
border-radius: 50%; border-radius: 50%;
} }
.page-content .layui-tab-title .layui-this{ .page-content .layui-tab-title .layui-this{
background: #fff url() 0 0 no-repeat; background: #fff 0 0 no-repeat;
} }
.page-content .layui-tab-bar{ .page-content .layui-tab-bar{
height:34px; height:34px;
@ -427,7 +437,7 @@ xblock{
top: 36px; top: 36px;
bottom: 0px; bottom: 0px;
width: 100%; width: 100%;
background: #fff url() 0 0 no-repeat; background: #fff 0 0 no-repeat;
padding: 0px; padding: 0px;
overflow: hidden; overflow: hidden;
} }
@ -531,7 +541,7 @@ table th, table td {
top: 36px; top: 36px;
bottom: 0px; bottom: 0px;
width: 100%; width: 100%;
background:rgb(255, 255, 255,0); background:rgb(255, 255, 255);
padding: 0px; padding: 0px;
overflow: hidden; overflow: hidden;
display: none; display: none;

@ -23,9 +23,31 @@ var ApiUtil = (function () {
url: url + uri url: url + uri
, data: params // 请求参数 , data: params // 请求参数
, callback: function (resp) { // 成功回调 , callback: function (resp) { // 成功回调
callback(resp); var code = resp.code
if (!code || code === '-9') {
layer.alert('系统错误');
return
}
if (code === '-100' || code === '18' || code === '21') { // 未登录
ApiUtil.logout()
return
}
if (code === '0') { // 成功
callback(resp)
} else {
layer.alert(resp.msg);
}
} }
}); });
} }
, getUrl: function () {
return url;
}
, createUrl: function (uri) {
if (!uri) {
throw new Error('name不能为空');
}
return url + formatUri(uri);
}
} }
})(); })();

@ -0,0 +1,24 @@
var Common = (function () {
function initProfiles() {
var $profileList = $('#profileList');
if ($profileList.length > 0) {
ApiUtil.post('system.profile.list', {}, function (resp) {
var list = resp.data;
var html = [];
for (var i = 0; i < list.length; i++) {
html.push('<li' + (i == 0 ? ' class="layui-this"' : '') + '>' + list[i] + '</li>');
}
$profileList.html(html.join(''))
$profileList.find('li').on('click', function () {
window.profile = $(this).text();
});
});
}
}
initProfiles();
return {}
})();

@ -0,0 +1,32 @@
layui.use('jquery', function () {
var $ = layui.jquery;
var $pageFrame = $('#pageFrame');
var $selectLi = null;
$('.left-nav').on('click', 'li', function (event) {
var $tagLi = $(this);
var $tagA = $tagLi.find('a').eq(0);
var href = $tagA.prop('href');
if (href) {
$pageFrame.prop('src', href + '?q=' + new Date().getTime());
if ($selectLi) {
$selectLi.removeClass('active')
}
$selectLi = $tagLi.addClass('active');
}
return false;
});
$('.container .left_open i').click(function(event) {
if($('.left-nav').css('left')=='0px'){
$('.left-nav').animate({left: '-221px'}, 100);
$('.page-content').animate({left: '0px'}, 100);
$('.page-content-bg').hide();
}else{
$('.left-nav').animate({left: '0px'}, 100);
$('.page-content').animate({left: '221px'}, 100);
if($(window).width()<768){
$('.page-content-bg').show();
}
}
});
});

File diff suppressed because one or more lines are too long

@ -342,6 +342,7 @@ $(function () {
}) })
var cateIds = []; var cateIds = [];
function getCateId(cateId) { function getCateId(cateId) {

File diff suppressed because one or more lines are too long

@ -2,32 +2,30 @@
<html class="x-admin-sm"> <html class="x-admin-sm">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>后台登录-X-admin2.1</title> <title>SOP Admin</title>
<meta name="renderer" content="webkit|ie-comp|ie-stand"> <meta name="renderer" content="webkit|ie-comp|ie-stand">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" <meta name="viewport"
content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi"/> content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8"/>
<meta http-equiv="Cache-Control" content="no-siteapp"/> <meta http-equiv="Cache-Control" content="no-siteapp"/>
<link rel="stylesheet" href="assets/css/font.css"> <link rel="stylesheet" href="assets/css/font.css">
<link rel="stylesheet" href="assets/css/xadmin.css"> <link rel="stylesheet" href="assets/css/xadmin.css">
<script type="text/javascript" src="assets/lib/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript" src="assets/lib/layui/layui.js" charset="utf-8"></script>
<script src="assets/lib/layui/layui.js" charset="utf-8"></script> <script type="text/javascript" src="assets/js/index.js"></script>
<script type="text/javascript" src="assets/js/xadmin.js"></script>
<script type="text/javascript" src="assets/js/cookie.js"></script>
<script> <script>
// 是否开启刷新记忆tab功能 // 是否开启刷新记忆tab功能
// var is_remember = false; // var is_remember = false;
</script> </script>
<style> <style>
.sub-menu cite { .sub-menu cite {
margin-left: 30px; margin-left: 0px;
} }
</style> </style>
</head> </head>
<body> <body>
<!-- 顶部开始 --> <!-- 顶部开始 -->
<div class="container"> <div class="container">
<div class="logo"><a href="./index.html">X-admin v2.1</a></div> <div class="logo"><a>SOP Admin</a></div>
<div class="left_open"> <div class="left_open">
<i title="展开左侧栏" class="iconfont">&#xe699;</i> <i title="展开左侧栏" class="iconfont">&#xe699;</i>
</div> </div>
@ -47,32 +45,42 @@
<div class="left-nav"> <div class="left-nav">
<div id="side-nav"> <div id="side-nav">
<ul id="nav"> <ul id="nav">
<li date-refresh="1">
<a href="pages/welcome.html">
<i class="layui-icon layui-icon-home"></i>
<cite>首页</cite>
</a>
</li>
<li class="open"> <li class="open">
<a href="javascript:;"> <a>
<i class="iconfont">&#xe6fc;</i> <i class="layui-icon layui-icon-template-1"></i>
<cite>配置管理</cite> <cite>配置管理</cite>
<i class="iconfont nav_right">&#xe6a6;</i>
</a> </a>
<ul class="sub-menu"> <ul class="sub-menu">
<li date-refresh="1"> <li date-refresh="1">
<a _href="member-list.html"> <a href="pages/todo.html">
<i class="layui-icon layui-icon-table"></i>
<cite>配置列表</cite> <cite>配置列表</cite>
</a> </a>
</li> </li>
</ul> </ul>
</li> </li>
<li class="open"> <li class="open">
<a href="javascript:;"> <a>
<i class="iconfont">&#xe6cb;</i> <i class="layui-icon layui-icon-app"></i>
<cite>服务管理</cite> <cite>服务管理</cite>
<i class="iconfont nav_right">&#xe6a6;</i>
</a> </a>
<ul class="sub-menu"> <ul class="sub-menu">
<li date-refresh="1"> <li date-refresh="1">
<a _href="member-list.html"> <a href="pages/service/serviceList.html">
<i class="layui-icon layui-icon-table"></i>
<cite>服务列表</cite> <cite>服务列表</cite>
</a>
</li>
<li date-refresh="1">
<a href="pages/service/routeManager.html">
<i class="layui-icon layui-icon-component"></i>
<cite>路由管理</cite>
</a> </a>
</li> </li>
</ul> </ul>
@ -84,24 +92,7 @@
<!-- 左侧菜单结束 --> <!-- 左侧菜单结束 -->
<!-- 右侧主体开始 --> <!-- 右侧主体开始 -->
<div class="page-content"> <div class="page-content">
<div class="layui-tab tab" lay-filter="xbs_tab" lay-allowclose="false"> <iframe id="pageFrame" src='pages/welcome.html' frameborder="0" scrolling="yes" class="x-iframe" width="100%" height="100%"></iframe>
<ul class="layui-tab-title">
<li class="home"><i class="layui-icon">&#xe68e;</i>我的桌面</li>
</ul>
<div class="layui-unselect layui-form-select layui-form-selected" id="tab_right">
<dl>
<dd data-type="this">关闭当前</dd>
<dd data-type="other">关闭其它</dd>
<dd data-type="all">关闭全部</dd>
</dl>
</div>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<iframe src='pages/welcome.html' frameborder="0" scrolling="yes" class="x-iframe"></iframe>
</div>
</div>
<div id="tab_show"></div>
</div>
</div> </div>
<div class="page-content-bg"></div> <div class="page-content-bg"></div>
<!-- 右侧主体结束 --> <!-- 右侧主体结束 -->

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html class="x-admin-sm">
<head>
<meta charset="UTF-8">
<title>SOP Admin</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8"/>
<link rel="stylesheet" href="../../assets/css/font.css">
<link rel="stylesheet" href="../../assets/css/xadmin.css">
</head>
<body>
<div class="x-nav">
<span class="layui-breadcrumb">
<a href="../welcome.html">首页</a>
<a><cite>服务管理</cite></a>
<a><cite>路由管理</cite></a>
</span>
<a class="layui-btn layui-btn-small layui-btn-normal" style="line-height:1.6em;margin-top:4px;float:right"
href="javascript:location.replace(location.href);" title="刷新">
<i class="layui-icon layui-icon-refresh" style="line-height:30px"></i></a>
</div>
<div class="x-body">
<div class="layui-tab" style="margin-top: 0px;" lay-filter="docDemoTabBrief">
<ul id="profileList" class="layui-tab-title">
</ul>
</div>
<div class="layui-col-md2">
<ul id="leftTree"></ul>
</div>
<div class="layui-col-md10">
<div class="layui-row">
<form class="layui-form layui-col-md12 x-so" action="" lay-filter="searchFrm">
路由名称:
<input name="id" class="layui-input" style="width: 200px;" placeholder="输入接口名或版本号">
<button class="layui-btn layui-btn-normal" lay-submit="" lay-filter="searchFilter">
<i class="layui-icon layui-icon-search"></i>搜索
</button>
</form>
</div>
<div class="layui-btn-group demoTable">
<button class="layui-btn layui-btn-ms layui-btn-normal" lay-event="addRoute">添加路由</button>
</div>
<table class="layui-hide" id="table"></table>
<script type="text/html" id="optBar">
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="edit">修改</a>
</script>
</div>
</div>
<script type="text/javascript" src="../../assets/js/lib.js"></script>
<script type="text/javascript" src="routeManager.js"></script>
</body>
</html>

@ -0,0 +1,95 @@
lib.use(['element', 'table', 'tree', 'form'], function () {
var form = layui.form;
var table = layui.table;
var currentServiceId;
var serverTable = table.render({
elem: '#table'
, url: ApiUtil.createUrl('route.list')
, cellMinWidth: 80 //全局定义常规单元格的最小宽度,layui 2.2.1 新增
, cols: [[
{field: 'id', title: 'id(接口名+版本号)'}
, {field: 'uri', title: 'uri'}
, {
field: 'ignoreValidate', width: 80, title: '忽略验证', templet: function (row) {
return row.ignoreValidate
? '<span class="x-red">是</span>'
: '<span>否</span>';
}
}
, {
field: 'disabled', title: '状态', width: 80, templet: function (row) {
return row.disabled
? '<span class="x-red">禁用</span>'
: '<span class="x-green">启用</span>';
}
}
, {fixed: 'right', title: '操作', toolbar: '#optBar', width: 100}
]]
});
$('#profileList').find('li').on('click', function () {
initTree($(this).text());
})
function initTree(profile) {
ApiUtil.post('service.list', {profile: profile}, function (resp) {
var serviceList = resp.data;
var children = [];
for (var i = 0; i < serviceList.length; i++) {
var serviceInfo = serviceList[i];
children.push({
id: i + 1,
name: serviceInfo.serviceId
})
}
// 清空表格
reloadRightPart({name: ''})
// 清空树
$('#leftTree').text('');
layui.tree({
elem: '#leftTree' //传入元素选择器
, nodes: [{ //节点
name: '服务列表(' + profile + ')'
, spread: true // 展开
, children: children
}]
, click: function (node) {
if (node.id) {
reloadRightPart(node)
}
}
});
});
}
/**
* 更新右边部分
* @param node 树节点
*/
function reloadRightPart(node) {
var serviceId = node.name;
currentServiceId = serviceId;
searchTable({
serviceId: serviceId
});
}
form.on('submit(searchFilter)', function (data) {
var param = data.field;
param.serviceId = currentServiceId;
searchTable(param)
return false;
});
function searchTable(param) {
serverTable.reload({
where: {
data: JSON.stringify(param)
, profile: window.profile
}
})
}
initTree('default');
});

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html class="x-admin-sm">
<head>
<meta charset="UTF-8">
<title>SOP Admin</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8"/>
<link rel="stylesheet" href="../../assets/css/font.css">
<link rel="stylesheet" href="../../assets/css/xadmin.css">
</head>
<body>
<div class="x-nav">
<span class="layui-breadcrumb">
<a href="../welcome.html">首页</a>
<a><cite>服务管理</cite></a>
<a><cite>服务列表</cite></a>
</span>
<a class="layui-btn layui-btn-small layui-btn-normal" style="line-height:1.6em;margin-top:4px;float:right"
href="javascript:location.replace(location.href);" title="刷新">
<i class="layui-icon layui-icon-refresh" style="line-height:30px"></i></a>
</div>
<div class="x-body">
<div class="layui-tab" style="margin-top: 0px;" lay-filter="docDemoTabBrief">
<ul id="profileList" class="layui-tab-title">
</ul>
</div>
<div class="layui-row">
<form class="layui-form layui-col-md12 x-so" action="" lay-filter="searchFrm">
服务名称:
<input name="serviceId" class="layui-input" style="width: 200px;" placeholder="输入服务名称(serviceId)">
<button class="layui-btn layui-btn-normal" lay-submit="" lay-filter="searchFilter">
<i class="layui-icon layui-icon-search"></i>搜索
</button>
</form>
</div>
<table class="layui-hide" id="table"></table>
<script type="text/html" id="optBar">
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="edit">修改</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">下线</a>
</script>
</div>
<script type="text/javascript" src="../../assets/js/lib.js"></script>
<script>
lib.use(['element', 'table', 'form'], function () {
var table = layui.table;
var form = layui.form;
var serverTable = table.render({
elem: '#table'
, url: ApiUtil.createUrl('service.list')
, cellMinWidth: 80 //全局定义常规单元格的最小宽度,layui 2.2.1 新增
, cols: [[
{field: 'serviceId', title: '服务名称', sort: true}
, {field: 'description', title: '描述'}
, {field: 'createTime', title: '添加时间', sort: true}
, {field: 'updateTime', title: '修改时间', sort: true}
, {fixed: 'right', title:'操作', toolbar: '#optBar', width:150}
]]
});
form.on('submit(searchFilter)', function(data){
var param = data.field;
serverTable.reload({
where: {
data: JSON.stringify(param)
}
})
return false;
});
});
</script>
</body>
</html>

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>todo</title>
</head>
<body>
待实现...
</body>
</html>

@ -1,165 +1,35 @@
<!DOCTYPE html> <!DOCTYPE html>
<html class="x-admin-sm"> <html class="x-admin-sm">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>欢迎页面-X-admin2.1</title> <title>SOP Admin</title>
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" /> <meta name="viewport"
<link rel="stylesheet" href="../assets/css/font.css"> content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8"/>
<link rel="stylesheet" href="../assets/css/xadmin.css"> <link rel="stylesheet" href="../assets/css/font.css">
</head> <link rel="stylesheet" href="../assets/css/xadmin.css">
<body>
<div class="x-body"> </head>
<blockquote class="layui-elem-quote">欢迎管理员: <body>
<span class="x-red">test</span>!当前时间:2018-04-25 20:50:53</blockquote> <div class="x-nav">
<fieldset class="layui-elem-field"> <span class="layui-breadcrumb">
<legend>数据统计</legend> <a><cite>首页</cite></a>
<div class="layui-field-box"> </span>
<div class="layui-col-md12"> <a class="layui-btn layui-btn-small layui-btn-normal" style="line-height:1.6em;margin-top:4px;float:right"
<div class="layui-card"> href="javascript:location.replace(location.href);" title="刷新">
<div class="layui-card-body"> <i class="layui-icon layui-icon-refresh" style="line-height:30px"></i></a>
<div class="layui-carousel x-admin-carousel x-admin-backlog" lay-anim="" lay-indicator="inside" lay-arrow="none" style="width: 100%; height: 90px;"> </div>
<div carousel-item=""> <div class="x-body">
<ul class="layui-row layui-col-space10 layui-this"> <blockquote class="layui-elem-quote">欢迎管理员:
<li class="layui-col-xs2"> <span class="x-red">admin</span>
<a href="javascript:;" class="x-admin-backlog-body"> </blockquote>
<h3>文章数</h3> </div>
<p>
<cite>66</cite></p> <script type="text/javascript" src="../assets/lib/layui/layui.js" charset="utf-8"></script>
</a> <script>
</li> layui.use('element', function () {
<li class="layui-col-xs2"> });
<a href="javascript:;" class="x-admin-backlog-body"> </script>
<h3>会员数</h3> </body>
<p>
<cite>12</cite></p>
</a>
</li>
<li class="layui-col-xs2">
<a href="javascript:;" class="x-admin-backlog-body">
<h3>回复数</h3>
<p>
<cite>99</cite></p>
</a>
</li>
<li class="layui-col-xs2">
<a href="javascript:;" class="x-admin-backlog-body">
<h3>商品数</h3>
<p>
<cite>67</cite></p>
</a>
</li>
<li class="layui-col-xs2">
<a href="javascript:;" class="x-admin-backlog-body">
<h3>文章数</h3>
<p>
<cite>67</cite></p>
</a>
</li>
<li class="layui-col-xs2">
<a href="javascript:;" class="x-admin-backlog-body">
<h3>文章数</h3>
<p>
<cite>6766</cite></p>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>系统通知</legend>
<div class="layui-field-box">
<table class="layui-table" lay-skin="line">
<tbody>
<tr>
<td >
<a class="x-a" href="/" target="_blank">新版x-admin 2.0上线了</a>
</td>
</tr>
<tr>
<td >
<a class="x-a" href="/" target="_blank">交流qq群:(519492808)</a>
</td>
</tr>
</tbody>
</table>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>系统信息</legend>
<div class="layui-field-box">
<table class="layui-table">
<tbody>
<tr>
<th>xxx版本</th>
<td>1.0.180420</td></tr>
<tr>
<th>服务器地址</th>
<td>x.xuebingsi.com</td></tr>
<tr>
<th>操作系统</th>
<td>WINNT</td></tr>
<tr>
<th>运行环境</th>
<td>Apache/2.4.23 (Win32) OpenSSL/1.0.2j mod_fcgid/2.3.9</td></tr>
<tr>
<th>PHP版本</th>
<td>5.6.27</td></tr>
<tr>
<th>PHP运行方式</th>
<td>cgi-fcgi</td></tr>
<tr>
<th>MYSQL版本</th>
<td>5.5.53</td></tr>
<tr>
<th>ThinkPHP</th>
<td>5.0.18</td></tr>
<tr>
<th>上传附件限制</th>
<td>2M</td></tr>
<tr>
<th>执行时间限制</th>
<td>30s</td></tr>
<tr>
<th>剩余空间</th>
<td>86015.2M</td></tr>
</tbody>
</table>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>开发团队</legend>
<div class="layui-field-box">
<table class="layui-table">
<tbody>
<tr>
<th>版权所有</th>
<td>xuebingsi(xuebingsi)
<a href="http://x.xuebingsi.com/" class='x-a' target="_blank">访问官网</a></td>
</tr>
<tr>
<th>开发者</th>
<td>马志斌(113664000@qq.com)</td></tr>
</tbody>
</table>
</div>
</fieldset>
<blockquote class="layui-elem-quote layui-quote-nm">感谢layui,百度Echarts,jquery,本系统由x-admin提供技术支持。</blockquote>
</div>
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?b393d153aeb26b46e9431fabaf0f6190";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</body>
</html> </html>

@ -40,7 +40,12 @@
<dependency> <dependency>
<groupId>org.apache.curator</groupId> <groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId> <artifactId>curator-framework</artifactId>
<version>4.2.0</version> <version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency> </dependency>
<dependency> <dependency>
@ -62,7 +67,14 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build> <build>
<plugins> <plugins>

@ -1,13 +0,0 @@
package com.gitee.sop.adminserver.api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
/**
* @author tanghc
*/
@ApiService
@ApiDoc("路由列表")
public class RouteListApi {
}

@ -1,4 +1,4 @@
package com.gitee.sop.adminserver.api; package com.gitee.sop.adminserver.api.config;
import com.gitee.easyopen.annotation.ApiService; import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc; import com.gitee.easyopen.doc.annotation.ApiDoc;

@ -1,12 +1,12 @@
package com.gitee.sop.adminserver.api; package com.gitee.sop.adminserver.api.demo;
import com.gitee.easyopen.annotation.Api; import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService; import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc; import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocField; import com.gitee.easyopen.doc.annotation.ApiDocField;
import com.gitee.easyopen.doc.annotation.ApiDocMethod; import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.sop.adminserver.api.param.GoodsParam; import com.gitee.sop.adminserver.api.demo.param.GoodsParam;
import com.gitee.sop.adminserver.api.result.Goods; import com.gitee.sop.adminserver.api.demo.result.Goods;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Arrays; import java.util.Arrays;

@ -1,4 +1,4 @@
package com.gitee.sop.adminserver.api.param; package com.gitee.sop.adminserver.api.demo.param;
import com.gitee.easyopen.doc.annotation.ApiDocField; import com.gitee.easyopen.doc.annotation.ApiDocField;
import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Length;

@ -1,4 +1,4 @@
package com.gitee.sop.adminserver.api.result; package com.gitee.sop.adminserver.api.demo.result;
import com.gitee.easyopen.doc.DataType; import com.gitee.easyopen.doc.DataType;
import com.gitee.easyopen.doc.annotation.ApiDocField; import com.gitee.easyopen.doc.annotation.ApiDocField;

@ -0,0 +1,57 @@
package com.gitee.sop.adminserver.api.service;
import com.alibaba.fastjson.JSON;
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.sop.adminserver.api.service.param.RouteSearchParam;
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
import com.gitee.sop.adminserver.bean.SopAdminConstants;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.recipes.cache.ChildData;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author tanghc
*/
@ApiService
@ApiDoc("服务管理")
public class RouteListApi {
@Api(name = "route.list")
@ApiDocMethod(description = "路由列表", elementClass = GatewayRouteDefinition.class)
List<GatewayRouteDefinition> listRoute(RouteSearchParam param) throws Exception {
if (StringUtils.isBlank(param.getServiceId())) {
return Collections.emptyList();
}
String searchPath = ZookeeperContext.getSopRouteRootPath(param.getProfile()) + "/" + param.getServiceId();
List<ChildData> childDataList = ZookeeperContext.getChildrenData(searchPath);
List<GatewayRouteDefinition> routeDefinitionList = childDataList.stream()
.map(childData -> {
String serviceNodeData = new String(childData.getData());
GatewayRouteDefinition routeDefinition = JSON.parseObject(serviceNodeData, GatewayRouteDefinition.class);
return routeDefinition;
})
.filter(gatewayRouteDefinition -> {
boolean isRoute = gatewayRouteDefinition.getOrder() != Integer.MIN_VALUE;
String id = param.getId();
if (StringUtils.isBlank(id)) {
return isRoute;
}else {
return isRoute && gatewayRouteDefinition.getId().contains(id);
}
})
.collect(Collectors.toList());
return routeDefinitionList;
}
}

@ -0,0 +1,57 @@
package com.gitee.sop.adminserver.api.service;
import com.alibaba.fastjson.JSON;
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.sop.adminserver.api.service.param.ServiceSearchParam;
import com.gitee.sop.adminserver.api.service.result.ServiceInfo;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import java.util.List;
import java.util.stream.Collectors;
import static com.gitee.sop.adminserver.bean.SopAdminConstants.SOP_SERVICE_ROUTE_PATH;
/**
* @author tanghc
*/
@ApiService
@ApiDoc("服务管理")
@Slf4j
public class ServiceApi {
@Autowired
private Environment environment;
@Api(name = "service.list")
@ApiDocMethod(description = "服务列表", elementClass = ServiceInfo.class)
List<ServiceInfo> listServiceInfo(ServiceSearchParam param) throws Exception {
String routeRootPath = ZookeeperContext.getSopRouteRootPath(param.getProfile());
List<ChildData> childDataList = ZookeeperContext.getChildrenData(routeRootPath);
List<ServiceInfo> serviceInfoList = childDataList.stream()
.map(childData -> {
String serviceNodeData = new String(childData.getData());
ServiceInfo serviceInfo = JSON.parseObject(serviceNodeData, ServiceInfo.class);
return serviceInfo;
})
.filter(serviceInfo -> {
if (StringUtils.isBlank(param.getServiceId())) {
return true;
} else {
return serviceInfo.getServiceId().contains(param.getServiceId());
}
})
.collect(Collectors.toList());
return serviceInfoList;
}
}

@ -0,0 +1,13 @@
package com.gitee.sop.adminserver.api.service.param;
import lombok.Getter;
import lombok.Setter;
/**
* @author tanghc
*/
@Getter
@Setter
public class RouteSearchParam extends ServiceSearchParam {
private String id;
}

@ -0,0 +1,17 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class ServiceSearchParam {
@ApiDocField(description = "profile")
private String profile;
@ApiDocField(description = "serviceId")
private String serviceId;
}

@ -0,0 +1,22 @@
package com.gitee.sop.adminserver.api.service.result;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class ServiceInfo {
@ApiDocField(description = "serviceId")
private String serviceId;
@ApiDocField(description = "创建时间")
private String createTime;
@ApiDocField(description = "修改时间")
private String updateTime;
@ApiDocField(description = "描述")
private String description;
}

@ -0,0 +1,34 @@
package com.gitee.sop.adminserver.api.system;
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 org.springframework.beans.factory.annotation.Value;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author tanghc
*/
@ApiService
@ApiDoc("系统接口")
public class SystemApi {
@Value("${sop-admin.profiles}")
private String profiles;
private List<String> profileList;
@ApiDocMethod(description = "获取profile列表")
@Api(name = "system.profile.list")
public List<String> listProfiles() {
if (profileList == null) {
String[] arr = profiles.split("\\,");
profileList = Stream.of(arr).collect(Collectors.toList());
}
return profileList;
}
}

@ -0,0 +1,17 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author tanghc
*/
@Data
public class GatewayFilterDefinition {
/** Filter Name */
private String name;
/** 对应的路由规则 */
private Map<String, String> args = new LinkedHashMap<>();
}

@ -0,0 +1,42 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import org.springframework.util.StringUtils;
import javax.validation.ValidationException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author thc
*/
@Data
public class GatewayPredicateDefinition {
public static final String GEN_KEY = "_genkey_";
/** 断言对应的Name */
private String name;
/** 配置的断言规则 */
private Map<String, String> args = new LinkedHashMap<>();
public GatewayPredicateDefinition() {
}
public GatewayPredicateDefinition(String text) {
int eqIdx = text.indexOf(61);
if (eqIdx <= 0) {
throw new ValidationException("Unable to parse GatewayPredicateDefinition text '" + text + "', must be of the form name=value");
} else {
this.setName(text.substring(0, eqIdx));
String[] args = StringUtils.tokenizeToStringArray(text.substring(eqIdx + 1), ",");
for(int i = 0; i < args.length; ++i) {
this.args.put(generateName(i), args[i]);
}
}
}
public static String generateName(int i) {
return GEN_KEY + i;
}
}

@ -0,0 +1,25 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class GatewayRouteDefinition {
/** 路由的Id */
private String id = "";
/** 路由断言集合配置 */
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
/** 路由过滤器集合配置 */
private List<GatewayFilterDefinition> filters = new ArrayList<>();
/** 路由规则转发的目标uri */
private String uri;
/** 路由执行的顺序 */
private int order = 0;
/** 是否忽略验证,业务参数验证除外 */
private boolean ignoreValidate;
}

@ -0,0 +1,27 @@
package com.gitee.sop.adminserver.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* @author thc
*/
@Data
public class ServiceRouteInfo {
/** 服务名称,对应spring.application.name */
private String serviceId;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createTime = new Date();
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date updateTime = new Date();
private String description;
@JSONField(serialize = false)
private List<GatewayRouteDefinition> routeDefinitionList;
}

@ -0,0 +1,12 @@
package com.gitee.sop.adminserver.bean;
/**
* @author tanghc
*/
public class SopAdminConstants {
/**
* zookeeper存放接口路由信息的根目录
*/
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
}

@ -0,0 +1,97 @@
package com.gitee.sop.adminserver.bean;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
/**
* @author tanghc
*/
@Configuration
public class ZookeeperContext {
public static final String SOP_ROUTE_ROOT_PATH = SopAdminConstants.SOP_SERVICE_ROUTE_PATH + "-%s";
private static CuratorFramework client;
@Value("${spring.cloud.zookeeper.connect-string}")
private String zookeeperServerAddr;
@Value("${spring.cloud.zookeeper.baseSleepTimeMs}")
private String baseSleepTimeMs;
@Value("${spring.cloud.zookeeper.maxRetries}")
private String maxRetries;
@PostConstruct
protected void after() {
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3)))
.build();
client.start();
setClient(client);
}
public static String getSopRouteRootPath(String profile) {
if (StringUtils.isBlank(profile)) {
profile = "default";
}
return String.format(SOP_ROUTE_ROOT_PATH, profile);
}
public static CuratorFramework getClient() {
return client;
}
public static void setClient(CuratorFramework client) {
ZookeeperContext.client = client;
}
public static boolean isPathExist(String path) {
try {
return client.checkExists().forPath(path) != null;
} catch (Exception e) {
return false;
}
}
/**
* 获取子节点数据
* @param parentPath 父节点
* @return
* @throws Exception
*/
public static List<ChildData> getChildrenData(String parentPath) throws Exception {
PathChildrenCache pathChildrenCache = buildPathChildrenCache(parentPath);
if (pathChildrenCache == null) {
return Collections.emptyList();
}
return pathChildrenCache.getCurrentData();
}
public static PathChildrenCache buildPathChildrenCache(String path) throws Exception {
if (!isPathExist(path)) {
return null;
}
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
// 且第三个参数要设置为true,不然ChildData对象中的getData返回null
PathChildrenCache childrenCache = new PathChildrenCache(client, path, true);
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
return childrenCache;
}
}

@ -1,14 +1,26 @@
package com.gitee.sop.adminserver.config; package com.gitee.sop.adminserver.config;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* @author thc
*/
@Configuration @Configuration
public class WebConfig extends WebMvcConfigurerAdapter { public class WebConfig extends WebMvcConfigurerAdapter {
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
//配置映射关系 //配置映射关系

@ -1,38 +0,0 @@
server.port=8082
#################easyopen基础配置#################
# 显示文档
easyopen.show-doc=true
# 本地秘钥
easyopen.app-secret.test=123456
# 关闭跨域,默认开启
#easyopen.cors=false
# 开启webflux
easyopen.mono=false
## 拦截器
#easyopen.interceptors[0]=com.gitee.easyopen.support.LimitInterceptor
#easyopen.interceptors[1]=com.gitee.easyopen.support.PermissionInterceptor
# 配置中心,config-server-port对应easyopen-config中的netty.server.port
#easyopen.app-name=app-normal
#easyopen.config-server-ip=127.0.0.1
#easyopen.config-server-port=8071
#easyopen.doc-url=http://127.0.0.1:8081/api/doc
#################redis基础配置#################
spring.redis.database=1
spring.redis.host=10.1.11.48
spring.redis.password=0987654321rfvujmtgbyhn
spring.redis.port=6379
# 连接超时时间 单位 ms(毫秒)
spring.redis.timeout=3000
logging.file=D:/logs/server/server
logging.level.com.gitee=debug

@ -0,0 +1,27 @@
server:
port: 8082
spring:
application:
name: sop-admin
cloud:
zookeeper:
connect-string: localhost:2181
baseSleepTimeMs: 3000
maxRetries: 3
easyopen:
show-doc: true
mono: false
ignore-validate: true
sop-admin:
profiles: default,prod,dev,test
logging:
level:
com:
gitee: debug

@ -26,4 +26,9 @@ public class BaseRouteDefinition {
* 是否忽略验证业务参数验证除外 * 是否忽略验证业务参数验证除外
*/ */
private boolean ignoreValidate; private boolean ignoreValidate;
/**
* 是否禁用
*/
private boolean disabled;
} }

@ -34,5 +34,5 @@ public class SopConstants {
/** /**
* zookeeper存放接口路由信息的根目录 * zookeeper存放接口路由信息的根目录
*/ */
public static final String SOP_SERVICE_ROUTE_PATH = "/sop-service-route"; public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
} }

@ -1,6 +1,8 @@
package com.gitee.sop.gatewaycommon.gateway.route; package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.bean.SopConstants; 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.param.ParamNames; import com.gitee.sop.gatewaycommon.param.ParamNames;
import com.gitee.sop.gatewaycommon.util.RequestUtil; import com.gitee.sop.gatewaycommon.util.RequestUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -59,7 +61,14 @@ public class NameVersionRoutePredicateFactory extends AbstractRoutePredicateFact
String nameVersion = config.param; String nameVersion = config.param;
String name = params.getOrDefault(ParamNames.API_NAME, ""); String name = params.getOrDefault(ParamNames.API_NAME, "");
String version = params.getOrDefault(ParamNames.VERSION_NAME, ""); String version = params.getOrDefault(ParamNames.VERSION_NAME, "");
return (name + version).equals(nameVersion); boolean match = (name + version).equals(nameVersion);
if (match) {
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
if (targetRoute != null && targetRoute.getRouteDefinition().isDisabled()) {
return false;
}
}
return match;
}; };
} }

@ -23,7 +23,7 @@ import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_SERVICE_ROUTE_PA
/** /**
* 路由管理采用zookeeper实现监听路由的增删改并适时更新到本地路由的存储格式为 * 路由管理采用zookeeper实现监听路由的增删改并适时更新到本地路由的存储格式为
* <pre> * <pre>
* /sop-service-route 根节点 * /com.gitee.sop.route 根节点
* /serviceId 服务节点名字为服务名 * /serviceId 服务节点名字为服务名
* /route1 路由节点名字为name+version存放路由信息 * /route1 路由节点名字为name+version存放路由信息
* /route2 * /route2
@ -38,12 +38,12 @@ import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_SERVICE_ROUTE_PA
@Slf4j @Slf4j
public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E extends BaseRouteDefinition, T extends TargetRoute> implements RouteManager { public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E extends BaseRouteDefinition, T extends TargetRoute> implements RouteManager {
protected String sopRouteRootPath = SOP_SERVICE_ROUTE_PATH;
protected Environment environment; protected Environment environment;
protected RouteRepository<T> routeRepository; protected RouteRepository<T> routeRepository;
protected String routeRootPath;
/** /**
* 返回路由根对象class * 返回路由根对象class
* *
@ -70,12 +70,15 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
public BaseRouteManager(Environment environment, RouteRepository<T> routeRepository) { public BaseRouteManager(Environment environment, RouteRepository<T> routeRepository) {
this.environment = environment; this.environment = environment;
this.routeRepository = routeRepository; this.routeRepository = routeRepository;
String profile = environment.getProperty("spring.profiles.active", "default");
this.routeRootPath = SOP_SERVICE_ROUTE_PATH + "-" + profile;
} }
@Override @Override
public void refresh() { public void refresh() {
try { try {
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string"); String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
String profile = environment.getProperty("spring.profiles.active", "default");
if (StringUtils.isEmpty(zookeeperServerAddr)) { if (StringUtils.isEmpty(zookeeperServerAddr)) {
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数"); throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
} }
@ -91,9 +94,9 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
.orSetData() .orSetData()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点 // 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded() .creatingParentContainersIfNeeded()
.forPath(sopRouteRootPath, "".getBytes()); .forPath(routeRootPath, "".getBytes());
this.watchServiceChange(client, sopRouteRootPath); this.watchServiceChange(client, routeRootPath);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -103,13 +106,13 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
* 监听微服务更改 * 监听微服务更改
* *
* @param client * @param client
* @param sopRouteRootPath * @param rootPath
* @throws Exception * @throws Exception
*/ */
protected void watchServiceChange(CuratorFramework client, String sopRouteRootPath) throws Exception { protected void watchServiceChange(CuratorFramework client, String rootPath) throws Exception {
// 为子节点添加watcher // 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件 // PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
PathChildrenCache childrenCache = new PathChildrenCache(client, sopRouteRootPath, true); PathChildrenCache childrenCache = new PathChildrenCache(client, rootPath, true);
/** /**
* StartMode: 初始化方式 * StartMode: 初始化方式
@ -122,7 +125,7 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的 // 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
List<ChildData> childDataList = childrenCache.getCurrentData(); List<ChildData> childDataList = childrenCache.getCurrentData();
log.info("========== 加载路由信息 =========="); log.info("========== 加载路由信息 ==========");
log.info("{} # 根节点", this.sopRouteRootPath); log.info("{} # 根节点", rootPath);
for (ChildData childData : childDataList) { for (ChildData childData : childDataList) {
String serviceNodeData = new String(childData.getData()); String serviceNodeData = new String(childData.getData());
R serviceRouteInfo = JSON.parseObject(serviceNodeData, getServiceRouteInfoClass()); R serviceRouteInfo = JSON.parseObject(serviceNodeData, getServiceRouteInfoClass());
@ -130,7 +133,7 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
log.info("\t{} # service节点,节点数据:{}", servicePath, serviceNodeData); log.info("\t{} # service节点,节点数据:{}", servicePath, serviceNodeData);
this.loadServiceRouteItem(client, serviceRouteInfo, servicePath); this.loadServiceRouteItem(client, serviceRouteInfo, servicePath);
} }
log.info("监听服务节点增删改,rootPath:{}", this.sopRouteRootPath); log.info("监听服务节点增删改,rootPath:{}", rootPath);
// 监听根节点下面的子节点 // 监听根节点下面的子节点
childrenCache.getListenable().addListener(new PathChildrenCacheListener() { childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override @Override

@ -49,6 +49,9 @@ public class SopRouteLocator implements RouteLocator, Ordered {
if (zuulTargetRoute == null) { if (zuulTargetRoute == null) {
return null; return null;
} }
if (zuulTargetRoute.getRouteDefinition().isDisabled()) {
return null;
}
return zuulTargetRoute.getTargetRouteDefinition(); return zuulTargetRoute.getTargetRouteDefinition();
} }

@ -16,6 +16,7 @@ public class CuratorTest extends TestCase {
/** /**
* 递归删除节点只能在测试环境用 * 递归删除节点只能在测试环境用
*
* @throws Exception * @throws Exception
*/ */
public void testDel() throws Exception { public void testDel() throws Exception {
@ -26,6 +27,21 @@ public class CuratorTest extends TestCase {
client.start(); client.start();
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH); try {
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH);
} catch (Exception e) {
}
try {
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH + "-default");
} catch (Exception e) {
}
try {
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH + "-dev");
} catch (Exception e) {
}
try {
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH + "-test");
} catch (Exception e) {
}
} }
} }

@ -31,7 +31,7 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
/** /**
* zookeeper存放接口路由信息的根目录 * zookeeper存放接口路由信息的根目录
*/ */
public static final String SOP_SERVICE_ROUTE_PATH = "/sop-service-route"; public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
public static final String PATH_START_CHAR = "/"; public static final String PATH_START_CHAR = "/";
/** /**
@ -42,10 +42,14 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
private static ServiceApiInfo.ApiMeta FIRST_API_META = new ServiceApiInfo.ApiMeta("_first.route_", "/", "v_000"); private static ServiceApiInfo.ApiMeta FIRST_API_META = new ServiceApiInfo.ApiMeta("_first.route_", "/", "v_000");
private final String routeRootPath;
private Environment environment; private Environment environment;
public ServiceZookeeperApiMetaManager(Environment environment) { public ServiceZookeeperApiMetaManager(Environment environment) {
this.environment = environment; this.environment = environment;
String profile = environment.getProperty("spring.profiles.active", "default");
this.routeRootPath = SOP_SERVICE_ROUTE_PATH + "-" + profile;
} }
@Override @Override
@ -70,6 +74,8 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
} }
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo(); ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo();
serviceRouteInfo.setServiceId(serviceApiInfo.getServiceId()); serviceRouteInfo.setServiceId(serviceApiInfo.getServiceId());
String description = environment.getProperty("spring.application.description");
serviceRouteInfo.setDescription(description);
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList); serviceRouteInfo.setRouteDefinitionList(routeDefinitionList);
return serviceRouteInfo; return serviceRouteInfo;
} }
@ -133,13 +139,14 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
*/ */
protected void uploadServiceRouteInfoToZookeeper(ServiceRouteInfo serviceRouteInfo) { protected void uploadServiceRouteInfoToZookeeper(ServiceRouteInfo serviceRouteInfo) {
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string"); String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
String profile = environment.getProperty("spring.profiles.active", "default");
if (StringUtils.isEmpty(zookeeperServerAddr)) { if (StringUtils.isEmpty(zookeeperServerAddr)) {
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数"); throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
} }
CuratorFramework client = null; CuratorFramework client = null;
try { try {
// 保存路径 // 保存路径
String savePath = SOP_SERVICE_ROUTE_PATH + "/" + serviceRouteInfo.getServiceId(); String savePath = routeRootPath + "/" + serviceRouteInfo.getServiceId();
client = CuratorFrameworkFactory.builder() client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr) .connectString(zookeeperServerAddr)
@ -172,7 +179,7 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
*/ */
protected String uploadFolder(CuratorFramework client, ServiceRouteInfo serviceRouteInfo) throws Exception { protected String uploadFolder(CuratorFramework client, ServiceRouteInfo serviceRouteInfo) throws Exception {
// 保存路径 // 保存路径
String savePath = SOP_SERVICE_ROUTE_PATH + "/" + serviceRouteInfo.getServiceId(); String savePath = routeRootPath + "/" + serviceRouteInfo.getServiceId();
String serviceRouteInfoJson = JSON.toJSONString(serviceRouteInfo); String serviceRouteInfoJson = JSON.toJSONString(serviceRouteInfo);
log.info("上传service目录到zookeeper,路径:{},内容:{}", savePath, serviceRouteInfoJson); log.info("上传service目录到zookeeper,路径:{},内容:{}", savePath, serviceRouteInfoJson);
this.saveNode(client, savePath, serviceRouteInfoJson.getBytes()); this.saveNode(client, savePath, serviceRouteInfoJson.getBytes());

@ -22,4 +22,9 @@ public class GatewayRouteDefinition {
private int order = 0; private int order = 0;
/** 是否忽略验证,业务参数验证除外 */ /** 是否忽略验证,业务参数验证除外 */
private boolean ignoreValidate; private boolean ignoreValidate;
/**
* 是否禁用
*/
private boolean disabled;
} }

@ -1,8 +1,10 @@
package com.gitee.sop.servercommon.route; package com.gitee.sop.servercommon.route;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data; import lombok.Data;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
@ -12,6 +14,15 @@ import java.util.List;
public class ServiceRouteInfo { public class ServiceRouteInfo {
/** 服务名称,对应spring.application.name */ /** 服务名称,对应spring.application.name */
private String serviceId; private String serviceId;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createTime = new Date();
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date updateTime = new Date();
private String description;
@JSONField(serialize = false) @JSONField(serialize = false)
private List<GatewayRouteDefinition> routeDefinitionList; private List<GatewayRouteDefinition> routeDefinitionList;
} }

@ -1,6 +1,7 @@
spring: spring:
application: application:
name: story-service name: story-service
description: story服务
cloud: cloud:
zookeeper: zookeeper:

Loading…
Cancel
Save