mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 12:56:28 +08:00
限流改造
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
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.PageInfo;
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.fastmybatis.core.query.Sort;
|
||||
import com.gitee.fastmybatis.core.query.expression.ValueExpression;
|
||||
import com.gitee.fastmybatis.core.util.MapperUtil;
|
||||
import com.gitee.fastmybatis.core.util.MyBeanUtil;
|
||||
import com.gitee.sop.adminserver.api.service.param.LimitNewAddParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.LimitNewParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.LimitNewUpdateParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
|
||||
import com.gitee.sop.adminserver.api.service.result.LimitNewVO;
|
||||
import com.gitee.sop.adminserver.bean.ConfigLimitDto;
|
||||
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
|
||||
import com.gitee.sop.adminserver.common.LimitEnum;
|
||||
import com.gitee.sop.adminserver.entity.ConfigLimit;
|
||||
import com.gitee.sop.adminserver.mapper.ConfigLimitMapper;
|
||||
import com.gitee.sop.adminserver.service.RouteConfigService;
|
||||
import com.gitee.sop.adminserver.service.RouteService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 限流
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@ApiService
|
||||
@ApiDoc("服务管理")
|
||||
@Slf4j
|
||||
public class LimitNewApi {
|
||||
|
||||
@Autowired
|
||||
RouteService routeService;
|
||||
|
||||
@Autowired
|
||||
RouteConfigService routeConfigService;
|
||||
|
||||
@Autowired
|
||||
ConfigLimitMapper configLimitMapper;
|
||||
|
||||
@Api(name = "config.limit.list")
|
||||
@ApiDocMethod(description = "限流列表(新)", elementClass = LimitNewVO.class)
|
||||
PageInfo<ConfigLimit> listLimit(LimitNewParam param) throws Exception {
|
||||
Query query = Query.build(param);
|
||||
query.orderby("route_id", Sort.ASC)
|
||||
.orderby("app_key", Sort.ASC)
|
||||
.orderby("order_index", Sort.ASC);
|
||||
PageInfo<ConfigLimit> pageInfo = MapperUtil.query(configLimitMapper, query);
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
@Api(name = "config.limit.add")
|
||||
@ApiDocMethod(description = "新增限流(新)")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createLimtit(LimitNewAddParam param) {
|
||||
ConfigLimit configLimit = new ConfigLimit();
|
||||
CopyUtil.copyPropertiesIgnoreNull(param, configLimit);
|
||||
configLimitMapper.save(configLimit);
|
||||
ConfigLimitDto configLimitDto = buildConfigLimitDto(configLimit);
|
||||
try {
|
||||
routeConfigService.sendLimitConfigMsg(configLimitDto);
|
||||
} catch (Exception e) {
|
||||
log.error("推送限流消息错误, param:{}", param, e);
|
||||
throw new ApiException("新增失败,请查看日志");
|
||||
}
|
||||
}
|
||||
|
||||
@Api(name = "config.limit.update")
|
||||
@ApiDocMethod(description = "修改限流(新)")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateLimtit(LimitNewUpdateParam param) {
|
||||
ConfigLimit configLimit = configLimitMapper.getById(param.getId());
|
||||
if (configLimit == null) {
|
||||
configLimit = new ConfigLimit();
|
||||
CopyUtil.copyPropertiesIgnoreNull(param, configLimit);
|
||||
configLimitMapper.save(configLimit);
|
||||
} else {
|
||||
CopyUtil.copyPropertiesIgnoreNull(param, configLimit);
|
||||
configLimitMapper.update(configLimit);
|
||||
}
|
||||
ConfigLimitDto configLimitDto = buildConfigLimitDto(configLimit);
|
||||
try {
|
||||
routeConfigService.sendLimitConfigMsg(configLimitDto);
|
||||
} catch (Exception e) {
|
||||
log.error("推送限流消息错误, param:{}", param, e);
|
||||
throw new ApiException("修改失败,请查看日志");
|
||||
}
|
||||
}
|
||||
|
||||
private ConfigLimitDto buildConfigLimitDto(ConfigLimit configLimit) {
|
||||
ConfigLimitDto configLimitDto = new ConfigLimitDto();
|
||||
CopyUtil.copyPropertiesIgnoreNull(configLimit, configLimitDto);
|
||||
return configLimitDto;
|
||||
}
|
||||
}
|
@@ -81,6 +81,21 @@ public class RouteApi {
|
||||
return routeDefinitionList;
|
||||
}
|
||||
|
||||
@Api(name = "route.list", version = "1.2")
|
||||
@ApiDocMethod(description = "路由列表1.2")
|
||||
List<RouteVO> listRoute2(RouteSearchParam param) throws Exception {
|
||||
List<RouteVO> routeDefinitionList = routeService.getRouteDefinitionList(param)
|
||||
.stream()
|
||||
.map(gatewayRouteDefinition -> {
|
||||
RouteVO vo = new RouteVO();
|
||||
BeanUtils.copyProperties(gatewayRouteDefinition, vo);
|
||||
return vo;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return routeDefinitionList;
|
||||
}
|
||||
|
||||
@Api(name = "route.update")
|
||||
@ApiDocMethod(description = "修改路由")
|
||||
void updateRoute(RouteParam param) throws Exception {
|
||||
|
@@ -0,0 +1,62 @@
|
||||
package com.gitee.sop.adminserver.api.service.param;
|
||||
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 限流
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class LimitNewAddParam {
|
||||
/** 路由id, 数据库字段:route_id */
|
||||
@ApiDocField(description = "routeId")
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:app_key */
|
||||
@ApiDocField(description = "appKey")
|
||||
private String appKey;
|
||||
|
||||
/** 限流ip,多个用英文逗号隔开, 数据库字段:limit_ip */
|
||||
@ApiDocField(description = "limitIp")
|
||||
private String limitIp;
|
||||
|
||||
@ApiDocField(description = "serviceId")
|
||||
@NotBlank(message = "serviceId can not null")
|
||||
private String serviceId;
|
||||
|
||||
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
|
||||
@ApiDocField(description = "限流策略,1:漏桶策略,2:令牌桶策略")
|
||||
@NotNull(message = "limitType不能为空")
|
||||
private Byte limitType;
|
||||
|
||||
/** 每秒可处理请求数, 数据库字段: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(message = "limitStatus不能为空")
|
||||
private Byte limitStatus;
|
||||
|
||||
@ApiDocField(description = "排序字段")
|
||||
@NotNull(message = "orderIndex不能为空")
|
||||
@Min(value = 0, message = "orderIndex必须大于等于0")
|
||||
private Integer orderIndex;
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package com.gitee.sop.adminserver.api.service.param;
|
||||
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||
import com.gitee.fastmybatis.core.query.Operator;
|
||||
import com.gitee.fastmybatis.core.query.annotation.Condition;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class LimitNewParam extends ServiceSearchParam {
|
||||
@ApiDocField(description = "路由id")
|
||||
@Condition(operator = Operator.like)
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:app_key */
|
||||
@ApiDocField(description = "appKey")
|
||||
@Condition(operator = Operator.like)
|
||||
private String appKey;
|
||||
|
||||
/** 限流ip,多个用英文逗号隔开, 数据库字段:limit_ip */
|
||||
@ApiDocField(description = "限流ip,多个用英文逗号隔开")
|
||||
@Condition(operator = Operator.like)
|
||||
private String limitIp;
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
package com.gitee.sop.adminserver.api.service.param;
|
||||
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 限流
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class LimitNewUpdateParam {
|
||||
@ApiDocField(description = "id")
|
||||
@NotNull(message = "id can not null")
|
||||
@Min(value = 1, message = "id不正确")
|
||||
private Long id;
|
||||
|
||||
/** 路由id, 数据库字段:route_id */
|
||||
@ApiDocField(description = "routeId")
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:app_key */
|
||||
@ApiDocField(description = "appKey")
|
||||
private String appKey;
|
||||
|
||||
/** 限流ip,多个用英文逗号隔开, 数据库字段:limit_ip */
|
||||
@ApiDocField(description = "limitIp")
|
||||
private String limitIp;
|
||||
|
||||
@ApiDocField(description = "serviceId")
|
||||
@NotBlank(message = "serviceId can not null")
|
||||
private String serviceId;
|
||||
|
||||
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
|
||||
@ApiDocField(description = "限流策略,1:漏桶策略,2:令牌桶策略")
|
||||
@NotNull(message = "limitType不能为空")
|
||||
private Byte limitType;
|
||||
|
||||
/** 每秒可处理请求数, 数据库字段: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(message = "limitStatus不能为空")
|
||||
private Byte limitStatus;
|
||||
|
||||
@ApiDocField(description = "排序字段")
|
||||
@NotNull(message = "orderIndex不能为空")
|
||||
@Min(value = 0, message = "orderIndex必须大于等于0")
|
||||
private Integer orderIndex;
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
package com.gitee.sop.adminserver.api.service.result;
|
||||
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 限流
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class LimitNewVO {
|
||||
/** 路由id, 数据库字段:route_id */
|
||||
@ApiDocField(description = "路由id")
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:app_key */
|
||||
@ApiDocField(description = "appKey")
|
||||
private String appKey;
|
||||
|
||||
/** 限流ip,多个用英文逗号隔开, 数据库字段:limit_ip */
|
||||
@ApiDocField(description = "限流ip,多个用英文逗号隔开")
|
||||
private String limitIp;
|
||||
|
||||
@ApiDocField(description = "限流key")
|
||||
private String limitKey;
|
||||
|
||||
/**
|
||||
* 接口名
|
||||
*/
|
||||
@ApiDocField(description = "接口名")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
@ApiDocField(description = "版本号")
|
||||
private String version;
|
||||
|
||||
@ApiDocField(description = "serviceId")
|
||||
private String serviceId;
|
||||
|
||||
/**
|
||||
* 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type
|
||||
*/
|
||||
@ApiDocField(description = "限流策略,1:漏桶策略,2:令牌桶策略")
|
||||
private Byte limitType;
|
||||
|
||||
/**
|
||||
* 每秒可处理请求数, 数据库字段: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;
|
||||
|
||||
/** 顺序,值小的优先执行, 数据库字段:order_index */
|
||||
@ApiDocField(description = "排序字段")
|
||||
private Integer orderIndex;
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package com.gitee.sop.adminserver.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class ConfigLimitDto {
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 路由id, 数据库字段:route_id */
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:app_key */
|
||||
private String appKey;
|
||||
|
||||
/** 限流ip,多个用英文逗号隔开, 数据库字段:limit_ip */
|
||||
private String limitIp;
|
||||
|
||||
/** 服务id, 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
|
||||
private Byte limitType;
|
||||
|
||||
/** 每秒可处理请求数, 数据库字段: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;
|
||||
|
||||
/** 顺序,值小的优先执行, 数据库字段:order_index */
|
||||
private Integer orderIndex;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -71,6 +71,10 @@ public class ZookeeperContext {
|
||||
return SOP_MSG_CHANNEL_PATH + "/route-conf";
|
||||
}
|
||||
|
||||
public static String getLimitConfigChannelPath() {
|
||||
return SOP_MSG_CHANNEL_PATH + "/limit-conf";
|
||||
}
|
||||
|
||||
public static CuratorFramework getClient() {
|
||||
return client;
|
||||
}
|
||||
|
@@ -0,0 +1,66 @@
|
||||
package com.gitee.sop.adminserver.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_limit
|
||||
* 备注:限流配置
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Table(name = "config_limit")
|
||||
@Data
|
||||
public class ConfigLimit {
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 路由id, 数据库字段:route_id */
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:app_key */
|
||||
private String appKey;
|
||||
|
||||
/** 限流ip,多个用英文逗号隔开, 数据库字段:limit_ip */
|
||||
private String limitIp;
|
||||
|
||||
/** 服务id, 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
|
||||
private Byte limitType;
|
||||
|
||||
/** 每秒可处理请求数, 数据库字段: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;
|
||||
|
||||
/** 顺序,值小的优先执行, 数据库字段:order_index */
|
||||
private Integer orderIndex;
|
||||
|
||||
/** 数据库字段: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.ConfigLimit;
|
||||
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ConfigLimitMapper extends CrudMapper<ConfigLimit, Long> {
|
||||
}
|
@@ -2,6 +2,7 @@ package com.gitee.sop.adminserver.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.gitee.sop.adminserver.bean.ChannelMsg;
|
||||
import com.gitee.sop.adminserver.bean.ConfigLimitDto;
|
||||
import com.gitee.sop.adminserver.bean.RouteConfigDto;
|
||||
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -27,4 +28,18 @@ public class RouteConfigService {
|
||||
log.info("消息推送--路由配置(update), path:{}, data:{}", path, jsonData);
|
||||
ZookeeperContext.createOrUpdateData(path, jsonData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送路由配置
|
||||
* @param routeConfigDto
|
||||
* @throws Exception
|
||||
*/
|
||||
public void sendLimitConfigMsg(ConfigLimitDto routeConfigDto) throws Exception {
|
||||
String configData = JSON.toJSONString(routeConfigDto);
|
||||
ChannelMsg channelMsg = new ChannelMsg("update", configData);
|
||||
String jsonData = JSON.toJSONString(channelMsg);
|
||||
String path = ZookeeperContext.getLimitConfigChannelPath();
|
||||
log.info("消息推送--限流配置(update), path:{}, data:{}", path, jsonData);
|
||||
ZookeeperContext.createOrUpdateData(path, jsonData);
|
||||
}
|
||||
}
|
||||
|
@@ -76,7 +76,7 @@ export const constantRoutes = [
|
||||
{
|
||||
path: 'limit',
|
||||
name: 'Limit',
|
||||
component: () => import('@/views/service/limit/index'),
|
||||
component: () => import('@/views/service/limit/index2'),
|
||||
meta: { title: '限流管理' }
|
||||
}
|
||||
]
|
||||
|
@@ -1,20 +1,19 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline">
|
||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
|
||||
<el-form-item label="appKey">
|
||||
<el-input v-model="searchFormData.appKey" :clearable="true" placeholder="appKey" size="mini" style="width: 250px;" />
|
||||
<el-input v-model="searchFormData.appKey" :clearable="true" placeholder="appKey" style="width: 250px;" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="onSearchTable">查询</el-button>
|
||||
<el-button type="primary" icon="el-icon-search" @click="onSearchTable">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" size="mini" icon="el-icon-plus" @click="onAdd">新增ISV</el-button>
|
||||
<el-button type="primary" size="mini" icon="el-icon-plus" style="margin-bottom: 10px;" @click="onAdd">新增ISV</el-button>
|
||||
<el-table
|
||||
:data="pageInfo.list"
|
||||
border
|
||||
fit
|
||||
highlight-current-row
|
||||
style="margin-top: 20px;"
|
||||
>
|
||||
<el-table-column
|
||||
prop="id"
|
||||
@@ -110,34 +109,40 @@
|
||||
:close-on-click-modal="false"
|
||||
@close="onIsvDialogClose"
|
||||
>
|
||||
<el-form ref="isvForm" :rules="rulesIsvForm" :model="isvDialogFormData">
|
||||
<el-form-item label="" :label-width="formLabelWidth">
|
||||
<el-form
|
||||
ref="isvForm"
|
||||
:rules="rulesIsvForm"
|
||||
:model="isvDialogFormData"
|
||||
label-width="120px"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item label="">
|
||||
<el-button size="mini" @click="onDataGen">一键生成数据</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item prop="appKey" label="appKey" :label-width="formLabelWidth">
|
||||
<el-form-item prop="appKey" label="appKey">
|
||||
<el-input v-model="isvDialogFormData.appKey" size="mini" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="signType" label="签名方式" :label-width="formLabelWidth">
|
||||
<el-form-item prop="signType" label="签名方式">
|
||||
<el-radio-group v-model="isvDialogFormData.signType">
|
||||
<el-radio :label="1" name="status">RSA2</el-radio>
|
||||
<el-radio :label="2" name="status">MD5</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="isvDialogFormData.signType === 2" prop="secret" label="secret" :label-width="formLabelWidth">
|
||||
<el-form-item v-show="isvDialogFormData.signType === 2" prop="secret" label="secret">
|
||||
<el-input v-model="isvDialogFormData.secret" size="mini" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="isvDialogFormData.signType === 1" prop="pubKey" label="公钥" :label-width="formLabelWidth">
|
||||
<el-form-item v-show="isvDialogFormData.signType === 1" prop="pubKey" label="公钥">
|
||||
<el-input v-model="isvDialogFormData.pubKey" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="isvDialogFormData.signType === 1" prop="priKey" label="私钥" :label-width="formLabelWidth">
|
||||
<el-form-item v-show="isvDialogFormData.signType === 1" prop="priKey" label="私钥">
|
||||
<el-input v-model="isvDialogFormData.priKey" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色" :label-width="formLabelWidth">
|
||||
<el-form-item label="角色">
|
||||
<el-checkbox-group v-model="isvDialogFormData.roleCode">
|
||||
<el-checkbox v-for="item in roles" :key="item.roleCode" :label="item.roleCode">{{ item.description }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" :label-width="formLabelWidth">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="isvDialogFormData.status">
|
||||
<el-radio :label="1" name="status">启用</el-radio>
|
||||
<el-radio :label="2" name="status">禁用</el-radio>
|
||||
@@ -175,7 +180,6 @@ export default {
|
||||
callback()
|
||||
}
|
||||
return {
|
||||
formLabelWidth: '120px',
|
||||
searchFormData: {
|
||||
appKey: ''
|
||||
},
|
||||
|
486
sop-admin/sop-admin-vue/src/views/service/limit/index2.vue
Normal file
486
sop-admin/sop-admin-vue/src/views/service/limit/index2.vue
Normal file
@@ -0,0 +1,486 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-container>
|
||||
<el-aside style="min-height: 300px;width: 200px;">
|
||||
<el-input v-model="filterText" prefix-icon="el-icon-search" placeholder="搜索服务..." style="margin-bottom:20px;" size="mini" clearable />
|
||||
<el-tree
|
||||
ref="tree2"
|
||||
:data="treeData"
|
||||
:props="defaultProps"
|
||||
:filter-node-method="filterNode"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false"
|
||||
empty-text="无数据"
|
||||
node-key="id"
|
||||
class="filter-tree"
|
||||
default-expand-all
|
||||
@node-click="onNodeClick"
|
||||
>
|
||||
<span slot-scope="{ node, data }" class="custom-tree-node">
|
||||
<span v-if="data.label.length < 15">{{ data.label }}</span>
|
||||
<span v-else>
|
||||
<el-tooltip :content="data.label" class="item" effect="light" placement="right">
|
||||
<span>{{ data.label.substring(0, 15) + '...' }}</span>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
</el-tree>
|
||||
</el-aside>
|
||||
<el-main style="padding-top:0">
|
||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
|
||||
<el-form-item label="路由ID">
|
||||
<el-input v-model="searchFormData.routeId" placeholder="接口名,支持模糊查询" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="AppKey">
|
||||
<el-input v-model="searchFormData.appKey" placeholder="AppKey,支持模糊查询" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="IP">
|
||||
<el-input v-model="searchFormData.limitIp" placeholder="ip,支持模糊查询" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="onSearchTable">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" size="mini" icon="el-icon-plus" style="margin-bottom: 10px;" @click="onAdd">新增限流</el-button>
|
||||
<el-table
|
||||
:data="pageInfo.list"
|
||||
border
|
||||
>
|
||||
<el-table-column
|
||||
prop="limitKey"
|
||||
label="限流维度"
|
||||
width="400"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<div v-html="limitRender(scope.row)"></div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="limitType"
|
||||
label="限流策略"
|
||||
width="120"
|
||||
>
|
||||
<template slot="header" slot-scope>
|
||||
限流策略 <i class="el-icon-question" style="cursor: pointer" @click="onLimitTypeTipClick"></i>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.limitType === 1">漏桶策略</span>
|
||||
<span v-if="scope.row.limitType === 2">令牌桶策略</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="info"
|
||||
label="限流信息"
|
||||
width="250"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-html="infoRender(scope.row)"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="limitStatus"
|
||||
label="状态"
|
||||
width="80"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.limitStatus === 1" style="color:#67C23A">已开启</span>
|
||||
<span v-if="scope.row.limitStatus === 0" style="color:#909399">已关闭</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="orderIndex"
|
||||
label="排序"
|
||||
width="80"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="gmtCreate"
|
||||
label="创建时间"
|
||||
width="160"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="gmtModified"
|
||||
label="修改时间"
|
||||
width="160"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
fixed="right"
|
||||
width="80"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
background
|
||||
style="margin-top: 5px"
|
||||
:current-page="pageInfo.pageIndex"
|
||||
:page-sizes="[5, 10, 20, 40]"
|
||||
:page-size="pageInfo.pageSize"
|
||||
:total="pageInfo.total"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="onSizeChange"
|
||||
@current-change="onPageIndexChange"
|
||||
/>
|
||||
<!-- dialog -->
|
||||
<el-dialog
|
||||
:title="dlgTitle"
|
||||
:visible.sync="limitDialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
@close="onLimitDialogClose"
|
||||
>
|
||||
<el-form
|
||||
ref="limitDialogForm"
|
||||
:model="limitDialogFormData"
|
||||
:rules="rulesLimit"
|
||||
label-width="150px"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item label="限流维度" prop="typeKey">
|
||||
<el-checkbox-group v-model="limitDialogFormData.typeKey">
|
||||
<el-checkbox v-model="limitDialogFormData.typeKey[0]" :label="1" name="typeKey" @change="checked=>onLimitKeyTypeChange(checked, 'routeId')">路由ID</el-checkbox>
|
||||
<el-checkbox v-model="limitDialogFormData.typeKey[1]" :label="2" name="typeKey" @change="checked=>onLimitKeyTypeChange(checked, 'appKey')">AppKey</el-checkbox>
|
||||
<el-checkbox v-model="limitDialogFormData.typeKey[2]" :label="3" name="typeKey" @change="checked=>onLimitKeyTypeChange(checked, 'limitIp')">IP</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="checkTypeKey(1)" prop="routeId" label="路由ID" :rules="checkTypeKey(1) ? rulesLimit.routeId : []">
|
||||
<el-select v-model="limitDialogFormData.routeId" filterable placeholder="可筛选" style="width: 300px;">
|
||||
<el-option
|
||||
v-for="item in routeList"
|
||||
:key="item.id"
|
||||
:label="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
<span style="float: left">{{ item.name }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.version }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="checkTypeKey(2)" prop="appKey" label="AppKey" :rules="checkTypeKey(2) ? rulesLimit.appKey : []">
|
||||
<el-input v-model="limitDialogFormData.appKey" placeholder="需要限流的appKey" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="checkTypeKey(3)" label="限流IP" prop="limitIp" :rules="checkTypeKey(3) ? rulesLimit.ip : []">
|
||||
<el-input v-model="limitDialogFormData.limitIp" type="textarea" :rows="2" placeholder="多个用英文逗号隔开" />
|
||||
</el-form-item>
|
||||
<el-form-item label="限流策略">
|
||||
<el-radio-group v-model="limitDialogFormData.limitType">
|
||||
<el-radio :label="1">漏桶策略</el-radio>
|
||||
<el-radio :label="2">令牌桶策略</el-radio>
|
||||
</el-radio-group>
|
||||
<i class="el-icon-question limit-tip" @click="onLimitTypeTipClick"></i>
|
||||
</el-form-item>
|
||||
<el-form-item label="开启状态">
|
||||
<el-switch
|
||||
v-model="limitDialogFormData.limitStatus"
|
||||
active-color="#13ce66"
|
||||
inactive-color="#ff4949"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
>
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="orderIndex">
|
||||
<el-input-number v-model="limitDialogFormData.orderIndex" controls-position="right" :min="0" />
|
||||
<el-tooltip class="item" content="值小优先执行" placement="top">
|
||||
<i class="el-icon-question limit-tip"></i>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="isLeakyType()" label="每秒可处理请求数" prop="execCountPerSecond" :rules="isLeakyType() ? rulesLimit.execCountPerSecond : []">
|
||||
<el-input-number v-model="limitDialogFormData.execCountPerSecond" controls-position="right" :min="1" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="isLeakyType()" label="错误码" prop="limitCode" :rules="isLeakyType() ? rulesLimit.limitCode : []">
|
||||
<el-input v-model="limitDialogFormData.limitCode" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="isLeakyType()" label="错误信息" prop="limitMsg" :rules="isLeakyType() ? rulesLimit.limitMsg : []">
|
||||
<el-input v-model="limitDialogFormData.limitMsg" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="isTokenType()" label="令牌桶容量" prop="tokenBucketCount" :rules="isTokenType() ? rulesLimit.tokenBucketCount : []">
|
||||
<el-input-number v-model="limitDialogFormData.tokenBucketCount" controls-position="right" :min="1" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="limitDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onLimitDialogSave">保 存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
filterText: '',
|
||||
treeData: [],
|
||||
tableData: [],
|
||||
serviceId: '',
|
||||
searchFormData: {},
|
||||
pageInfo: {
|
||||
list: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
},
|
||||
routeList: [],
|
||||
defaultProps: {
|
||||
children: 'children',
|
||||
label: 'label'
|
||||
},
|
||||
// dialog
|
||||
dlgTitle: '设置限流',
|
||||
limitDialogVisible: false,
|
||||
limitDialogFormData: {
|
||||
id: 0,
|
||||
routeId: '',
|
||||
appKey: '',
|
||||
limitIp: '',
|
||||
limitKey: '',
|
||||
execCountPerSecond: 5,
|
||||
limitCode: '',
|
||||
limitMsg: '',
|
||||
tokenBucketCount: 5,
|
||||
limitStatus: 0, // 0: 停用,1:启用
|
||||
limitType: 1,
|
||||
orderIndex: 0,
|
||||
typeKey: []
|
||||
},
|
||||
rulesLimit: {
|
||||
typeKey: [
|
||||
{ type: 'array', required: true, message: '请至少选择一个', trigger: 'change' }
|
||||
],
|
||||
routeId: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' },
|
||||
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
appKey: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' },
|
||||
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
ip: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' },
|
||||
{ min: 1, max: 500, message: '长度在 1 到 500 个字符', trigger: 'blur' }
|
||||
],
|
||||
// leaky
|
||||
execCountPerSecond: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' }
|
||||
],
|
||||
limitCode: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' },
|
||||
{ min: 1, max: 64, message: '长度在 1 到 64 个字符', trigger: 'blur' }
|
||||
],
|
||||
limitMsg: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' },
|
||||
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
// token
|
||||
tokenBucketCount: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' }
|
||||
],
|
||||
orderIndex: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filterText(val) {
|
||||
this.$refs.tree2.filter(val)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadTree()
|
||||
},
|
||||
methods: {
|
||||
// 加载树
|
||||
loadTree: function() {
|
||||
this.post('service.list', {}, function(resp) {
|
||||
const respData = resp.data
|
||||
this.treeData = this.convertToTreeData(respData, 0)
|
||||
})
|
||||
},
|
||||
// 树搜索
|
||||
filterNode(value, data) {
|
||||
if (!value) return true
|
||||
return data.label.indexOf(value) !== -1
|
||||
},
|
||||
// 树点击事件
|
||||
onNodeClick(data, node, tree) {
|
||||
if (data.parentId) {
|
||||
this.serviceId = data.label
|
||||
this.searchFormData.serviceId = this.serviceId
|
||||
this.loadTable()
|
||||
this.loadRouteList(this.serviceId)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 数组转成树状结构
|
||||
* @param data 数据结构 [{
|
||||
"_parentId": 14,
|
||||
"gmtCreate": "2019-01-15 09:44:38",
|
||||
"gmtUpdate": "2019-01-15 09:44:38",
|
||||
"id": 15,
|
||||
"isShow": 1,
|
||||
"name": "用户注册",
|
||||
"orderIndex": 10000,
|
||||
"parentId": 14
|
||||
},...]
|
||||
* @param pid 初始父节点id,一般是0
|
||||
* @return 返回结果 [{
|
||||
label: '一级 1',
|
||||
children: [{
|
||||
label: '二级 1-1',
|
||||
children: [{
|
||||
label: '三级 1-1-1'
|
||||
}]
|
||||
}]
|
||||
}
|
||||
*/
|
||||
convertToTreeData(data, pid) {
|
||||
const result = []
|
||||
const root = {
|
||||
label: '服务列表',
|
||||
parentId: pid
|
||||
}
|
||||
const children = []
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = { label: data[i].serviceId, parentId: 1 }
|
||||
children.push(item)
|
||||
}
|
||||
root.children = children
|
||||
result.push(root)
|
||||
return result
|
||||
},
|
||||
// table
|
||||
loadTable: function() {
|
||||
this.post('config.limit.list', this.searchFormData, function(resp) {
|
||||
this.pageInfo = resp.data
|
||||
})
|
||||
},
|
||||
loadRouteList: function(serviceId) {
|
||||
this.post('route.list/1.2', { serviceId: serviceId }, function(resp) {
|
||||
this.routeList = resp.data
|
||||
})
|
||||
},
|
||||
onAdd: function() {
|
||||
if (!this.serviceId) {
|
||||
this.tip('请选择服务', 'info')
|
||||
return
|
||||
}
|
||||
this.dlgTitle = '新增限流'
|
||||
this.limitDialogFormData.id = 0
|
||||
this.limitDialogVisible = true
|
||||
},
|
||||
onSearchTable: function() {
|
||||
this.loadTable()
|
||||
},
|
||||
onTableUpdate: function(row) {
|
||||
this.dlgTitle = '修改限流'
|
||||
this.limitDialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
Object.assign(this.limitDialogFormData, row)
|
||||
if (row.routeId) {
|
||||
this.limitDialogFormData.typeKey.push(1)
|
||||
}
|
||||
if (row.appKey) {
|
||||
this.limitDialogFormData.typeKey.push(2)
|
||||
}
|
||||
if (row.limitIp) {
|
||||
this.limitDialogFormData.typeKey.push(3)
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm(formName) {
|
||||
const frm = this.$refs[formName]
|
||||
frm && frm.resetFields()
|
||||
},
|
||||
onLimitDialogClose: function() {
|
||||
this.resetForm('limitDialogForm')
|
||||
this.limitDialogVisible = false
|
||||
},
|
||||
infoRender: function(row) {
|
||||
const html = []
|
||||
if (row.limitType === 1) {
|
||||
html.push('每秒可处理请求数:' + row.execCountPerSecond)
|
||||
html.push('<br>subCode:' + row.limitCode)
|
||||
html.push('<br>subMsg:' + row.limitMsg)
|
||||
} else if (row.limitType === 2) {
|
||||
html.push('令牌桶容量:' + row.tokenBucketCount)
|
||||
}
|
||||
return html.join('')
|
||||
},
|
||||
onLimitDialogSave: function() {
|
||||
this.$refs['limitDialogForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.limitDialogFormData.serviceId = this.serviceId
|
||||
const uri = this.limitDialogFormData.id ? 'config.limit.update' : 'config.limit.add'
|
||||
this.post(uri, this.limitDialogFormData, function(resp) {
|
||||
this.limitDialogVisible = false
|
||||
this.loadTable()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onLimitTypeTipClick: function() {
|
||||
const leakyRemark = '漏桶策略:每秒处理固定数量的请求,超出请求返回错误信息。'
|
||||
const tokenRemark = '令牌桶策略:每秒放置固定数量的令牌数,每个请求进来后先去拿令牌,拿到了令牌才能继续,拿不到则等候令牌重新生成了再拿。'
|
||||
const content = leakyRemark + '<br>' + tokenRemark
|
||||
this.$alert(content, '限流策略', {
|
||||
dangerouslyUseHTMLString: true
|
||||
})
|
||||
},
|
||||
onSizeChange: function(size) {
|
||||
this.searchFormData.pageSize = size
|
||||
this.loadTable()
|
||||
},
|
||||
onPageIndexChange: function(pageIndex) {
|
||||
this.searchFormData.pageIndex = pageIndex
|
||||
this.loadTable()
|
||||
},
|
||||
onLimitKeyTypeChange: function(checked, name) {
|
||||
if (!checked) {
|
||||
this.limitDialogFormData[name] = ''
|
||||
}
|
||||
},
|
||||
checkTypeKey: function(val) {
|
||||
return this.limitDialogFormData.typeKey.find((value, index, arr) => {
|
||||
return value === val
|
||||
})
|
||||
},
|
||||
isLeakyType: function() {
|
||||
return this.limitDialogFormData.limitType === 1
|
||||
},
|
||||
isTokenType: function() {
|
||||
return this.limitDialogFormData.limitType === 2
|
||||
},
|
||||
limitRender: function(row) {
|
||||
const html = []
|
||||
const val = []
|
||||
html.push('(')
|
||||
if (row.routeId) {
|
||||
val.push(row.routeId)
|
||||
html.push('路由ID')
|
||||
}
|
||||
if (row.appKey) {
|
||||
val.push(' + ' + row.appKey)
|
||||
html.push(' + AppKey')
|
||||
}
|
||||
if (row.limitIp) {
|
||||
val.push(' + ' + row.limitIp)
|
||||
html.push(' + IP')
|
||||
}
|
||||
html.push(')')
|
||||
return val.join('') + '<br>' + html.join('')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.limit-tip {
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline">
|
||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
|
||||
<el-form-item label="serviceId">
|
||||
<el-input v-model="searchFormData.serviceId" :clearable="true" placeholder="serviceId" size="mini" style="width: 250px;" />
|
||||
<el-input v-model="searchFormData.serviceId" :clearable="true" placeholder="serviceId" style="width: 250px;" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="onSearchTable">查询</el-button>
|
||||
<el-button type="primary" icon="el-icon-search" @click="onSearchTable">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
|
@@ -27,12 +27,12 @@
|
||||
</el-tree>
|
||||
</el-aside>
|
||||
<el-main style="padding-top:0">
|
||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline">
|
||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
|
||||
<el-form-item label="路由名称">
|
||||
<el-input v-model="searchFormData.id" placeholder="输入接口名或版本号" size="mini" />
|
||||
<el-input v-model="searchFormData.id" placeholder="输入接口名或版本号" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="onSearchTable">查询</el-button>
|
||||
<el-button type="primary" icon="el-icon-search" @click="onSearchTable">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
@@ -111,17 +111,21 @@
|
||||
</el-table>
|
||||
<!-- route dialog -->
|
||||
<el-dialog title="修改路由" :visible.sync="routeDialogVisible" :close-on-click-modal="false">
|
||||
<el-form :model="routeDialogFormData">
|
||||
<el-form-item label="id" :label-width="formLabelWidth">
|
||||
<el-form
|
||||
:model="routeDialogFormData"
|
||||
label-width="120px"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item label="id">
|
||||
<el-input v-model="routeDialogFormData.id" readonly="readonly" />
|
||||
</el-form-item>
|
||||
<el-form-item label="uri" :label-width="formLabelWidth">
|
||||
<el-form-item label="uri">
|
||||
<el-input v-model="routeDialogFormData.uri" />
|
||||
</el-form-item>
|
||||
<el-form-item label="path" :label-width="formLabelWidth">
|
||||
<el-form-item label="path">
|
||||
<el-input v-model="routeDialogFormData.path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" :label-width="formLabelWidth">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="routeDialogFormData.status">
|
||||
<el-radio :label="1" name="status">启用</el-radio>
|
||||
<el-radio :label="2" name="status" style="color:#F56C6C">禁用</el-radio>
|
||||
@@ -134,12 +138,16 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
<!-- auth dialog -->
|
||||
<el-dialog title="修改路由" :visible.sync="authDialogVisible" :close-on-click-modal="false">
|
||||
<el-form :model="authDialogFormData">
|
||||
<el-form-item label="id" :label-width="formLabelWidth">
|
||||
<el-dialog title="路由授权" :visible.sync="authDialogVisible" :close-on-click-modal="false">
|
||||
<el-form
|
||||
:model="authDialogFormData"
|
||||
label-width="120px"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item label="id">
|
||||
<el-input v-model="authDialogFormData.routeId" readonly="readonly" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色" :label-width="formLabelWidth">
|
||||
<el-form-item label="角色">
|
||||
<el-checkbox-group v-model="authDialogFormData.roleCode">
|
||||
<el-checkbox v-for="item in roles" :key="item.roleCode" :label="item.roleCode">{{ item.description }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
@@ -172,9 +180,7 @@ export default {
|
||||
routeDialogFormData: {
|
||||
status: 1
|
||||
},
|
||||
formLabelWidth: '120px',
|
||||
routeDialogVisible: false,
|
||||
|
||||
roles: [],
|
||||
authDialogFormData: {
|
||||
routeId: '',
|
||||
|
@@ -6,8 +6,10 @@ import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor;
|
||||
import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager;
|
||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultLimitConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
||||
import com.gitee.sop.gatewaycommon.result.DataNameBuilder;
|
||||
@@ -106,6 +108,11 @@ public class ApiConfig {
|
||||
*/
|
||||
private RouteConfigManager routeConfigManager = new DefaultRouteConfigManager();
|
||||
|
||||
/**
|
||||
* 限流配置
|
||||
*/
|
||||
private LimitConfigManager limitConfigManager = new DefaultLimitConfigManager();
|
||||
|
||||
/**
|
||||
* 限流管理
|
||||
*/
|
||||
|
@@ -0,0 +1,99 @@
|
||||
package com.gitee.sop.gatewaycommon.bean;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@Data
|
||||
public class ConfigLimitDto {
|
||||
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 路由id, 数据库字段:route_id */
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:app_key */
|
||||
private String appKey;
|
||||
|
||||
/** 限流ip,多个用英文逗号隔开, 数据库字段:limit_ip */
|
||||
private String limitIp;
|
||||
|
||||
/** 服务id, 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
|
||||
private Byte limitType;
|
||||
|
||||
/** 每秒可处理请求数, 数据库字段: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;
|
||||
|
||||
/** 顺序,值小的优先执行, 数据库字段:order_index */
|
||||
private Integer orderIndex;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
|
||||
|
||||
/**
|
||||
* 漏桶计数器
|
||||
*/
|
||||
private LoadingCache<Long, AtomicLong> counter = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(2, TimeUnit.SECONDS)
|
||||
.build(new CacheLoader<Long, AtomicLong>() {
|
||||
@Override
|
||||
public AtomicLong load(Long seconds) throws Exception {
|
||||
return new AtomicLong(0);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 令牌桶
|
||||
*/
|
||||
@Getter(AccessLevel.PRIVATE)
|
||||
@Setter(AccessLevel.PRIVATE)
|
||||
private volatile RateLimiter rateLimiter;
|
||||
|
||||
public synchronized void initRateLimiter() {
|
||||
rateLimiter = RateLimiter.create(tokenBucketCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取令牌桶
|
||||
* @return
|
||||
*/
|
||||
public RateLimiter fetchRateLimiter() {
|
||||
if (rateLimiter == null) {
|
||||
synchronized (this.id) {
|
||||
if (rateLimiter == null) {
|
||||
rateLimiter = RateLimiter.create(tokenBucketCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rateLimiter;
|
||||
}
|
||||
}
|
@@ -73,7 +73,7 @@ public class RouteConfig {
|
||||
* 获取令牌桶
|
||||
* @return
|
||||
*/
|
||||
public synchronized RateLimiter fetchRateLimiter() {
|
||||
public RateLimiter fetchRateLimiter() {
|
||||
if (rateLimiter == null) {
|
||||
synchronized (this.routeId) {
|
||||
if (rateLimiter == null) {
|
||||
|
@@ -1,19 +1,15 @@
|
||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto;
|
||||
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
||||
import com.gitee.sop.gatewaycommon.limit.LimitType;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorImpl;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||
import com.gitee.sop.gatewaycommon.util.RouteUtil;
|
||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
@@ -21,7 +17,10 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
@@ -36,26 +35,25 @@ public class LimitFilter implements GlobalFilter, Ordered {
|
||||
if (!apiConfig.isOpenLimit()) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
Map<String, ?> apiParam = exchange.getAttribute(SopConstants.CACHE_API_PARAM);
|
||||
String routeId = apiParam.get(ParamNames.API_NAME).toString() + apiParam.get(ParamNames.VERSION_NAME);
|
||||
RouteConfigManager routeConfigManager = apiConfig.getRouteConfigManager();
|
||||
RouteConfig routeConfig = routeConfigManager.get(routeId);
|
||||
if (routeConfig == null) {
|
||||
return chain.filter(exchange);
|
||||
ApiParam apiParam = exchange.getAttribute(SopConstants.CACHE_API_PARAM);
|
||||
ConfigLimitDto configLimitDto = this.findConfigLimitDto(apiConfig, apiParam, exchange);
|
||||
if (configLimitDto == null) {
|
||||
return null;
|
||||
}
|
||||
// 某个路由限流功能未开启
|
||||
if (routeConfig.getLimitStatus() == RouteConfig.LIMIT_STATUS_CLOSE) {
|
||||
return chain.filter(exchange);
|
||||
// 单个限流功能未开启
|
||||
if (configLimitDto.getLimitStatus() == RouteConfig.LIMIT_STATUS_CLOSE) {
|
||||
return null;
|
||||
}
|
||||
byte limitType = routeConfig.getLimitType().byteValue();
|
||||
byte limitType = configLimitDto.getLimitType().byteValue();
|
||||
LimitManager limitManager = ApiConfig.getInstance().getLimitManager();
|
||||
// 如果是漏桶策略
|
||||
if (limitType == LimitType.LEAKY_BUCKET.getType()) {
|
||||
boolean acquire = limitManager.acquire(routeConfig);
|
||||
boolean acquire = limitManager.acquire(configLimitDto);
|
||||
if (!acquire) {
|
||||
throw new ApiException(new ErrorImpl(routeConfig.getLimitCode(), routeConfig.getLimitMsg()));
|
||||
throw new ApiException(new ErrorImpl(configLimitDto.getLimitCode(), configLimitDto.getLimitMsg()));
|
||||
}
|
||||
} else if (limitType == LimitType.TOKEN_BUCKET.getType()) {
|
||||
limitManager.acquireToken(routeConfig);
|
||||
limitManager.acquireToken(configLimitDto);
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
@@ -64,4 +62,33 @@ public class LimitFilter implements GlobalFilter, Ordered {
|
||||
public int getOrder() {
|
||||
return Orders.LIMIT_ORDER;
|
||||
}
|
||||
|
||||
protected ConfigLimitDto findConfigLimitDto(ApiConfig apiConfig, ApiParam apiParam, ServerWebExchange exchange) {
|
||||
LimitConfigManager limitConfigManager = apiConfig.getLimitConfigManager();
|
||||
|
||||
String routeId = apiParam.fetchNameVersion();
|
||||
String appKey = apiParam.fetchAppKey();
|
||||
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
|
||||
|
||||
String[] limitKeys = new String[]{
|
||||
routeId,
|
||||
appKey,
|
||||
routeId + appKey,
|
||||
|
||||
ip + routeId,
|
||||
ip + appKey,
|
||||
ip + routeId + appKey,
|
||||
};
|
||||
|
||||
List<ConfigLimitDto> limitConfigList = new ArrayList<>();
|
||||
for (String limitKey : limitKeys) {
|
||||
ConfigLimitDto configLimitDto = limitConfigManager.get(limitKey);
|
||||
limitConfigList.add(configLimitDto);
|
||||
}
|
||||
if (limitConfigList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Collections.sort(limitConfigList, Comparator.comparing(ConfigLimitDto::getOrderIndex));
|
||||
return limitConfigList.get(0);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.gitee.sop.gatewaycommon.limit;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto;
|
||||
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -14,24 +15,24 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
public class DefaultLimitManager implements LimitManager {
|
||||
|
||||
@Override
|
||||
public double acquireToken(RouteConfig routeConfig) {
|
||||
public double acquireToken(ConfigLimitDto routeConfig) {
|
||||
if (routeConfig.getLimitStatus() == RouteConfig.LIMIT_STATUS_CLOSE) {
|
||||
return 0;
|
||||
}
|
||||
if (LimitType.LEAKY_BUCKET.getType() == routeConfig.getLimitType().byteValue()) {
|
||||
throw new IllegalStateException("漏桶策略无法调用此方法");
|
||||
return 0;
|
||||
}
|
||||
return routeConfig.fetchRateLimiter().acquire();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean acquire(RouteConfig routeConfig) {
|
||||
public boolean acquire(ConfigLimitDto routeConfig) {
|
||||
if (routeConfig.getLimitStatus() == RouteConfig.LIMIT_STATUS_CLOSE) {
|
||||
return true;
|
||||
}
|
||||
if (LimitType.TOKEN_BUCKET.getType() == routeConfig.getLimitType().byteValue()) {
|
||||
throw new IllegalStateException("令牌桶策略无法调用此方法");
|
||||
return true;
|
||||
}
|
||||
int execCountPerSecond = routeConfig.getExecCountPerSecond();
|
||||
long currentSeconds = System.currentTimeMillis() / 1000;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package com.gitee.sop.gatewaycommon.limit;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto;
|
||||
|
||||
/**
|
||||
* 限流管理
|
||||
@@ -15,7 +15,7 @@ public interface LimitManager {
|
||||
* @param routeConfig 路由配置
|
||||
* @return 返回耗时时间,秒
|
||||
*/
|
||||
double acquireToken(RouteConfig routeConfig);
|
||||
double acquireToken(ConfigLimitDto routeConfig);
|
||||
|
||||
/**
|
||||
* 是否需要限流,如果使用{@link LimitType#LEAKY_BUCKET
|
||||
@@ -24,6 +24,6 @@ public interface LimitManager {
|
||||
* @param routeConfig 路由配置
|
||||
* @return 如果返回true,表示可以执行业务代码,返回false则需要限流
|
||||
*/
|
||||
boolean acquire(RouteConfig routeConfig);
|
||||
boolean acquire(ConfigLimitDto routeConfig);
|
||||
|
||||
}
|
||||
|
@@ -49,6 +49,10 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
||||
return ApiConfig.getInstance().getRouteConfigManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
LimitConfigManager limitConfigManager() {
|
||||
return ApiConfig.getInstance().getLimitConfigManager();
|
||||
}
|
||||
/**
|
||||
* 跨域过滤器
|
||||
*
|
||||
|
@@ -0,0 +1,80 @@
|
||||
package com.gitee.sop.gatewaycommon.manager;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class DefaultLimitConfigManager implements LimitConfigManager {
|
||||
|
||||
/**
|
||||
* key: limitKey
|
||||
*/
|
||||
protected static Map<String, ConfigLimitDto> limitCache = new ConcurrentHashMap<>();
|
||||
|
||||
protected static Map<Long, Set<String>> idKeyMap = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void update(ConfigLimitDto configLimitDto) {
|
||||
configLimitDto.initRateLimiter();
|
||||
Long id = configLimitDto.getId();
|
||||
this.remove(id);
|
||||
Set<String> keys = this.buildKeys(configLimitDto);
|
||||
idKeyMap.put(id, keys);
|
||||
for (String key : keys) {
|
||||
this.doUpdate(key, configLimitDto);
|
||||
}
|
||||
}
|
||||
|
||||
protected Set<String> buildKeys(ConfigLimitDto configLimitDto) {
|
||||
Set<String> keys = new HashSet<>();
|
||||
String routeId = Optional.ofNullable(configLimitDto.getRouteId()).orElse("");
|
||||
String appKey = Optional.ofNullable(configLimitDto.getAppKey()).orElse("");
|
||||
String limitIp = Optional.ofNullable(configLimitDto.getLimitIp()).orElse("").replaceAll("\\s", "");
|
||||
|
||||
String baseKey = routeId.trim() + appKey.trim();
|
||||
keys.add(baseKey);
|
||||
|
||||
if (StringUtils.isNotBlank(limitIp)) {
|
||||
String[] ips = limitIp.split("\\,|\\,");
|
||||
for (String ip : ips) {
|
||||
keys.add(ip + baseKey);
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
protected void doUpdate(String key, ConfigLimitDto configLimitDto) {
|
||||
if (StringUtils.isBlank(key)) {
|
||||
return;
|
||||
}
|
||||
limitCache.put(key, configLimitDto);
|
||||
}
|
||||
|
||||
protected void remove(Long id) {
|
||||
Set<String> list = idKeyMap.getOrDefault(id, Collections.emptySet());
|
||||
for (String key : list) {
|
||||
limitCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigLimitDto get(String limitKey) {
|
||||
return limitCache.get(limitKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package com.gitee.sop.gatewaycommon.manager;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
|
||||
import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface LimitConfigManager extends BeanInitializer {
|
||||
/**
|
||||
* 更新限流配置
|
||||
* @param configLimitDto 路由配置
|
||||
*/
|
||||
void update(ConfigLimitDto configLimitDto);
|
||||
|
||||
/**
|
||||
* 获取限流配置
|
||||
* @param limitKey 路由id
|
||||
* @return 返回ConfigLimitDto
|
||||
*/
|
||||
ConfigLimitDto get(String limitKey);
|
||||
}
|
@@ -76,6 +76,10 @@ public class ZookeeperContext {
|
||||
return SOP_MSG_CHANNEL_PATH + "/route-conf";
|
||||
}
|
||||
|
||||
public static String getLimitConfigChannelPath() {
|
||||
return SOP_MSG_CHANNEL_PATH + "/limit-conf";
|
||||
}
|
||||
|
||||
public static CuratorFramework getClient() {
|
||||
return client;
|
||||
}
|
||||
|
@@ -11,7 +11,9 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -126,4 +128,31 @@ public class RequestUtil {
|
||||
return IOUtils.toString(request.getInputStream(), UTF8);
|
||||
}
|
||||
|
||||
public static String getIP(HttpServletRequest request) {
|
||||
String ipAddress = request.getHeader("x-forwarded-for");
|
||||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
|
||||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
|
||||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = request.getRemoteAddr();
|
||||
if ("127.0.0.1".equals(ipAddress)) {
|
||||
try {
|
||||
InetAddress inet = InetAddress.getLocalHost();
|
||||
ipAddress = inet.getHostAddress();
|
||||
} catch (UnknownHostException var3) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ipAddress != null && ipAddress.length() > 15 && ipAddress.indexOf(",") > 0) {
|
||||
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
|
||||
}
|
||||
|
||||
return ipAddress;
|
||||
}
|
||||
}
|
||||
|
@@ -1,19 +1,25 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.filter;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto;
|
||||
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
||||
import com.gitee.sop.gatewaycommon.limit.LimitType;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorImpl;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.util.RequestUtil;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import com.netflix.zuul.exception.ZuulException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 限流拦截器
|
||||
* @author tanghc
|
||||
@@ -30,33 +36,62 @@ public class PreLimitFilter extends BaseZuulFilter {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doRun(RequestContext requestContext) throws ZuulException {
|
||||
protected Object doRun(RequestContext requestContext) throws ZuulException
|
||||
{
|
||||
ApiConfig apiConfig = ApiConfig.getInstance();
|
||||
// 限流功能未开启,直接返回
|
||||
if (!apiConfig.isOpenLimit()) {
|
||||
return null;
|
||||
}
|
||||
ApiParam apiParam = ZuulContext.getApiParam();
|
||||
String routeId = apiParam.getRouteId();
|
||||
RouteConfigManager routeConfigManager = apiConfig.getRouteConfigManager();
|
||||
RouteConfig routeConfig = routeConfigManager.get(routeId);
|
||||
if (routeConfig == null) {
|
||||
ConfigLimitDto configLimitDto = this.findConfigLimitDto(apiConfig, apiParam, requestContext.getRequest());
|
||||
if (configLimitDto == null) {
|
||||
return null;
|
||||
}
|
||||
// 某个路由限流功能未开启
|
||||
if (routeConfig.getLimitStatus() == RouteConfig.LIMIT_STATUS_CLOSE) {
|
||||
// 单个限流功能未开启
|
||||
if (configLimitDto.getLimitStatus() == RouteConfig.LIMIT_STATUS_CLOSE) {
|
||||
return null;
|
||||
}
|
||||
byte limitType = routeConfig.getLimitType().byteValue();
|
||||
byte limitType = configLimitDto.getLimitType().byteValue();
|
||||
LimitManager limitManager = ApiConfig.getInstance().getLimitManager();
|
||||
// 如果是漏桶策略
|
||||
if (limitType == LimitType.LEAKY_BUCKET.getType()) {
|
||||
boolean acquire = limitManager.acquire(routeConfig);
|
||||
boolean acquire = limitManager.acquire(configLimitDto);
|
||||
if (!acquire) {
|
||||
throw new ApiException(new ErrorImpl(routeConfig.getLimitCode(), routeConfig.getLimitMsg()));
|
||||
throw new ApiException(new ErrorImpl(configLimitDto.getLimitCode(), configLimitDto.getLimitMsg()));
|
||||
}
|
||||
} else if (limitType == LimitType.TOKEN_BUCKET.getType()) {
|
||||
limitManager.acquireToken(routeConfig);
|
||||
limitManager.acquireToken(configLimitDto);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected ConfigLimitDto findConfigLimitDto(ApiConfig apiConfig, ApiParam apiParam, HttpServletRequest request) {
|
||||
LimitConfigManager limitConfigManager = apiConfig.getLimitConfigManager();
|
||||
|
||||
String routeId = apiParam.fetchNameVersion();
|
||||
String appKey = apiParam.fetchAppKey();
|
||||
String ip = RequestUtil.getIP(request);
|
||||
|
||||
String[] limitKeys = new String[]{
|
||||
routeId,
|
||||
appKey,
|
||||
routeId + appKey,
|
||||
|
||||
ip + routeId,
|
||||
ip + appKey,
|
||||
ip + routeId + appKey,
|
||||
};
|
||||
|
||||
List<ConfigLimitDto> limitConfigList = new ArrayList<>();
|
||||
for (String limitKey : limitKeys) {
|
||||
ConfigLimitDto configLimitDto = limitConfigManager.get(limitKey);
|
||||
limitConfigList.add(configLimitDto);
|
||||
}
|
||||
if (limitConfigList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Collections.sort(limitConfigList, Comparator.comparing(ConfigLimitDto::getOrderIndex));
|
||||
return limitConfigList.get(0);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,67 @@
|
||||
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_limit
|
||||
* 备注:限流配置
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Table(name = "config_limit")
|
||||
@Data
|
||||
public class ConfigLimit {
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 路由id, 数据库字段:route_id */
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:app_key */
|
||||
private String appKey;
|
||||
|
||||
/** 限流ip,多个用英文逗号隔开, 数据库字段:limit_ip */
|
||||
private String limitIp;
|
||||
|
||||
|
||||
/** 服务id, 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
|
||||
private Byte limitType;
|
||||
|
||||
/** 每秒可处理请求数, 数据库字段: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;
|
||||
|
||||
/** 顺序,值小的优先执行, 数据库字段:order_index */
|
||||
private Integer orderIndex;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
package com.gitee.sop.gateway.manager;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.sop.gateway.mapper.ConfigLimitMapper;
|
||||
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||
import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultLimitConfigManager;
|
||||
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 javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* 限流配置管理
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
public class DbLimitConfigManager extends DefaultLimitConfigManager {
|
||||
|
||||
@Autowired
|
||||
ConfigLimitMapper configLimitMapper;
|
||||
|
||||
@Autowired
|
||||
Environment environment;
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Query query = new Query();
|
||||
configLimitMapper.list(query)
|
||||
.stream()
|
||||
.forEach(configLimit -> putVal(configLimit));
|
||||
|
||||
}
|
||||
|
||||
protected void putVal(Object object) {
|
||||
ConfigLimitDto configLimitDto = new ConfigLimitDto();
|
||||
MyBeanUtil.copyPropertiesIgnoreNull(object, configLimitDto);
|
||||
this.update(configLimitDto);
|
||||
}
|
||||
|
||||
|
||||
@PostConstruct
|
||||
protected void after() throws Exception {
|
||||
ZookeeperContext.setEnvironment(environment);
|
||||
String path = ZookeeperContext.getLimitConfigChannelPath();
|
||||
ZookeeperContext.listenPath(path, nodeCache -> {
|
||||
String nodeData = new String(nodeCache.getCurrentData().getData());
|
||||
ChannelMsg channelMsg = JSON.parseObject(nodeData, ChannelMsg.class);
|
||||
final ConfigLimitDto configLimitDto = JSON.parseObject(channelMsg.getData(), ConfigLimitDto.class);
|
||||
switch (channelMsg.getOperation()) {
|
||||
case "reload":
|
||||
log.info("重新加载限流配置信息,configLimitDto:{}", configLimitDto);
|
||||
load();
|
||||
break;
|
||||
case "update":
|
||||
log.info("更新限流配置信息,configLimitDto:{}", configLimitDto);
|
||||
update(configLimitDto);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -12,5 +12,6 @@ public class ManagerInitializer {
|
||||
apiConfig.setIsvManager(new DbIsvManager());
|
||||
apiConfig.setIsvRoutePermissionManager(new DbIsvRoutePermissionManager());
|
||||
apiConfig.setRouteConfigManager(new DbRouteConfigManager());
|
||||
apiConfig.setLimitConfigManager(new DbLimitConfigManager());
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,11 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.ConfigLimit;
|
||||
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ConfigLimitMapper extends CrudMapper<ConfigLimit, Long> {
|
||||
}
|
Reference in New Issue
Block a user