This commit is contained in:
六如
2024-10-10 11:35:34 +08:00
parent d9fb25dc0a
commit e23911b075
193 changed files with 2435 additions and 1008 deletions

View File

@@ -0,0 +1,18 @@
package com.gitee.sop.gateway;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan(basePackages = {
"com.gitee.sop.gateway.mapper"
})
@SpringBootApplication(scanBasePackages = "com.gitee.sop")
public class SopGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(SopGatewayApplication.class, args);
}
}

View File

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

View File

@@ -0,0 +1,36 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.Table;
import java.util.Date;
/**
* 表名config_gray
* 备注:服务灰度配置
*
* @author 六如
*/
@Table(name = "config_gray",pk = @Pk(name = "id"))
@Data
public class ConfigGray {
/** 数据库字段id */
private Long id;
/** 数据库字段service_id */
private String serviceId;
/** 用户key多个用引文逗号隔开, 数据库字段user_key_content */
private String userKeyContent;
/** 需要灰度的接口goods.get1.0=1.2,多个用英文逗号隔开, 数据库字段name_version_content */
private String nameVersionContent;
/** 数据库字段gmt_create */
private Date gmtCreate;
/** 数据库字段gmt_modified */
private Date gmtModified;
}

View File

@@ -0,0 +1,35 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.Table;
import java.util.Date;
/**
* 表名config_gray_instance
*
* @author 六如
*/
@Table(name = "config_gray_instance",pk = @Pk(name = "id"))
@Data
public class ConfigGrayInstance {
/** 数据库字段id */
private Long id;
/** instance_id, 数据库字段instance_id */
private String instanceId;
/** service_id, 数据库字段service_id */
private String serviceId;
/** 0禁用1启用, 数据库字段status */
private Byte status;
/** 数据库字段gmt_create */
private Date gmtCreate;
/** 数据库字段gmt_modified */
private Date gmtModified;
}

View File

@@ -0,0 +1,61 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.Table;
import java.util.Date;
/**
* 表名config_limit
* 备注:限流配置
*
* @author 六如
*/
@Table(name = "config_limit",pk = @Pk(name = "id"))
@Data
public class ConfigLimit {
/** 数据库字段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;
}

View File

@@ -0,0 +1,26 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.Table;
/**
* 表名config_route_base
* 备注:路由配置表
*
* @author 六如
*/
@Table(name = "config_route_base",pk = @Pk(name = "id"))
@Data
public class ConfigRouteBase {
/** 数据库字段id */
private Long id;
/** 路由id, 数据库字段route_id */
private String routeId;
/** 状态1启用2禁用, 数据库字段status */
private Byte status;
}

View File

@@ -0,0 +1,66 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.Table;
import java.util.Date;
/**
* 表名config_service_route
* 备注:路由配置
*
* @author 六如
*/
@Table(name = "config_service_route",pk = @Pk(name = "id"))
@Data
public class ConfigServiceRoute {
/** 数据库字段id */
private String id;
/** 数据库字段service_id */
private String serviceId;
/** 接口名, 数据库字段name */
private String name;
/** 版本号, 数据库字段version */
private String version;
/** 路由断言SpringCloudGateway专用, 数据库字段predicates */
private String predicates;
/** 路由过滤器SpringCloudGateway专用, 数据库字段filters */
private String filters;
/** 路由规则转发的目标uri, 数据库字段uri */
private String uri;
/** uri后面跟的path, 数据库字段path */
private String path;
/** 路由执行的顺序, 数据库字段order_index */
private Integer orderIndex;
/** 是否忽略验证,业务参数验证除外, 数据库字段ignore_validate */
private Byte ignoreValidate;
/** 状态0待审核1启用2禁用, 数据库字段status */
private Byte status;
/** 是否合并结果, 数据库字段merge_result */
private Byte mergeResult;
/** 是否需要授权才能访问, 数据库字段permission */
private Byte permission;
/** 是否需要token, 数据库字段need_token */
private Byte needToken;
/** 数据库字段gmt_create */
private Date gmtCreate;
/** 数据库字段gmt_modified */
private Date gmtModified;
}

View File

@@ -0,0 +1,28 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
/**
* @author 六如
*/
@Data
public class IsvDetailDTO {
/** appKey, 数据库字段app_key */
private String appKey;
/** 0启用1禁用, 数据库字段status */
private Byte status;
/** secret, 数据库字段secret */
private String secret;
/** 开发者生成的公钥, 数据库字段public_key_isv */
private String publicKeyIsv;
/** 平台生成的私钥, 数据库字段private_key_platform */
private String privateKeyPlatform;
/** 签名类型1:RSA2,2:MD5 */
private Byte signType;
}

View File

@@ -0,0 +1,36 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.Table;
import java.util.Date;
/**
* 表名isv_info
* 备注isv信息表
*
* @author 六如
*/
@Table(name = "isv_info",pk = @Pk(name = "id"))
@Data
public class IsvInfo {
/** 数据库字段id */
private Long id;
/** appKey, 数据库字段app_key */
private String appKey;
/** 1启用2禁用, 数据库字段status */
private Byte status;
/** 1:RSA2,2:MD5, 数据库字段sign_type */
private Byte signType;
/** 数据库字段gmt_create */
private Date gmtCreate;
/** 数据库字段gmt_modified */
private Date gmtModified;
}

View File

@@ -0,0 +1,61 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.Table;
import java.time.LocalDateTime;
import java.util.Date;
/**
* 表名monitor_info
* 备注:接口监控信息
*
* @author 六如
*/
@Table(name = "monitor_info",pk = @Pk(name = "id"))
@Data
public class MonitorInfo {
/** 数据库字段id */
private Long id;
/** 路由id, 数据库字段route_id */
private String routeId;
/** 接口名, 数据库字段name */
private String name;
/** 版本号, 数据库字段version */
private String version;
/** 数据库字段service_id */
private String serviceId;
/** 数据库字段instance_id */
private String instanceId;
/** 请求耗时最长时间, 数据库字段max_time */
private Integer maxTime;
/** 请求耗时最小时间, 数据库字段min_time */
private Integer minTime;
/** 总时长,毫秒, 数据库字段total_time */
private Long totalTime;
/** 总调用次数, 数据库字段total_request_count */
private Long totalRequestCount;
/** 成功次数, 数据库字段success_count */
private Long successCount;
/** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段error_count */
private Long errorCount;
/** 数据库字段gmt_create */
private Date gmtCreate;
/** 数据库字段gmt_modified */
private Date gmtModified;
}

View File

@@ -0,0 +1,49 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.Table;
import java.time.LocalDateTime;
import java.util.Date;
/**
* 表名monitor_info_error
*
* @author 六如
*/
@Table(name = "monitor_info_error",pk = @Pk(name = "id"))
@Data
public class MonitorInfoError {
/** 数据库字段id */
private Long id;
/** 错误id,md5Hex(instanceId + routeId + errorMsg), 数据库字段error_id */
private String errorId;
/** 实例id, 数据库字段instance_id */
private String instanceId;
/** 数据库字段route_id */
private String routeId;
/** 数据库字段error_msg */
private String errorMsg;
/** http status非200错误, 数据库字段error_status */
private Integer errorStatus;
/** 错误次数, 数据库字段count */
private Integer count;
/** 数据库字段is_deleted */
@com.gitee.fastmybatis.core.annotation.LogicDelete
private Byte isDeleted;
/** 数据库字段gmt_create */
private Date gmtCreate;
/** 数据库字段gmt_modified */
private Date gmtModified;
}

View File

@@ -0,0 +1,33 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.Table;
import java.util.Date;
/**
* 表名perm_isv_role
* 备注isv角色
*
* @author 六如
*/
@Table(name = "perm_isv_role",pk = @Pk(name = "id"))
@Data
public class PermIsvRole {
/** 数据库字段id */
private Long id;
/** isv_info表id, 数据库字段isv_id */
private Long isvId;
/** 角色code, 数据库字段role_code */
private String roleCode;
/** 数据库字段gmt_create */
private Date gmtCreate;
/** 数据库字段gmt_modified */
private Date gmtModified;
}

View File

@@ -0,0 +1,33 @@
package com.gitee.sop.gateway.entity;
import lombok.Data;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.Table;
import java.util.Date;
/**
* 表名perm_role_permission
* 备注:角色权限表
*
* @author 六如
*/
@Table(name = "perm_role_permission",pk = @Pk(name = "id"))
@Data
public class PermRolePermission {
/** 数据库字段id */
private Long id;
/** 角色表code, 数据库字段role_code */
private String roleCode;
/** api_id, 数据库字段route_id */
private String routeId;
/** 数据库字段gmt_create */
private Date gmtCreate;
/** 数据库字段gmt_modified */
private Date gmtModified;
}

View File

@@ -0,0 +1,43 @@
package com.gitee.sop.gateway.interceptor;
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor;
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.sync.SopAsyncConfigurer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 用于收集监控数据
*
* @author 六如
*/
@Component
@Slf4j
public class MonitorRouteInterceptor implements RouteInterceptor {
@Autowired
SopAsyncConfigurer sopAsyncConfigurer;
@Autowired
MonitorRouteInterceptorService monitorRouteInterceptorService;
@Override
public void preRoute(RouteInterceptorContext context) {
}
@Override
public void afterRoute(RouteInterceptorContext context) {
sopAsyncConfigurer.getAsyncExecutor().execute(()-> {
monitorRouteInterceptorService.storeRequestInfo(context);
});
}
@Override
public int getOrder() {
return -1000;
}
}

View File

@@ -0,0 +1,164 @@
package com.gitee.sop.gateway.interceptor;
import com.gitee.sop.gateway.manager.DbMonitorInfoManager;
import com.gitee.sop.gatewaycommon.bean.LRUCache;
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext;
import com.gitee.sop.gatewaycommon.monitor.MonitorDTO;
import com.gitee.sop.gatewaycommon.monitor.MonitorData;
import com.gitee.sop.gatewaycommon.monitor.MonitorManager;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.sync.MyNamedThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author 六如
*/
@Service
@Slf4j
public class MonitorRouteInterceptorService {
/**
* 刷新到数据库时间间隔,秒
*/
@Value("${sop.monitor.flush-period-seconds:30}")
int flushPeriodSeconds;
/**
* 定时任务每n秒执行一次
*/
@Value("${sop.monitor.schedule-period-seconds:30}")
int schedulePeriodSeconds;
/**
* 错误数量容量
*/
@Value("${sop.monitor.error-count-capacity:50}")
int monitorErrorCapacity;
@Autowired
DbMonitorInfoManager dbMonitorInfoManager;
@Autowired
MonitorManager monitorManager;
/**
* 记录接口调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数.
* 需要考虑并发情况。
*/
public synchronized void storeRequestInfo(RouteInterceptorContext context) {
ApiParam apiParam = context.getApiParam();
ServiceInstance serviceInstance = context.getServiceInstance();
String routeId = apiParam.getRouteId();
int spendTime = (int)(context.getFinishTimeMillis() - context.getBeginTimeMillis());
// 这步操作是线程安全的底层调用了ConcurrentHashMap.computeIfAbsent
String key = getMonitorKey(routeId, serviceInstance.getInstanceId());
MonitorData monitorData = monitorManager.getMonitorInfo(key, (k) -> this.createMonitorInfo(apiParam, serviceInstance));
monitorData.storeMaxTime(spendTime);
monitorData.storeMinTime(spendTime);
monitorData.getTotalRequestCount().incrementAndGet();
monitorData.getTotalTime().addAndGet(spendTime);
if (context.isSuccessRequest()) {
monitorData.getSuccessCount().incrementAndGet();
} else {
monitorData.getErrorCount().incrementAndGet();
String errorMsg = context.getServiceErrorMsg();
monitorData.addErrorMsg(errorMsg, context.getResponseStatus());
}
}
private String getMonitorKey(String routeId, String instanceId) {
return routeId + instanceId;
}
/**
* 刷新到数据库
*/
private synchronized void flushDb() {
Map<String, MonitorData> monitorData = monitorManager.getMonitorData();
if (monitorData.isEmpty()) {
return;
}
LocalDateTime checkTime = LocalDateTime.now();
List<String> tobeRemoveKeys = new ArrayList<>();
List<MonitorDTO> tobeSaveBatch = new ArrayList<>(monitorData.size());
monitorData.forEach((key, value) -> {
LocalDateTime flushTime = value.getFlushTime();
if (flushTime.isEqual(checkTime) || flushTime.isBefore(checkTime)) {
log.debug("刷新监控数据到数据库, MonitorData:{}", value);
tobeRemoveKeys.add(key);
MonitorDTO monitorDTO = getMonitorDTO(value);
tobeSaveBatch.add(monitorDTO);
}
});
dbMonitorInfoManager.saveMonitorInfoBatch(tobeSaveBatch);
for (String key : tobeRemoveKeys) {
monitorData.remove(key);
}
}
private MonitorDTO getMonitorDTO(MonitorData monitorData) {
MonitorDTO monitorDTO = new MonitorDTO();
monitorDTO.setRouteId(monitorData.getRouteId());
monitorDTO.setName(monitorData.getName());
monitorDTO.setVersion(monitorData.getVersion());
monitorDTO.setServiceId(monitorData.getServiceId());
monitorDTO.setInstanceId(monitorData.getInstanceId());
monitorDTO.setMaxTime(monitorData.getMaxTime());
monitorDTO.setMinTime(monitorData.getMinTime());
monitorDTO.setTotalTime(monitorData.getTotalTime().longValue());
monitorDTO.setTotalRequestCount(monitorData.getTotalRequestCount().longValue());
monitorDTO.setSuccessCount(monitorData.getSuccessCount().longValue());
monitorDTO.setErrorCount(monitorData.getErrorCount().longValue());
monitorDTO.setErrorMsgList(monitorData.getMonitorErrorMsgMap().values());
return monitorDTO;
}
private MonitorData createMonitorInfo(ApiParam apiParam, ServiceInstance serviceInstance) {
MonitorData monitorData = new MonitorData();
monitorData.setRouteId(apiParam.getRouteId());
monitorData.setName(apiParam.fetchName());
monitorData.setVersion(apiParam.fetchVersion());
monitorData.setServiceId(apiParam.fetchServiceId());
monitorData.setInstanceId(serviceInstance.getInstanceId());
monitorData.setTotalTime(new AtomicInteger());
monitorData.setMaxTime(0);
monitorData.setMinTime(0);
monitorData.setSuccessCount(new AtomicInteger());
monitorData.setTotalRequestCount(new AtomicInteger());
monitorData.setErrorCount(new AtomicInteger());
monitorData.setFlushTime(getFlushTime());
monitorData.setMonitorErrorMsgMap(new LRUCache<>(monitorErrorCapacity));
return monitorData;
}
private LocalDateTime getFlushTime() {
return LocalDateTime.now()
.plusSeconds(flushPeriodSeconds);
}
@PostConstruct
public void after() {
// 每隔schedulePeriodSeconds秒执行一次
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1, new MyNamedThreadFactory("monitorSchedule"));
// 延迟执行随机5~14秒
int delay = 5 + new Random().nextInt(10);
scheduledThreadPoolExecutor.scheduleWithFixedDelay(this::flushDb, delay, schedulePeriodSeconds, TimeUnit.SECONDS);
}
}

View File

@@ -0,0 +1,49 @@
package com.gitee.sop.gateway.interceptor;
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor;
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import java.net.URI;
/**
* 演示拦截器
*
* @author 六如
*/
@Slf4j
@Component
public class MyRouteInterceptor implements RouteInterceptor {
@Override
public void preRoute(RouteInterceptorContext context) {
ApiParam apiParam = context.getApiParam();
ServerWebExchange exchange = (ServerWebExchange) context.getRequestContext();
URI uri = exchange.getRequest().getURI();
log.info("请求URL:{}, 请求接口:{}, request_id:{}, app_id:{}, ip:{}",
uri,
apiParam.fetchNameVersion(),
apiParam.fetchRequestId(),
apiParam.fetchAppKey(),
apiParam.fetchIp()
);
}
@Override
public void afterRoute(RouteInterceptorContext context) {
String serviceErrorMsg = context.getServiceErrorMsg();
if (StringUtils.hasText(serviceErrorMsg)) {
log.error("错误信息:{}", serviceErrorMsg);
}
}
@Override
public int getOrder() {
return 0;
}
}

View File

@@ -0,0 +1,120 @@
package com.gitee.sop.gateway.manager;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.entity.ConfigGray;
import com.gitee.sop.gateway.entity.ConfigGrayInstance;
import com.gitee.sop.gateway.mapper.ConfigGrayInstanceMapper;
import com.gitee.sop.gateway.mapper.ConfigGrayMapper;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceGrayDefinition;
import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager;
import com.gitee.sop.gatewaycommon.route.RegistryEvent;
import com.gitee.sop.gatewaycommon.loadbalancer.ServiceGrayConfig;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 存放用户key这里放在本机内容如果灰度发布保存的用户id数量偏多可放在redis中
*
* @author 六如
*/
@Slf4j
@Service
public class DbEnvGrayManager extends DefaultEnvGrayManager implements RegistryEvent {
private static final int STATUS_ENABLE = 1;
private static final Function<String[], String> FUNCTION_KEY = arr -> arr[0];
private static final Function<String[], String> FUNCTION_VALUE = arr -> arr[1];
@Autowired
private ConfigGrayMapper configGrayMapper;
@Autowired
private ConfigGrayInstanceMapper configGrayInstanceMapper;
@Override
public void onRegistry(InstanceDefinition instanceDefinition) {
String instanceId = instanceDefinition.getInstanceId();
ConfigGrayInstance grayInstance = configGrayInstanceMapper.getByColumn("instance_id", instanceId);
if (grayInstance != null && grayInstance.getStatus() == STATUS_ENABLE) {
log.info("实例[{}]开启灰度发布", grayInstance.getInstanceId());
this.openGray(grayInstance.getInstanceId(), grayInstance.getServiceId());
}
}
@Override
public void onRemove(String serviceId) {
}
@Override
public void load() {
List<ConfigGray> list = configGrayMapper.list(new Query());
for (ConfigGray configGray : list) {
this.setServiceGrayConfig(configGray);
}
}
/**
* 设置用户key
*
* @param configGray 灰度配置
*/
public void setServiceGrayConfig(ConfigGray configGray) {
if (configGray == null) {
return;
}
String userKeyData = configGray.getUserKeyContent();
String nameVersionContent = configGray.getNameVersionContent();
String[] userKeys = StringUtils.split(userKeyData, ',');
String[] nameVersionList = StringUtils.split(nameVersionContent, ',');
log.info("灰度配置userKeys.length:{}, nameVersionList:{}", userKeys.length, Arrays.toString(nameVersionList));
Set<String> userKeySet = Stream.of(userKeys)
.collect(Collectors.toCollection(Sets::newConcurrentHashSet));
Map<String, String> grayNameVersionMap = Stream.of(nameVersionList)
.map(nameVersion -> StringUtils.split(nameVersion, '='))
.collect(Collectors.toConcurrentMap(FUNCTION_KEY, FUNCTION_VALUE));
ServiceGrayConfig serviceGrayConfig = new ServiceGrayConfig();
serviceGrayConfig.setServiceId(configGray.getServiceId());
serviceGrayConfig.setUserKeys(userKeySet);
serviceGrayConfig.setGrayNameVersion(grayNameVersionMap);
this.saveServiceGrayConfig(serviceGrayConfig);
}
@Override
public void process(ChannelMsg channelMsg) {
ServiceGrayDefinition userKeyDefinition = channelMsg.toObject(ServiceGrayDefinition.class);
String serviceId = userKeyDefinition.getServiceId();
switch (channelMsg.getOperation()) {
case "set":
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
setServiceGrayConfig(configGray);
break;
case "open":
openGray(userKeyDefinition.getInstanceId(), serviceId);
break;
case "close":
closeGray(userKeyDefinition.getInstanceId());
break;
default:
}
}
}

View File

@@ -0,0 +1,55 @@
package com.gitee.sop.gateway.manager;
import com.gitee.sop.gateway.mapper.IPBlacklistMapper;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 限流配置管理
*
* @author 六如
*/
@Slf4j
@Service
public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
@Autowired
private IPBlacklistMapper ipBlacklistMapper;
@Override
public void load() {
List<String> ipList = ipBlacklistMapper.listAllIP();
log.info("加载IP黑名单, size:{}", ipList.size());
ipList.forEach(this::add);
}
@Override
public void process(ChannelMsg channelMsg) {
final IPDto ipDto = channelMsg.toObject(IPDto.class);
String ip = ipDto.getIp();
switch (channelMsg.getOperation()) {
case "add":
log.info("添加IP黑名单ip:{}", ip);
add(ip);
break;
case "delete":
log.info("移除IP黑名单ip:{}", ip);
remove(ip);
break;
default:
}
}
@Data
private static class IPDto {
private String ip;
}
}

View File

@@ -0,0 +1,58 @@
package com.gitee.sop.gateway.manager;
import com.gitee.sop.gateway.entity.IsvDetailDTO;
import com.gitee.sop.gateway.mapper.IsvInfoMapper;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.IsvDefinition;
import com.gitee.sop.gatewaycommon.secret.CacheIsvManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author 六如
*/
@Slf4j
@Service
public class DbIsvManager extends CacheIsvManager {
public DbIsvManager() {
ApiConfig.getInstance().setIsvManager(this);
}
@Autowired
private IsvInfoMapper isvInfoMapper;
@Override
public void load() {
List<IsvDetailDTO> isvInfoList = isvInfoMapper.listIsvDetail();
isvInfoList
.forEach(isvInfo -> {
IsvDefinition isvDefinition = new IsvDefinition();
BeanUtils.copyProperties(isvInfo, isvDefinition);
this.getIsvCache().put(isvDefinition.getAppKey(), isvDefinition);
});
}
@Override
public void process(ChannelMsg channelMsg) {
final IsvDefinition isvDefinition = channelMsg.toObject(IsvDefinition.class);
switch (channelMsg.getOperation()) {
case "update":
log.info("更新ISV信息isvDefinition:{}", isvDefinition);
update(isvDefinition);
break;
case "remove":
log.info("删除ISVisvDefinition:{}", isvDefinition);
remove(isvDefinition.getAppKey());
break;
default:
}
}
}

View File

@@ -0,0 +1,149 @@
package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.entity.IsvInfo;
import com.gitee.sop.gateway.entity.PermIsvRole;
import com.gitee.sop.gateway.entity.PermRolePermission;
import com.gitee.sop.gateway.mapper.IsvInfoMapper;
import com.gitee.sop.gateway.mapper.PermIsvRoleMapper;
import com.gitee.sop.gateway.mapper.PermRolePermissionMapper;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.IsvRoutePermission;
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
/**
* 从数据库中读取路由权限信息
*
* @author 六如
*/
@Slf4j
@Service
public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManager {
@Autowired
Environment environment;
@Autowired
PermIsvRoleMapper permIsvRoleMapper;
@Autowired
PermRolePermissionMapper permRolePermissionMapper;
@Autowired
IsvInfoMapper isvInfoMapper;
@Override
public void load() {
// key: appKey, value: roleCodeList
Map<String, List<String>> appKeyRoleCodeMap = this.getIsvRoleCode();
for (Map.Entry<String, List<String>> entry : appKeyRoleCodeMap.entrySet()) {
this.loadIsvRoutePermission(entry.getKey(), entry.getValue());
}
}
public void loadIsvRoutePermission(String appKey, List<String> roleCodeList) {
Collections.sort(roleCodeList);
List<String> routeIdList = this.getRouteIdList(roleCodeList);
String roleCodeListMd5 = DigestUtils.md5Hex(JSON.toJSONString(routeIdList));
IsvRoutePermission isvRoutePermission = new IsvRoutePermission();
isvRoutePermission.setAppKey(appKey);
isvRoutePermission.setRouteIdList(routeIdList);
isvRoutePermission.setRouteIdListMd5(roleCodeListMd5);
this.update(isvRoutePermission);
}
/**
* 获取ISV对应的角色
* @return 返回ISV角色信息keyappIdvalue角色code列表
*/
public Map<String, List<String>> getIsvRoleCode() {
Query query = new Query();
List<PermIsvRole> permIsvRoles = permIsvRoleMapper.list(query);
Map<String, List<String>> appKeyRoleCodeMap = permIsvRoles.stream()
.map(permIsvRole -> {
IsvInfo isvInfo = isvInfoMapper.getById(permIsvRole.getIsvId());
if (isvInfo == null) {
return null;
}
IsvRole isvRole = new IsvRole();
isvRole.appKey = isvInfo.getAppKey();
isvRole.roleCode = permIsvRole.getRoleCode();
return isvRole;
})
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(IsvRole::getAppKey,
mapping(IsvRole::getRoleCode, toList()))
);
return appKeyRoleCodeMap;
}
/**
* 获取角色对应的路由
*
* @param roleCodeList
* @return
*/
public List<String> getRouteIdList(List<String> roleCodeList) {
if (CollectionUtils.isEmpty(roleCodeList)) {
return Collections.emptyList();
}
Query query = new Query();
query.in("role_code", roleCodeList);
List<PermRolePermission> rolePermissionList = permRolePermissionMapper.list(query);
return rolePermissionList.stream()
.map(PermRolePermission::getRouteId)
.sorted()
.collect(Collectors.toList());
}
@Override
public void process(ChannelMsg channelMsg) {
final IsvRoutePermission isvRoutePermission = channelMsg.toObject(IsvRoutePermission.class);
switch (channelMsg.getOperation()) {
case "reload":
log.info("重新加载路由权限信息isvRoutePermission:{}", isvRoutePermission);
try {
load();
} catch (Exception e) {
log.error("重新加载路由权限失败, channelMsg:{}", channelMsg, e);
}
break;
case "update":
log.info("更新ISV路由权限信息isvRoutePermission:{}", isvRoutePermission);
update(isvRoutePermission);
break;
case "remove":
log.info("删除ISV路由权限信息isvRoutePermission:{}", isvRoutePermission);
remove(isvRoutePermission.getAppKey());
break;
default:
}
}
@Data
static class IsvRole {
private String appKey;
private String roleCode;
}
}

View File

@@ -0,0 +1,60 @@
package com.gitee.sop.gateway.manager;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.entity.ConfigLimit;
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.util.MyBeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 限流配置管理
* @author 六如
*/
@Slf4j
@Service
public class DbLimitConfigManager extends DefaultLimitConfigManager {
@Autowired
ConfigLimitMapper configLimitMapper;
@Override
public void load(String serviceId) {
Query query = new Query();
if (StringUtils.isNotBlank(serviceId)) {
query.eq("service_id", serviceId);
}
configLimitMapper.list(query)
.forEach(this::putVal);
}
protected void putVal(ConfigLimit object) {
ConfigLimitDto configLimitDto = new ConfigLimitDto();
MyBeanUtil.copyPropertiesIgnoreNull(object, configLimitDto);
this.update(configLimitDto);
}
@Override
public void process(ChannelMsg channelMsg) {
final ConfigLimitDto configLimitDto = channelMsg.toObject(ConfigLimitDto.class);
switch (channelMsg.getOperation()) {
case "reload":
log.info("重新加载限流配置信息configLimitDto:{}", configLimitDto);
load(configLimitDto.getServiceId());
break;
case "update":
log.info("更新限流配置信息configLimitDto:{}", configLimitDto);
update(configLimitDto);
break;
default:
}
}
}

View File

@@ -0,0 +1,64 @@
package com.gitee.sop.gateway.manager;
import com.gitee.sop.gateway.mapper.MonitorInfoErrorMapper;
import com.gitee.sop.gateway.mapper.MonitorInfoMapper;
import com.gitee.sop.gatewaycommon.monitor.MonitorDTO;
import com.gitee.sop.gatewaycommon.monitor.MonitorErrorMsg;
import com.gitee.sop.gatewaycommon.monitor.RouteErrorCount;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author 六如
*/
@Service
public class DbMonitorInfoManager {
@Autowired
private MonitorInfoMapper monitorInfoMapper;
@Autowired
private MonitorInfoErrorMapper monitorInfoErrorMapper;
@Value("${sop.monitor.error-count-capacity:50}")
int limitCount;
public void saveMonitorInfoBatch(List<MonitorDTO> list) {
if (CollectionUtils.isEmpty(list)) {
return;
}
monitorInfoMapper.saveMonitorInfoBatch(list);
this.saveMonitorInfoErrorBatch(list);
}
private void saveMonitorInfoErrorBatch(List<MonitorDTO> list) {
List<RouteErrorCount> routeErrorCounts = monitorInfoErrorMapper.listRouteErrorCountAll();
// 路由id对应的错误次数keyrouteIdvalue错误次数
Map<String, Integer> routeErrorCountsMap = routeErrorCounts.stream()
.collect(Collectors.toMap(RouteErrorCount::getRouteId, RouteErrorCount::getCount));
List<MonitorErrorMsg> monitorErrorMsgList = list.stream()
.filter(monitorDTO -> CollectionUtils.isNotEmpty(monitorDTO.getErrorMsgList()))
.flatMap(monitorDTO -> {
int limit = limitCount - routeErrorCountsMap.getOrDefault(monitorDTO.getRouteId(), 0);
// 容量已满
if (limit <= 0) {
return null;
}
// 截取剩余
return monitorDTO.getErrorMsgList().stream().limit(limit);
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(monitorErrorMsgList)) {
monitorInfoErrorMapper.saveMonitorInfoErrorBatch(monitorErrorMsgList);
}
}
}

View File

@@ -0,0 +1,51 @@
package com.gitee.sop.gateway.manager;
import com.gitee.sop.gateway.mapper.ConfigRouteMapper;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author 六如
*/
@Slf4j
@Service
public class DbRouteConfigManager extends DefaultRouteConfigManager {
@Autowired
private ConfigRouteMapper configRouteMapper;
@Autowired
private Environment environment;
@Override
public void load(String serviceId) {
List<RouteConfig> routeConfigs = StringUtils.isBlank(serviceId) ? configRouteMapper.listAllRouteConfig()
: configRouteMapper.listRouteConfig(serviceId);
routeConfigs.forEach(this::save);
}
@Override
public void process(ChannelMsg channelMsg) {
final RouteConfig routeConfig = channelMsg.toObject(RouteConfig.class);
switch (channelMsg.getOperation()) {
case "reload":
log.info("重新加载路由配置信息routeConfigDto:{}", routeConfig);
load(null);
break;
case "update":
log.info("更新路由配置信息routeConfigDto:{}", routeConfig);
update(routeConfig);
break;
default:
}
}
}

View File

@@ -0,0 +1,86 @@
package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.entity.ConfigServiceRoute;
import com.gitee.sop.gateway.mapper.ConfigServiceRouteMapper;
import com.gitee.sop.gateway.mapper.SystemLockMapper;
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.route.RoutesProcessor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author 六如
*/
@Slf4j
@Component
public class DbRoutesProcessor implements RoutesProcessor {
@Autowired
private ConfigServiceRouteMapper configServiceRouteMapper;
@Autowired
private SystemLockMapper systemLockMapper;
@Override
public void removeAllRoutes(String serviceId) {
// 删除serviceId下所有的路由
Query delServiceQuery = new Query().eq("service_id", serviceId);
configServiceRouteMapper.deleteByQuery(delServiceQuery);
}
@Transactional(rollbackFor = Exception.class)
@Override
public synchronized void saveRoutes(ServiceRouteInfo serviceRouteInfo, InstanceDefinition instance) {
// 抢锁,没抢到阻塞在这里
systemLockMapper.lock();
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
int result = systemLockMapper.insert(time + serviceRouteInfo.getMd5());
// 抢到锁,插入失败,表示其它实例已经处理完毕,这里直接返回
if (result == 0) {
return;
}
log.info("保存路由信息到数据库instance: {}", instance);
String serviceId = serviceRouteInfo.getServiceId();
List<ConfigServiceRoute> configServiceRoutes = serviceRouteInfo
.getRouteDefinitionList()
.parallelStream()
.map(routeDefinition -> {
ConfigServiceRoute configServiceRoute = new ConfigServiceRoute();
configServiceRoute.setId(routeDefinition.getId());
configServiceRoute.setName(routeDefinition.getName());
configServiceRoute.setVersion(routeDefinition.getVersion());
configServiceRoute.setUri(routeDefinition.getUri());
configServiceRoute.setPath(routeDefinition.getPath());
configServiceRoute.setFilters(JSON.toJSONString(routeDefinition.getFilters()));
configServiceRoute.setPredicates(JSON.toJSONString(routeDefinition.getPredicates()));
configServiceRoute.setIgnoreValidate((byte) routeDefinition.getIgnoreValidate());
configServiceRoute.setMergeResult((byte) routeDefinition.getMergeResult());
configServiceRoute.setStatus((byte) routeDefinition.getStatus());
configServiceRoute.setPermission((byte) routeDefinition.getPermission());
configServiceRoute.setOrderIndex(routeDefinition.getOrder());
configServiceRoute.setNeedToken((byte)routeDefinition.getNeedToken());
configServiceRoute.setServiceId(serviceId);
return configServiceRoute;
})
.collect(Collectors.toList());
// 删除serviceId下所有的路由
this.removeAllRoutes(serviceId);
if (CollectionUtils.isNotEmpty(configServiceRoutes)) {
// 批量保存
configServiceRouteMapper.saveBatch(configServiceRoutes);
}
}
}

View File

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

View File

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

View File

@@ -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 六如
*/
public interface ConfigLimitMapper extends CrudMapper<ConfigLimit, Long> {
}

View File

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

View File

@@ -0,0 +1,27 @@
package com.gitee.sop.gateway.mapper;
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @author 六如
*/
@Mapper
public interface ConfigRouteMapper {
@Select("SELECT t.id AS routeId, t2.status " +
"FROM config_service_route t " +
"LEFT JOIN config_route_base t2 ON t.id=t2.route_id " +
"WHERE t.service_id=#{serviceId}")
List<RouteConfig> listRouteConfig(@Param("serviceId") String serviceId);
@Select("SELECT t.id AS routeId, t2.status " +
"FROM config_service_route t " +
"LEFT JOIN config_route_base t2 ON t.id=t2.route_id ")
List<RouteConfig> listAllRouteConfig();
}

View File

@@ -0,0 +1,11 @@
package com.gitee.sop.gateway.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.gateway.entity.ConfigServiceRoute;
/**
* @author 六如
*/
public interface ConfigServiceRouteMapper extends CrudMapper<ConfigServiceRoute, String> {
}

View File

@@ -0,0 +1,22 @@
package com.gitee.sop.gateway.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* IP黑名单
* @author 六如
*/
@Mapper
public interface IPBlacklistMapper {
/**
* 获取所有IP
* @return
*/
@Select("SELECT ip FROM config_ip_blacklist")
List<String> listAllIP();
}

View File

@@ -0,0 +1,30 @@
package com.gitee.sop.gateway.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.gateway.entity.IsvDetailDTO;
import com.gitee.sop.gateway.entity.IsvInfo;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @author 六如
*/
public interface IsvInfoMapper extends CrudMapper<IsvInfo, Long> {
/**
* 获取所有的isv信息
* @return 所有的isv信息
*/
@Select("SELECT " +
" t.app_key appKey " +
" ,t.status " +
" ,t2.sign_type signType " +
" ,t2.secret " +
" ,t2.public_key_isv publicKeyIsv " +
" ,t2.private_key_platform privateKeyPlatform " +
"FROM isv_info t " +
"INNER JOIN isv_keys t2 ON t.app_key = t2.app_key")
List<IsvDetailDTO> listIsvDetail();
}

View File

@@ -0,0 +1,36 @@
package com.gitee.sop.gateway.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.gateway.entity.MonitorInfoError;
import com.gitee.sop.gatewaycommon.monitor.MonitorErrorMsg;
import com.gitee.sop.gatewaycommon.monitor.RouteErrorCount;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
/**
* @author 六如
*/
public interface MonitorInfoErrorMapper extends CrudMapper<MonitorInfoError, Long> {
@Update("UPDATE monitor_info_error " +
"SET is_deleted=0 " +
",count=count + 1 " +
"WHERE route_id=#{routeId} AND error_id=#{errorId}")
int updateError(@Param("routeId") String routeId,@Param("errorId") String errorId);
int saveMonitorInfoErrorBatch(@Param("list") List<MonitorErrorMsg> list);
@Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" +
"WHERE is_deleted=0 \n" +
"GROUP BY route_id")
List<RouteErrorCount> listRouteErrorCount();
@Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" +
"WHERE is_deleted=0 \n" +
"GROUP BY route_id")
List<RouteErrorCount> listRouteErrorCountAll();
}

View File

@@ -0,0 +1,39 @@
package com.gitee.sop.gateway.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.gateway.entity.MonitorInfo;
import com.gitee.sop.gatewaycommon.monitor.MonitorDTO;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import java.util.List;
/**
* @author 六如
*/
public interface MonitorInfoMapper extends CrudMapper<MonitorInfo, Long> {
/**
* 更新监控状态
*
* @return 返回影响行数
*/
@Update("UPDATE monitor_info " +
"set max_time=case when max_time < #{maxTime} then #{maxTime} else max_time end " +
",min_time=case when min_time > #{minTime} then #{minTime} else min_time end " +
",total_request_count=total_request_count + #{totalRequestCount} " +
",total_time=total_time + #{totalTime} " +
",success_count=success_count + #{successCount} " +
",error_count=error_count + #{errorCount} " +
"where route_id=#{routeId}")
int updateMonitorInfo(MonitorDTO monitorDTO);
/**
* 批量插入监控数据
* @param list 监控数据
* @return 返回影响行数
*/
int saveMonitorInfoBatch(@Param("list") List<MonitorDTO> list);
}

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
package com.gitee.sop.gateway.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.ResultType;
import org.apache.ibatis.annotations.Select;
/**
* @author 六如
*/
@Mapper
public interface SystemLockMapper {
/**
* 插入唯一值
* @param content 唯一值
* @return 1返回成功0插入失败
*/
@Insert("INSERT IGNORE INTO system_lock(content) VALUES (#{content})")
@ResultType(int.class)
int insert(@Param("content") String content);
@Select("SELECT id FROM system_lock WHERE id=1 FOR UPDATE")
@ResultType(long.class)
long lock();
}

View File

@@ -0,0 +1,47 @@
# \u56FA\u5B9A\u4E0D\u53D8\uFF0C\u4E0D\u80FD\u6539
spring.application.name=sop-gateway
# \u4E0D\u7528\u6539\uFF0C\u5982\u679C\u8981\u6539\uFF0C\u8BF7\u5168\u5C40\u66FF\u6362\u4FEE\u6539
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
# \u6587\u4EF6\u4E0A\u4F20\u914D\u7F6E
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
# \u4E0D\u9650\u5236gateway\u8BF7\u6C42\u4F53\u5927\u5C0F
spring.codec.max-in-memory-size=-1
# \u7F51\u5173\u5165\u53E3
sop.gateway-index-path=/
# \u5F00\u542Frestful\u8BF7\u6C42\uFF0C\u9ED8\u8BA4\u5F00\u542F
sop.restful.enable=true
# restful\u8BF7\u6C42\u524D\u7F00
sop.restful.path=/rest
# nacos cloud\u914D\u7F6E
spring.cloud.nacos.discovery.server-addr=${register.url}
# eureka\u5730\u5740
eureka.client.serviceUrl.defaultZone=${register.url}
# \u6570\u636E\u5E93\u914D\u7F6E
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username=${mysql.username}
spring.datasource.password=${mysql.password}
#\u8FDE\u63A5\u6C60
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=500000
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
#spring.cloud.gateway.discovery.locator.enabled=true
# \u54CD\u5E94\u8D85\u65F6\uFF0C\u9ED8\u8BA410\u79D2\uFF0810000\uFF09
spring.cloud.gateway.httpclient.response-timeout=10000
spring.cloud.gateway.httpclient.pool.max-idle-time=60000
# \u4E0D\u7528\u6539
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified

View File

@@ -0,0 +1,4 @@
# 自定义自动配置类,如果有多个类,使用逗号(,)分隔,\正斜杠标示换行还可以读取到指定的类
org.springframework.boot.env.EnvironmentPostProcessor=com.gitee.sop.gatewaycommon.config.SopGatewayEnvironmentPostProcessor
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gitee.sop.gatewaycommon.config.SopGatewayAutoConfiguration,\
com.gitee.sop.bridge.SopRegisterAutoConfiguration

View File

@@ -0,0 +1,18 @@
# \u66F4\u591A\u914D\u7F6E\uFF0C\u89C1\uFF1AMETA-INF/gateway.properties
server.port=8081
# mysql\u6570\u636E\u5E93\u914D\u7F6E
mysql.host=localhost:3306
mysql.username=root
mysql.password=root
# nacos\u6CE8\u518C\u4E2D\u5FC3\u5730\u5740
register.url=127.0.0.1:8848
# \u4E0A\u4F20\u6587\u4EF6\u6700\u5927\u5BB9\u91CF\uFF0C\u9ED8\u8BA410MB
spring.servlet.multipart.max-file-size=10MB
# \u54CD\u5E94\u8D85\u65F6\uFF0C\u9ED8\u8BA410\u79D2\uFF0810000\uFF09
#spring.cloud.gateway.httpclient.response-timeout=10000
logging.level.com.gitee=debug

View File

@@ -0,0 +1 @@
spring.profiles.active=dev

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 注意文件名必须跟Dao类名字一致因为是根据文件名做关联。 -->
<mapper namespace="com.gitee.sop.gateway.mapper.MonitorInfoErrorMapper">
<insert id="saveMonitorInfoErrorBatch">
INSERT INTO `monitor_info_error` (
`error_id`,
`instance_id`,
`route_id`,
`error_msg`,
`error_status`,
`count`)
VALUES
<foreach collection="list" item="data" separator="," >
(#{data.errorId},
#{data.instanceId},
#{data.routeId},
#{data.errorMsg},
#{data.errorStatus},
#{data.count})
</foreach>
ON DUPLICATE KEY UPDATE
error_msg = VALUES(error_msg)
, error_status = VALUES(error_status)
, `count`= `count` + VALUES(count)
, is_deleted = 0
</insert>
</mapper>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 注意文件名必须跟Dao类名字一致因为是根据文件名做关联。 -->
<mapper namespace="com.gitee.sop.gateway.mapper.MonitorInfoMapper">
<insert id="saveMonitorInfoBatch">
INSERT INTO `monitor_info` (
`route_id`,
`name`,
`version`,
`service_id`,
`instance_id`,
`max_time`,
`min_time`,
`total_time`,
`total_request_count`,
`success_count`,
`error_count`)
VALUES
<foreach collection="list" item="data" separator="," >
(#{data.routeId},
#{data.name},
#{data.version},
#{data.serviceId},
#{data.instanceId},
#{data.maxTime},
#{data.minTime},
#{data.totalTime},
#{data.totalRequestCount},
#{data.successCount},
#{data.errorCount})
</foreach>
<![CDATA[
ON DUPLICATE KEY UPDATE
max_time = case when max_time < VALUES(max_time) then VALUES(max_time) else max_time end
,min_time = case when min_time > VALUES(min_time) then VALUES(min_time) else min_time end
,total_time = total_time + VALUES(total_time)
,total_request_count = total_request_count + VALUES(total_request_count)
,success_count = success_count + VALUES(success_count)
,error_count = error_count + VALUES(error_count)
]]></insert>
</mapper>

View File

@@ -0,0 +1,22 @@
//package com.gitee.sop.gateway;
//
//import junit.framework.TestCase;
//import org.apache.commons.lang3.StringUtils;
//
///**
// * @author 六如
// */
//public class ExcludeTest extends TestCase {
// public void testRegex() {
// String serviceId = "com.aaa.bbb.story-service";
// String sopServiceExcludeRegex = "com\\..*;story\\-.*";
// if (StringUtils.isNotBlank(sopServiceExcludeRegex)) {
// String[] regexArr = sopServiceExcludeRegex.split(";");
// for (String regex : regexArr) {
// if (serviceId.matches(regex)) {
// System.out.println("111");
// }
// }
// }
// }
//}

View File

@@ -0,0 +1,62 @@
package com.gitee.sop.gateway;
import com.gitee.sop.gatewaycommon.util.LoadBalanceUtil;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 轮询选择一台机器。
*
* @author 六如
*/
public class RoundRobinTest extends TestCase {
public void testDo() {
String serviceId = "order-service";
List<String> serverList = new ArrayList<>(Arrays.asList("server1", "server2", "server3"));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
}
public void testAdd() {
String serviceId = "order-service";
List<String> serverList = new ArrayList<>(Arrays.asList("server1", "server2", "server3"));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
// 中途添加一个服务器
serverList.add("server4");
System.out.println(chooseRoundRobinServer(serviceId, serverList));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
}
public void testRemove() {
String serviceId = "order-service";
List<String> serverList = new ArrayList<>(Arrays.asList("server1", "server2", "server3"));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
// 中途减少一台服务器
serverList.remove(2);
System.out.println(chooseRoundRobinServer(serviceId, serverList));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
System.out.println(chooseRoundRobinServer(serviceId, serverList));
}
/**
* 轮询选择一台机器。
* 假设有N台服务器S = {S1, S2, …, Sn}一个指示变量i表示上一次选择的服务器ID。变量i被初始化为N-1。
*
* @param serviceId serviceId
* @param servers 服务器列表
* @return 返回一台服务器实例
*/
private <T> T chooseRoundRobinServer(String serviceId, List<T> servers) {
return LoadBalanceUtil.chooseByRoundRobin(serviceId, servers);
}
}

View File

@@ -0,0 +1,17 @@
package com.gitee.sop.gateway;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SopGatewayApplicationTests {
@Test
public void contextLoads() {
}
}

View File

@@ -0,0 +1,30 @@
package com.gitee.sop.gateway;
import junit.framework.TestCase;
import org.junit.Assert;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
/**
* @author 六如
*/
public class UrlPatternTest extends TestCase {
private PathMatcher pathMatcher = new AntPathMatcher();
public void testA() {
Assert.assertTrue(match("/food/get/{id}", "/food/get/2"));
Assert.assertTrue(match("/food/get/{id}1.0", "/food/get/21.0"));
}
/**
* @param pattern food/get/{id}
* @param lookupPath /food/get/2
*
* @return
*/
public boolean match(String pattern, String lookupPath) {
return this.pathMatcher.match(pattern, lookupPath);
}
}