parent
45ab847197
commit
a3b87a5f5f
@ -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'); |
||||
|
||||
}); |
@ -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("修改失败,请查看日志"); |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
@ -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; |
||||
} |
@ -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; |
||||
|
||||
|
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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; |
||||
|
||||
|
||||
} |
@ -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); |
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
@ -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; |
||||
} |
||||
}); |
||||
} |
||||
} |
@ -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…
Reference in new issue