tanghc 6 years ago
parent 45ab847197
commit a3b87a5f5f
  1. 2
      sop-admin/sop-admin-front/assets/css/xadmin.css
  2. 10
      sop-admin/sop-admin-front/pages/dashboard/dashboard.html
  3. 101
      sop-admin/sop-admin-front/pages/service/limitManager.html
  4. 165
      sop-admin/sop-admin-front/pages/service/limitManager.js
  5. 42
      sop-admin/sop-admin-front/pages/service/routeManager.html
  6. 46
      sop-admin/sop-admin-front/pages/service/routeManager.js
  7. 2
      sop-admin/sop-admin-server/pom.xml
  8. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvFormVO.java
  9. 6
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvVO.java
  10. 115
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LimitApi.java
  11. 82
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/RouteApi.java
  12. 45
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/LimitParam.java
  13. 14
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteParam.java
  14. 10
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteSearchParam.java
  15. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/ServiceSearchParam.java
  16. 54
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/LimitVO.java
  17. 8
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/LoginForm.java
  18. 8
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/GatewayRouteDefinition.java
  19. 37
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/RouteConfigDto.java
  20. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ZookeeperContext.java
  21. 36
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/LimitEnum.java
  22. 40
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/ConfigRouteBase.java
  23. 55
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/ConfigRouteLimit.java
  24. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/ConfigRouteBaseMapper.java
  25. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/ConfigRouteLimitMapper.java
  26. 15
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigRouteBaseService.java
  27. 30
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RouteConfigService.java
  28. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RoutePermissionService.java
  29. 53
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RouteService.java
  30. 7
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java
  31. 9
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/BaseRouteDefinition.java
  32. 46
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RouteConfig.java
  33. 37
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RouteConfigDto.java
  34. 7
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/NameVersionRoutePredicateFactory.java
  35. 49
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultRouteConfigManager.java
  36. 25
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteConfigManager.java
  37. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ZookeeperContext.java
  38. 5
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorEnum.java
  39. 130
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/MyBeanUtil.java
  40. 9
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java
  41. 1
      sop-common/sop-gateway-common/src/main/resources/i18n/open/error_en.properties
  42. 2
      sop-common/sop-gateway-common/src/main/resources/i18n/open/error_zh_CN.properties
  43. 2
      sop-example/sop-book/sop-book-web/pom.xml
  44. 2
      sop-example/sop-easyopen/pom.xml
  45. 39
      sop-gateway/src/main/java/com/gitee/sop/gateway/entity/ConfigRouteBase.java
  46. 54
      sop-gateway/src/main/java/com/gitee/sop/gateway/entity/ConfigRouteLimit.java
  47. 1
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbIsvManager.java
  48. 83
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbRouteConfigManager.java
  49. 11
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/ManagerInitializer.java
  50. 11
      sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/ConfigRouteBaseMapper.java
  51. 11
      sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/ConfigRouteLimitMapper.java

@ -676,3 +676,5 @@ table th, table td {
}
.x-win {display: none;padding: 20px;}
.layui-this{font-weight: bold;}

@ -49,7 +49,7 @@
<cite>首页</cite>
</a>
</li>
<li class="open">
<!--<li class="open">
<a>
<i class="layui-icon layui-icon-template-1"></i>
<cite>配置管理</cite>
@ -62,7 +62,7 @@
</a>
</li>
</ul>
</li>
</li>-->
<li class="open">
<a>
<i class="layui-icon layui-icon-app"></i>
@ -81,6 +81,12 @@
<cite>路由管理</cite>
</a>
</li>
<li date-refresh="1">
<a href="../service/limitManager.html">
<i class="layui-icon layui-icon-chart"></i>
<cite>限流管理</cite>
</a>
</li>
</ul>
</li>
<li class="open">

@ -0,0 +1,101 @@
<!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">
<style>
.layui-form-label{width: 100px;}
.layui-input-block{margin-left: 130px;}
</style>
</head>
<body>
<div class="x-nav">
<span class="layui-breadcrumb">
<a href="../dashboard/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" style="padding-top:5px;">
<div class="layui-row">
<div class="layui-tab" lay-filter="serviceTabFilter" style="margin-top: 0;">
<ul id="serviceTab" 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="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>
<table class="layui-hide" id="limitTable" lay-filter="limitTableFilter"></table>
</div>
</div>
<div id="updateWin" class="x-win">
<form id="updateForm" class="layui-form" action="" lay-filter="updateWinFilter">
<input type="hidden" name="serviceId" />
<div class="layui-form-item">
<label class="layui-form-label">id</label>
<div class="layui-input-block">
<input type="text" name="id" readonly="readonly" class="layui-input"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">限流策略</label>
<div class="layui-input-block">
<input type="radio" name="type" value="1" title="漏桶策略" lay-filter="limitTypeFilter">
<input type="radio" name="type" value="2" title="令牌桶策略" lay-filter="limitTypeFilter">
</div>
</div>
<div class="layui-form-item limit-type type1">
<label class="layui-form-label">每秒可处理请求数</label>
<div class="layui-input-block">
<input type="text" name="execCountPerSecond" lay-verify="required" class="layui-input"/>
</div>
</div>
<div class="layui-form-item limit-type type1">
<label class="layui-form-label">错误码</label>
<div class="layui-input-block">
<input type="text" name="limitCode" lay-verify="required" class="layui-input"/>
</div>
</div>
<div class="layui-form-item limit-type type1">
<label class="layui-form-label">错误信息</label>
<div class="layui-input-block">
<input type="text" name="limitMsg" lay-verify="required" class="layui-input"/>
</div>
</div>
<div class="layui-form-item limit-type type2">
<label class="layui-form-label">令牌桶容量</label>
<div class="layui-input-block">
<input type="text" name="tokenBucketCount" class="layui-input"/>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-normal" lay-submit="" lay-filter="updateWinSubmitFilter">保存</button>
<button type="button" class="layui-btn layui-btn-primary" onclick="layer.closeAll()">取消</button>
</div>
</div>
</form>
</div>
<script type="text/javascript" src="../../assets/js/lib.js"></script>
<script type="text/javascript" src="limitManager.js"></script>
</body>
</html>

@ -0,0 +1,165 @@
lib.importJs('../../assets/js/routerole.js')
.use(['element', 'table', 'tree', 'form'], function () {
var LIMIT_STATUS = {
'1': '<span class="x-green">已开启</span>'
,'0': '<span class="x-red">已关闭</span>'
}
// 限流策略,1:漏桶策略,2:令牌桶策略
var LIMIT_TYPE = {
'1': '漏桶策略'
,'2': '令牌桶策略'
}
var element = layui.element;
var form = layui.form;
var updateForm = layui.Form('updateForm');
var addForm = layui.Form('addForm');
var authForm = layui.Form('authForm');
var table = layui.table;
var currentServiceId;
var limitTable;
var smTitle;
form.on('submit(searchFilter)', function (data) {
var param = data.field;
param.serviceId = currentServiceId;
searchTable(param)
return false;
});
// 监听修改提交
form.on('submit(updateWinSubmitFilter)', function(data) {
ApiUtil.post('route.update', data.field, function (resp) {
layer.closeAll();
limitTable.reload();
})
return false;
});
form.on('radio(limitTypeFilter)', function(data){
$('.limit-type').hide();
$('.type' + data.value).show();
});
element.on('tab(serviceTabFilter)', function(data){
loadLimitTable(this.innerHTML);
});
function initServiceTab() {
ApiUtil.post('service.list', {}, function (resp) {
var serviceList = resp.data;
var html = [];
for (var i = 0; i < serviceList.length; i++) {
var serviceInfo = serviceList[i];
var clazz = i === 0 ? 'class="layui-this"' : '';
html.push('<li ' + clazz + '>' + serviceInfo.serviceId + '</li>');
}
$('#serviceTab').html(html.join(''));
if (serviceList.length > 0) {
loadLimitTable(serviceList[0].serviceId);
}
});
}
function loadLimitTable(serviceId) {
currentServiceId = serviceId;
searchTable({
serviceId: serviceId
});
smTitle = '[ <strong>serviceId:</strong>' + currentServiceId + ' ]';
}
/**
* 查询表格
* @param params
*/
function searchTable(params) {
var postData = {
data: JSON.stringify(params)
};
if (!limitTable) {
limitTable = renderTable(postData);
} else {
limitTable.reload({
where: postData
})
}
}
function renderTable(postData) {
var limitTable = table.render({
elem: '#limitTable'
, url: ApiUtil.createUrl('route.limit.list')
, where: postData
, headers: {access_token: ApiUtil.getAccessToken()}
, cellMinWidth: 80 //全局定义常规单元格的最小宽度,layui 2.2.1 新增
, cols: [[
{field: 'id', title: 'id(接口名+版本号)', width: 200}
, {
field: 'type', title: '限流策略', width: 80, templet: function (row) {
return LIMIT_TYPE[row.type + ''];
}
}
, {
field: 'info', title: '限流信息', width: 500, templet: function (row) {
if (row.limitStatus == 0) {
return '--'
}
var html = [];
if (row.type == 1) {
html.push('每秒可处理请求数:' + row.execCountPerSecond);
html.push('错误码:' + row.limitCode);
html.push('错误信息:' + row.limitMsg);
} else if(row.type == 2) {
html.push('令牌桶容量:' + row.tokenBucketCount);
}
return html.join(',');
}
}
, {
field: 'limitStatus', title: '状态', width: 80, templet: function (row) {
return LIMIT_STATUS[row.limitStatus + ''];
}
}
, {
fixed: 'right', title: '操作', width: 100, templet: function (row) {
var html = ['<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="edit">修改</a>'];
return html.join('');
}
}
]]
});
//监听单元格事件
table.on('tool(limitTableFilter)', function(obj) {
var data = obj.data;
var event = obj.event;
if(event === 'edit'){
//表单初始赋值
data.serviceId = currentServiceId;
updateForm.setData(data);
$('.limit-type').hide();
$('.type' + data.type).show();
layer.open({
type: 1
,title: '修改限流' + smTitle
,area: ['600px', '380px']
,content: $('#updateWin') //这里content是一个DOM,注意:最好该元素要存放在body最外层,否则可能被其它的相对元素所影响
});
}
});
return limitTable;
}
initServiceTab();
RouteRole.loadAllRole(form, 'roleArea');
});

@ -21,15 +21,12 @@
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="x-body" style="padding-top:5px;">
<div class="layui-row">
<div class="layui-col-md2">
<ul id="leftTree"></ul>
<div class="layui-tab" lay-filter="serviceTabFilter" style="margin-top: 0;">
<ul id="serviceTab" class="layui-tab-title">
</ul>
</div>
<div id="optTip" class="layui-col-md2" style="margin-top: 20px;">
<i class="layui-icon layui-icon-return"></i>点击树节点查看
</div>
<div id="rightPart" class="layui-col-md10" style="display: none;">
<div class="layui-row">
<form class="layui-form layui-col-md12 x-so" action="" lay-filter="searchFrm">
路由名称:
@ -51,9 +48,6 @@
</div>
</div>
</div>
<div id="addWin" class="x-win">
<form id="addForm" class="layui-form" action="" lay-filter="addWinFilter">
<input type="hidden" name="serviceId" />
@ -75,20 +69,6 @@
<input type="text" name="path" class="layui-input"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">合并结果</label>
<div class="layui-input-block">
<input type="radio" name="mergeResult" value="1" title="合并">
<input type="radio" name="mergeResult" value="0" title="不合并">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">忽略验证</label>
<div class="layui-input-block">
<input type="radio" name="ignoreValidate" value="1" title="是">
<input type="radio" name="ignoreValidate" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
@ -126,20 +106,6 @@
<input type="text" name="path" class="layui-input"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">合并结果</label>
<div class="layui-input-block">
<input type="radio" name="mergeResult" value="1" title="合并">
<input type="radio" name="mergeResult" value="0" title="不合并">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">忽略验证</label>
<div class="layui-input-block">
<input type="radio" name="ignoreValidate" value="1" title="是">
<input type="radio" name="ignoreValidate" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">

@ -5,6 +5,7 @@ lib.importJs('../../assets/js/routerole.js')
,'1': '<span class="x-green">已启用</span>'
,'2': '<span class="x-red">已禁用</span>'
}
var element = layui.element;
var form = layui.form;
var updateForm = layui.Form('updateForm');
var addForm = layui.Form('addForm');
@ -50,43 +51,30 @@ lib.importJs('../../assets/js/routerole.js')
return false;
});
element.on('tab(serviceTabFilter)', function(data){
loadRouteTable(this.innerHTML);
});
function initTree() {
function initServiceTab() {
ApiUtil.post('service.list', {}, function (resp) {
var serviceList = resp.data;
var children = [];
var html = [];
for (var i = 0; i < serviceList.length; i++) {
var serviceInfo = serviceList[i];
children.push({
id: i + 1,
name: serviceInfo.serviceId
})
var clazz = i === 0 ? 'class="layui-this"' : '';
html.push('<li ' + clazz + '>' + serviceInfo.serviceId + '</li>');
}
$('#serviceTab').html(html.join(''));
layui.tree({
elem: '#leftTree' //传入元素选择器
, nodes: [{ //节点
name: '服务列表'
, spread: true // 展开
, children: children
}]
, click: function (node) {
if (node.id) {
reloadRightPart(node)
}
if (serviceList.length > 0) {
loadRouteTable(serviceList[0].serviceId);
}
});
});
}
/**
* 更新右边部分
* @param node 树节点
*/
function reloadRightPart(node) {
$('#optTip').hide();
$('#rightPart').show();
var serviceId = node.name;
function loadRouteTable(serviceId) {
currentServiceId = serviceId;
searchTable({
serviceId: serviceId
@ -174,7 +162,7 @@ lib.importJs('../../assets/js/routerole.js')
layer.open({
type: 1
,title: '修改路由' + smTitle
,area: ['500px', '460px']
,area: ['500px', '350px']
,content: $('#updateWin') //这里content是一个DOM,注意:最好该元素要存放在body最外层,否则可能被其它的相对元素所影响
});
} else if (event === 'auth') {
@ -210,7 +198,7 @@ lib.importJs('../../assets/js/routerole.js')
layer.open({
type: 1
,title: '添加路由' + smTitle
,area: ['500px', '460px']
,area: ['500px', '350px']
,content: $('#addWin')
});
}
@ -218,7 +206,7 @@ lib.importJs('../../assets/js/routerole.js')
return routeTable;
}
initTree();
initServiceTab();
RouteRole.loadAllRole(form, 'roleArea');

@ -37,7 +37,7 @@
<dependency>
<groupId>net.oschina.durcframework</groupId>
<artifactId>easyopen-spring-boot-starter</artifactId>
<version>1.16.0</version>
<version>1.16.2</version>
</dependency>
<dependency>

@ -8,10 +8,10 @@ import lombok.Data;
*/
@Data
public class IsvFormVO {
@ApiDocField(description = "appKey")
@ApiDocField(description = "appKey", example = "aaaa")
private String appKey;
@ApiDocField(description = "secret")
@ApiDocField(description = "secret", example = "bbbb")
private String secret;
@ApiDocField(description = "pubKey")

@ -12,15 +12,15 @@ import java.util.List;
@Data
public class IsvVO {
/** 数据库字段:id */
@ApiDocField(description = "id")
@ApiDocField(description = "id", example = "1")
private Long id;
/** appKey, 数据库字段:app_key */
@ApiDocField(description = "appKey")
@ApiDocField(description = "appKey", example = "aaaa")
private String appKey;
/** secret, 数据库字段:secret */
@ApiDocField(description = "appKey")
@ApiDocField(description = "secret", example = "bbbb")
private String secret;
/** 公钥, 数据库字段:pub_key */

@ -0,0 +1,115 @@
package com.gitee.sop.adminserver.api.service;
import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.exception.ApiException;
import com.gitee.easyopen.util.CopyUtil;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.adminserver.api.service.param.LimitParam;
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
import com.gitee.sop.adminserver.api.service.result.LimitVO;
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
import com.gitee.sop.adminserver.bean.RouteConfigDto;
import com.gitee.sop.adminserver.common.LimitEnum;
import com.gitee.sop.adminserver.entity.ConfigRouteLimit;
import com.gitee.sop.adminserver.mapper.ConfigRouteLimitMapper;
import com.gitee.sop.adminserver.service.RouteConfigService;
import com.gitee.sop.adminserver.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 限流
*
* @author tanghc
*/
@ApiService
@ApiDoc("服务管理")
@Slf4j
public class LimitApi {
@Autowired
RouteService routeService;
@Autowired
RouteConfigService routeConfigService;
@Autowired
ConfigRouteLimitMapper configRouteLimitMapper;
@Api(name = "route.limit.list")
@ApiDocMethod(description = "限流列表", elementClass = LimitVO.class)
List<LimitVO> listLimit(RouteSearchParam param) throws Exception {
List<GatewayRouteDefinition> routeDefinitionList = routeService.getRouteDefinitionList(param);
List<String> routeIdList = getRouteIdList(routeDefinitionList);
// key:routeId
Map<String, ConfigRouteLimit> routeLimitMap = getStoreConfigRouteLimit(routeIdList);
List<LimitVO> gatewayRouteDefinitions = routeDefinitionList
.stream()
.map(gatewayRouteDefinition -> {
LimitVO vo = new LimitVO();
CopyUtil.copyProperties(gatewayRouteDefinition, vo);
ConfigRouteLimit configRouteLimit = routeLimitMap.getOrDefault(gatewayRouteDefinition.getId(), getDefaultLimit());
CopyUtil.copyPropertiesIgnoreNull(configRouteLimit, vo);
return vo;
})
.collect(Collectors.toList());
return gatewayRouteDefinitions;
}
private List<String> getRouteIdList(List<GatewayRouteDefinition> routeDefinitionList) {
return routeDefinitionList
.stream()
.map(GatewayRouteDefinition::getId)
.collect(Collectors.toList());
}
private Map<String, ConfigRouteLimit> getStoreConfigRouteLimit(List<String> routeIdList) {
Query query = new Query();
query.in("route_id", routeIdList);
// key:routeId
Map<String, ConfigRouteLimit> routeLimitMap = configRouteLimitMapper.list(query)
.stream()
.collect(Collectors.toMap(ConfigRouteLimit::getRouteId, Function.identity()));
return routeLimitMap;
}
private ConfigRouteLimit getDefaultLimit() {
ConfigRouteLimit configRouteLimit = new ConfigRouteLimit();
configRouteLimit.setType(LimitEnum.TYPE_LEAKY_BUCKET.getVal());
configRouteLimit.setLimitStatus(LimitEnum.STATUS_CLOSE.getVal());
return configRouteLimit;
}
@Api(name = "route.limit.update")
@ApiDocMethod(description = "修改限流")
@Transactional(rollbackFor = Exception.class)
public void updateLimtit(LimitParam param) {
String routeId = param.getRouteId();
ConfigRouteLimit configRouteLimit = configRouteLimitMapper.getByColumn("route_id", routeId);
if (configRouteLimit == null) {
configRouteLimit = new ConfigRouteLimit();
CopyUtil.copyPropertiesIgnoreNull(param, configRouteLimit);
configRouteLimitMapper.save(configRouteLimit);
} else {
CopyUtil.copyPropertiesIgnoreNull(param, configRouteLimit);
configRouteLimitMapper.updateIgnoreNull(configRouteLimit);
}
RouteConfigDto routeConfigDto = new RouteConfigDto();
CopyUtil.copyPropertiesIgnoreNull(param, routeConfigDto);
try {
routeConfigService.sendRouteConfigMsg(routeConfigDto);
} catch (Exception e) {
log.error("推送限流消息错误, param:{}", param, e);
throw new ApiException("修改失败,请查看日志");
}
}
}

@ -6,6 +6,7 @@ import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.exception.ApiException;
import com.gitee.easyopen.util.CopyUtil;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.adminserver.api.isv.result.RoleVO;
import com.gitee.sop.adminserver.api.service.param.RouteParam;
@ -14,23 +15,26 @@ import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
import com.gitee.sop.adminserver.api.service.result.RouteVO;
import com.gitee.sop.adminserver.api.service.result.ServiceInfo;
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
import com.gitee.sop.adminserver.bean.RouteConfigDto;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.entity.ConfigRouteBase;
import com.gitee.sop.adminserver.entity.PermRole;
import com.gitee.sop.adminserver.entity.PermRolePermission;
import com.gitee.sop.adminserver.mapper.ConfigRouteBaseMapper;
import com.gitee.sop.adminserver.mapper.PermRoleMapper;
import com.gitee.sop.adminserver.mapper.PermRolePermissionMapper;
import com.gitee.sop.adminserver.service.RouteConfigService;
import com.gitee.sop.adminserver.service.RoutePermissionService;
import com.gitee.sop.adminserver.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@ -49,35 +53,23 @@ public class RouteApi {
@Autowired
PermRoleMapper permRoleMapper;
@Autowired
ConfigRouteBaseMapper configRouteBaseMapper;
@Autowired
RoutePermissionService routePermissionService;
@Autowired
RouteConfigService routeConfigService;
@Autowired
RouteService routeService;
@Api(name = "route.list")
@ApiDocMethod(description = "路由列表")
List<RouteVO> listRoute(RouteSearchParam param) throws Exception {
if (StringUtils.isBlank(param.getServiceId())) {
return Collections.emptyList();
}
String searchPath = ZookeeperContext.getSopRouteRootPath() + "/" + param.getServiceId();
List<ChildData> childDataList = ZookeeperContext.getChildrenData(searchPath);
List<RouteVO> 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);
}
})
List<RouteVO> routeDefinitionList = routeService.getRouteDefinitionList(param)
.stream()
.map(gatewayRouteDefinition -> {
RouteVO vo = new RouteVO();
BeanUtils.copyProperties(gatewayRouteDefinition, vo);
@ -96,8 +88,10 @@ public class RouteApi {
String zookeeperRoutePath = serviceIdPath + "/" + param.getId();
String data = ZookeeperContext.getData(zookeeperRoutePath);
GatewayRouteDefinition routeDefinition = JSON.parseObject(data, GatewayRouteDefinition.class);
BeanUtils.copyProperties(param, routeDefinition);
CopyUtil.copyPropertiesIgnoreNull(param, routeDefinition);
ZookeeperContext.updatePathData(zookeeperRoutePath, JSON.toJSONString(routeDefinition));
this.updateRouteConfig(param);
}
@Api(name = "route.add")
@ -109,7 +103,7 @@ public class RouteApi {
throw new ApiException("id已存在");
}
GatewayRouteDefinition routeDefinition = new GatewayRouteDefinition();
BeanUtils.copyProperties(param, routeDefinition);
CopyUtil.copyPropertiesIgnoreNull(param, routeDefinition);
ZookeeperContext.createNewData(zookeeperRoutePath, JSON.toJSONString(routeDefinition));
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setServiceId(param.getServiceId());
@ -118,6 +112,37 @@ public class RouteApi {
serviceInfo.setCreateTime(now);
serviceInfo.setUpdateTime(now);
ZookeeperContext.updatePathData(serviceIdPath, JSON.toJSONString(serviceInfo));
this.updateRouteConfig(param);
}
private void updateRouteConfig(RouteParam param) {
try {
String routeId = param.getId();
ConfigRouteBase configRouteBase = configRouteBaseMapper.getByColumn("route_id", routeId);
boolean doSave = configRouteBase == null;
if (doSave) {
configRouteBase = new ConfigRouteBase();
configRouteBase.setRouteId(param.getId());
}
configRouteBase.setStatus(param.getStatus().byteValue());
int i = doSave ? configRouteBaseMapper.save(configRouteBase)
: configRouteBaseMapper.update(configRouteBase);
if (i > 0) {
this.sendMsg(param);
}
} catch (Exception e) {
log.error("发送msg失败", e);
}
}
private void sendMsg(RouteParam param) throws Exception {
RouteConfigDto routeConfigDto = new RouteConfigDto();
routeConfigDto.setRouteId(param.getId());
routeConfigDto.setStatus(param.getStatus());
routeConfigService.sendRouteConfigMsg(routeConfigDto);
}
@Api(name = "route.role.get")
@ -166,9 +191,10 @@ public class RouteApi {
}
try {
routePermissionService.sendRoutePermissionReloadToZookeeper();
routePermissionService.sendRoutePermissionReloadMsg();
} catch (Exception e) {
log.info("消息推送--路由权限(reload)失败", e);
throw new ApiException("修改失败,请查看日志");
}
}

@ -0,0 +1,45 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 限流
* @author tanghc
*/
@Data
public class LimitParam {
@ApiDocField(description = "routeId")
@NotBlank
private String routeId;
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:type */
@ApiDocField(description = "限流策略,1:漏桶策略,2:令牌桶策略")
@NotNull
private Byte type;
/** 每秒可处理请求数, 数据库字段:exec_count_per_second */
@ApiDocField(description = "每秒可处理请求数")
private Integer execCountPerSecond;
/** 返回的错误码, 数据库字段:limit_code */
@ApiDocField(description = "返回的错误码")
private String limitCode;
/** 返回的错误信息, 数据库字段:limit_msg */
@ApiDocField(description = "返回的错误信息")
private String limitMsg;
/** 令牌桶容量, 数据库字段:token_bucket_count */
@ApiDocField(description = "令牌桶容量")
private Integer tokenBucketCount;
/** 1:开启,0关闭, 数据库字段:limit_status */
@ApiDocField(description = "1:开启,0关闭")
@NotNull
private Byte limitStatus;
}

@ -38,14 +38,6 @@ public class RouteParam {
@ApiDocField(description = "路由path")
private String path;
/**
* 是否忽略验证业务参数验证除外
*/
@NotNull
@ApiDocField(description = "是否忽略验证,1:是,0:否")
private Integer ignoreValidate;
/**
* 状态
*/
@ -53,10 +45,4 @@ public class RouteParam {
@ApiDocField(description = "状态,0:审核,1:启用,2:禁用")
private Integer status;
/**
* 状态
*/
@NotNull
@ApiDocField(description = "是否合并结果,1:是,0:否")
private Integer mergeResult;
}

@ -4,12 +4,18 @@ import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* @author tanghc
*/
@Getter
@Setter
public class RouteSearchParam extends ServiceSearchParam {
@ApiDocField(description = "id")
public class RouteSearchParam {
@ApiDocField(description = "服务名serviceId")
@NotBlank(message = "serviceId不能为空")
private String serviceId;
@ApiDocField(description = "路由id")
private String id;
}

@ -4,6 +4,8 @@ import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* @author tanghc
*/
@ -11,6 +13,6 @@ import lombok.Setter;
@Setter
public class ServiceSearchParam {
@ApiDocField(description = "serviceId")
@ApiDocField(description = "服务名serviceId")
private String serviceId;
}

@ -0,0 +1,54 @@
package com.gitee.sop.adminserver.api.service.result;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
/**
* 限流
*
* @author tanghc
*/
@Data
public class LimitVO {
@ApiDocField(description = "路由id")
private String id;
@ApiDocField(description = "serviceId")
private String serviceId;
/**
* 限流策略1漏桶策略2令牌桶策略, 数据库字段type
*/
@ApiDocField(description = "限流策略,1:漏桶策略,2:令牌桶策略")
private Byte type;
/**
* 每秒可处理请求数, 数据库字段exec_count_per_second
*/
@ApiDocField(description = "每秒可处理请求数")
private Integer execCountPerSecond;
/**
* 返回的错误码, 数据库字段limit_code
*/
@ApiDocField(description = "返回的错误码")
private String limitCode;
/**
* 返回的错误信息, 数据库字段limit_msg
*/
@ApiDocField(description = "返回的错误信息")
private String limitMsg;
/**
* 令牌桶容量, 数据库字段token_bucket_count
*/
@ApiDocField(description = "令牌桶容量")
private Integer tokenBucketCount;
/**
* 1:开启0关闭, 数据库字段limit_status
*/
@ApiDocField(description = "1:开启,0关闭")
private Byte limitStatus;
}

@ -1,9 +1,17 @@
package com.gitee.sop.adminserver.api.system.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class LoginForm {
@NotBlank(message = "用户名不能为空")
@ApiDocField(description = "用户名", required = true)
private String username;
@NotBlank(message = "密码不能为空")
@ApiDocField(description = "密码", required = true)
private String password;
}

@ -1,5 +1,6 @@
package com.gitee.sop.adminserver.bean;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import java.util.ArrayList;
@ -13,6 +14,7 @@ public class GatewayRouteDefinition {
/**
* 路由的Id
*/
@ApiDocField(description = "路由id")
private String id = "";
/**
@ -28,11 +30,13 @@ public class GatewayRouteDefinition {
/**
* 路由规则转发的目标uri
*/
@ApiDocField(description = "uri")
private String uri;
/**
* uri后面跟的path
*/
@ApiDocField(description = "path")
private String path;
/**
@ -43,20 +47,24 @@ public class GatewayRouteDefinition {
/**
* 是否忽略验证业务参数验证除外
*/
@ApiDocField(description = "是否忽略验证,业务参数验证除外,1:忽略,0:不忽略")
private int ignoreValidate;
/**
* 状态0待审核1启用2禁用
*/
@ApiDocField(description = "状态,0:待审核,1:启用,2:禁用")
private int status = 1;
/**
* 合并结果
*/
@ApiDocField(description = "合并结果,1:合并,2:不合并")
private int mergeResult = 1;
/**
* 是否需要授权才能访问
*/
@ApiDocField(description = "是否需要授权才能访问,1:是,2:否")
private int permission;
}

@ -0,0 +1,37 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class RouteConfigDto {
private String routeId;
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:type */
private Byte type;
/** 每秒可处理请求数, 数据库字段:exec_count_per_second */
private Integer execCountPerSecond;
/** 返回的错误码, 数据库字段:limit_code */
private String limitCode;
/** 返回的错误信息, 数据库字段:limit_msg */
private String limitMsg;
/** 令牌桶容量, 数据库字段:token_bucket_count */
private Integer tokenBucketCount;
/** 限流开启状态,1:开启,0关闭, 数据库字段:limit_status */
private Byte limitStatus;
/**
* 状态0待审核1启用2禁用
*/
private Integer status;
}

@ -67,6 +67,10 @@ public class ZookeeperContext {
return SOP_MSG_CHANNEL_PATH + "/isv-route-permission";
}
public static String getRouteConfigChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/route-conf";
}
public static CuratorFramework getClient() {
return client;
}

@ -0,0 +1,36 @@
package com.gitee.sop.adminserver.common;
/**
* 限流
* @author tanghc
*/
public enum LimitEnum {
/**
* 限流策略1漏桶策略
*/
TYPE_LEAKY_BUCKET((byte) 1),
/**
* 限流策略2令牌桶策略
*/
TYPE_TOKEN_BUCKET((byte) 2),
/**
* 1:开启
*/
STATUS_OPEN((byte) 1),
/**
* 0关闭
*/
STATUS_CLOSE((byte) 0),
;
LimitEnum(byte val) {
this.val = val;
}
private byte val;
public byte getVal() {
return val;
}
}

@ -0,0 +1,40 @@
package com.gitee.sop.adminserver.entity;
import lombok.Data;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* 表名config_route_base
* 备注路由配置表
*
* @author tanghc
*/
@Table(name = "config_route_base")
@Data
public class ConfigRouteBase {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Long id;
/** 路由id, 数据库字段:route_id */
private String routeId;
/** 状态,1:启用,2:禁用, 数据库字段:status */
private Byte status;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -0,0 +1,55 @@
package com.gitee.sop.adminserver.entity;
import lombok.Data;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* 表名config_route_limit
* 备注路由限流配置
*
* @author tanghc
*/
@Table(name = "config_route_limit")
@Data
public class ConfigRouteLimit {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Integer id;
/** 路由id, 数据库字段:route_id */
private String routeId;
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:type */
private Byte type;
/** 每秒可处理请求数, 数据库字段:exec_count_per_second */
private Integer execCountPerSecond;
/** 返回的错误码, 数据库字段:limit_code */
private String limitCode;
/** 返回的错误信息, 数据库字段:limit_msg */
private String limitMsg;
/** 令牌桶容量, 数据库字段:token_bucket_count */
private Integer tokenBucketCount;
/** 1:开启,0关闭, 数据库字段:limit_status */
private Byte limitStatus;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -0,0 +1,12 @@
package com.gitee.sop.adminserver.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.adminserver.entity.ConfigRouteBase;
/**
* @author tanghc
*/
public interface ConfigRouteBaseMapper extends CrudMapper<ConfigRouteBase, Long> {
}

@ -0,0 +1,12 @@
package com.gitee.sop.adminserver.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.adminserver.entity.ConfigRouteLimit;
/**
* @author tanghc
*/
public interface ConfigRouteLimitMapper extends CrudMapper<ConfigRouteLimit, Integer> {
}

@ -0,0 +1,15 @@
package com.gitee.sop.adminserver.service;
import org.springframework.stereotype.Service;
/**
* @author tanghc
*/
@Service
public class ConfigRouteBaseService {
public void updateConfigRouteBase() {
}
}

@ -0,0 +1,30 @@
package com.gitee.sop.adminserver.service;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.adminserver.bean.ChannelMsg;
import com.gitee.sop.adminserver.bean.RouteConfigDto;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author tanghc
*/
@Service
@Slf4j
public class RouteConfigService {
/**
* 发送路由配置消息
* @param routeConfigDto
* @throws Exception
*/
public void sendRouteConfigMsg(RouteConfigDto routeConfigDto) throws Exception {
String configData = JSON.toJSONString(routeConfigDto);
ChannelMsg channelMsg = new ChannelMsg("update", configData);
String jsonData = JSON.toJSONString(channelMsg);
String path = ZookeeperContext.getRouteConfigChannelPath();
log.info("消息推送--路由配置(update), path:{}, data:{}", path, jsonData);
ZookeeperContext.createOrUpdateData(path, jsonData);
}
}

@ -93,7 +93,7 @@ public class RoutePermissionService {
/**
* 推送所有路由权限到zookeeper
*/
public void sendRoutePermissionReloadToZookeeper() throws Exception {
public void sendRoutePermissionReloadMsg() throws Exception {
ChannelMsg channelMsg = new ChannelMsg("reload", null);
String jsonData = JSON.toJSONString(channelMsg);
String path = ZookeeperContext.getIsvRoutePermissionChannelPath();

@ -0,0 +1,53 @@
package com.gitee.sop.adminserver.service;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
/**
* @author tanghc
*/
@Service
public class RouteService {
public List<GatewayRouteDefinition> getRouteDefinitionList(RouteSearchParam param) throws Exception {
if (StringUtils.isBlank(param.getServiceId())) {
return Collections.emptyList();
}
String searchPath = ZookeeperContext.getSopRouteRootPath() + "/" + param.getServiceId();
List<ChildData> childDataList = ZookeeperContext.getChildrenData(searchPath);
List<GatewayRouteDefinition> routeDefinitionStream = 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(toList())
;
return routeDefinitionStream;
}
}

@ -4,7 +4,9 @@ import com.gitee.sop.gatewaycommon.gateway.param.GatewayParamBuilder;
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResult;
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor;
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager;
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import com.gitee.sop.gatewaycommon.secret.CacheIsvManager;
@ -94,6 +96,11 @@ public class ApiConfig {
*/
private IsvRoutePermissionManager isvRoutePermissionManager = new DefaultIsvRoutePermissionManager();
/**
* 路由配置管理
*/
private RouteConfigManager routeConfigManager = new DefaultRouteConfigManager();
// -------- fields ---------
/**

@ -34,11 +34,6 @@ public class BaseRouteDefinition {
*/
private int ignoreValidate;
/**
* 状态0待审核1启用2禁用
*/
private int status = 1;
/**
* 是否合并结果
*/
@ -48,8 +43,4 @@ public class BaseRouteDefinition {
* 接口是否需要授权才能访问
*/
private int permission;
public boolean enable() {
return status == RouteStatus.ENABLE.getStatus();
}
}

@ -0,0 +1,46 @@
package com.gitee.sop.gatewaycommon.bean;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class RouteConfig {
public static final byte STATUS_ENABLE = 1;
public static final byte LIMIT_STATUS_CLOSE = 0;
private String routeId;
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:type */
private Byte type;
/** 每秒可处理请求数, 数据库字段:exec_count_per_second */
private Integer execCountPerSecond;
/** 返回的错误码, 数据库字段:limit_code */
private String limitCode;
/** 返回的错误信息, 数据库字段:limit_msg */
private String limitMsg;
/** 令牌桶容量, 数据库字段:token_bucket_count */
private Integer tokenBucketCount;
/** 限流开启状态,1:开启,0关闭, 数据库字段:limit_status */
private Byte limitStatus = LIMIT_STATUS_CLOSE;
/**
* 状态0待审核1启用2禁用
*/
private Byte status = STATUS_ENABLE;
/**
* 是否启用
* @return
*/
public boolean enable() {
return status == STATUS_ENABLE;
}
}

@ -0,0 +1,37 @@
package com.gitee.sop.gatewaycommon.bean;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class RouteConfigDto {
private String routeId;
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:type */
private Byte type;
/** 每秒可处理请求数, 数据库字段:exec_count_per_second */
private Integer execCountPerSecond;
/** 返回的错误码, 数据库字段:limit_code */
private String limitCode;
/** 返回的错误信息, 数据库字段:limit_msg */
private String limitMsg;
/** 令牌桶容量, 数据库字段:token_bucket_count */
private Integer tokenBucketCount;
/** 限流开启状态,1:开启,0关闭, 数据库字段:limit_status */
private Byte limitStatus;
/**
* 状态0待审核1启用2禁用
*/
private Byte status;
}

@ -1,7 +1,10 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import com.gitee.sop.gatewaycommon.util.RequestUtil;
@ -64,7 +67,9 @@ public class NameVersionRoutePredicateFactory extends AbstractRoutePredicateFact
boolean match = (name + version).equals(nameVersion);
if (match) {
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
if (targetRoute != null && !targetRoute.getRouteDefinition().enable()) {
RouteConfigManager routeConfigManager = ApiConfig.getInstance().getRouteConfigManager();
RouteConfig routeConfig = routeConfigManager.get(nameVersion);
if (targetRoute != null && !routeConfig.enable()) {
return false;
}
}

@ -0,0 +1,49 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
import com.gitee.sop.gatewaycommon.bean.RouteConfigDto;
import com.gitee.sop.gatewaycommon.util.MyBeanUtil;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author tanghc
*/
public class DefaultRouteConfigManager implements RouteConfigManager {
/**
* key: routeId
*/
protected static Map<String, RouteConfig> routeConfigMap = new ConcurrentHashMap<>(64);
private static RouteConfig DEFAULT_CONFIG;
public DefaultRouteConfigManager() {
DEFAULT_CONFIG = this.newRouteConfig();
}
@Override
public void load() {
}
@Override
public void update(RouteConfigDto routeConfigDto) {
String key = routeConfigDto.getRouteId();
RouteConfig routeConfig = routeConfigMap.get(key);
if (routeConfig == null) {
routeConfig = newRouteConfig();
routeConfigMap.put(key, routeConfig);
}
MyBeanUtil.copyPropertiesIgnoreNull(routeConfigDto, routeConfig);
}
protected RouteConfig newRouteConfig() {
return new RouteConfig();
}
@Override
public RouteConfig get(String routeId) {
return routeConfigMap.getOrDefault(routeId, DEFAULT_CONFIG);
}
}

@ -0,0 +1,25 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
import com.gitee.sop.gatewaycommon.bean.RouteConfigDto;
/**
* 路由配置管理
* @author tanghc
*/
public interface RouteConfigManager {
void load();
/**
* 更新路由配置
* @param routeConfigDto
*/
void update(RouteConfigDto routeConfigDto);
/**
* 获取路由配置
* @param routeId
* @return
*/
RouteConfig get(String routeId);
}

@ -74,6 +74,10 @@ public class ZookeeperContext {
return SOP_MSG_CHANNEL_PATH + "/isv-route-permission";
}
public static String getRouteConfigChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/route-conf";
}
public static CuratorFramework getClient() {
return client;
}

@ -10,8 +10,11 @@ public enum ErrorEnum {
/** 服务暂不可用 */
ISP_UNKNOW_ERROR(Codes.CODE_UNKNOW, "isp.unknow-error"),
/** */
/** 服务暂不可用 */
AOP_UNKNOW_ERROR(Codes.CODE_UNKNOW, "aop.unknow-error"),
/** 服务不可用,路由被禁用 */
ISP_API_DISABLED(Codes.CODE_UNKNOW, "isp.service-not-available"),
/** 无效的访问令牌 */
AOP_INVALID_AUTH_TOKEN(Codes.CODE_AUTH, "aop.invalid-auth-token"),

@ -0,0 +1,130 @@
package com.gitee.sop.gatewaycommon.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 对象拷贝
*
* @author tanghc
*/
public class MyBeanUtil extends org.springframework.beans.BeanUtils {
/**
* 属性拷贝,第一个参数中的属性值拷贝到第二个参数中<br>
* 注意:当第一个参数中的属性有null值时,不会拷贝进去
*
* @param source 源对象
* @param target 目标对象
* @throws BeansException
*/
public static void copyPropertiesIgnoreNull(Object source, Object target)
throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null) {
PropertyDescriptor sourcePd = getPropertyDescriptor(
source.getClass(), targetPd.getName());
if (sourcePd != null && sourcePd.getReadMethod() != null) {
try {
Method readMethod = sourcePd.getReadMethod();
if (!Modifier.isPublic(readMethod.getDeclaringClass()
.getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
// 这里判断value是否为空 当然这里也能进行一些特殊要求的处理
// 例如绑定时格式转换等等
if (value != null) {
if (!Modifier.isPublic(writeMethod
.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
} catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy properties from source to target field name mismatch:" + targetPd.getName(),
ex);
}
}
}
}
}
/**
* 属性拷贝把map中的值拷贝到target中去
*
* @param map map对象
* @param target 目标对象
*/
public static void copyPropertiesForMap(Map<String, Object> map, Object target) {
Assert.notNull(map, "map must not be null");
Assert.notNull(target, "Target must not be null");
Object pojo = mapToPojo(map, target.getClass());
copyProperties(pojo, target);
}
/**
* 将实体对象转换成Map
*
* @param pojo 实体类
* @return 返回map
*/
public static Map<String, Object> pojoToMap(Object pojo) {
if (pojo == null) {
return Collections.emptyMap();
}
String json = JSON.toJSONString(pojo);
return JSON.parseObject(json);
}
/**
* 将map对象转换成普通类
*
* @param <T> 普通类类型
* @param map map对象
* @param pojoClass 普通类
* @return 返回普通类
*/
public static <T> T mapToPojo(Map<String, Object> map, Class<T> pojoClass) {
return new JSONObject(map).toJavaObject(pojoClass);
}
/**
* map集合转换成对象集合
*
* @param <T> 普通类类型
* @param list map集合
* @param pojoClass 待转换的对象类型
* @return 返回对象集合
*/
public static <T> List<T> mapListToObjList(List<Map<String, Object>> list, Class<T> pojoClass) {
if (list == null) {
return Collections.emptyList();
}
List<T> retList = new ArrayList<>(list.size());
for (Map<String, Object> map : list) {
retList.add(mapToPojo(map, pojoClass));
}
return retList;
}
}

@ -1,5 +1,8 @@
package com.gitee.sop.gatewaycommon.zuul.route;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
@ -51,8 +54,10 @@ public class SopRouteLocator implements RouteLocator, Ordered {
return null;
}
// 路由是否启用
if (!zuulTargetRoute.getRouteDefinition().enable()) {
throw ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException();
RouteConfigManager routeConfigManager = ApiConfig.getInstance().getRouteConfigManager();
RouteConfig routeConfig = routeConfigManager.get(zuulTargetRoute.getRouteDefinition().getId());
if (!routeConfig.enable()) {
throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException();
}
return zuulTargetRoute.getTargetRouteDefinition();
}

@ -5,6 +5,7 @@ open.error_10000=Success
open.error_20000=Service is temporarily unavailable
open.error_20000_isp.unknow-error=Service is temporarily unavailable
open.error_20000_aop.unknow-error=Service is temporarily unavailable
open.error_20000_isp.service-not-available=Service is temporarily unavailable
open.error_20001=Insufficient authorization authority
open.error_20001_aop.invalid-auth-token=Invalid access token

@ -3,6 +3,7 @@
#open.error_20000=服务不可用
#open.error_20000_isp.unknow-error=服务暂不可用
#open.error_20000_aop.unknow-error=服务暂不可用
#open.error_20000_isp.service-not-available=服务暂不可用
#
#open.error_20001=授权权限不足
#open.error_20001_aop.invalid-auth-token=无效的访问令牌
@ -59,6 +60,7 @@ open.error_10000=Success
open.error_20000=\u670d\u52a1\u4e0d\u53ef\u7528
open.error_20000_isp.unknow-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528
open.error_20000_aop.unknow-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528
open.error_20000_isp.service-not-available=\u670d\u52a1\u6682\u4e0d\u53ef\u7528
open.error_20001=\u6388\u6743\u6743\u9650\u4e0d\u8db3
open.error_20001_aop.invalid-auth-token=\u65e0\u6548\u7684\u8bbf\u95ee\u4ee4\u724c

@ -23,7 +23,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.gitee.sop</groupId>

@ -29,7 +29,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>

@ -0,0 +1,39 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* 表名config_route_base
* 备注路由配置表
*
* @author tanghc
*/
@Table(name = "config_route_base")
@Data
public class ConfigRouteBase {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Long id;
/** 路由id, 数据库字段:route_id */
private String routeId;
/** 状态,1:启用,2:禁用, 数据库字段:status */
private Byte status;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -0,0 +1,54 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* 表名config_route_limit
* 备注路由限流配置
*
* @author tanghc
*/
@Table(name = "config_route_limit")
@Data
public class ConfigRouteLimit {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Integer id;
/** 路由id, 数据库字段:route_id */
private String routeId;
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:type */
private Byte type;
/** 每秒可处理请求数, 数据库字段:exec_count_per_second */
private Integer execCountPerSecond;
/** 返回的错误码, 数据库字段:limit_code */
private String limitCode;
/** 返回的错误信息, 数据库字段:limit_msg */
private String limitMsg;
/** 令牌桶容量, 数据库字段:token_bucket_count */
private Integer tokenBucketCount;
/** 1:开启,0关闭, 数据库字段:limit_status */
private Byte limitStatus;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -33,7 +33,6 @@ public class DbIsvManager extends CacheIsvManager {
@Override
public void load(Function<Object, String> secretGetter) {
log.info("从数据库读取ISV信息,保存到本地");
List<IsvInfo> isvInfoList = isvInfoMapper.list(new Query());
isvInfoList.stream()
.forEach(isvInfo -> {

@ -0,0 +1,83 @@
package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.mapper.ConfigRouteBaseMapper;
import com.gitee.sop.gateway.mapper.ConfigRouteLimitMapper;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
import com.gitee.sop.gatewaycommon.bean.RouteConfigDto;
import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager;
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
import com.gitee.sop.gatewaycommon.util.MyBeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author tanghc
*/
@Component
@Slf4j
public class DbRouteConfigManager extends DefaultRouteConfigManager {
@Autowired
ConfigRouteBaseMapper configRouteBaseMapper;
@Autowired
ConfigRouteLimitMapper configRouteLimitMapper;
@Autowired
Environment environment;
@Override
public void load() {
Query query = new Query();
configRouteBaseMapper.list(query)
.stream()
.forEach(configRouteBase -> {
String key = configRouteBase.getRouteId();
putVal(key, configRouteBase);
});
configRouteLimitMapper.list(query)
.stream()
.forEach(configRouteLimit -> {
String key = configRouteLimit.getRouteId();
putVal(key, configRouteLimit);
});
}
protected void putVal(String key, Object object) {
RouteConfig routeConfig = routeConfigMap.getOrDefault(key, newRouteConfig());
MyBeanUtil.copyPropertiesIgnoreNull(object, routeConfig);
routeConfigMap.put(key, routeConfig);
}
@PostConstruct
protected void after() throws Exception {
ZookeeperContext.setEnvironment(environment);
String path = ZookeeperContext.getRouteConfigChannelPath();
ZookeeperContext.listenPath(path, nodeCache -> {
String nodeData = new String(nodeCache.getCurrentData().getData());
ChannelMsg channelMsg = JSON.parseObject(nodeData, ChannelMsg.class);
final RouteConfigDto routeConfigDto = JSON.parseObject(channelMsg.getData(), RouteConfigDto.class);
switch (channelMsg.getOperation()) {
case "reload":
log.info("重新加载路由配置信息,routeConfigDto:{}", routeConfigDto);
load();
break;
case "update":
log.info("更新路由配置信息,routeConfigDto:{}", routeConfigDto);
update(routeConfigDto);
break;
}
});
}
}

@ -13,15 +13,19 @@ import org.springframework.stereotype.Component;
@Slf4j
public class ManagerInitializer {
@Autowired
private DbIsvManager dbIsvManager;
DbIsvManager dbIsvManager;
@Autowired
private DbIsvRoutePermissionManager dbIsvRoutePermissionManager;
DbIsvRoutePermissionManager dbIsvRoutePermissionManager;
@Autowired
DbRouteConfigManager dbRouteConfigManager;
public void init() {
ApiConfig apiConfig = ApiConfig.getInstance();
apiConfig.setIsvManager(dbIsvManager);
apiConfig.setIsvRoutePermissionManager(dbIsvRoutePermissionManager);
apiConfig.setRouteConfigManager(dbRouteConfigManager);
// 从数据库加载isv信息
log.debug("从数据库加载isv信息");
@ -32,5 +36,8 @@ public class ManagerInitializer {
log.debug("从数据库加载路由权限信息");
dbIsvRoutePermissionManager.load();
log.debug("从数据库加载路由配置信息");
dbRouteConfigManager.load();
}
}

@ -0,0 +1,11 @@
package com.gitee.sop.gateway.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.gateway.entity.ConfigRouteBase;
/**
* @author tanghc
*/
public interface ConfigRouteBaseMapper extends CrudMapper<ConfigRouteBase, Long> {
}

@ -0,0 +1,11 @@
package com.gitee.sop.gateway.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.gateway.entity.ConfigRouteLimit;
/**
* @author tanghc
*/
public interface ConfigRouteLimitMapper extends CrudMapper<ConfigRouteLimit, Integer> {
}
Loading…
Cancel
Save