vertx改为动态加载配置
This commit is contained in:
parent
0a5b0e8d78
commit
cbdc0a8279
@ -0,0 +1,13 @@
|
|||||||
|
package com.sf.vertx.api.pojo;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class AddressRetryStrategy implements Serializable {
|
||||||
|
private static final long serialVersionUID = -336914981251646244L;
|
||||||
|
private Integer threshold = 3; // 2, // 失败次数
|
||||||
|
private Integer timeWindow = 20; //1, //时间窗口,单位s
|
||||||
|
private Integer periodicTime = 3; // vertx定时任务循环执行时间间隔
|
||||||
|
}
|
@ -10,9 +10,11 @@ public class AppConfig implements Serializable {
|
|||||||
private static final long serialVersionUID = 1518165296680157119L;
|
private static final long serialVersionUID = 1518165296680157119L;
|
||||||
private String appCode; // 应用唯一码, app访问uri添加前缀,用于网关区分多应用
|
private String appCode; // 应用唯一码, app访问uri添加前缀,用于网关区分多应用
|
||||||
private boolean exclusiveService; // 预留字段, 独立端口
|
private boolean exclusiveService; // 预留字段, 独立端口
|
||||||
private Integer exclusiveGatewayConfigId; // 独享网关配置编号
|
private Integer exclusiveGatewayConfigId; // 预留字段, 独享网关配置编号
|
||||||
private EnvironmentConfig environmentConfig; // 环境配置
|
private EnvironmentConfig environmentConfig; // 环境配置
|
||||||
private List<SacService> service; // 服务
|
private List<SacService> service; // 服务
|
||||||
private AdvancedConfig advancedConfig; // 高级配置
|
|
||||||
private DataSecurity dataSecurity; // 数据加解密
|
private DataSecurity dataSecurity; // 数据加解密
|
||||||
|
private ApiCurrentLimitingConfig apiCurrentLimitingConfig; // 接口限流配置
|
||||||
|
private AppCurrentLimitingConfig appCurrentLimitingConfig; // APP限流配置
|
||||||
|
private AdvancedConfig advancedConfig; // 高级配置
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,7 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class DataSecurity implements Serializable {
|
public class DataSecurity implements Serializable {
|
||||||
private static final long serialVersionUID = 5034274428665340830L;
|
private static final long serialVersionUID = 5034274428665340830L;
|
||||||
private String key; //加密串
|
private String algorithm; // 加密算法(ECC、RSA 和国密(SM2))
|
||||||
private String algorithm; // 国密加解密
|
|
||||||
private String publicKey; // 公钥
|
private String publicKey; // 公钥
|
||||||
private String privateKey; // 私钥
|
private String privateKey; // 私钥 (当加密算法为 ECC 或国密SM2时,填写私钥内容。当加密算法为 RSA 时,分别填写公私钥内容。)
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,5 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class EnvironmentConfig implements Serializable {
|
public class EnvironmentConfig implements Serializable {
|
||||||
private static final long serialVersionUID = -3952046909425019869L;
|
private static final long serialVersionUID = -3952046909425019869L;
|
||||||
private Integer defaultId; // 默认环境配置编号
|
|
||||||
private Map<Integer, List<Node>> environmentGroup; // 环境节点
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,6 @@ public class GatewayInterface implements Serializable {
|
|||||||
private String uri;
|
private String uri;
|
||||||
private boolean uriRegular; // uri 正则
|
private boolean uriRegular; // uri 正则
|
||||||
private String method; // 大写
|
private String method; // 大写
|
||||||
|
private Strategy strategy; // 策略
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,9 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class HttpClientOptionsConfig implements Serializable {
|
public class HttpClientOptionsConfig implements Serializable {
|
||||||
private static final long serialVersionUID = -6302301564759941097L;
|
private static final long serialVersionUID = -6302301564759941097L;
|
||||||
private int maxPoolSize;
|
private Integer maxPoolSize;
|
||||||
private int connectTimeout;
|
private Integer connectTimeout;
|
||||||
private int http2KeepAliveTimeout;
|
private Integer http2KeepAliveTimeout;
|
||||||
private int idleTimeout;
|
private Integer idleTimeout;
|
||||||
|
private Integer timeout;
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,7 @@ public class Node implements Comparable<Node>, Serializable {
|
|||||||
private Integer weight;
|
private Integer weight;
|
||||||
private Integer effectiveWeight;
|
private Integer effectiveWeight;
|
||||||
private Integer currentWeight;
|
private Integer currentWeight;
|
||||||
private boolean createHttp; // 协议
|
private String protocol; // 协议
|
||||||
private boolean createHttps;
|
|
||||||
|
|
||||||
public Node() {
|
public Node() {
|
||||||
}
|
}
|
||||||
@ -40,21 +39,13 @@ public class Node implements Comparable<Node>, Serializable {
|
|||||||
this.effectiveWeight = effectiveWeight;
|
this.effectiveWeight = effectiveWeight;
|
||||||
this.currentWeight = currentWeight;
|
this.currentWeight = currentWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCreateHttp() {
|
public String getProtocol() {
|
||||||
return createHttp;
|
return protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreateHttp(boolean createHttp) {
|
public void setProtocol(String protocol) {
|
||||||
this.createHttp = createHttp;
|
this.protocol = protocol;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCreateHttps() {
|
|
||||||
return createHttps;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreateHttps(boolean createHttps) {
|
|
||||||
this.createHttps = createHttps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIp() {
|
public String getIp() {
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.sf.vertx.api.pojo;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class RouteContent implements Serializable {
|
||||||
|
private static final long serialVersionUID = -4405058529530680618L;
|
||||||
|
private ServerAddress serverAddress; // 服务地址
|
||||||
|
private Integer weight; //权重值
|
||||||
|
private String headerKey; //请求头
|
||||||
|
private List<String> headerValues; // ["v1","v2"],
|
||||||
|
private String matchType; // 匹配类型,EQ,IN
|
||||||
|
}
|
@ -1,16 +1,14 @@
|
|||||||
package com.sf.vertx.api.pojo;
|
package com.sf.vertx.api.pojo;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Map;
|
import java.util.List;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class Router implements Serializable {
|
public class Router implements Serializable {
|
||||||
private static final long serialVersionUID = -4471811880134025210L;
|
private static final long serialVersionUID = -4471811880134025210L;
|
||||||
private String domain; // 域名
|
private String routeType; // 路由类型 WEIGHT_ROUTE ,HEADER_ROUTE
|
||||||
private Map<String, String> headers; // 头字段
|
private List<RouteContent> routeContent; // 路由的配置信息
|
||||||
private String headerVal; // 头字段
|
|
||||||
private Integer environmentId; // 环境配置编号
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,9 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class SacService implements Serializable {
|
public class SacService implements Serializable {
|
||||||
private static final long serialVersionUID = -5171112142954536813L;
|
private static final long serialVersionUID = -5171112142954536813L;
|
||||||
private Strategy strategy; // 策略
|
private String serviceName; // 服务名
|
||||||
|
private String serviceModel; // 模式, NORMAL, ROUTE
|
||||||
|
private ServerAddress serverAddress; // NORMAL模式的服务地址
|
||||||
private List<GatewayInterface> uriList; // uri列表
|
private List<GatewayInterface> uriList; // uri列表
|
||||||
private Router router; // 路由
|
private Router routeConfig; // 路由
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
//package com.sf.vertx.api.pojo;
|
package com.sf.vertx.api.pojo;
|
||||||
//
|
|
||||||
//import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
//
|
|
||||||
//import lombok.Data;
|
import lombok.Data;
|
||||||
//
|
|
||||||
//@Data
|
@Data
|
||||||
//public class ServerAddress implements Serializable {
|
public class ServerAddress implements Serializable {
|
||||||
// private static final long serialVersionUID = 2821255113510132943L;
|
private static final long serialVersionUID = 7446602403871080553L;
|
||||||
// private boolean createHttp; // 协议
|
private String address;
|
||||||
// private boolean createHttps;
|
private String protocol; // "http"
|
||||||
// private String ip;
|
private String host;
|
||||||
// private int port;
|
private int port;
|
||||||
//}
|
private String path; // 前缀
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -13,4 +13,15 @@ import lombok.Data;
|
|||||||
public class Strategy implements Serializable {
|
public class Strategy implements Serializable {
|
||||||
private static final long serialVersionUID = -8831406773224882471L;
|
private static final long serialVersionUID = -8831406773224882471L;
|
||||||
// 限流熔断
|
// 限流熔断
|
||||||
|
private String type;// CURRENT_LIMITING 限流策略, 熔断策略, CIRCUIT_BREAKER
|
||||||
|
private Integer threshold; // 2, // 限流阈值(APP总和)
|
||||||
|
private Integer timeWindow; //1, //时间窗口,单位s
|
||||||
|
private Integer recovery_interval; //熔断后的恢复时间间隔,单位s
|
||||||
|
private String defaultResponse;
|
||||||
|
// ": "{
|
||||||
|
// \"msg\": \"接口繁忙请重试\",
|
||||||
|
// \"code\": 501,
|
||||||
|
// \"data\": \"到达限流阈值\",
|
||||||
|
// }" // 默认限流响应,JSON字符串。
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,10 @@ import lombok.Data;
|
|||||||
public class VertxConfig implements Serializable {
|
public class VertxConfig implements Serializable {
|
||||||
private static final long serialVersionUID = -1706421732809219829L;
|
private static final long serialVersionUID = -1706421732809219829L;
|
||||||
private Integer port; // 启动端口
|
private Integer port; // 启动端口
|
||||||
|
private String appHeaderKey;
|
||||||
|
private String appHeaderServiceName;
|
||||||
private VertxOptionsConfig vertxOptionsConfig;
|
private VertxOptionsConfig vertxOptionsConfig;
|
||||||
private HttpClientOptionsConfig httpClientOptionsConfig; // 配置Vert端口连接池
|
private HttpClientOptionsConfig httpClientOptionsConfig; // 配置Vert端口连接池
|
||||||
|
private AddressRetryStrategy addressRetryStrategy;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ public class VertxOptionsConfig {
|
|||||||
private int eventLoopPoolSize;
|
private int eventLoopPoolSize;
|
||||||
private int workerPoolSize ;
|
private int workerPoolSize ;
|
||||||
private int internalBlockingPoolSize;
|
private int internalBlockingPoolSize;
|
||||||
private long blockedThreadCheckInterval;
|
private long blockedThreadCheckInterval; // 3000000000 打印thread wait日志
|
||||||
private long maxEventLoopExecuteTime;
|
private long maxEventLoopExecuteTime;
|
||||||
private long maxWorkerExecuteTime;
|
private long maxWorkerExecuteTime;
|
||||||
//private ClusterManager clusterManager;
|
//private ClusterManager clusterManager;
|
||||||
|
@ -5,7 +5,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
import io.vertx.core.http.impl.Http1xServerRequest;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,11 +14,15 @@ public class RedisKeyConfig {
|
|||||||
public static String APP_CURRENT_LIMITING_CONFIG_KEY = null;
|
public static String APP_CURRENT_LIMITING_CONFIG_KEY = null;
|
||||||
public static String APP_CONFIG_PREFIX_KEY = null;
|
public static String APP_CONFIG_PREFIX_KEY = null;
|
||||||
public static String VERTX_CONFIG_STRING_KEY = null;
|
public static String VERTX_CONFIG_STRING_KEY = null;
|
||||||
|
public static String VERTX_ADDRESS_RETRY_STRATEGY_SET_KEY = null;
|
||||||
|
public static String VERTX_ADDRESS_RETRY_STRATEGY_KEY = null;
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
APP_CONFIG_PREFIX_KEY = BASE_REDIS_KEY + vertxEnvironment;
|
APP_CONFIG_PREFIX_KEY = BASE_REDIS_KEY + vertxEnvironment;
|
||||||
APP_CONFIG_SET_KEY = APP_CONFIG_PREFIX_KEY + ":set";
|
APP_CONFIG_SET_KEY = APP_CONFIG_PREFIX_KEY + ":set";
|
||||||
APP_CURRENT_LIMITING_CONFIG_KEY = APP_CONFIG_PREFIX_KEY + ":app";
|
APP_CURRENT_LIMITING_CONFIG_KEY = APP_CONFIG_PREFIX_KEY + ":app:limiting";
|
||||||
VERTX_CONFIG_STRING_KEY = BASE_REDIS_KEY + vertxEnvironment + ":vertx";
|
VERTX_CONFIG_STRING_KEY = BASE_REDIS_KEY + vertxEnvironment + ":vertx";
|
||||||
|
VERTX_ADDRESS_RETRY_STRATEGY_KEY = APP_CONFIG_PREFIX_KEY + ":addAddressRetryStrategy";
|
||||||
|
VERTX_ADDRESS_RETRY_STRATEGY_SET_KEY = VERTX_ADDRESS_RETRY_STRATEGY_KEY + ":set";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
package com.sf.vertx.handle;
|
package com.sf.vertx.handle;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
||||||
|
import com.sf.vertx.utils.ProxyTool;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2014 Red Hat, Inc.
|
* Copyright 2014 Red Hat, Inc.
|
||||||
*
|
*
|
||||||
@ -33,16 +43,6 @@ import io.vertx.ext.web.RoutingContext;
|
|||||||
import io.vertx.ext.web.impl.FileUploadImpl;
|
import io.vertx.ext.web.impl.FileUploadImpl;
|
||||||
import io.vertx.ext.web.impl.RoutingContextInternal;
|
import io.vertx.ext.web.impl.RoutingContextInternal;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import com.sf.vertx.init.DynamicBuildServer;
|
|
||||||
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="http://tfox.org">Tim Fox</a>
|
* @author <a href="http://tfox.org">Tim Fox</a>
|
||||||
*/
|
*/
|
||||||
@ -80,7 +80,7 @@ public class BodyHandlerImpl implements BodyHandler {
|
|||||||
// TODO 改造了这个地方
|
// TODO 改造了这个地方
|
||||||
final HttpServerRequest request = context.request();
|
final HttpServerRequest request = context.request();
|
||||||
final HttpServerResponse response = context.response();
|
final HttpServerResponse response = context.response();
|
||||||
String sacAppHeaderKey = request.getHeader(DynamicBuildServer.SAC_APP_HEADER_KEY);
|
String sacAppHeaderKey = request.getHeader(AppConfigServiceImpl.getSacAppHeaderKey());
|
||||||
if (StringUtils.isNotBlank(sacAppHeaderKey) && AppConfigServiceImpl.appDataSecurity(sacAppHeaderKey)) {
|
if (StringUtils.isNotBlank(sacAppHeaderKey) && AppConfigServiceImpl.appDataSecurity(sacAppHeaderKey)) {
|
||||||
// 加解密在proxy拦截器解析跳转
|
// 加解密在proxy拦截器解析跳转
|
||||||
// =======源码流程
|
// =======源码流程
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.sf.vertx.handle;
|
package com.sf.vertx.handle;
|
||||||
|
|
||||||
import com.sf.vertx.api.pojo.AppCurrentLimitingConfig;
|
import com.sf.vertx.api.pojo.AppCurrentLimitingConfig;
|
||||||
import com.sf.vertx.init.DynamicBuildServer;
|
|
||||||
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
||||||
|
import com.sf.vertx.utils.ProxyTool;
|
||||||
|
|
||||||
import io.vertx.ext.web.RoutingContext;
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import io.vertx.ext.web.handler.HttpException;
|
import io.vertx.ext.web.handler.HttpException;
|
||||||
@ -26,7 +26,7 @@ public class RateLimitHandlerRedisImpl implements RateLimitHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(RoutingContext rc) {
|
public void handle(RoutingContext rc) {
|
||||||
String sacAppHeaderKey = rc.request().headers().get(DynamicBuildServer.SAC_APP_HEADER_KEY);
|
String sacAppHeaderKey = rc.request().headers().get(AppConfigServiceImpl.getSacAppHeaderKey());
|
||||||
// TODO 判断是否开启限流配置
|
// TODO 判断是否开启限流配置
|
||||||
log.info("RateLimitHandlerRedisImpl request:{}", sacAppHeaderKey);
|
log.info("RateLimitHandlerRedisImpl request:{}", sacAppHeaderKey);
|
||||||
// rc.next();
|
// rc.next();
|
||||||
|
@ -7,7 +7,6 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||||||
import com.sf.vertx.api.pojo.ApiCurrentLimitingConfig;
|
import com.sf.vertx.api.pojo.ApiCurrentLimitingConfig;
|
||||||
import com.sf.vertx.api.pojo.AppCurrentLimitingConfig;
|
import com.sf.vertx.api.pojo.AppCurrentLimitingConfig;
|
||||||
import com.sf.vertx.constans.RedisKeyConfig;
|
import com.sf.vertx.constans.RedisKeyConfig;
|
||||||
import com.sf.vertx.init.DynamicBuildServer;
|
|
||||||
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
||||||
import com.sf.vertx.utils.SpringUtils;
|
import com.sf.vertx.utils.SpringUtils;
|
||||||
|
|
||||||
@ -18,19 +17,23 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class RedisRateLimiter {
|
public class RedisRateLimiter {
|
||||||
public Boolean acquire(RoutingContext rc, int pattern) {
|
public Boolean acquire(RoutingContext rc, int pattern) {
|
||||||
String appCode = rc.request().headers().get(DynamicBuildServer.SAC_APP_HEADER_KEY);
|
String appCode = rc.request().headers().get(AppConfigServiceImpl.getSacAppHeaderKey());
|
||||||
// TODO 先测试app模式,后面通过app缓存获取模式
|
// TODO 先测试app模式,后面通过app缓存获取模式
|
||||||
String key = null;
|
String key = null;
|
||||||
switch (pattern) {
|
switch (pattern) {
|
||||||
case 1:
|
case 1:
|
||||||
AppCurrentLimitingConfig appCurrentLimitingConfig = AppConfigServiceImpl.getAppCurrentLimitingConfig(appCode);
|
AppCurrentLimitingConfig appCurrentLimitingConfig = AppConfigServiceImpl
|
||||||
|
.getAppCurrentLimitingConfig(appCode);
|
||||||
key = RedisKeyConfig.APP_CURRENT_LIMITING_CONFIG_KEY + ":" + appCode;
|
key = RedisKeyConfig.APP_CURRENT_LIMITING_CONFIG_KEY + ":" + appCode;
|
||||||
return rateLimiter(key, appCode, appCurrentLimitingConfig.getThreshold(), appCurrentLimitingConfig.getTimeWindow());
|
return rateLimiter(key, appCode, appCurrentLimitingConfig.getThreshold(),
|
||||||
|
appCurrentLimitingConfig.getTimeWindow());
|
||||||
case 2:
|
case 2:
|
||||||
ApiCurrentLimitingConfig apiCurrentLimitingConfig = AppConfigServiceImpl.getApiCurrentLimitingConfig(appCode);
|
ApiCurrentLimitingConfig apiCurrentLimitingConfig = AppConfigServiceImpl
|
||||||
|
.getApiCurrentLimitingConfig(appCode);
|
||||||
key = RedisKeyConfig.APP_CURRENT_LIMITING_CONFIG_KEY + ":" + appCode + ":" + rc.request().uri() + ":"
|
key = RedisKeyConfig.APP_CURRENT_LIMITING_CONFIG_KEY + ":" + appCode + ":" + rc.request().uri() + ":"
|
||||||
+ rc.request().method();
|
+ rc.request().method();
|
||||||
return rateLimiter(key, appCode, apiCurrentLimitingConfig.getThreshold(), apiCurrentLimitingConfig.getTimeWindow());
|
return rateLimiter(key, appCode, apiCurrentLimitingConfig.getThreshold(),
|
||||||
|
apiCurrentLimitingConfig.getTimeWindow());
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -44,29 +47,18 @@ public class RedisRateLimiter {
|
|||||||
Object count = redisTemplate.opsForValue().get(key);
|
Object count = redisTemplate.opsForValue().get(key);
|
||||||
// redisTemplate.delete(key);
|
// redisTemplate.delete(key);
|
||||||
log.info("count:{}", count);
|
log.info("count:{}", count);
|
||||||
if (count == null) {
|
// 初始化,设置过期时间
|
||||||
// 初始化,设置过期时间
|
ThreadUtil.execAsync(() -> {
|
||||||
ThreadUtil.execAsync(() -> {
|
add(timeWindow, redisTemplate, key);
|
||||||
add(timeWindow, redisTemplate, key);
|
});
|
||||||
});
|
return count != null && Integer.valueOf(count.toString()) <= threshold ? true : false;
|
||||||
} else if (Integer.valueOf(count.toString()) < threshold) {
|
}
|
||||||
ThreadUtil.execAsync(() -> {
|
|
||||||
increment(redisTemplate, key);
|
private void add(Integer timeWindow, RedisTemplate<String, Integer> redisTemplate, String key) {
|
||||||
});
|
// log.info("redis app threshold: {}", redisTemplate.opsForValue().get(key));
|
||||||
} else {
|
Long incr = redisTemplate.opsForValue().increment(key);
|
||||||
return false;
|
if (incr == 1) { // 创建,才设置时间窗口
|
||||||
|
redisTemplate.expire(key, timeWindow, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add(Integer timeWindow, RedisTemplate<String, Integer> redisTemplate,
|
|
||||||
String key) {
|
|
||||||
redisTemplate.opsForValue().increment(key);
|
|
||||||
log.info("redis app threshold: {}", redisTemplate.opsForValue().get(key));
|
|
||||||
redisTemplate.expire(key, timeWindow, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void increment(RedisTemplate<String, Integer> redisTemplate, String key) {
|
|
||||||
redisTemplate.opsForValue().increment(key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,14 @@ package com.sf.vertx.handle;
|
|||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.RoutingContext;
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import io.vertx.ext.web.handler.HttpException;
|
import io.vertx.ext.web.handler.HttpException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@Slf4j
|
||||||
public class RestfulFailureHandlerImpl implements RestfulFailureHandler {
|
public class RestfulFailureHandlerImpl implements RestfulFailureHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(RoutingContext frc) {
|
public void handle(RoutingContext frc) {
|
||||||
Throwable failure = frc.failure();
|
Throwable failure = frc.failure();
|
||||||
|
log.info("Throwable error:{}", failure);
|
||||||
if (failure instanceof HttpException) {
|
if (failure instanceof HttpException) {
|
||||||
HttpException httpException = (HttpException) failure;
|
HttpException httpException = (HttpException) failure;
|
||||||
// frc.response().setStatusCode(404).end();
|
// frc.response().setStatusCode(404).end();
|
||||||
@ -17,7 +19,9 @@ public class RestfulFailureHandlerImpl implements RestfulFailureHandler {
|
|||||||
.putHeader("Content-Type", "application/json").end(dataJson.toBuffer());
|
.putHeader("Content-Type", "application/json").end(dataJson.toBuffer());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
frc.response().setStatusCode(500).setStatusMessage("Server internal error:" + failure.getMessage()).end();
|
frc.response().setChunked(true).setStatusCode(400)
|
||||||
|
.putHeader("Content-Type", "application/json").end("{\n" + " \"msg\": \"接口繁忙请重试\",\n" + " \"code\": 501,\n" + " \"data\": \"到达限流阈值\"\n" + "}");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package com.sf.vertx.init;
|
package com.sf.vertx.init;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -10,36 +8,31 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.boot.ApplicationArguments;
|
import org.springframework.boot.ApplicationArguments;
|
||||||
import org.springframework.boot.ApplicationRunner;
|
import org.springframework.boot.ApplicationRunner;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.sf.vertx.api.pojo.AppConfig;
|
|
||||||
import com.sf.vertx.api.pojo.GatewayInterface;
|
|
||||||
import com.sf.vertx.api.pojo.Node;
|
|
||||||
import com.sf.vertx.api.pojo.SacService;
|
|
||||||
import com.sf.vertx.api.pojo.VertxConfig;
|
import com.sf.vertx.api.pojo.VertxConfig;
|
||||||
import com.sf.vertx.arithmetic.roundRobin.SacLoadBalancing;
|
|
||||||
import com.sf.vertx.arithmetic.roundRobin.WeightedRoundRobin;
|
|
||||||
import com.sf.vertx.constans.RedisKeyConfig;
|
import com.sf.vertx.constans.RedisKeyConfig;
|
||||||
import com.sf.vertx.handle.BodyHandler;
|
import com.sf.vertx.handle.BodyHandler;
|
||||||
import com.sf.vertx.handle.ProxyHandler;
|
import com.sf.vertx.handle.ProxyHandler;
|
||||||
import com.sf.vertx.handle.RateLimitHandler;
|
import com.sf.vertx.handle.RateLimitHandler;
|
||||||
import com.sf.vertx.handle.RestfulFailureHandler;
|
import com.sf.vertx.handle.RestfulFailureHandler;
|
||||||
import com.sf.vertx.security.MainSecurity;
|
|
||||||
import com.sf.vertx.service.AppConfigService;
|
import com.sf.vertx.service.AppConfigService;
|
||||||
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
||||||
|
import com.sf.vertx.utils.Filter;
|
||||||
|
import com.sf.vertx.utils.ProxyTool;
|
||||||
|
import com.sf.vertx.utils.SpringUtils;
|
||||||
|
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.Vertx;
|
import io.vertx.core.Vertx;
|
||||||
import io.vertx.core.VertxOptions;
|
import io.vertx.core.VertxOptions;
|
||||||
import io.vertx.core.http.HttpClient;
|
import io.vertx.core.http.HttpClient;
|
||||||
|
import io.vertx.core.http.HttpClientOptions;
|
||||||
import io.vertx.core.http.HttpServer;
|
import io.vertx.core.http.HttpServer;
|
||||||
import io.vertx.core.http.HttpServerOptions;
|
import io.vertx.core.http.HttpServerOptions;
|
||||||
import io.vertx.core.http.HttpServerRequest;
|
|
||||||
import io.vertx.core.json.JsonObject;
|
|
||||||
import io.vertx.core.net.SocketAddress;
|
|
||||||
import io.vertx.ext.web.Router;
|
import io.vertx.ext.web.Router;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
import io.vertx.ext.web.client.WebClient;
|
||||||
import io.vertx.ext.web.handler.HttpException;
|
import io.vertx.httpproxy.Body;
|
||||||
import io.vertx.httpproxy.HttpProxy;
|
import io.vertx.httpproxy.HttpProxy;
|
||||||
import io.vertx.httpproxy.ProxyContext;
|
import io.vertx.httpproxy.ProxyContext;
|
||||||
import io.vertx.httpproxy.ProxyInterceptor;
|
import io.vertx.httpproxy.ProxyInterceptor;
|
||||||
@ -56,11 +49,12 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Order(value = 10)
|
@Order(value = 10)
|
||||||
@Component
|
@Component
|
||||||
public class DynamicBuildServer implements ApplicationRunner {
|
public class DynamicBuildServer implements ApplicationRunner {
|
||||||
public static final String SAC_APP_HEADER_KEY = "sacAppCode";
|
|
||||||
private static ConcurrentHashMap<Integer, SacLoadBalancing> SAC_LOADBALANCING_MAP = new ConcurrentHashMap<Integer, SacLoadBalancing>();
|
|
||||||
@Value("${server.vertx.server.default.port}")
|
@Value("${server.vertx.server.default.port}")
|
||||||
private Integer serverDefaultPort;
|
private Integer serverDefaultPort;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate<String, String> redisTemplate;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AppConfigService appConfigService;
|
private AppConfigService appConfigService;
|
||||||
|
|
||||||
@ -71,6 +65,10 @@ public class DynamicBuildServer implements ApplicationRunner {
|
|||||||
public void run(ApplicationArguments args) throws Exception {
|
public void run(ApplicationArguments args) throws Exception {
|
||||||
// 初始化redis key
|
// 初始化redis key
|
||||||
redisKeyConfig.init();
|
redisKeyConfig.init();
|
||||||
|
// 从redis同步app配置
|
||||||
|
appConfigService.loadAppConfig();
|
||||||
|
// 从redis同步vertx配置
|
||||||
|
appConfigService.loadVertxConfig();
|
||||||
// 加载vertx、应用配置
|
// 加载vertx、应用配置
|
||||||
appStartLoadData();
|
appStartLoadData();
|
||||||
}
|
}
|
||||||
@ -79,14 +77,26 @@ public class DynamicBuildServer implements ApplicationRunner {
|
|||||||
* 应用启动, 从redis读取配置,初始化vertx服务
|
* 应用启动, 从redis读取配置,初始化vertx服务
|
||||||
*/
|
*/
|
||||||
private void appStartLoadData() {
|
private void appStartLoadData() {
|
||||||
|
VertxConfig vertxConfig = AppConfigServiceImpl.getVertxConfig();
|
||||||
// TODO 编解码线程池,后面优化协程等方式
|
// TODO 编解码线程池,后面优化协程等方式
|
||||||
Vertx VERTX = Vertx.vertx(new VertxOptions().setWorkerPoolSize(20));
|
VertxOptions vertxOptions = new VertxOptions();
|
||||||
|
long blockedThreadCheckInterval = vertxConfig == null || vertxConfig.getVertxOptionsConfig() == null ? -1
|
||||||
|
: vertxConfig.getVertxOptionsConfig().getBlockedThreadCheckInterval();
|
||||||
|
int workerPoolSize = vertxConfig == null || vertxConfig.getVertxOptionsConfig() == null ? -1
|
||||||
|
: vertxConfig.getVertxOptionsConfig().getWorkerPoolSize();
|
||||||
|
if (workerPoolSize != -1) {
|
||||||
|
vertxOptions.setWorkerPoolSize(workerPoolSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockedThreadCheckInterval != -1) {
|
||||||
|
vertxOptions.setBlockedThreadCheckInterval(blockedThreadCheckInterval); // 不打印Thread blocked 阻塞日志
|
||||||
|
}
|
||||||
|
Vertx VERTX = Vertx.vertx(vertxOptions);
|
||||||
// 创建HTTP监听
|
// 创建HTTP监听
|
||||||
// 所有ip都能访问
|
// 所有ip都能访问
|
||||||
HttpServerOptions httpServerOptions = new HttpServerOptions().setHost("0.0.0.0");
|
HttpServerOptions httpServerOptions = new HttpServerOptions().setHost("0.0.0.0");
|
||||||
HttpServer server = VERTX.createHttpServer(httpServerOptions);
|
HttpServer server = VERTX.createHttpServer(httpServerOptions);
|
||||||
Router mainHttpRouter = Router.router(VERTX);
|
Router mainHttpRouter = Router.router(VERTX);
|
||||||
VertxConfig vertxConfig = appConfigService.loadVertxConfig();
|
|
||||||
Integer serverPort = (vertxConfig == null || vertxConfig.getPort() == null) ? serverDefaultPort
|
Integer serverPort = (vertxConfig == null || vertxConfig.getPort() == null) ? serverDefaultPort
|
||||||
: vertxConfig.getPort();
|
: vertxConfig.getPort();
|
||||||
log.info("serverPort:{}", serverPort);
|
log.info("serverPort:{}", serverPort);
|
||||||
@ -98,30 +108,38 @@ public class DynamicBuildServer implements ApplicationRunner {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ConcurrentHashMap<String, AppConfig> cacheAppConfig = appConfigService.loadAllConfig();
|
|
||||||
// HttpClientOptions clientOptions = new HttpClientOptions();
|
// HttpClientOptions clientOptions = new HttpClientOptions();
|
||||||
// clientOptions.setMaxPoolSize(20); // 最大连接池大小
|
// clientOptions.setMaxPoolSize(20); // 最大连接池大小
|
||||||
// clientOptions.setConnectTimeout(5000); // 连接超时 毫秒
|
// clientOptions.setConnectTimeout(2000); // 连接超时 毫秒
|
||||||
// clientOptions.setHttp2KeepAliveTimeout(1);
|
// clientOptions.setHttp2KeepAliveTimeout(1);
|
||||||
// clientOptions.setIdleTimeout(1000); // 连接空闲超时 毫秒
|
// clientOptions.setIdleTimeout(1000); // 连接空闲超时 毫秒
|
||||||
// HttpClient proxyClient = VERTX.createHttpClient(clientOptions);
|
// HttpClient proxyClient = VERTX.createHttpClient(clientOptions);
|
||||||
HttpClient proxyClient = VERTX.createHttpClient();
|
HttpClient proxyClient = VERTX.createHttpClient();
|
||||||
HttpProxy proxy = HttpProxy.reverseProxy(proxyClient);
|
HttpProxy proxy = HttpProxy.reverseProxy(proxyClient);
|
||||||
proxy.originSelector(request -> Future.succeededFuture(resolveOriginAddress(cacheAppConfig, request)));
|
proxy.originSelector(request -> Future.succeededFuture(ProxyTool.resolveOriginAddress(request)));
|
||||||
proxy.addInterceptor(new ProxyInterceptor() {
|
proxy.addInterceptor(new ProxyInterceptor() {
|
||||||
@Override
|
@Override
|
||||||
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
|
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
|
||||||
// 修改uri context.request().setURI();
|
// 修改uri context.request().setURI();
|
||||||
String sacAppHeaderKey = context.request().headers().get(DynamicBuildServer.SAC_APP_HEADER_KEY);
|
String sacAppHeaderKey = context.request().headers().get(AppConfigServiceImpl.getSacAppHeaderKey());
|
||||||
log.info("addInterceptor uri appCode:{}", sacAppHeaderKey);
|
log.info("addInterceptor uri appCode:{}", sacAppHeaderKey);
|
||||||
// 判断是否需要加解析
|
// 判断是否需要加解析
|
||||||
if (AppConfigServiceImpl.appDataSecurity(sacAppHeaderKey)) {
|
if (AppConfigServiceImpl.appDataSecurity(sacAppHeaderKey)) {
|
||||||
|
|
||||||
// String data = decode(null, sacAppHeaderKey);
|
// String data = decode(null, sacAppHeaderKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
return context.sendRequest();
|
return context.sendRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<Void> handleProxyResponse(ProxyContext context) {
|
||||||
|
// 调试代码,获取reponse body
|
||||||
|
Filter filter = new Filter();
|
||||||
|
ProxyResponse proxyResponse = context.response();
|
||||||
|
Body body = proxyResponse.getBody();
|
||||||
|
proxyResponse.setBody(Body.body(filter.init(context.request().getURI(), body.stream(), false)));
|
||||||
|
// 继续拦截链
|
||||||
|
return context.sendResponse();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
WebClient mainWebClient = WebClient.create(VERTX);
|
WebClient mainWebClient = WebClient.create(VERTX);
|
||||||
// mainHttpRouter.route().handler(ProxyHandler.create(proxy));
|
// mainHttpRouter.route().handler(ProxyHandler.create(proxy));
|
||||||
@ -132,146 +150,28 @@ public class DynamicBuildServer implements ApplicationRunner {
|
|||||||
int pattern = 2;// 1:app,2:接口默认,3:服务接口配置限流策略
|
int pattern = 2;// 1:app,2:接口默认,3:服务接口配置限流策略
|
||||||
mainHttpRouter.route().handler(RateLimitHandler.create(instance, pattern)).handler(BodyHandler.create())
|
mainHttpRouter.route().handler(RateLimitHandler.create(instance, pattern)).handler(BodyHandler.create())
|
||||||
.handler(ProxyHandler.create(mainWebClient, proxy)).failureHandler(RestfulFailureHandler.create());
|
.handler(ProxyHandler.create(mainWebClient, proxy)).failureHandler(RestfulFailureHandler.create());
|
||||||
//mainHttpRouter.route().handler(BodyHandler.create()).handler(ProxyHandler.create(mainWebClient,proxy));
|
// mainHttpRouter.route().handler(BodyHandler.create()).handler(ProxyHandler.create(mainWebClient,proxy));
|
||||||
}
|
|
||||||
|
|
||||||
public SocketAddress resolveOriginAddress(ConcurrentHashMap<String, AppConfig> cacheAppConfig,
|
// 服务健康检测重试
|
||||||
HttpServerRequest request) {
|
Integer periodicTime = AppConfigServiceImpl.getVertxConfig().getAddressRetryStrategy() != null
|
||||||
String appCode = request.getHeader(SAC_APP_HEADER_KEY);
|
&& AppConfigServiceImpl.getVertxConfig().getAddressRetryStrategy().getPeriodicTime() > 0
|
||||||
log.info("uri appCode:{}", appCode);
|
? AppConfigServiceImpl.getVertxConfig().getAddressRetryStrategy().getPeriodicTime()
|
||||||
// TODO 不存在, 抛异常给前端
|
: 3;
|
||||||
|
|
||||||
AppConfig appConfig = cacheAppConfig.get(appCode);
|
// TODO 是否开启健康检测
|
||||||
if (appConfig != null) {
|
long timerID = VERTX.setPeriodic(periodicTime, id -> {
|
||||||
Integer environmentId = null;
|
Set<String> set = redisTemplate.opsForZSet().range(RedisKeyConfig.APP_CONFIG_SET_KEY, 0, -1);
|
||||||
SacLoadBalancing sacLoadBalancing = null;
|
for (String appCode : set) {
|
||||||
// 2、是否存在server服务配置
|
Set<String> setAddressRetryStrategy = redisTemplate.opsForZSet()
|
||||||
if (appConfig.getService() != null && appConfig.getService().size() > 0) {
|
.range(RedisKeyConfig.VERTX_ADDRESS_RETRY_STRATEGY_SET_KEY + ":" + appCode, 0, -1);
|
||||||
// header传递服务名, 这样就不需要遍历,提高性能
|
for (String address : setAddressRetryStrategy) {
|
||||||
for (SacService service : appConfig.getService()) {
|
// 发起请求,测试服务是否可用
|
||||||
// uri是否匹配
|
// TODO 调用后端配置的健康检测地址
|
||||||
boolean match = false;
|
|
||||||
for (GatewayInterface gatewayInterface : service.getUriList()) {
|
|
||||||
String domain = request.authority().port() == -1 ? request.authority().host()
|
|
||||||
: request.authority().host() + ":" + request.authority().port();
|
|
||||||
// uri匹配(正则或全量匹配)
|
|
||||||
match = gatewayInterface.isUriRegular() ? regexMatch(gatewayInterface.getUri(), request.uri())
|
|
||||||
: StringUtils.equals(gatewayInterface.getUri(), request.uri());
|
|
||||||
match = match ? StringUtils.equals(gatewayInterface.getMethod(), request.method().name())
|
|
||||||
: false;
|
|
||||||
// domain匹配
|
|
||||||
if (match) {
|
|
||||||
boolean domainVertify = service.getRouter() != null
|
|
||||||
&& StringUtils.isNotBlank(service.getRouter().getDomain())
|
|
||||||
&& StringUtils.equals(service.getRouter().getDomain(), domain) ? true : false;
|
|
||||||
if (domainVertify) {
|
|
||||||
environmentId = service.getRouter().getEnvironmentId();
|
|
||||||
} else {
|
|
||||||
environmentId = appConfig.getEnvironmentConfig().getDefaultId();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
environmentId = appConfig.getEnvironmentConfig().getDefaultId();
|
|
||||||
}
|
|
||||||
// 初始化负载均衡
|
|
||||||
if (SAC_LOADBALANCING_MAP.get(environmentId) != null) {
|
|
||||||
sacLoadBalancing = SAC_LOADBALANCING_MAP.get(environmentId);
|
|
||||||
} else {
|
|
||||||
// 并发影响不大, 只要初始化成功一次即可
|
|
||||||
List<Node> nodeList = appConfig.getEnvironmentConfig().getEnvironmentGroup().get(environmentId);
|
|
||||||
if (nodeList != null && nodeList.size() > 0) {
|
|
||||||
// 初始化负载均衡算法
|
|
||||||
sacLoadBalancing = roundRobin(nodeList);
|
|
||||||
SAC_LOADBALANCING_MAP.put(environmentId, sacLoadBalancing);
|
|
||||||
} else {
|
|
||||||
// TODO 抛出异常
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// TODO 区分https、http
|
|
||||||
Node node = sacLoadBalancing.selectNode();
|
|
||||||
SocketAddress socketAddress = SocketAddress.inetSocketAddress(node.getPort(), node.getIp());
|
|
||||||
log.info("负载均衡跳转地址:{}", socketAddress.host() + ";" + socketAddress.port());
|
|
||||||
return socketAddress;
|
|
||||||
}
|
|
||||||
// TODO 如果没找到, 如何处理
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private String getUriAppCode(String uri) {
|
|
||||||
// // 1、判断appCode
|
|
||||||
// int count = 0;
|
|
||||||
// int appCodeLen = 16;
|
|
||||||
// StringBuffer appCode = new StringBuffer();
|
|
||||||
// for (int i = 0; i < uri.length(); i++) {
|
|
||||||
// // 限制长度
|
|
||||||
// if (appCode.length() == appCodeLen) {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// char ch = uri.charAt(i);
|
|
||||||
// if (ch == '/') {
|
|
||||||
// if (++count == 2) {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// appCode.append(ch);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return appCode.toString();
|
|
||||||
// }
|
|
||||||
|
|
||||||
private static boolean regexMatch(String pattern, String target) {
|
|
||||||
return Pattern.matches(pattern, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 解密
|
|
||||||
*/
|
|
||||||
private static String decode(AppConfig appConfig, String bodyJson) {
|
|
||||||
String algorithm = appConfig.getDataSecurity().getAlgorithm();
|
|
||||||
String data = null;
|
|
||||||
switch (algorithm) {
|
|
||||||
case "aes":
|
|
||||||
// 解密
|
|
||||||
String key = appConfig.getDataSecurity().getKey();
|
|
||||||
data = MainSecurity.aesDecrypt(bodyJson, key);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 加密
|
|
||||||
*/
|
|
||||||
private static String encryption(AppConfig appConfig, String responseData) {
|
|
||||||
String algorithm = appConfig.getDataSecurity().getAlgorithm();
|
|
||||||
String data = null;
|
|
||||||
switch (algorithm) {
|
|
||||||
case "aes":
|
|
||||||
String key = appConfig.getDataSecurity().getKey();
|
|
||||||
data = MainSecurity.aesEncrypt(responseData.toString(), key);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SacLoadBalancing roundRobin(List<Node> nodeList) {
|
|
||||||
WeightedRoundRobin weightedRoundRobin = new WeightedRoundRobin();
|
|
||||||
for (Node node : nodeList) {
|
|
||||||
int weight = node.getWeight() != null ? node.getWeight() : 1;
|
|
||||||
node.setWeight(weight);
|
|
||||||
node.setCurrentWeight(weight);
|
|
||||||
node.setEffectiveWeight(weight);
|
|
||||||
WeightedRoundRobin.totalWeight += node.getEffectiveWeight();
|
|
||||||
}
|
|
||||||
weightedRoundRobin.init(nodeList);
|
|
||||||
return weightedRoundRobin;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
package com.sf.vertx.service;
|
package com.sf.vertx.service;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import com.sf.vertx.api.pojo.AppConfig;
|
import com.sf.vertx.api.pojo.AppConfig;
|
||||||
import com.sf.vertx.api.pojo.VertxConfig;
|
import com.sf.vertx.api.pojo.VertxConfig;
|
||||||
|
import com.sf.vertx.arithmetic.roundRobin.SacLoadBalancing;
|
||||||
|
|
||||||
public interface AppConfigService {
|
public interface AppConfigService {
|
||||||
ConcurrentHashMap<String, AppConfig> loadAllConfig();
|
|
||||||
|
void loadAppConfig() throws Exception;
|
||||||
|
|
||||||
void addAppConfig(String appConfig);
|
void addAppConfig(String appConfig);
|
||||||
|
|
||||||
void deleteAppConfig(AppConfig appConfig);
|
void deleteAppConfig(AppConfig appConfig);
|
||||||
|
|
||||||
VertxConfig loadVertxConfig();
|
void loadVertxConfig();
|
||||||
|
|
||||||
void addVertxConfig(VertxConfig vertxConfig);
|
void addVertxConfig(VertxConfig vertxConfig);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package com.sf.vertx.service.impl;
|
package com.sf.vertx.service.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -15,9 +18,15 @@ import com.alibaba.fastjson2.TypeReference;
|
|||||||
import com.sf.vertx.api.pojo.ApiCurrentLimitingConfig;
|
import com.sf.vertx.api.pojo.ApiCurrentLimitingConfig;
|
||||||
import com.sf.vertx.api.pojo.AppConfig;
|
import com.sf.vertx.api.pojo.AppConfig;
|
||||||
import com.sf.vertx.api.pojo.AppCurrentLimitingConfig;
|
import com.sf.vertx.api.pojo.AppCurrentLimitingConfig;
|
||||||
|
import com.sf.vertx.api.pojo.Node;
|
||||||
|
import com.sf.vertx.api.pojo.RouteContent;
|
||||||
|
import com.sf.vertx.api.pojo.SacService;
|
||||||
import com.sf.vertx.api.pojo.VertxConfig;
|
import com.sf.vertx.api.pojo.VertxConfig;
|
||||||
|
import com.sf.vertx.arithmetic.roundRobin.SacLoadBalancing;
|
||||||
import com.sf.vertx.constans.RedisKeyConfig;
|
import com.sf.vertx.constans.RedisKeyConfig;
|
||||||
import com.sf.vertx.service.AppConfigService;
|
import com.sf.vertx.service.AppConfigService;
|
||||||
|
import com.sf.vertx.utils.ProxyTool;
|
||||||
|
import com.sf.vertx.utils.SpringUtils;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@ -28,9 +37,36 @@ public class AppConfigServiceImpl implements AppConfigService {
|
|||||||
private String vertxEnvironment;
|
private String vertxEnvironment;
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisTemplate<String, String> redisTemplate;
|
private RedisTemplate<String, String> redisTemplate;
|
||||||
|
private static VertxConfig VERTX_CONFIG = new VertxConfig();
|
||||||
private static final ConcurrentHashMap<String, AppConfig> CACHE_APP_CONFIG = new ConcurrentHashMap<>();
|
private static final ConcurrentHashMap<String, AppConfig> CACHE_APP_CONFIG = new ConcurrentHashMap<>();
|
||||||
private static final ConcurrentHashMap<String, AppCurrentLimitingConfig> CACHE_APP_CURRENT_LIMITING_CONFIG = new ConcurrentHashMap<>();
|
private static final ConcurrentHashMap<String, AppCurrentLimitingConfig> CACHE_APP_CURRENT_LIMITING_CONFIG = new ConcurrentHashMap<>();
|
||||||
private static final ConcurrentHashMap<String, ApiCurrentLimitingConfig> CACHE_API_CURRENT_LIMITING_CONFIG = new ConcurrentHashMap<>();
|
private static final ConcurrentHashMap<String, ApiCurrentLimitingConfig> CACHE_API_CURRENT_LIMITING_CONFIG = new ConcurrentHashMap<>();
|
||||||
|
private static final ConcurrentHashMap<String, SacService> CACHE_APP_SERVICE = new ConcurrentHashMap<>();
|
||||||
|
private static ConcurrentHashMap<String, SacLoadBalancing> SAC_LOADBALANCING_MAP = new ConcurrentHashMap<String, SacLoadBalancing>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static void addAddressRetryStrategy(String address, String appCode) {
|
||||||
|
String setKey = RedisKeyConfig.VERTX_ADDRESS_RETRY_STRATEGY_SET_KEY + ":" + appCode;
|
||||||
|
String key = RedisKeyConfig.VERTX_ADDRESS_RETRY_STRATEGY_KEY + ":" + appCode + ":" + address;
|
||||||
|
RedisTemplate<String, String> redisTemplate = SpringUtils.getBean("redisTemplate", RedisTemplate.class);
|
||||||
|
Long thresholdCount = redisTemplate.opsForValue().increment(key);
|
||||||
|
// log.info("redis app threshold: {}", redisTemplate.opsForValue().get(key));
|
||||||
|
Integer timeWindow = VERTX_CONFIG.getAddressRetryStrategy() != null
|
||||||
|
&& VERTX_CONFIG.getAddressRetryStrategy().getTimeWindow() > 0
|
||||||
|
? VERTX_CONFIG.getAddressRetryStrategy().getTimeWindow()
|
||||||
|
: 20;
|
||||||
|
if(thresholdCount == 1) { // 创建,才设置时间窗口
|
||||||
|
redisTemplate.expire(key, timeWindow, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
Integer threshold = AppConfigServiceImpl.getVertxConfig().getAddressRetryStrategy() != null
|
||||||
|
&& AppConfigServiceImpl.getVertxConfig().getAddressRetryStrategy().getThreshold() > 0
|
||||||
|
? AppConfigServiceImpl.getVertxConfig().getAddressRetryStrategy().getThreshold()
|
||||||
|
: 3;
|
||||||
|
if(thresholdCount > threshold) {
|
||||||
|
// 设置服务不可用
|
||||||
|
redisTemplate.opsForZSet().add(setKey, appCode + ":" + address, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean appDataSecurity(String appCode) {
|
public static boolean appDataSecurity(String appCode) {
|
||||||
return CACHE_APP_CONFIG.get(appCode) != null && CACHE_APP_CONFIG.get(appCode).getDataSecurity() != null ? true
|
return CACHE_APP_CONFIG.get(appCode) != null && CACHE_APP_CONFIG.get(appCode).getDataSecurity() != null ? true
|
||||||
@ -40,15 +76,45 @@ public class AppConfigServiceImpl implements AppConfigService {
|
|||||||
public static AppCurrentLimitingConfig getAppCurrentLimitingConfig(String appCode) {
|
public static AppCurrentLimitingConfig getAppCurrentLimitingConfig(String appCode) {
|
||||||
return CACHE_APP_CURRENT_LIMITING_CONFIG.get(appCode);
|
return CACHE_APP_CURRENT_LIMITING_CONFIG.get(appCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApiCurrentLimitingConfig getApiCurrentLimitingConfig(String appCode) {
|
public static ApiCurrentLimitingConfig getApiCurrentLimitingConfig(String appCode) {
|
||||||
return CACHE_API_CURRENT_LIMITING_CONFIG.get(appCode);
|
return CACHE_API_CURRENT_LIMITING_CONFIG.get(appCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static AppConfig getAppConfig(String appCode) {
|
||||||
|
return CACHE_APP_CONFIG.get(appCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VertxConfig getVertxConfig() {
|
||||||
|
return VERTX_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSacAppHeaderKey() {
|
||||||
|
return VERTX_CONFIG.getAppHeaderKey() != null ? VERTX_CONFIG.getAppHeaderKey() : "sacAppCode";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getAppHeaderServiceName() {
|
||||||
|
return VERTX_CONFIG.getAppHeaderServiceName() != null ? VERTX_CONFIG.getAppHeaderServiceName()
|
||||||
|
: "sacAppServiceName";
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 加载vertx配置
|
||||||
|
*/
|
||||||
|
public void loadVertxConfig() {
|
||||||
|
String vertxConfigKey = RedisKeyConfig.VERTX_CONFIG_STRING_KEY;
|
||||||
|
String vertxConfigValue = redisTemplate.opsForValue().get(vertxConfigKey);
|
||||||
|
if (StringUtils.isNotBlank(vertxConfigValue)) {
|
||||||
|
VERTX_CONFIG = JSONObject.parseObject(vertxConfigValue, VertxConfig.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* 从redis加载数据
|
* 从redis加载数据
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public ConcurrentHashMap<String, AppConfig> loadAllConfig() {
|
public void loadAppConfig() throws Exception {
|
||||||
Set<String> set = redisTemplate.opsForZSet().range(RedisKeyConfig.APP_CONFIG_SET_KEY, 0, -1);
|
Set<String> set = redisTemplate.opsForZSet().range(RedisKeyConfig.APP_CONFIG_SET_KEY, 0, -1);
|
||||||
for (String appCode : set) {
|
for (String appCode : set) {
|
||||||
String appCodeKey = RedisKeyConfig.APP_CONFIG_PREFIX_KEY + ":" + appCode;
|
String appCodeKey = RedisKeyConfig.APP_CONFIG_PREFIX_KEY + ":" + appCode;
|
||||||
@ -58,39 +124,56 @@ public class AppConfigServiceImpl implements AppConfigService {
|
|||||||
});
|
});
|
||||||
CACHE_APP_CONFIG.put(appCode, appConfig);
|
CACHE_APP_CONFIG.put(appCode, appConfig);
|
||||||
|
|
||||||
|
// app、api默认限流
|
||||||
|
CACHE_API_CURRENT_LIMITING_CONFIG.put(appCode, appConfig.getApiCurrentLimitingConfig());
|
||||||
|
CACHE_APP_CURRENT_LIMITING_CONFIG.put(appCode, appConfig.getAppCurrentLimitingConfig());
|
||||||
|
|
||||||
|
// app router负载均衡
|
||||||
|
for (SacService sacService : appConfig.getService()) {
|
||||||
|
CACHE_APP_SERVICE.put(appCode + ";" + sacService.getServiceName(), sacService);
|
||||||
|
List<Node> nodeList = new ArrayList<>();
|
||||||
|
// 获取service模式
|
||||||
|
if (StringUtils.equals(sacService.getServiceModel(), "NORMAL")) {
|
||||||
|
Node node = new Node();
|
||||||
|
node.setIp(sacService.getServerAddress().getHost());
|
||||||
|
node.setPort(sacService.getServerAddress().getPort());
|
||||||
|
node.setWeight(0);
|
||||||
|
node.setProtocol(sacService.getServerAddress().getProtocol());
|
||||||
|
nodeList.add(node);
|
||||||
|
} else if (StringUtils.equals(sacService.getServiceModel(), "ROUTE")) {
|
||||||
|
if (sacService.getRouteConfig() != null
|
||||||
|
&& StringUtils.equals(sacService.getRouteConfig().getRouteType(), "WEIGHT_ROUTE")) {
|
||||||
|
for (RouteContent routeContent : sacService.getRouteConfig().getRouteContent()) {
|
||||||
|
Node node = new Node();
|
||||||
|
node.setIp(routeContent.getServerAddress().getHost());
|
||||||
|
node.setPort(routeContent.getServerAddress().getPort());
|
||||||
|
node.setWeight(routeContent.getWeight() != null && routeContent.getWeight() > 0
|
||||||
|
? routeContent.getWeight()
|
||||||
|
: 0);
|
||||||
|
node.setProtocol(sacService.getServerAddress().getProtocol());
|
||||||
|
nodeList.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeList.size() > 0) {
|
||||||
|
// 初始化负载均衡算法
|
||||||
|
String key = appCode + ";" + sacService.getServiceName();
|
||||||
|
SacLoadBalancing sacLoadBalancing = ProxyTool.roundRobin(nodeList);
|
||||||
|
SAC_LOADBALANCING_MAP.put(key, sacLoadBalancing);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 限流
|
|
||||||
ApiCurrentLimitingConfig apiCurrentLimitingConfig = new ApiCurrentLimitingConfig();
|
|
||||||
apiCurrentLimitingConfig.setThreshold(1);
|
|
||||||
apiCurrentLimitingConfig.setTimeWindow(10);
|
|
||||||
apiCurrentLimitingConfig.setDefaultResponse(
|
|
||||||
"{\n" + " \"msg\": \"接口繁忙请重试\",\n" + " \"code\": 501,\n" + " \"data\": \"到达限流阈值\"\n" + "}");
|
|
||||||
CACHE_API_CURRENT_LIMITING_CONFIG.put("dsafdsfadafhappd", apiCurrentLimitingConfig);
|
|
||||||
CACHE_API_CURRENT_LIMITING_CONFIG.put("dsafdsfadafhappC", apiCurrentLimitingConfig);
|
|
||||||
|
|
||||||
AppCurrentLimitingConfig appCurrentLimitingConfig = new AppCurrentLimitingConfig();
|
|
||||||
appCurrentLimitingConfig.setThreshold(1);
|
|
||||||
appCurrentLimitingConfig.setTimeWindow(10);
|
|
||||||
appCurrentLimitingConfig.setDefaultResponse(
|
|
||||||
"{\n" + " \"msg\": \"接口繁忙请重试\",\n" + " \"code\": 501,\n" + " \"data\": \"到达限流阈值\"\n" + "}");
|
|
||||||
CACHE_APP_CURRENT_LIMITING_CONFIG.put("dsafdsfadafhappd", appCurrentLimitingConfig);
|
|
||||||
CACHE_APP_CURRENT_LIMITING_CONFIG.put("dsafdsfadafhappC", appCurrentLimitingConfig);
|
|
||||||
|
|
||||||
log.info("cacheAppConfig:{}", JSON.toJSONString(CACHE_APP_CONFIG));
|
|
||||||
|
|
||||||
return CACHE_APP_CONFIG;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppConfig getAppConfig(String appCode) {
|
public static SacLoadBalancing getSacLoadBalancing(String appCode, String serviceName) {
|
||||||
String appCodeKey = RedisKeyConfig.APP_CONFIG_PREFIX_KEY + ":" + appCode;
|
return SAC_LOADBALANCING_MAP.get(appCode + ";" + serviceName);
|
||||||
String appCodeValue = redisTemplate.opsForValue().get(appCodeKey);
|
}
|
||||||
if (StringUtils.isNotBlank(appCodeValue)) {
|
|
||||||
AppConfig appConfig = JSONObject.parseObject(appCodeValue, AppConfig.class);
|
public static SacService getSacService(String appCode, String serviceName) {
|
||||||
return appConfig;
|
return CACHE_APP_SERVICE.get(appCode + ";" + serviceName);
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
@ -123,19 +206,6 @@ public class AppConfigServiceImpl implements AppConfigService {
|
|||||||
// String queue = RedisKeyConfig.VETX_ENVIRONMENT_KEY+vertxEnvironment+":list";
|
// String queue = RedisKeyConfig.VETX_ENVIRONMENT_KEY+vertxEnvironment+":list";
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
|
||||||
* 加载vertx配置
|
|
||||||
*/
|
|
||||||
public VertxConfig loadVertxConfig() {
|
|
||||||
String vertxConfigKey = RedisKeyConfig.VERTX_CONFIG_STRING_KEY;
|
|
||||||
String vertxConfigValue = redisTemplate.opsForValue().get(vertxConfigKey);
|
|
||||||
if (StringUtils.isNotBlank(vertxConfigValue)) {
|
|
||||||
VertxConfig vertxConfig = JSONObject.parseObject(vertxConfigValue, VertxConfig.class);
|
|
||||||
return vertxConfig;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* 新增、修改
|
* 新增、修改
|
||||||
*
|
*
|
||||||
|
91
sf-vertx/src/main/java/com/sf/vertx/utils/Filter.java
Normal file
91
sf-vertx/src/main/java/com/sf/vertx/utils/Filter.java
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package com.sf.vertx.utils;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import io.vertx.core.Handler;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.streams.ReadStream;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class Filter implements ReadStream<Buffer> {
|
||||||
|
|
||||||
|
private final AtomicBoolean paused = new AtomicBoolean();
|
||||||
|
private ReadStream<Buffer> stream;
|
||||||
|
private Buffer expected = Buffer.buffer();
|
||||||
|
private Handler<Buffer> dataHandler;
|
||||||
|
private Handler<Throwable> exceptionHandler;
|
||||||
|
private Handler<Void> endHandler;
|
||||||
|
|
||||||
|
/***
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
* @param encryption true: 请求参数解密, false: 返回数据 加密
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ReadStream<Buffer> init(String uri, ReadStream<Buffer> s, Boolean encryption) {
|
||||||
|
stream = s;
|
||||||
|
stream.handler(buff -> {
|
||||||
|
if (dataHandler != null) {
|
||||||
|
byte[] bytes = new byte[buff.length()];
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
// bytes[i] = (byte) (('a' - 'A') + buff.getByte(i));
|
||||||
|
bytes[i] = (byte) (buff.getByte(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
String res = new String(bytes);
|
||||||
|
log.info("request uri:{}, return data:{}", uri, res);
|
||||||
|
expected.appendBytes(bytes);
|
||||||
|
dataHandler.handle(Buffer.buffer(bytes));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stream.exceptionHandler(err -> {
|
||||||
|
if (exceptionHandler != null) {
|
||||||
|
exceptionHandler.handle(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stream.endHandler(v -> {
|
||||||
|
if (endHandler != null) {
|
||||||
|
endHandler.handle(v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadStream<Buffer> pause() {
|
||||||
|
paused.set(true);
|
||||||
|
stream.pause();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadStream<Buffer> resume() {
|
||||||
|
stream.resume();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadStream<Buffer> fetch(long amount) {
|
||||||
|
stream.fetch(amount);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadStream<Buffer> exceptionHandler(Handler<Throwable> handler) {
|
||||||
|
exceptionHandler = handler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadStream<Buffer> handler(Handler<Buffer> handler) {
|
||||||
|
dataHandler = handler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadStream<Buffer> endHandler(Handler<Void> handler) {
|
||||||
|
endHandler = handler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
123
sf-vertx/src/main/java/com/sf/vertx/utils/ProxyTool.java
Normal file
123
sf-vertx/src/main/java/com/sf/vertx/utils/ProxyTool.java
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package com.sf.vertx.utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import com.sf.vertx.api.pojo.AppConfig;
|
||||||
|
import com.sf.vertx.api.pojo.Node;
|
||||||
|
import com.sf.vertx.api.pojo.RouteContent;
|
||||||
|
import com.sf.vertx.api.pojo.SacService;
|
||||||
|
import com.sf.vertx.arithmetic.roundRobin.SacLoadBalancing;
|
||||||
|
import com.sf.vertx.arithmetic.roundRobin.WeightedRoundRobin;
|
||||||
|
import com.sf.vertx.service.AppConfigService;
|
||||||
|
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
||||||
|
|
||||||
|
import io.vertx.core.http.HttpServerRequest;
|
||||||
|
import io.vertx.core.net.SocketAddress;
|
||||||
|
import io.vertx.ext.web.handler.HttpException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 反向代理工具类
|
||||||
|
*
|
||||||
|
* @author xy
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ProxyTool {
|
||||||
|
public final static Map<Integer, String> _ERROR = new HashMap<>();
|
||||||
|
static {
|
||||||
|
_ERROR.put(400, "Bad Request");
|
||||||
|
_ERROR.put(401, "Unauthorized");
|
||||||
|
_ERROR.put(403, "Forbidden");
|
||||||
|
_ERROR.put(404, "Not Found");
|
||||||
|
_ERROR.put(413, "Request Entity Too Large");
|
||||||
|
_ERROR.put(415, "Unsupported Media Type");
|
||||||
|
_ERROR.put(500, "Internal Server Error");
|
||||||
|
_ERROR.put(502, "Bad Gateway");
|
||||||
|
_ERROR.put(503, "Service Unavailable");
|
||||||
|
_ERROR.put(504, "Gateway Timeout");
|
||||||
|
_ERROR.put(504, "Gateway Timeout");
|
||||||
|
_ERROR.put(10000, "无法找到路由地址");
|
||||||
|
_ERROR.put(10001, "加解密算法传递错误");
|
||||||
|
};
|
||||||
|
|
||||||
|
public static SocketAddress resolveOriginAddress(HttpServerRequest request) {
|
||||||
|
String appCode = request.getHeader(AppConfigServiceImpl.getSacAppHeaderKey());
|
||||||
|
String appHeaderServiceName = request.getHeader(AppConfigServiceImpl.getAppHeaderServiceName());
|
||||||
|
log.info("uri:{}, header appCode:{},appHeaderServiceName:{}", request.uri(), appCode, appHeaderServiceName);
|
||||||
|
AppConfig appConfig = AppConfigServiceImpl.getAppConfig(appCode);
|
||||||
|
if (appConfig != null) {
|
||||||
|
SacService sacService = AppConfigServiceImpl.getSacService(appCode, appHeaderServiceName);
|
||||||
|
if (sacService != null) {
|
||||||
|
SacLoadBalancing sacLoadBalancing = null;
|
||||||
|
// 获取service模式
|
||||||
|
if (StringUtils.equals(sacService.getServiceModel(), "NORMAL")
|
||||||
|
|| StringUtils.equals(sacService.getServiceModel(), "ROUTE")) {
|
||||||
|
sacLoadBalancing = AppConfigServiceImpl.getSacLoadBalancing(appCode, appHeaderServiceName);
|
||||||
|
} else if (sacService.getRouteConfig() != null
|
||||||
|
&& StringUtils.equals(sacService.getRouteConfig().getRouteType(), "HEADER_ROUTE")) {
|
||||||
|
List<Node> nodeList = new ArrayList<>();
|
||||||
|
for (RouteContent routeContent : sacService.getRouteConfig().getRouteContent()) {
|
||||||
|
// 判断是否uri匹配
|
||||||
|
String headerRouteKey = request.getHeader(routeContent.getHeaderKey());
|
||||||
|
if (routeContent.getHeaderValues() != null
|
||||||
|
&& routeContent.getHeaderValues().contains(headerRouteKey)) {
|
||||||
|
Node node = new Node();
|
||||||
|
node.setIp(routeContent.getServerAddress().getHost());
|
||||||
|
node.setPort(routeContent.getServerAddress().getPort());
|
||||||
|
node.setWeight(0);
|
||||||
|
node.setProtocol(sacService.getServerAddress().getProtocol());
|
||||||
|
nodeList.add(node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeList.size() > 0) {
|
||||||
|
sacLoadBalancing = ProxyTool.roundRobin(nodeList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sacLoadBalancing == null) {
|
||||||
|
log.error("app config error. appCode:{},serviceName:{},RouteType:{}, not find config.", appCode,
|
||||||
|
appHeaderServiceName, sacService.getRouteConfig().getRouteType());
|
||||||
|
throw new HttpException(10000, _ERROR.get(10000));
|
||||||
|
}
|
||||||
|
// TODO 区分https、http
|
||||||
|
Node node = sacLoadBalancing.selectNode();
|
||||||
|
SocketAddress socketAddress = SocketAddress.inetSocketAddress(node.getPort(), node.getIp());
|
||||||
|
log.info("sacLoadBalancing address:{},port:{}", socketAddress.host(), socketAddress.port());
|
||||||
|
return socketAddress;
|
||||||
|
} else {
|
||||||
|
log.error("app config error. appCode:{},serviceName:{}, appCode, serviceName not find config.", appCode,
|
||||||
|
appHeaderServiceName);
|
||||||
|
throw new HttpException(10000, _ERROR.get(10000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.error("app config error. appCode:{},serviceName:{}, appCode, serviceName not find config.", appCode,
|
||||||
|
appHeaderServiceName);
|
||||||
|
throw new HttpException(10000, _ERROR.get(10000));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean regexMatch(String pattern, String target) {
|
||||||
|
return Pattern.matches(pattern, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SacLoadBalancing roundRobin(List<Node> nodeList) {
|
||||||
|
WeightedRoundRobin weightedRoundRobin = new WeightedRoundRobin();
|
||||||
|
for (Node node : nodeList) {
|
||||||
|
int weight = node.getWeight() != null ? node.getWeight() : 1;
|
||||||
|
node.setWeight(weight);
|
||||||
|
node.setCurrentWeight(weight);
|
||||||
|
node.setEffectiveWeight(weight);
|
||||||
|
WeightedRoundRobin.totalWeight += node.getEffectiveWeight();
|
||||||
|
}
|
||||||
|
weightedRoundRobin.init(nodeList);
|
||||||
|
return weightedRoundRobin;
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.vertx.httpproxy.impl;
|
package io.vertx.httpproxy.impl;
|
||||||
|
|
||||||
|
import java.net.ConnectException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -19,10 +20,13 @@ import java.util.function.BiFunction;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.sf.vertx.init.DynamicBuildServer;
|
import com.sf.vertx.api.pojo.DataSecurity;
|
||||||
|
import com.sf.vertx.api.pojo.VertxConfig;
|
||||||
import com.sf.vertx.security.MainSecurity;
|
import com.sf.vertx.security.MainSecurity;
|
||||||
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
import com.sf.vertx.service.impl.AppConfigServiceImpl;
|
||||||
|
import com.sf.vertx.utils.ProxyTool;
|
||||||
|
|
||||||
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.buffer.Buffer;
|
import io.vertx.core.buffer.Buffer;
|
||||||
@ -35,8 +39,10 @@ import io.vertx.core.http.HttpServerRequest;
|
|||||||
import io.vertx.core.http.HttpServerResponse;
|
import io.vertx.core.http.HttpServerResponse;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.core.net.NetSocket;
|
import io.vertx.core.net.NetSocket;
|
||||||
|
import io.vertx.core.net.SocketAddress;
|
||||||
import io.vertx.ext.web.RoutingContext;
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
import io.vertx.ext.web.handler.HttpException;
|
||||||
import io.vertx.httpproxy.Body;
|
import io.vertx.httpproxy.Body;
|
||||||
import io.vertx.httpproxy.HttpProxy;
|
import io.vertx.httpproxy.HttpProxy;
|
||||||
import io.vertx.httpproxy.ProxyContext;
|
import io.vertx.httpproxy.ProxyContext;
|
||||||
@ -163,9 +169,30 @@ public class ReverseProxy implements HttpProxy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 返回错误
|
||||||
|
*
|
||||||
|
* @param proxyRequest
|
||||||
|
* @param sc
|
||||||
|
*/
|
||||||
private void end(ProxyRequest proxyRequest, int sc) {
|
private void end(ProxyRequest proxyRequest, int sc) {
|
||||||
proxyRequest.response().release().setStatusCode(sc).putHeader(HttpHeaders.CONTENT_LENGTH, "0").setBody(null)
|
// TODO 处理反向代理返回结果
|
||||||
.send();
|
if (sc == 502) {
|
||||||
|
JsonObject dataJson = new JsonObject(
|
||||||
|
"{\n" + " \"msg\": \"服务连接失败\",\n" + " \"code\": 502,\n" + " \"data\": \"服务连接失败\"\n" + "}");
|
||||||
|
proxyRequest.response().release().setStatusCode(sc).putHeader("content-type", "application/json")
|
||||||
|
.putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(dataJson.size()))
|
||||||
|
.setBody(Body.body(dataJson.toBuffer())).send();
|
||||||
|
} else {
|
||||||
|
// proxyRequest.response().release().setStatusCode(sc).putHeader(HttpHeaders.CONTENT_LENGTH, "0").setBody(null)
|
||||||
|
// .send();
|
||||||
|
String commonError = "{\n" + " \"msg\": \"网关执行失败,默认错误信息\",\n" + " \"code\": " + sc + ",\n"
|
||||||
|
+ " \"data\": \"网关执行失败,默认错误信息\"\n" + "}";
|
||||||
|
Buffer buffer = Buffer.buffer(commonError);
|
||||||
|
proxyRequest.response().release().setStatusCode(sc).putHeader("content-type", "application/json")
|
||||||
|
.putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(commonError.length()))
|
||||||
|
.setBody(Body.body(buffer)).send();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Future<HttpClientRequest> resolveOrigin(HttpServerRequest proxiedRequest) {
|
private Future<HttpClientRequest> resolveOrigin(HttpServerRequest proxiedRequest) {
|
||||||
@ -225,39 +252,62 @@ public class ReverseProxy implements HttpProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Future<ProxyResponse> sendProxyRequest(ProxyRequest proxyRequest) {
|
private Future<ProxyResponse> sendProxyRequest(ProxyRequest proxyRequest) {
|
||||||
// TODO 改造了这个地方
|
// TODO 服务熔断策略, 如果已经熔断,将剔除负载均衡策略
|
||||||
// 创建一个响应并设置参数
|
|
||||||
// ctx.request().response().setStatusCode(200)
|
|
||||||
// .putHeader("content-type", "text/plain").end(body);
|
|
||||||
// 发起一个请求
|
// 发起一个请求
|
||||||
String sacAppHeaderKey = proxyRequest.headers().get(DynamicBuildServer.SAC_APP_HEADER_KEY);
|
String sacAppHeaderKey = proxyRequest.headers().get(AppConfigServiceImpl.getSacAppHeaderKey());
|
||||||
if (StringUtils.isNotBlank(sacAppHeaderKey) && AppConfigServiceImpl.appDataSecurity(sacAppHeaderKey)) {
|
if (StringUtils.isNotBlank(sacAppHeaderKey) && AppConfigServiceImpl.appDataSecurity(sacAppHeaderKey)) {
|
||||||
String body = ctx.getBodyAsString();
|
String body = ctx.getBodyAsString();
|
||||||
String bodyData = MainSecurity.aesDecrypt(body, "dadddsdfadfadsfa33323223");
|
String bodyData = bodyDecrypt(body, sacAppHeaderKey);
|
||||||
|
VertxConfig vertxConfig = AppConfigServiceImpl.getVertxConfig();
|
||||||
|
long timeout = vertxConfig.getHttpClientOptionsConfig() != null
|
||||||
|
&& vertxConfig.getHttpClientOptionsConfig().getTimeout() > 0
|
||||||
|
? vertxConfig.getHttpClientOptionsConfig().getTimeout()
|
||||||
|
: 1000;
|
||||||
|
long idleTimeout = vertxConfig.getHttpClientOptionsConfig() != null
|
||||||
|
&& vertxConfig.getHttpClientOptionsConfig().getIdleTimeout() > 0
|
||||||
|
? vertxConfig.getHttpClientOptionsConfig().getTimeout()
|
||||||
|
: 1000;
|
||||||
return Future.future(p -> {
|
return Future.future(p -> {
|
||||||
mainWebClient.post(9198, "127.0.0.1", "/vertx/body").sendJson(bodyData, h -> {
|
SocketAddress socketAddress = ProxyTool.resolveOriginAddress(proxyRequest.proxiedRequest());
|
||||||
if (h.succeeded()) {
|
mainWebClient.post(socketAddress.port(), socketAddress.host(), proxyRequest.getURI())
|
||||||
// 释放资源
|
.putHeaders(proxyRequest.headers()).timeout(timeout).idleTimeout(idleTimeout)
|
||||||
proxyRequest.release();
|
.sendJson(bodyData, h -> {
|
||||||
JsonObject responseData = h.result().bodyAsJsonObject();
|
if (h.succeeded()) {
|
||||||
log.info("responseData:{}", responseData);
|
// 释放资源
|
||||||
// 加密
|
proxyRequest.release();
|
||||||
String dataStr = MainSecurity.aesEncrypt(responseData.toString(), "dadddsdfadfadsfa33323223");
|
JsonObject responseData = h.result().bodyAsJsonObject();
|
||||||
log.info("aesEncrypt dataStr:{}", dataStr);
|
log.info("responseData:{}", responseData);
|
||||||
ctx.request().response().setStatusCode(200).putHeader("content-type", "application/json")
|
// 加密
|
||||||
.end(dataStr);
|
String dataStr = bodyEncrypt(bodyData, sacAppHeaderKey);
|
||||||
Buffer buffer = Buffer.buffer(dataStr);
|
log.info("aesEncrypt dataStr:{}", dataStr);
|
||||||
ProxyResponse proxyResponse = proxyRequest.response().setStatusCode(200)
|
Buffer buffer = Buffer.buffer(dataStr);
|
||||||
.putHeader("content-type", "application/json").setBody(Body.body(buffer));
|
ProxyResponse proxyResponse = proxyRequest.response().setStatusCode(200)
|
||||||
p.complete(proxyResponse);
|
.putHeader("content-type", "application/json").setBody(Body.body(buffer));
|
||||||
} else {
|
p.complete(proxyResponse);
|
||||||
p.fail(h.cause());
|
} else {
|
||||||
}
|
log.info("error: {}", h.cause());
|
||||||
});
|
if (h.cause() instanceof ConnectException) {
|
||||||
|
log.info("connection url is error:{}", h.getClass());
|
||||||
|
// TODO 是否开启健康检测, 需要做重试,熔断
|
||||||
|
ThreadUtil.execAsync(() -> {
|
||||||
|
String appCode = proxyRequest.proxiedRequest().getHeader(AppConfigServiceImpl.getSacAppHeaderKey());
|
||||||
|
AppConfigServiceImpl.addAddressRetryStrategy(socketAddress.hostAddress(), appCode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
end(proxyRequest, 502);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Future<HttpClientRequest> f = resolveOrigin(proxyRequest.proxiedRequest());
|
Future<HttpClientRequest> f = resolveOrigin(proxyRequest.proxiedRequest());
|
||||||
f.onFailure(err -> {
|
f.onFailure(err -> {
|
||||||
|
log.info("error:{}", err);
|
||||||
|
if (err instanceof ConnectException) {
|
||||||
|
// TODO 配置重试策略
|
||||||
|
// Connection refused: /127.0.0.1:9199
|
||||||
|
log.info("connection url is error:{}", err.getMessage());
|
||||||
|
}
|
||||||
// Should this be done here ? I don't think so
|
// Should this be done here ? I don't think so
|
||||||
HttpServerRequest proxiedRequest = proxyRequest.proxiedRequest();
|
HttpServerRequest proxiedRequest = proxyRequest.proxiedRequest();
|
||||||
proxiedRequest.resume();
|
proxiedRequest.resume();
|
||||||
@ -295,5 +345,29 @@ public class ReverseProxy implements HttpProxy {
|
|||||||
|
|
||||||
return sendResponse();
|
return sendResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String bodyEncrypt(String body, String sacAppHeaderKey) {
|
||||||
|
DataSecurity dataSecurity = AppConfigServiceImpl.getAppConfig(sacAppHeaderKey).getDataSecurity();
|
||||||
|
switch (dataSecurity.getAlgorithm()) {
|
||||||
|
case "AES":
|
||||||
|
return MainSecurity.aesEncrypt(body, dataSecurity.getPrivateKey());
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log.info(" appcode:{}, encrypt key config is error.", sacAppHeaderKey);
|
||||||
|
throw new HttpException(10001);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String bodyDecrypt(String body, String sacAppHeaderKey) {
|
||||||
|
DataSecurity dataSecurity = AppConfigServiceImpl.getAppConfig(sacAppHeaderKey).getDataSecurity();
|
||||||
|
switch (dataSecurity.getAlgorithm()) {
|
||||||
|
case "AES":
|
||||||
|
return MainSecurity.aesDecrypt(body, dataSecurity.getPrivateKey());
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log.info(" appcode:{}, decrypt key config is error.", sacAppHeaderKey);
|
||||||
|
throw new HttpException(10001);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user