Vert.x网关改造
This commit is contained in:
parent
b20639a02c
commit
2efcb5a275
71
zt-vertx/pom.xml
Normal file
71
zt-vertx/pom.xml
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.smarterFramework</groupId>
|
||||
<artifactId>zt-vertx</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<name>zt-vertx</name>
|
||||
<description>中天vertx网关</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<zt-vertx.version>1.0.0</zt-vertx.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
<fastjson.version>2.0.34</fastjson.version>
|
||||
<hutool.version>5.8.22</hutool.version>
|
||||
<lombok.version>1.18.26</lombok.version>
|
||||
</properties>
|
||||
|
||||
<!-- 依赖声明 -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- 阿里JSON解析器 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.smarterFramework</groupId>
|
||||
<artifactId>zt-vertx-api</artifactId>
|
||||
<version>${zt-vertx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<modules>
|
||||
<module>zt-vertx-service</module>
|
||||
<module>zt-vertx-api</module>
|
||||
</modules>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
41
zt-vertx/zt-vertx-api/pom.xml
Normal file
41
zt-vertx/zt-vertx-api/pom.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>com.smarterFramework</groupId>
|
||||
<artifactId>zt-vertx</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>zt-vertx-api</artifactId>
|
||||
|
||||
<description>
|
||||
gateway网关
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- 阿里JSON解析器 -->
|
||||
<!--<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>-->
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -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定时任务循环执行时间间隔
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AdvancedConfig implements Serializable {
|
||||
private static final long serialVersionUID = -6935223493505821401L;
|
||||
private Integer retryStrategy; // 请求失败重试次数
|
||||
private Integer timeout; // 请求超时时间
|
||||
private String cacheConfig; // 响应数据的缓存策略
|
||||
private String zipConfig;// 响应压缩
|
||||
private String monitorCconfig;// 监控指标
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApiConfig implements Serializable {
|
||||
private static final long serialVersionUID = 5774283776114726263L;
|
||||
private String apiCode;
|
||||
private String uri;
|
||||
private String method; // 大写
|
||||
private Long timeout = 3000L; // 超时时间,单位毫秒
|
||||
private Integer mockDefaultHttpStatus;
|
||||
private String mockDefaultResponse;
|
||||
private List<Strategy> strategy; // 策略
|
||||
private List<MockExpectation> mockExpectations; // mock期望
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AppConfig implements Serializable {
|
||||
private static final long serialVersionUID = 1518165296680157119L;
|
||||
private String appCode; // 应用唯一码, app访问uri添加前缀,用于网关区分多应用
|
||||
private boolean exclusiveService; // 预留字段, 独立端口
|
||||
private Integer exclusiveGatewayCode; // 预留字段, 独享网关配置编号
|
||||
//private EnvironmentConfig environmentConfig; // 环境配置
|
||||
private List<SacService> service; // 服务
|
||||
private DataSecurity dataSecurity; // 数据加解密
|
||||
private Strategy apiCurrentLimitingConfig; // 接口限流配置
|
||||
private Strategy appCurrentLimitingConfig; // APP限流配置
|
||||
private AdvancedConfig advancedConfig; // 高级配置
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DataSecurity implements Serializable {
|
||||
private static final long serialVersionUID = 5034274428665340830L;
|
||||
private String algorithm; // 加密算法(ECC、RSA 和国密(SM2))
|
||||
private String publicKey; // 公钥
|
||||
private String privateKey; // 私钥 (当加密算法为 ECC 或国密SM2时,填写私钥内容。当加密算法为 RSA 时,分别填写公私钥内容。)
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class EnvironmentConfig implements Serializable {
|
||||
private static final long serialVersionUID = -3952046909425019869L;
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class HttpClientOptionsConfig implements Serializable {
|
||||
private static final long serialVersionUID = -6302301564759941097L;
|
||||
private Integer maxPoolSize;
|
||||
private Integer connectTimeout;
|
||||
private Integer http2KeepAliveTimeout;
|
||||
private Integer idleTimeout;
|
||||
private Integer timeout;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 网关服务-接口Mock期望
|
||||
*/
|
||||
@Data
|
||||
public class MockExpectation {
|
||||
|
||||
/**
|
||||
* http状态码
|
||||
*/
|
||||
private Integer httpStatus;
|
||||
|
||||
/**
|
||||
* Mock响应,JSON字符串
|
||||
*/
|
||||
private String mockResponse;
|
||||
|
||||
/**
|
||||
* 匹配条件
|
||||
*/
|
||||
private List<MockMatchCondition> matchConditions;
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 接口Mock匹配条件
|
||||
*/
|
||||
@Data
|
||||
public class MockMatchCondition {
|
||||
|
||||
/**
|
||||
* 参数位置,header、query、body
|
||||
*/
|
||||
private String parameterPosition;
|
||||
|
||||
/**
|
||||
* 参数键值
|
||||
*/
|
||||
private String parameterKey;
|
||||
|
||||
/**
|
||||
* 参数值,包含、不包含类型允许多个值,所以使用数组,其余类型都只有一个值。
|
||||
*/
|
||||
private List<String> parameterValue;
|
||||
|
||||
/**
|
||||
* 匹配类型
|
||||
*/
|
||||
private String matchType;
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 网关服务-接口Mock
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MockResponse {
|
||||
|
||||
/**
|
||||
* http状态码
|
||||
*/
|
||||
private Integer httpStatus;
|
||||
|
||||
/**
|
||||
* Mock响应,JSON字符串
|
||||
*/
|
||||
private String mockResponse;
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* String ip:负载IP <br>
|
||||
* final Integer weight:权重,保存配置的权重 <br>
|
||||
* Integer effectiveWeight:有效权重,轮询的过程权重可能变化 <br>
|
||||
* Integer currentWeight:当前权重,比对该值大小获取节点<br>
|
||||
* 第一次加权轮询时:currentWeight = weight = effectiveWeight <br>
|
||||
* 后面每次加权轮询时:currentWeight 的值都会不断变化,其他权重不变 <br>
|
||||
*/
|
||||
public class Node implements Comparable<Node>, Serializable {
|
||||
private static final long serialVersionUID = -2846988871213226377L;
|
||||
private String ip;
|
||||
private Integer port;
|
||||
private Integer weight;
|
||||
private Integer effectiveWeight;
|
||||
private Integer currentWeight;
|
||||
private String protocol; // 协议
|
||||
|
||||
public Node() {
|
||||
}
|
||||
|
||||
public Node(String ip, Integer weight) {
|
||||
this.ip = ip;
|
||||
this.weight = weight;
|
||||
this.effectiveWeight = weight;
|
||||
this.currentWeight = weight;
|
||||
}
|
||||
|
||||
public Node(String ip, Integer weight, Integer effectiveWeight, Integer currentWeight) {
|
||||
this.ip = ip;
|
||||
this.weight = weight;
|
||||
this.effectiveWeight = effectiveWeight;
|
||||
this.currentWeight = currentWeight;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public Integer getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(Integer weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public Integer getEffectiveWeight() {
|
||||
return effectiveWeight;
|
||||
}
|
||||
|
||||
public void setEffectiveWeight(Integer effectiveWeight) {
|
||||
this.effectiveWeight = effectiveWeight;
|
||||
}
|
||||
|
||||
public Integer getCurrentWeight() {
|
||||
return currentWeight;
|
||||
}
|
||||
|
||||
public void setCurrentWeight(Integer currentWeight) {
|
||||
this.currentWeight = currentWeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Node node) {
|
||||
return currentWeight > node.currentWeight ? 1 : (currentWeight.equals(node.currentWeight) ? 0 : -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ip='" + ip + "', weight=" + weight + ", effectiveWeight=" + effectiveWeight + ", currentWeight="
|
||||
+ currentWeight + "}";
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Router implements Serializable {
|
||||
private static final long serialVersionUID = -4471811880134025210L;
|
||||
private String routeType; // 路由类型 WEIGHT_ROUTE ,HEADER_ROUTE
|
||||
private List<RouteContent> routeContent; // 路由的配置信息
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SacService implements Serializable {
|
||||
private static final long serialVersionUID = -5171112142954536813L;
|
||||
private String serviceName; // 服务名
|
||||
private String serviceType; // 服务类型,SAC=SAC规范服务,OPEN=开放服务
|
||||
private String serviceModel; // 模式, NORMAL, ROUTE
|
||||
private ServerAddress serverAddress; // NORMAL模式的服务地址
|
||||
private List<ApiConfig> apiConfig; // request set header sacApiCode
|
||||
private Router routeConfig; // 路由
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ServerAddress implements Serializable {
|
||||
private static final long serialVersionUID = 7446602403871080553L;
|
||||
private String address;
|
||||
private String protocol; // "http"
|
||||
private String host;
|
||||
private int port;
|
||||
private String path; // 前缀
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/***
|
||||
* 策略
|
||||
* @author xy
|
||||
*
|
||||
*/
|
||||
@Data
|
||||
public class Strategy implements Serializable {
|
||||
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字符串。
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class VertxConfig implements Serializable {
|
||||
private static final long serialVersionUID = -1706421732809219829L;
|
||||
private Integer port; // 启动端口
|
||||
private String appCodeHeaderKey = "sacAppCode";
|
||||
private String apiCodeHeaderKey = "sacApiCode";
|
||||
private String rateLimitModel = "local"; // local,redis 负载均衡模式
|
||||
private VertxOptionsConfig vertxOptionsConfig;
|
||||
private HttpClientOptionsConfig httpClientOptionsConfig; // 配置Vert端口连接池
|
||||
private AddressRetryStrategy addressRetryStrategy;
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.sf.vertx.api.pojo;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class VertxOptionsConfig {
|
||||
private int eventLoopPoolSize;
|
||||
private int workerPoolSize ;
|
||||
private int internalBlockingPoolSize;
|
||||
private long blockedThreadCheckInterval; // 3000000000 打印thread wait日志
|
||||
private long maxEventLoopExecuteTime;
|
||||
private long maxWorkerExecuteTime;
|
||||
//private ClusterManager clusterManager;
|
||||
private boolean haEnabled;
|
||||
private int quorumSize;
|
||||
private String haGroup;
|
||||
private long warningExceptionTime;
|
||||
private boolean preferNativeTransport;
|
||||
private TimeUnit maxEventLoopExecuteTimeUnit;
|
||||
private TimeUnit maxWorkerExecuteTimeUnit;
|
||||
private TimeUnit warningExceptionTimeUnit;
|
||||
private TimeUnit blockedThreadCheckIntervalUnit;
|
||||
private boolean disableTCCL;
|
||||
private Boolean useDaemonThread;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.sf.vertx.arithmetic.roundRobin;
|
||||
|
||||
import com.sf.vertx.api.pojo.Node;
|
||||
|
||||
public interface SacLoadBalancing {
|
||||
Node selectNode();
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package com.sf.vertx.arithmetic.roundRobin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.sf.vertx.api.pojo.Node;
|
||||
|
||||
/**
|
||||
* 加权轮询算法
|
||||
* https://www.cnblogs.com/dennyLee2025/p/16128477.html
|
||||
*/
|
||||
public class WeightedRoundRobin implements SacLoadBalancing {
|
||||
|
||||
private List<Node> nodes = new ArrayList<>();
|
||||
// 权重之和
|
||||
public Integer totalWeight = 0;
|
||||
// 准备模拟数据
|
||||
// static {
|
||||
// nodes.add(new Node("192.168.1.101", 1));
|
||||
// nodes.add(new Node("192.168.1.102", 3));
|
||||
// nodes.add(new Node("192.168.1.103", 2));
|
||||
// nodes.forEach(node -> totalWeight += node.getEffectiveWeight());
|
||||
// }
|
||||
|
||||
public void init(List<Node> serverAddressList) {
|
||||
nodes = serverAddressList;
|
||||
nodes.forEach(node -> totalWeight += node.getEffectiveWeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照当前权重(currentWeight)最大值获取IP
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public Node selectNode() {
|
||||
if (nodes == null || nodes.size() <= 0)
|
||||
return null;
|
||||
if (nodes.size() == 1)
|
||||
return nodes.get(0);
|
||||
|
||||
Node nodeOfMaxWeight = null; // 保存轮询选中的节点信息
|
||||
// 之前写错的代码
|
||||
// synchronized (nodes){
|
||||
synchronized (WeightedRoundRobin.class) {
|
||||
// 打印信息对象:避免并发时打印出来的信息太乱,不利于观看结果
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(Thread.currentThread().getName() + "==加权轮询--[当前权重]值的变化:" + printCurrentWeight(nodes));
|
||||
|
||||
// 选出当前权重最大的节点
|
||||
Node tempNodeOfMaxWeight = null;
|
||||
for (Node node : nodes) {
|
||||
if (tempNodeOfMaxWeight == null)
|
||||
tempNodeOfMaxWeight = node;
|
||||
else
|
||||
tempNodeOfMaxWeight = tempNodeOfMaxWeight.compareTo(node) > 0 ? tempNodeOfMaxWeight : node;
|
||||
}
|
||||
// 必须new个新的节点实例来保存信息,否则引用指向同一个堆实例,后面的set操作将会修改节点信息
|
||||
nodeOfMaxWeight = new Node(tempNodeOfMaxWeight.getIp(), tempNodeOfMaxWeight.getWeight(),
|
||||
tempNodeOfMaxWeight.getEffectiveWeight(), tempNodeOfMaxWeight.getCurrentWeight());
|
||||
nodeOfMaxWeight.setProtocol(tempNodeOfMaxWeight.getProtocol());
|
||||
nodeOfMaxWeight.setPort(tempNodeOfMaxWeight.getPort());
|
||||
// 调整当前权重比:按权重(effectiveWeight)的比例进行调整,确保请求分发合理。
|
||||
tempNodeOfMaxWeight.setCurrentWeight(tempNodeOfMaxWeight.getCurrentWeight() - totalWeight);
|
||||
sb.append(" -> " + printCurrentWeight(nodes));
|
||||
|
||||
nodes.forEach(node -> node.setCurrentWeight(node.getCurrentWeight() + node.getEffectiveWeight()));
|
||||
|
||||
sb.append(" -> " + printCurrentWeight(nodes));
|
||||
System.out.println(sb); // 打印权重变化过程
|
||||
}
|
||||
return nodeOfMaxWeight;
|
||||
}
|
||||
|
||||
// 格式化打印信息
|
||||
private String printCurrentWeight(List<Node> nodes) {
|
||||
StringBuffer stringBuffer = new StringBuffer("[");
|
||||
nodes.forEach(node -> stringBuffer.append(node.getCurrentWeight() + ","));
|
||||
return stringBuffer.substring(0, stringBuffer.length() - 1) + "]";
|
||||
}
|
||||
|
||||
// 并发测试:两个线程循环获取节点
|
||||
public static void main(String[] args) {
|
||||
List<Node> serverAddressList = new ArrayList<>();
|
||||
Node node1 = new Node("192.168.1.101", 1);
|
||||
serverAddressList.add(node1);
|
||||
Node node2 = new Node("192.168.1.102", 3);
|
||||
serverAddressList.add(node2);
|
||||
|
||||
Node node3 = new Node("192.168.1.103", 2);
|
||||
serverAddressList.add(node3);
|
||||
Thread thread = new Thread(() -> {
|
||||
WeightedRoundRobin weightedRoundRobin1 = new WeightedRoundRobin();
|
||||
weightedRoundRobin1.init(serverAddressList);
|
||||
for (int i = 1; i <= weightedRoundRobin1.totalWeight; i++) {
|
||||
Node node = weightedRoundRobin1.selectNode();
|
||||
System.out.println(Thread.currentThread().getName() + "==第" + i + "次轮询选中[当前权重最大]的节点:" + node + "\n");
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
WeightedRoundRobin weightedRoundRobin2 = new WeightedRoundRobin();
|
||||
weightedRoundRobin2.init(serverAddressList);
|
||||
for (int i = 1; i <= weightedRoundRobin2.totalWeight; i++) {
|
||||
Node node = weightedRoundRobin2.selectNode();
|
||||
System.out.println(Thread.currentThread().getName() + "==第" + i + "次轮询选中[当前权重最大]的节点:" + node + "\n");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package com.sf.vertx.enums;
|
||||
|
||||
/**
|
||||
* VertX网关错误
|
||||
*
|
||||
* @author zoukun
|
||||
*/
|
||||
public enum GatewayError {
|
||||
// CLIENT ERROR
|
||||
BAD_REQUEST(400, "Bad Request", ErrorType.CLIENT_ERROR),
|
||||
UNAUTHORIZED(401, "Unauthorized", ErrorType.CLIENT_ERROR),
|
||||
FORBIDDEN(403, "Forbidden", ErrorType.CLIENT_ERROR),
|
||||
NOT_FOUND(404, "Not Found", ErrorType.CLIENT_ERROR),
|
||||
METHOD_NOT_ALLOWED(405, "Method Not Allowed", ErrorType.CLIENT_ERROR),
|
||||
NOT_ACCEPTABLE(406, "Not Acceptable", ErrorType.CLIENT_ERROR),
|
||||
PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required", ErrorType.CLIENT_ERROR),
|
||||
REQUEST_TIMEOUT(408, "Request Timeout", ErrorType.CLIENT_ERROR),
|
||||
CONFLICT(409, "Conflict", ErrorType.CLIENT_ERROR),
|
||||
GONE(410, "Gone", ErrorType.CLIENT_ERROR),
|
||||
LENGTH_REQUIRED(411, "Length Required", ErrorType.CLIENT_ERROR),
|
||||
PRECONDITION_FAILED(412, "Precondition Failed", ErrorType.CLIENT_ERROR),
|
||||
PAYLOAD_TOO_LARGE(413, "Payload Too Large", ErrorType.CLIENT_ERROR),
|
||||
URI_TOO_LONG(414, "URI Too Long", ErrorType.CLIENT_ERROR),
|
||||
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type", ErrorType.CLIENT_ERROR),
|
||||
REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable", ErrorType.CLIENT_ERROR),
|
||||
EXPECTATION_FAILED(417, "Expectation Failed", ErrorType.CLIENT_ERROR),
|
||||
UNPROCESSABLE_ENTITY(422, "Unprocessable Entity", ErrorType.CLIENT_ERROR),
|
||||
LOCKED(423, "Locked", ErrorType.CLIENT_ERROR),
|
||||
FAILED_DEPENDENCY(424, "Failed Dependency", ErrorType.CLIENT_ERROR),
|
||||
TOO_EARLY(425, "Too Early", ErrorType.CLIENT_ERROR),
|
||||
UPGRADE_REQUIRED(426, "Upgrade Required", ErrorType.CLIENT_ERROR),
|
||||
PRECONDITION_REQUIRED(428, "Precondition Required", ErrorType.CLIENT_ERROR),
|
||||
TOO_MANY_REQUESTS(429, "Too Many Requests", ErrorType.CLIENT_ERROR),
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large", ErrorType.CLIENT_ERROR),
|
||||
UNAVAILABLE_FOR_LEGAL_REASONS(451, "Unavailable For Legal Reasons", ErrorType.CLIENT_ERROR),
|
||||
|
||||
// SERVER_ERROR
|
||||
INTERNAL_SERVER_ERROR(500, "Internal Server Error", ErrorType.SERVER_ERROR),
|
||||
NOT_IMPLEMENTED(501, "Not Implemented", ErrorType.SERVER_ERROR),
|
||||
BAD_GATEWAY(502, "Bad Gateway", ErrorType.SERVER_ERROR),
|
||||
SERVICE_UNAVAILABLE(503, "Service Unavailable", ErrorType.SERVER_ERROR),
|
||||
GATEWAY_TIMEOUT(504, "Gateway Timeout", ErrorType.SERVER_ERROR),
|
||||
HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported", ErrorType.SERVER_ERROR),
|
||||
INSUFFICIENT_STORAGE(507, "Insufficient Storage", ErrorType.SERVER_ERROR),
|
||||
NOT_EXTENDED(510, "Not Extended", ErrorType.SERVER_ERROR),
|
||||
NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required", ErrorType.SERVER_ERROR),
|
||||
|
||||
// SERVICE_ERROR
|
||||
DEFAULT_SERVICE_ERROR(10000, "Service request failed, please try again later", ErrorType.SERVICE_ERROR), // 默认业务错误提示
|
||||
APP_ACCESS_PROHIBITED(10001, "应用禁止访问,请联系管理员", ErrorType.SERVICE_ERROR),
|
||||
APP_SERVICE_NOT_FOUND(10002, "APP service not found", ErrorType.SERVICE_ERROR),
|
||||
API_SERVICE_NOT_FOUND(10003, "API service not found", ErrorType.SERVICE_ERROR),
|
||||
PARAMETER_TRANSFER_ERROR(10010, "参数传递错误", ErrorType.SERVICE_ERROR),
|
||||
UNABLE_TO_FIND_ROUTING_ADDRESS(10011, "无法找到路由地址", ErrorType.SERVICE_ERROR),
|
||||
UNABLE_TO_FIND_MATCHING_ENCRYPTION_ALGORITHM(10012, "无法找到匹配的加解密算法", ErrorType.SERVICE_ERROR),
|
||||
UNABLE_TO_FIND_MATCHING_CIRCUIT_BREAKER_STRATEGY(10013, "无法找到匹配的熔断策略", ErrorType.SERVICE_ERROR),
|
||||
REVERSE_PROXY_EXECUTION_ERROR(10014, "反向代理执行错误", ErrorType.SERVICE_ERROR),
|
||||
REQUEST_URL_RESTRICTED_BY_FLOW(10015, "请求url被限流", ErrorType.SERVICE_ERROR),
|
||||
REQUEST_URL_IS_BROKEN(10016, "请求url被熔断", ErrorType.SERVICE_ERROR),
|
||||
APP_REQUEST_URL_RESTRICTED_BY_FLOW(10017, "应用请求url被限流", ErrorType.SERVICE_ERROR),
|
||||
;
|
||||
|
||||
private final int code;
|
||||
private final String reasonPhrase;
|
||||
|
||||
private final ErrorType errorType;
|
||||
|
||||
GatewayError(int code, String reasonPhrase, ErrorType errorType) {
|
||||
this.code = code;
|
||||
this.reasonPhrase = reasonPhrase;
|
||||
this.errorType = errorType;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public ErrorType getErrorType() {
|
||||
return errorType;
|
||||
}
|
||||
|
||||
public String getReasonPhrase() {
|
||||
return reasonPhrase;
|
||||
}
|
||||
|
||||
public static GatewayError getByCode(int code) {
|
||||
for (GatewayError value : GatewayError.values()) {
|
||||
if (value.code == code) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网关错误类型
|
||||
*/
|
||||
public enum ErrorType {
|
||||
|
||||
CLIENT_ERROR(4),
|
||||
SERVER_ERROR(5),
|
||||
/**
|
||||
* 业务错误,建议业务错误返回HTTP状态码为200,业务码放在返回信息当中。
|
||||
*/
|
||||
SERVICE_ERROR(10),
|
||||
;
|
||||
|
||||
private final int value;
|
||||
|
||||
ErrorType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int value() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.sf.vertx.enums;
|
||||
|
||||
|
||||
/**
|
||||
* 网关路由类型
|
||||
*
|
||||
* @author zoukun
|
||||
*/
|
||||
public enum GatewayRouteType {
|
||||
WEIGHT_ROUTE("WEIGHT_ROUTE", "权重路由策略"),
|
||||
HEADER_ROUTE("HEADER_ROUTE", "请求头路由策略"),
|
||||
;
|
||||
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
GatewayRouteType(String code, String info) {
|
||||
this.code = code;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public static GatewayRouteType getByCode(String code) {
|
||||
for (GatewayRouteType value : GatewayRouteType.values()) {
|
||||
if (value.code.equals(code)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.sf.vertx.enums;
|
||||
|
||||
|
||||
/**
|
||||
* 网关服务模式
|
||||
*
|
||||
* @author zoukun
|
||||
*/
|
||||
public enum GatewayServiceModel {
|
||||
NORMAL("NORMAL", "普通模式"),
|
||||
ROUTE("ROUTE", "路由模式"),
|
||||
;
|
||||
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
GatewayServiceModel(String code, String info) {
|
||||
this.code = code;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public static GatewayServiceModel getByCode(String code) {
|
||||
for (GatewayServiceModel value : GatewayServiceModel.values()) {
|
||||
if (value.code.equals(code)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.sf.vertx.enums;
|
||||
|
||||
|
||||
/**
|
||||
* 网关服务类型
|
||||
*
|
||||
* @author zoukun
|
||||
*/
|
||||
public enum GatewayServiceType {
|
||||
SAC("SAC", "SAC规范服务"),
|
||||
OPEN("OPEN", "开放服务"),
|
||||
;
|
||||
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
GatewayServiceType(String code, String info) {
|
||||
this.code = code;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public static GatewayServiceType getByCode(String code) {
|
||||
for (GatewayServiceType value : GatewayServiceType.values()) {
|
||||
if (value.code.equals(code)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.sf.vertx.enums;
|
||||
|
||||
/**
|
||||
* 匹配方式
|
||||
*
|
||||
* @author zoukun
|
||||
*/
|
||||
public enum MatchType
|
||||
{
|
||||
EQ("EQ", "等于"),
|
||||
NOT_EQ("NOT_EQ", "不等于"),
|
||||
GT("GT", "大于"),
|
||||
GE("GE", "大于或等于"),
|
||||
LT("LT", "小于"),
|
||||
LE("LE", "小于或等于"),
|
||||
IN("IN", "包含"),
|
||||
NOT_IN("NOT_IN", "不包含"),
|
||||
IS_NULL("IS_NULL", "等于空"),
|
||||
NOT_NULL("NOT_NULL", "不等于空"),
|
||||
;
|
||||
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
MatchType(String code, String info)
|
||||
{
|
||||
this.code = code;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getInfo()
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
public static MatchType getByCode(String code){
|
||||
for (MatchType value : MatchType.values()) {
|
||||
if (value.code.equalsIgnoreCase(code)){
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.sf.vertx.enums;
|
||||
|
||||
/**
|
||||
* 支持的请求方法
|
||||
*
|
||||
* @author zoukun
|
||||
*/
|
||||
public enum RequestMethod
|
||||
{
|
||||
GET("GET", "GET"),
|
||||
POST("POST", "POST"),
|
||||
PUT("PUT", "PUT"),
|
||||
DELETE("DELETE", "DELETE"),
|
||||
HEAD("HEAD", "HEAD"),
|
||||
;
|
||||
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
RequestMethod(String code, String info)
|
||||
{
|
||||
this.code = code;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getInfo()
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
public static RequestMethod getByCode(String code){
|
||||
for (RequestMethod value : RequestMethod.values()) {
|
||||
if (value.code.equalsIgnoreCase(code)){
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
8
zt-vertx/zt-vertx-service/Dockerfile
Normal file
8
zt-vertx/zt-vertx-service/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM openjdk:17
|
||||
# 复制jar文件到路径
|
||||
COPY sf-vertx/target/sf-vertx.jar /usr/local/sf-vertx.jar
|
||||
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
|
||||
# 指定路径
|
||||
WORKDIR /usr/local
|
||||
|
||||
ENTRYPOINT ["java","-jar","sf-vertx.jar"]
|
242
zt-vertx/zt-vertx-service/pom.xml
Normal file
242
zt-vertx/zt-vertx-service/pom.xml
Normal file
@ -0,0 +1,242 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>com.smarterFramework</groupId>
|
||||
<artifactId>zt-vertx</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>zt-vertx-service</artifactId>
|
||||
|
||||
<description>
|
||||
gateway网关
|
||||
</description>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<vertx.version>4.5.7</vertx.version>
|
||||
<hystrix.version>1.5.2</hystrix.version>
|
||||
<resilience4j.version>2.2.0</resilience4j.version>
|
||||
<fastjson.version>2.0.34</fastjson.version>
|
||||
<commons.collections.version>3.2.2</commons.collections.version>
|
||||
<hutool.version>5.8.22</hutool.version>
|
||||
<lombok.version>1.18.26</lombok.version>
|
||||
<log4j.version>2.17.2</log4j.version>
|
||||
<slf4j.version>1.7.25</slf4j.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-bom</artifactId>
|
||||
<version>${resilience4j.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-stack-depchain</artifactId>
|
||||
<version>${vertx.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web-proxy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web-client</artifactId>
|
||||
</dependency>
|
||||
<!-- yaml配置文件支持 -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-config-yaml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-redis-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.smarterFramework</groupId>
|
||||
<artifactId>sf-vertx-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<!--<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-hazelcast</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-unit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>-->
|
||||
<!-- Generators -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-codegen</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-docgen</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>-->
|
||||
|
||||
|
||||
<!--<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-circuit-breaker</artifactId>
|
||||
</dependency>-->
|
||||
<!--<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-junit5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<!-- 阿里JSON解析器 -->
|
||||
<!-- <dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>-->
|
||||
|
||||
<!-- 日志依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>${log4j.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j-impl</artifactId>
|
||||
<version>${log4j.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-ratelimiter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-bulkhead</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- collections工具类 -->
|
||||
<!--<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<version>${commons.collections.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!– for metrics –>
|
||||
<groupId>org.hdrhistogram</groupId>
|
||||
<artifactId>HdrHistogram</artifactId>
|
||||
<version>2.1.12</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!– For tests and examples –>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
<version>${hystrix.version}</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.hdrhistogram</groupId>
|
||||
<artifactId>HdrHistogram</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jayway.restassured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<version>2.8.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!–<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>–>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-consul-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.studio</groupId>
|
||||
<artifactId>org.apache.commons.codec</artifactId>
|
||||
<version>1.8</version>
|
||||
</dependency>-->
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,21 @@
|
||||
package com.sf.vertx;
|
||||
|
||||
|
||||
import com.sf.vertx.init.GatewayConfigVerticle;
|
||||
import com.sf.vertx.init.GatewayRPCVerticle;
|
||||
import io.vertx.core.Vertx;
|
||||
|
||||
/**
|
||||
* 启动程序
|
||||
*
|
||||
* @author ztzh
|
||||
*/
|
||||
public class VertxStart {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("vertx.logger-delegate-factory-class-name","io.vertx.core.logging.Log4j2LogDelegateFactory");
|
||||
Vertx vertx = Vertx.vertx();
|
||||
vertx.deployVerticle(GatewayConfigVerticle.class.getName());
|
||||
vertx.deployVerticle(GatewayRPCVerticle.class.getName());
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
package com.sf.vertx.config;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONReader;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
|
||||
*/
|
||||
/**
|
||||
* Redis使用FastJson序列化
|
||||
*
|
||||
* @author ztzh
|
||||
*//*
|
||||
|
||||
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
|
||||
{
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
private Class<T> clazz;
|
||||
|
||||
public FastJson2JsonRedisSerializer(Class<T> clazz)
|
||||
{
|
||||
super();
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(T t) throws SerializationException
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
return new byte[0];
|
||||
}
|
||||
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(byte[] bytes) throws SerializationException
|
||||
{
|
||||
if (bytes == null || bytes.length <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String str = new String(bytes, DEFAULT_CHARSET);
|
||||
|
||||
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
package com.sf.vertx.config;
|
||||
|
||||
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
*/
|
||||
/**
|
||||
* redis配置
|
||||
*
|
||||
* @author ztzh
|
||||
*//*
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedisConfig extends CachingConfigurerSupport
|
||||
{
|
||||
@Bean(name = "redisTemplate")
|
||||
@SuppressWarnings(value = { "unchecked", "rawtypes" })
|
||||
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
|
||||
{
|
||||
RedisTemplate<Object, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
|
||||
|
||||
// 使用StringRedisSerializer来序列化和反序列化redis的key值
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(serializer);
|
||||
|
||||
// Hash的key也采用StringRedisSerializer的序列化方式
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultRedisScript<Long> limitScript()
|
||||
{
|
||||
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.setScriptText(limitScriptText());
|
||||
redisScript.setResultType(Long.class);
|
||||
return redisScript;
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* 限流脚本
|
||||
*//*
|
||||
|
||||
private String limitScriptText()
|
||||
{
|
||||
return "local key = KEYS[1]\n" +
|
||||
"local count = tonumber(ARGV[1])\n" +
|
||||
"local time = tonumber(ARGV[2])\n" +
|
||||
"local current = redis.call('get', key);\n" +
|
||||
"if current and tonumber(current) > count then\n" +
|
||||
" return tonumber(current);\n" +
|
||||
"end\n" +
|
||||
"current = redis.call('incr', key)\n" +
|
||||
"if tonumber(current) == 1 then\n" +
|
||||
" redis.call('expire', key, time)\n" +
|
||||
"end\n" +
|
||||
"return tonumber(current);";
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
package com.sf.vertx.constans;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class RedisKeyConfig {
|
||||
|
||||
@Value("${server.vertx.environment}")
|
||||
private String vertxEnvironment;
|
||||
|
||||
public static final String BASE_REDIS_KEY = "vertx:config:";
|
||||
public static String APP_CONFIG_PREFIX_KEY = null;
|
||||
public static String APP_CONFIG_SET_KEY = null;
|
||||
public static String APP_CURRENT_LIMITING_CONFIG_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() {
|
||||
APP_CONFIG_PREFIX_KEY = BASE_REDIS_KEY + vertxEnvironment;
|
||||
APP_CONFIG_SET_KEY = APP_CONFIG_PREFIX_KEY + ":set";
|
||||
APP_CURRENT_LIMITING_CONFIG_KEY = APP_CONFIG_PREFIX_KEY + ":app:limiting";
|
||||
VERTX_CONFIG_STRING_KEY = APP_CONFIG_PREFIX_KEY + ":vertx";
|
||||
VERTX_ADDRESS_RETRY_STRATEGY_KEY = APP_CONFIG_PREFIX_KEY + ":addAddressRetryStrategy";
|
||||
VERTX_ADDRESS_RETRY_STRATEGY_SET_KEY = VERTX_ADDRESS_RETRY_STRATEGY_KEY + ":set";
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,21 @@
|
||||
package com.sf.vertx.constans;
|
||||
|
||||
public class SACConstants {
|
||||
|
||||
public static final String CACHE_KEY_CONNECTOR = ":";
|
||||
|
||||
public static final String APP_CONFIG = "appConfig";
|
||||
|
||||
public static final String API_CONFIG = "apiConfig";
|
||||
|
||||
public static final String API_SERVICE_TYPE = "apiServiceType";
|
||||
|
||||
public static final String CIRCUIT_BREAKER = "CIRCUIT_BREAKER";
|
||||
|
||||
/**
|
||||
* 业务码 key
|
||||
*/
|
||||
public static final String GATEWAY_SERVICE_CODE = "serviceCode";
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
package com.sf.vertx.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.sf.vertx.api.pojo.AppConfig;
|
||||
import com.sf.vertx.api.pojo.VertxConfig;
|
||||
import com.sf.vertx.handle.AppConfigHandler;
|
||||
import com.sf.vertx.service.AppConfigService;
|
||||
|
||||
*/
|
||||
/***
|
||||
* 测试redis
|
||||
*
|
||||
* @author xy
|
||||
*
|
||||
*//*
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/vertx")
|
||||
public class AppConfigController {
|
||||
@Autowired
|
||||
private AppConfigService appConfigService;
|
||||
|
||||
@PostMapping("/app/config")
|
||||
public JSONObject addAppConfig(@RequestBody AppConfig appConfig) {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
appConfigService.saveAppConfig(appConfig);
|
||||
json.put("code", 200);
|
||||
json.put("msg", "success");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
json.put("code", 500);
|
||||
json.put("msg", e.getMessage());
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@DeleteMapping("/app/config")
|
||||
public JSONObject deleteAppConfig(@RequestBody AppConfig appConfig) {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
appConfigService.deleteAppConfig(appConfig);
|
||||
json.put("code", 200);
|
||||
json.put("msg", "success");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
json.put("code", 500);
|
||||
json.put("msg", e.getMessage());
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@PostMapping("/app/disable/appCode")
|
||||
public JSONObject addDisabledAppcode(@RequestBody AppConfig appConfig) {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
AppConfigHandler.addDisabledAppcode(appConfig.getAppCode());
|
||||
json.put("code", 200);
|
||||
json.put("msg", "success");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
json.put("code", 500);
|
||||
json.put("msg", e.getMessage());
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@DeleteMapping("/app/disable/appCode")
|
||||
public JSONObject removeDisabledAppcode(@RequestBody AppConfig appConfig) {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
AppConfigHandler.removeDisabledAppcode(appConfig.getAppCode());
|
||||
json.put("code", 200);
|
||||
json.put("msg", "success");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
json.put("code", 500);
|
||||
json.put("msg", e.getMessage());
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@PostMapping("/vertx/config")
|
||||
public JSONObject saveVertxConfig(@RequestBody VertxConfig vertxConfig) {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
appConfigService.saveVertxConfig(vertxConfig);
|
||||
json.put("code", 200);
|
||||
json.put("msg", "success");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
json.put("code", 500);
|
||||
json.put("msg", e.getMessage());
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
@ -0,0 +1,39 @@
|
||||
package com.sf.vertx.exception;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
public final class HttpMockException extends RuntimeException {
|
||||
private static final long serialVersionUID = -6984329893540102440L;
|
||||
private final int statusCode;
|
||||
private final String payload;
|
||||
|
||||
public HttpMockException() {
|
||||
this(500, null, null);
|
||||
}
|
||||
|
||||
public HttpMockException(int statusCode) {
|
||||
this(statusCode, null, null);
|
||||
}
|
||||
|
||||
public HttpMockException(int statusCode, Throwable cause) {
|
||||
this(statusCode, null, cause);
|
||||
}
|
||||
|
||||
public HttpMockException(int statusCode, String payload) {
|
||||
this(statusCode, payload, null);
|
||||
}
|
||||
|
||||
public HttpMockException(int statusCode, String payload, Throwable cause) {
|
||||
super(HttpResponseStatus.valueOf(statusCode).reasonPhrase(), cause, false, false);
|
||||
this.statusCode = statusCode;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public String getPayload() {
|
||||
return payload;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.sf.vertx.exception;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
public class MockException extends RuntimeException {
|
||||
private static final long serialVersionUID = 7975954645547803571L;
|
||||
private final int statusCode;
|
||||
private final String payload;
|
||||
|
||||
public MockException() {
|
||||
this(500, null, null);
|
||||
}
|
||||
|
||||
public MockException(int statusCode) {
|
||||
this(statusCode, null, null);
|
||||
}
|
||||
|
||||
public MockException(int statusCode, Throwable cause) {
|
||||
this(statusCode, null, cause);
|
||||
}
|
||||
|
||||
public MockException(int statusCode, String payload) {
|
||||
this(statusCode, payload, null);
|
||||
}
|
||||
|
||||
public MockException(int statusCode, String payload, Throwable cause) {
|
||||
super(HttpResponseStatus.valueOf(statusCode).reasonPhrase(), cause, false, false);
|
||||
this.statusCode = statusCode;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public String getPayload() {
|
||||
return payload;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.sf.vertx.exception;
|
||||
|
||||
import com.sf.vertx.enums.GatewayError;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*/
|
||||
public class ServiceException extends RuntimeException {
|
||||
private static final long serialVersionUID = 7975954645547803572L;
|
||||
private final int statusCode;
|
||||
private final String payload;
|
||||
|
||||
public ServiceException() {
|
||||
this(GatewayError.DEFAULT_SERVICE_ERROR);
|
||||
}
|
||||
|
||||
public ServiceException(GatewayError gatewayError, String payload) {
|
||||
this(gatewayError.getCode(), payload, null);
|
||||
}
|
||||
|
||||
public ServiceException(GatewayError gatewayError) {
|
||||
this(gatewayError.getCode(), gatewayError.getReasonPhrase(), null);
|
||||
}
|
||||
|
||||
private ServiceException(int statusCode, String payload, Throwable cause) {
|
||||
super("(" + statusCode + ")" + payload, cause, false, false);
|
||||
this.statusCode = statusCode;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public String getPayload() {
|
||||
return payload;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import io.vertx.codegen.annotations.VertxGen;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
*/
|
||||
/***
|
||||
* 接口mock处理
|
||||
*
|
||||
* @author zk
|
||||
*
|
||||
*//*
|
||||
|
||||
@VertxGen
|
||||
public interface ApiMockHandler extends Handler<RoutingContext> {
|
||||
|
||||
static ApiMockHandler create() {
|
||||
return new ApiMockHandlerImpl();
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import com.sf.vertx.api.pojo.MockResponse;
|
||||
import com.sf.vertx.enums.GatewayError;
|
||||
import com.sf.vertx.exception.MockException;
|
||||
import com.sf.vertx.exception.ServiceException;
|
||||
import com.sf.vertx.utils.AppUtils;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ApiMockHandlerImpl implements ApiMockHandler {
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext rc) {
|
||||
try {
|
||||
// mock
|
||||
MockResponse mockResponse = AppUtils.mock(rc);
|
||||
if (mockResponse != null) {
|
||||
rc.fail(new MockException(mockResponse.getHttpStatus(), mockResponse.getMockResponse()));
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("ApiMockHandlerImpl:",e);
|
||||
rc.fail(new ServiceException(GatewayError.DEFAULT_SERVICE_ERROR));
|
||||
return;
|
||||
}
|
||||
rc.next();
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import io.vertx.codegen.annotations.VertxGen;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
*/
|
||||
/***
|
||||
* 限流熔断, redis存储
|
||||
* @author xy
|
||||
*
|
||||
*//*
|
||||
|
||||
@VertxGen
|
||||
public interface ApiRateLimitHandler extends Handler<RoutingContext> {
|
||||
|
||||
static ApiRateLimitHandler create(String instance) {
|
||||
switch (instance) {
|
||||
case "redis":
|
||||
//RedisRateLimiter redisRateLimiter = new RedisRateLimiter();
|
||||
//return new RateLimitHandlerRedisImpl(redisRateLimiter);
|
||||
default:
|
||||
// 本地缓存
|
||||
return new ApiRateLimitHandlerImpl();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import com.sf.vertx.constans.RedisKeyConfig;
|
||||
import com.sf.vertx.enums.GatewayError;
|
||||
import com.sf.vertx.exception.ServiceException;
|
||||
import com.sf.vertx.pojo.SacCurrentLimiting;
|
||||
|
||||
import io.github.resilience4j.core.functions.CheckedRunnable;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiter;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.handler.HttpException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.sf.vertx.constans.SACConstants.CACHE_KEY_CONNECTOR;
|
||||
|
||||
*/
|
||||
/***
|
||||
* 内存存储
|
||||
*
|
||||
* @author xy
|
||||
*
|
||||
*//*
|
||||
|
||||
@Slf4j
|
||||
public class ApiRateLimitHandlerImpl implements ApiRateLimitHandler {
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext rc) {
|
||||
log.info("Enter ApiRateLimitHandlerImpl.handle()");
|
||||
String appCode = rc.request().headers().get(AppConfigHandler.getAppCodeHeaderKey());
|
||||
String apiCode = rc.request().headers().get(AppConfigHandler.getApiCodeHeaderKey());
|
||||
|
||||
SacCurrentLimiting currentLimiting = AppConfigHandler.getApiCurrentLimiting(appCode, apiCode);
|
||||
|
||||
if(currentLimiting != null) {
|
||||
String key = RedisKeyConfig.APP_CURRENT_LIMITING_CONFIG_KEY + CACHE_KEY_CONNECTOR + appCode + CACHE_KEY_CONNECTOR + apiCode + CACHE_KEY_CONNECTOR + rc.request().uri()
|
||||
+ CACHE_KEY_CONNECTOR + rc.request().method();
|
||||
RateLimiter rateLimiter = currentLimiting.getRegistry().rateLimiter(key);
|
||||
CheckedRunnable restrictedCall = RateLimiter.decorateCheckedRunnable(rateLimiter, rc::next);
|
||||
try {
|
||||
restrictedCall.run();
|
||||
} catch (Throwable t) {
|
||||
//t.printStackTrace();
|
||||
log.info("api ratelimit:{}", key);
|
||||
rc.fail(new ServiceException(GatewayError.REQUEST_URL_RESTRICTED_BY_FLOW, currentLimiting.getStrategy().getDefaultResponse()));
|
||||
}
|
||||
} else {
|
||||
rc.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,718 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import com.sf.vertx.api.pojo.*;
|
||||
import com.sf.vertx.enums.GatewayServiceType;
|
||||
import com.sf.vertx.enums.MatchType;
|
||||
import com.sf.vertx.enums.RequestMethod;
|
||||
import com.sf.vertx.security.MainSecurity;
|
||||
import com.sf.vertx.utils.AppUtils;
|
||||
import io.vertx.core.http.*;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.handler.CorsHandler;
|
||||
import io.vertx.ext.web.handler.HttpException;
|
||||
import io.vertx.httpproxy.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.JoinConfig;
|
||||
import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.config.TcpIpConfig;
|
||||
import com.sf.vertx.arithmetic.roundRobin.SacLoadBalancing;
|
||||
import com.sf.vertx.constans.RedisKeyConfig;
|
||||
import com.sf.vertx.init.SacVertxConfig;
|
||||
import com.sf.vertx.pojo.ClusterEventMsg;
|
||||
import com.sf.vertx.pojo.SacCurrentLimiting;
|
||||
import com.sf.vertx.utils.ProxyTool;
|
||||
|
||||
import cn.hutool.core.collection.ConcurrentHashSet;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||
import io.vertx.circuitbreaker.CircuitBreaker;
|
||||
import io.vertx.circuitbreaker.CircuitBreakerOptions;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.VertxOptions;
|
||||
import io.vertx.core.net.JksOptions;
|
||||
import io.vertx.core.spi.cluster.ClusterManager;
|
||||
import io.vertx.ext.web.Route;
|
||||
import io.vertx.ext.web.Router;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.spi.cluster.hazelcast.HazelcastClusterManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.sf.vertx.constans.SACConstants.API_CONFIG;
|
||||
import static com.sf.vertx.constans.SACConstants.APP_CONFIG;
|
||||
|
||||
|
||||
*/
|
||||
/***
|
||||
* vertx配置维护
|
||||
*
|
||||
* @author xy
|
||||
*
|
||||
*//*
|
||||
|
||||
@Slf4j
|
||||
public class AppConfigHandler {
|
||||
private static VertxConfig VERTX_CONFIG = new VertxConfig();
|
||||
public static Vertx VERTX;
|
||||
private static SacVertxConfig sacVertxConfig;
|
||||
private static RedisTemplate<String, String> redisTemplate;
|
||||
public static CircuitBreaker CONNECTION_CIRCUIT_BREAKER;
|
||||
// global cache app config
|
||||
private static final ConcurrentHashMap<String, AppConfig> CACHE_APP_CONFIG_MAP = new ConcurrentHashMap<>();
|
||||
// global api config appCode - RateLimiterRegistry
|
||||
private static final ConcurrentHashMap<String, SacCurrentLimiting> GLOBAL_API_CURRENT_LIMITING_MAP = new ConcurrentHashMap<>();
|
||||
// global app config appCode - Strategy
|
||||
private static final ConcurrentHashMap<String, SacCurrentLimiting> GLOBAL_APP_CURRENT_LIMITING_MAP = new ConcurrentHashMap<>();
|
||||
// appCode:apiCode:SacLoadBalancing
|
||||
private static ConcurrentHashMap<String, SacLoadBalancing> LOADBALANCING_MAP = new ConcurrentHashMap<>();
|
||||
// appCode:apiCode - ApiConfig
|
||||
private static ConcurrentHashMap<String, ApiConfig> APICODE_CONFIG_MAP = new ConcurrentHashMap<>();
|
||||
// apiCode限流配置 appCode:apiCode - RateLimiterRegistry
|
||||
private static ConcurrentHashMap<String, SacCurrentLimiting> APICODE_CONFIG_CURRENT_LIMITING_MAP = new ConcurrentHashMap<>();
|
||||
// 服务类型, apiCode限流配置 appCode:apiCode - 服务类型,SAC=SAC规范服务,OPEN=开放服务
|
||||
private static ConcurrentHashMap<String, String> APICODE_CONFIG_SERVICE_TYPE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
// 负载均衡路由类型 appCode:apiCode - routerType
|
||||
// 执行流程 routerType= <br/>
|
||||
// 1、serviceNodel="NORMAL", serviceNodel="ROUTE" and RouteType = "WEIGHT_ROUTE"
|
||||
// <br/>
|
||||
// return LOADBALANCING_MAP
|
||||
// 2、serviceNodel="ROUTE", RouteType = "HEADER_ROUTE" <br/>
|
||||
// return APICODE_CONFIG_ROUTERCONENT_MAP
|
||||
private static ConcurrentHashMap<String, Integer> APICODE_CONFIG_ROUTERTYPE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
private static ConcurrentHashMap<String, List<RouteContent>> APICODE_CONFIG_HEADER_ROUTERCONENT_MAP = new ConcurrentHashMap<>();
|
||||
// apiCode熔断配置 appCode:apiCode - CircuitBreaker
|
||||
private static ConcurrentHashMap<String, CircuitBreaker> APICODE_CONFIG_CIRCUIT_BREAKER_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
// apicode uri = * - appConfig
|
||||
private static ConcurrentHashMap<String, AppConfig> APICODE_APPCONFIG_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
// 禁用appCode
|
||||
private static ConcurrentHashSet<String> DISABLED_APPCODE = new ConcurrentHashSet<String>();
|
||||
|
||||
public static AppConfig getAppConfigByDomain(String domain) {
|
||||
return APICODE_APPCONFIG_MAP.get(domain);
|
||||
}
|
||||
|
||||
public static Integer routerType(String key) {
|
||||
return APICODE_CONFIG_ROUTERTYPE_MAP.get(key) != null ? APICODE_CONFIG_ROUTERTYPE_MAP.get(key) : 1;
|
||||
}
|
||||
|
||||
public static List<RouteContent> routerHeaderConentList(String key) {
|
||||
return APICODE_CONFIG_HEADER_ROUTERCONENT_MAP.get(key);
|
||||
}
|
||||
|
||||
public static Boolean isServiceTypeOpen(String key) {
|
||||
return APICODE_CONFIG_SERVICE_TYPE_MAP.get(key) != null
|
||||
&& StringUtils.equals(APICODE_CONFIG_SERVICE_TYPE_MAP.get(key), "OPEN");
|
||||
}
|
||||
|
||||
public static Boolean isServiceTypeSac(String key) {
|
||||
return APICODE_CONFIG_SERVICE_TYPE_MAP.get(key) != null
|
||||
&& StringUtils.equals(APICODE_CONFIG_SERVICE_TYPE_MAP.get(key), "SAC");
|
||||
}
|
||||
|
||||
public static String getServiceTypeOpen(String key) {
|
||||
return APICODE_CONFIG_SERVICE_TYPE_MAP.get(key);
|
||||
}
|
||||
|
||||
public static String sacResponseHeaderKey() {
|
||||
return sacVertxConfig.getSacResponseHeaderKey();
|
||||
}
|
||||
|
||||
public static String rpcUri() {
|
||||
return sacVertxConfig.getRpcUri();
|
||||
}
|
||||
|
||||
public static void removeDisabledAppcode(String appCode) {
|
||||
DISABLED_APPCODE.remove(appCode);
|
||||
}
|
||||
|
||||
public static void addDisabledAppcode(String appCode) {
|
||||
DISABLED_APPCODE.add(appCode);
|
||||
}
|
||||
|
||||
public static boolean isDisabledAppcode(String appCode) {
|
||||
return DISABLED_APPCODE.contains(appCode);
|
||||
}
|
||||
|
||||
public static SacCurrentLimiting getGlobalAppCurrentLimitingConfig(String appCode) {
|
||||
return GLOBAL_APP_CURRENT_LIMITING_MAP.get(appCode);
|
||||
}
|
||||
|
||||
public static AppConfig getAppConfig(String appCode) {
|
||||
return CACHE_APP_CONFIG_MAP.get(appCode);
|
||||
}
|
||||
|
||||
public static void init(RedisTemplate<String, String> _redisTemplate, SacVertxConfig _sacVertxConfig) {
|
||||
redisTemplate = _redisTemplate;
|
||||
sacVertxConfig = _sacVertxConfig;
|
||||
}
|
||||
|
||||
public static boolean isDataSecurity(String appCode) {
|
||||
return CACHE_APP_CONFIG_MAP.get(appCode) != null && CACHE_APP_CONFIG_MAP.get(appCode).getDataSecurity() != null;
|
||||
}
|
||||
|
||||
public static boolean isApiCodeCircuitBreaker(String key) {
|
||||
return APICODE_CONFIG_CIRCUIT_BREAKER_MAP.get(key) != null;
|
||||
}
|
||||
|
||||
public static CircuitBreaker getApiCodeCircuitBreaker(String key) {
|
||||
return APICODE_CONFIG_CIRCUIT_BREAKER_MAP.get(key);
|
||||
}
|
||||
|
||||
*/
|
||||
/***
|
||||
* 是否解析, 走独立请求
|
||||
*
|
||||
* @return
|
||||
*//*
|
||||
|
||||
public static boolean isAnalysisBody(String appCode, String apiCode, String contentType) {
|
||||
String apiCodeCacheKey = appCode + ":" + apiCode;
|
||||
// Mock解析body
|
||||
if (APICODE_CONFIG_MAP.get(apiCodeCacheKey) != null && APICODE_CONFIG_MAP.get(apiCodeCacheKey).getMockDefaultHttpStatus() != null) {
|
||||
return true;
|
||||
}
|
||||
ApiConfig apicodeConfig = AppConfigHandler.getApicodeConfig(apiCodeCacheKey);
|
||||
|
||||
// SAC请求方式GET,DELETE,HEAD请求需要解析body,SAC请求方式参数统一使用body传递,可能需要二次处理
|
||||
if (isServiceTypeSac(apiCodeCacheKey)) {
|
||||
RequestMethod requestMethod = RequestMethod.getByCode(apicodeConfig.getMethod());
|
||||
if (RequestMethod.GET.equals(requestMethod)
|
||||
|| RequestMethod.DELETE.equals(requestMethod)
|
||||
|| RequestMethod.HEAD.equals(requestMethod)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (StringUtils.startsWith(contentType, "multipart")) {
|
||||
return false;
|
||||
}
|
||||
String keyCircuitBreaker = apiCodeCacheKey + ":" + "CIRCUIT_BREAKER";
|
||||
CircuitBreaker circuitBreaker = AppConfigHandler.getApiCodeCircuitBreaker(keyCircuitBreaker);
|
||||
boolean isDataSecurity = AppConfigHandler.isDataSecurity(appCode);
|
||||
// 文件上传不走加解密
|
||||
return (isDataSecurity || circuitBreaker != null);
|
||||
}
|
||||
|
||||
*/
|
||||
/***
|
||||
* 优先apicode配置限流、无法找到匹配全局限流
|
||||
*
|
||||
* @param appCode
|
||||
* @param apiCode
|
||||
* @return
|
||||
*//*
|
||||
|
||||
public static SacCurrentLimiting getApiCurrentLimiting(String appCode, String apiCode) {
|
||||
String key = appCode + ":" + apiCode;
|
||||
SacCurrentLimiting sacCurrentLimiting = APICODE_CONFIG_CURRENT_LIMITING_MAP.get(key) != null
|
||||
? APICODE_CONFIG_CURRENT_LIMITING_MAP.get(key)
|
||||
: null;
|
||||
sacCurrentLimiting = sacCurrentLimiting != null ? sacCurrentLimiting
|
||||
: (GLOBAL_API_CURRENT_LIMITING_MAP.get(appCode) != null ? GLOBAL_API_CURRENT_LIMITING_MAP.get(appCode)
|
||||
: null);
|
||||
return sacCurrentLimiting;
|
||||
}
|
||||
|
||||
public static VertxConfig getVertxConfig() {
|
||||
return VERTX_CONFIG;
|
||||
}
|
||||
|
||||
public static String getAppCodeHeaderKey() {
|
||||
return VERTX_CONFIG.getAppCodeHeaderKey();
|
||||
}
|
||||
|
||||
public static String getApiCodeHeaderKey() {
|
||||
return VERTX_CONFIG.getApiCodeHeaderKey();
|
||||
}
|
||||
|
||||
public static SacLoadBalancing getLoadBalancing(String key) {
|
||||
return LOADBALANCING_MAP.get(key);
|
||||
}
|
||||
|
||||
public static ApiConfig getApicodeConfig(String key) {
|
||||
return APICODE_CONFIG_MAP.get(key);
|
||||
}
|
||||
|
||||
public static Long getApicodeConfigTimeOut(String key) {
|
||||
return APICODE_CONFIG_MAP.get(key) != null ? APICODE_CONFIG_MAP.get(key).getTimeout() : 3000L;
|
||||
}
|
||||
|
||||
|
||||
private static RateLimiterRegistry createRateLimiter(Strategy strategy) {
|
||||
RateLimiterConfig config = RateLimiterConfig.custom()
|
||||
.limitRefreshPeriod(Duration.ofSeconds(strategy.getTimeWindow()))
|
||||
.limitForPeriod(strategy.getThreshold()).timeoutDuration(Duration.ofMillis(0)).build();
|
||||
RateLimiterRegistry registry = RateLimiterRegistry.of(config);
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static void initRateLimiter(String appCode, Strategy strategy,
|
||||
ConcurrentHashMap<String, SacCurrentLimiting> map) {
|
||||
RateLimiterRegistry registry = createRateLimiter(strategy);
|
||||
SacCurrentLimiting sacCurrentLimiting = new SacCurrentLimiting();
|
||||
sacCurrentLimiting.setStrategy(strategy);
|
||||
sacCurrentLimiting.setRegistry(registry);
|
||||
map.put(appCode, sacCurrentLimiting);
|
||||
}
|
||||
|
||||
*/
|
||||
/***
|
||||
* 从redis加载数据
|
||||
*
|
||||
* @throws Exception
|
||||
*//*
|
||||
|
||||
public static void initAllAppConfig() {
|
||||
Set<String> set = redisTemplate.opsForZSet().range(RedisKeyConfig.APP_CONFIG_SET_KEY, 0, -1);
|
||||
for (String appCode : set) {
|
||||
AppConfigHandler.initAppConfig(appCode, false);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
/***
|
||||
* 加载vertx配置
|
||||
*//*
|
||||
|
||||
public static void initVertxConfig() {
|
||||
String vertxConfigKey = RedisKeyConfig.VERTX_CONFIG_STRING_KEY;
|
||||
String vertxConfigValue = redisTemplate.opsForValue().get(vertxConfigKey);
|
||||
if (StringUtils.isNotBlank(vertxConfigValue)) {
|
||||
VERTX_CONFIG = JSONObject.parseObject(vertxConfigValue, VertxConfig.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static void delAppConfigCache(String appCode) {
|
||||
AppConfig appConfig = CACHE_APP_CONFIG_MAP.get(appCode);
|
||||
if (appConfig != null) {
|
||||
// app、api默认限流
|
||||
GLOBAL_API_CURRENT_LIMITING_MAP.remove(appCode);
|
||||
GLOBAL_APP_CURRENT_LIMITING_MAP.remove(appCode);
|
||||
for (SacService sacService : appConfig.getService()) {
|
||||
if (sacService.getApiConfig() != null && sacService.getApiConfig().size() > 0) {
|
||||
for (ApiConfig apiConfig : sacService.getApiConfig()) {
|
||||
String key = appCode + ":" + apiConfig.getApiCode();
|
||||
APICODE_CONFIG_MAP.remove(key);
|
||||
LOADBALANCING_MAP.remove(key);
|
||||
APICODE_CONFIG_SERVICE_TYPE_MAP.remove(key);
|
||||
APICODE_CONFIG_CURRENT_LIMITING_MAP.remove(key);
|
||||
APICODE_APPCONFIG_MAP.remove(apiConfig.getApiCode());
|
||||
String keyCircuitBreaker = key + ":" + "CIRCUIT_BREAKER";
|
||||
CircuitBreaker circuitBreaker = APICODE_CONFIG_CIRCUIT_BREAKER_MAP.get(keyCircuitBreaker);
|
||||
if (circuitBreaker != null) {
|
||||
circuitBreaker.close();
|
||||
APICODE_CONFIG_CIRCUIT_BREAKER_MAP.remove(keyCircuitBreaker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 应用配置
|
||||
CACHE_APP_CONFIG_MAP.remove(appCode);
|
||||
}
|
||||
}
|
||||
|
||||
public static void initAppConfig(String appCode, boolean isDelLocalCache) {
|
||||
// 是否需要先删除
|
||||
if (isDelLocalCache) {
|
||||
delAppConfigCache(appCode);
|
||||
}
|
||||
|
||||
String appCodeKey = RedisKeyConfig.APP_CONFIG_PREFIX_KEY + ":" + appCode;
|
||||
String appCodeValue = redisTemplate.opsForValue().get(appCodeKey);
|
||||
if (StringUtils.isNotBlank(appCodeValue)) {
|
||||
AppConfig appConfig = JSON.parseObject(appCodeValue, new TypeReference<AppConfig>() {
|
||||
});
|
||||
CACHE_APP_CONFIG_MAP.put(appCode, appConfig);
|
||||
|
||||
// app、api默认限流
|
||||
if (appConfig.getApiCurrentLimitingConfig() != null) {
|
||||
initRateLimiter(appCode, appConfig.getApiCurrentLimitingConfig(), GLOBAL_API_CURRENT_LIMITING_MAP);
|
||||
}
|
||||
|
||||
if (appConfig.getAppCurrentLimitingConfig() != null) {
|
||||
initRateLimiter(appCode, appConfig.getAppCurrentLimitingConfig(), GLOBAL_APP_CURRENT_LIMITING_MAP);
|
||||
}
|
||||
|
||||
// app router负载均衡
|
||||
List<RouteContent> routeContentList = null;
|
||||
for (SacService sacService : appConfig.getService()) {
|
||||
int routerType = 1;
|
||||
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()
|
||||
: 1);
|
||||
node.setProtocol(routeContent.getServerAddress().getProtocol());
|
||||
nodeList.add(node);
|
||||
}
|
||||
} else if (sacService.getRouteConfig() != null
|
||||
&& StringUtils.equals(sacService.getRouteConfig().getRouteType(), "HEADER_ROUTE")) {
|
||||
routerType = 2;
|
||||
routeContentList = sacService.getRouteConfig().getRouteContent();
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化apiConfig
|
||||
if (sacService.getApiConfig() != null && sacService.getApiConfig().size() > 0) {
|
||||
for (ApiConfig apiConfig : sacService.getApiConfig()) {
|
||||
String key = appCode + ":" + apiConfig.getApiCode();
|
||||
APICODE_CONFIG_MAP.put(key, apiConfig);
|
||||
if (sacService.getServiceType() != null) {
|
||||
APICODE_CONFIG_SERVICE_TYPE_MAP.put(key, sacService.getServiceType());
|
||||
}
|
||||
|
||||
// OPEN模式, 域名映射
|
||||
if (isServiceTypeOpen(key) && StringUtils.equals(apiConfig.getUri(), "*")) {
|
||||
APICODE_APPCONFIG_MAP.put(apiConfig.getApiCode(), appConfig);
|
||||
}
|
||||
|
||||
// 负载均衡模式
|
||||
APICODE_CONFIG_ROUTERTYPE_MAP.put(key, routerType);
|
||||
switch (routerType) {
|
||||
case 1:
|
||||
if (nodeList.size() > 0) {
|
||||
// 初始化负载均衡算法
|
||||
SacLoadBalancing sacLoadBalancing = ProxyTool.roundRobin(nodeList);
|
||||
LOADBALANCING_MAP.put(key, sacLoadBalancing);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
APICODE_CONFIG_HEADER_ROUTERCONENT_MAP.put(key, routeContentList);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (apiConfig.getStrategy() != null && apiConfig.getStrategy().size() > 0) {
|
||||
for (Strategy strategy : apiConfig.getStrategy()) {
|
||||
if (StringUtils.equals(strategy.getType(), "CURRENT_LIMITING")) {
|
||||
RateLimiterRegistry registry = createRateLimiter(strategy);
|
||||
SacCurrentLimiting sacCurrentLimiting = new SacCurrentLimiting();
|
||||
sacCurrentLimiting.setStrategy(strategy);
|
||||
sacCurrentLimiting.setRegistry(registry);
|
||||
APICODE_CONFIG_CURRENT_LIMITING_MAP.put(key, sacCurrentLimiting);
|
||||
} else if (StringUtils.equals(strategy.getType(), "CIRCUIT_BREAKER")) {
|
||||
String keyCircuitBreaker = key + ":" + "CIRCUIT_BREAKER";
|
||||
// interfaceBreaker = CircuitBreaker.create("interfaceBreaker", VERTX,
|
||||
// new CircuitBreakerOptions().setMaxFailures(3).setMaxRetries(5).setTimeout(2000)
|
||||
// .setFallbackOnFailure(true)
|
||||
// ).openHandler(v -> {
|
||||
// log.info("Circuit opened");
|
||||
// }).closeHandler(v -> {
|
||||
// log.info("Circuit closed");
|
||||
// });//.retryPolicy(retryCount -> retryCount * 100L);
|
||||
|
||||
// apiCode熔断
|
||||
CircuitBreaker circuitBreaker = CircuitBreaker
|
||||
.create(keyCircuitBreaker + "-circuit-breaker", VERTX,
|
||||
new CircuitBreakerOptions().setMaxFailures(strategy.getThreshold()) // 最大失败数
|
||||
.setFailuresRollingWindow(strategy.getTimeWindow() * 1000) // 毫秒
|
||||
//.setTimeout(apiConfig.getTimeout()) // 超时时间,不要开启, 配置会设置接口超时, 这个参数有bug,半开超时会卡死
|
||||
.setFallbackOnFailure(true) // 失败后是否调用回退函数(fallback)
|
||||
.setResetTimeout(strategy.getRecovery_interval() * 1000) // 在开启状态下,尝试重试之前所需时间
|
||||
).openHandler(v -> {
|
||||
log.info(keyCircuitBreaker + " Circuit open");
|
||||
}).halfOpenHandler(v -> {
|
||||
log.info(keyCircuitBreaker + "Circuit halfOpen");
|
||||
}).closeHandler(v -> {
|
||||
log.info(keyCircuitBreaker + "Circuit close");
|
||||
});
|
||||
APICODE_CONFIG_CIRCUIT_BREAKER_MAP.put(keyCircuitBreaker, circuitBreaker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void createVertx() {
|
||||
// TODO 编解码线程池,后面优化协程等方式
|
||||
VertxOptions vertxOptions = new VertxOptions();
|
||||
loadVertxOptions(vertxOptions);
|
||||
VERTX = Vertx.vertx(vertxOptions);
|
||||
|
||||
initConnectionCircuitBreaker();
|
||||
createVertxRouter();
|
||||
consumerClusterEventMsg();
|
||||
}
|
||||
|
||||
private static Config hazelcastConfig(SacVertxConfig sacVertxConfig) {
|
||||
// 集群
|
||||
Config hazelcastConfig = new Config();
|
||||
hazelcastConfig.setClusterName(sacVertxConfig.getClusterName()); // 集群名字
|
||||
NetworkConfig networkConfig = new NetworkConfig();
|
||||
networkConfig.setPort(sacVertxConfig.getNetworkPort());
|
||||
networkConfig.setPortAutoIncrement(sacVertxConfig.isPortAutoIncrement());
|
||||
|
||||
JoinConfig join = new JoinConfig();
|
||||
TcpIpConfig tcpIpConfig = new TcpIpConfig();
|
||||
tcpIpConfig.setEnabled(true);
|
||||
String[] clusterIps = sacVertxConfig.getClusterIp().split(",");
|
||||
List<String> members = Arrays.asList(clusterIps);
|
||||
tcpIpConfig.setMembers(members);
|
||||
join.setTcpIpConfig(tcpIpConfig);
|
||||
networkConfig.setJoin(join);
|
||||
hazelcastConfig.setNetworkConfig(networkConfig);
|
||||
|
||||
// TODO 还有问题,不会使用
|
||||
// ManagementCenterConfig managementCenterConfig = new ManagementCenterConfig();
|
||||
// Set<String> interfaces = new HashSet<>();
|
||||
// interfaces.add("http://192.168.1.68:8080/mancenter");
|
||||
// managementCenterConfig.setTrustedInterfaces(interfaces);
|
||||
// hazelcastConfig.setManagementCenterConfig(managementCenterConfig);
|
||||
return hazelcastConfig;
|
||||
}
|
||||
|
||||
public static void createHazelcastClusterVertx() {
|
||||
Config hazelcastConfig = hazelcastConfig(sacVertxConfig);
|
||||
ClusterManager hazelcastClusterManager = new HazelcastClusterManager(hazelcastConfig);
|
||||
// TODO 编解码线程池,后面优化协程等方式
|
||||
VertxOptions vertxOptions = new VertxOptions();
|
||||
loadVertxOptions(vertxOptions);
|
||||
vertxOptions.setClusterManager(hazelcastClusterManager);
|
||||
Vertx.clusteredVertx(vertxOptions, res -> {
|
||||
if (res.succeeded()) {
|
||||
VERTX = res.result();
|
||||
log.info("hazelcastClusterManager create success");
|
||||
initConnectionCircuitBreaker();
|
||||
createVertxRouter();
|
||||
consumerClusterEventMsg();
|
||||
} else {
|
||||
res.cause().printStackTrace();
|
||||
log.info("hazelcastClusterManager create failure");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void consumerClusterEventMsg() {
|
||||
// 订阅消息
|
||||
VERTX.eventBus().consumer("sac_cluster_event", message -> {
|
||||
if (message.body() != null) {
|
||||
ClusterEventMsg msg = JSONObject.parseObject(message.body().toString(), ClusterEventMsg.class);
|
||||
log.info("Received message: {}", msg);
|
||||
// message.reply("我是返回数据===" + message.body());
|
||||
if (msg.getType() == 1) {
|
||||
if (msg.getOperation() == 1) {
|
||||
// 初始化AppConfig本地缓存
|
||||
AppConfigHandler.initAppConfig(msg.getAppCode(), true);
|
||||
} else if (msg.getOperation() == 3) {
|
||||
// 删除本地缓存
|
||||
delAppConfigCache(msg.getAppCode());
|
||||
} else if (msg.getOperation() == 4) {
|
||||
// 禁用app
|
||||
addDisabledAppcode(msg.getAppCode());
|
||||
} else if (msg.getOperation() == 5) {
|
||||
// 启用app
|
||||
removeDisabledAppcode(msg.getAppCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
*/
|
||||
/***
|
||||
* 发布消息,订阅消息
|
||||
*
|
||||
* @param msg
|
||||
*//*
|
||||
|
||||
public static void publishClusterEventMsg(ClusterEventMsg msg) {
|
||||
VERTX.eventBus().publish("sac_cluster_event", JSONObject.toJSONString(msg));
|
||||
}
|
||||
|
||||
private static void createVertxRouter() {
|
||||
// consul初始化
|
||||
// ConsulHandler.init(vertx);
|
||||
|
||||
// 从redis同步app配置
|
||||
initAllAppConfig();
|
||||
|
||||
VertxConfig vertxConfig = AppConfigHandler.getVertxConfig();
|
||||
// 创建HTTP监听
|
||||
// 所有ip都能访问
|
||||
HttpServerOptions httpServerOptions = new HttpServerOptions().setHost("0.0.0.0");
|
||||
if (sacVertxConfig.isSSLs()) {
|
||||
httpServerOptions.setSsl(true)
|
||||
.setKeyStoreOptions(new JksOptions().setPassword("changeit").setPath("keystore.jks"));
|
||||
}
|
||||
HttpServer server = VERTX.createHttpServer(httpServerOptions);
|
||||
Router mainHttpRouter = Router.router(VERTX);
|
||||
Integer serverPort = vertxConfig.getPort() == null ? sacVertxConfig.getPort() : vertxConfig.getPort();
|
||||
log.info("serverPort:{}", serverPort);
|
||||
server.requestHandler(mainHttpRouter).listen(serverPort, h -> {
|
||||
if (h.succeeded()) {
|
||||
log.info("HTTP端口监听成功:{}", serverPort);
|
||||
} else {
|
||||
log.error("HTTP端口监听失败:{}", serverPort);
|
||||
}
|
||||
});
|
||||
|
||||
// HttpClientOptions clientOptions = new HttpClientOptions();
|
||||
// clientOptions.setMaxPoolSize(20); // 最大连接池大小
|
||||
// clientOptions.setConnectTimeout(2000); // 连接超时 毫秒
|
||||
// clientOptions.setHttp2KeepAliveTimeout(1);
|
||||
// clientOptions.setIdleTimeout(1000); // 连接空闲超时 毫秒
|
||||
// HttpClient proxyClient = VERTX.createHttpClient(clientOptions);
|
||||
HttpClient proxyClient = VERTX.createHttpClient();
|
||||
HttpProxy proxy = HttpProxy.reverseProxy(proxyClient);
|
||||
proxy.originSelector(request -> Future.succeededFuture(ProxyTool.resolveOriginAddress(request)));
|
||||
proxy.addInterceptor(new ProxyInterceptor() {
|
||||
@Override
|
||||
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
|
||||
// if(StringUtils.equals(sacAppHeaderKey, "dsafdsfadafhappC")) {
|
||||
// // 会跳转到 RestfulFailureHandlerImpl
|
||||
// throw new HttpException(10003);
|
||||
// }
|
||||
String appCode = context.request().headers().get(getAppCodeHeaderKey());
|
||||
String apiCode = context.request().headers().get(getApiCodeHeaderKey());
|
||||
String key = appCode + ":" + apiCode;
|
||||
if (isServiceTypeSac(key)) {
|
||||
String uri = APICODE_CONFIG_MAP.get(key).getUri();
|
||||
String method = APICODE_CONFIG_MAP.get(key).getMethod();
|
||||
context.request().setURI(uri).setMethod(HttpMethod.valueOf(method));
|
||||
}
|
||||
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);
|
||||
String rateLimitModel = vertxConfig.getRateLimitModel();
|
||||
Route routeSac = mainHttpRouter.post(rpcUri());
|
||||
routeSac.handler(CorsHandler.create().addRelativeOrigin(".*"))
|
||||
.handler(ParameterCheckHandler.create(GatewayServiceType.SAC))
|
||||
.handler(AppRateLimitHandler.create(rateLimitModel))
|
||||
.handler(ApiRateLimitHandler.create(rateLimitModel))
|
||||
.handler(BodyHandler.create().setHandleFileUploads(false))
|
||||
.handler(ApiMockHandler.create())
|
||||
.handler(ProxyHandler.create(mainWebClient, proxy))
|
||||
.failureHandler(RestfulFailureHandler.create());
|
||||
// mainHttpRouter.route().handler(ProxyHandler.create(mainWebClient, proxy));
|
||||
|
||||
Route routeOpen = mainHttpRouter.route();
|
||||
routeOpen.handler(CorsHandler.create().addRelativeOrigin(".*"))
|
||||
.handler(ParameterCheckHandler.create(GatewayServiceType.OPEN))
|
||||
.handler(AppRateLimitHandler.create(rateLimitModel))
|
||||
.handler(ApiRateLimitHandler.create(rateLimitModel))
|
||||
.handler(BodyHandler.create().setHandleFileUploads(false))
|
||||
.handler(ApiMockHandler.create())
|
||||
.handler(ProxyHandler.create(mainWebClient, proxy))
|
||||
.failureHandler(RestfulFailureHandler.create());
|
||||
}
|
||||
|
||||
*/
|
||||
/***
|
||||
* 初始化connection Breaker
|
||||
*//*
|
||||
|
||||
private static void initConnectionCircuitBreaker() {
|
||||
CONNECTION_CIRCUIT_BREAKER = CircuitBreaker.create("connectionCircuitBreaker-circuit-breaker", VERTX,
|
||||
new CircuitBreakerOptions().setMaxFailures(3) // 最大失败数
|
||||
.setTimeout(2000) // 超时时间
|
||||
.setFallbackOnFailure(true) // 失败后是否调用回退函数(fallback)
|
||||
.setResetTimeout(10000) // 在开启状态下,尝试重试之前所需时间
|
||||
).openHandler(v -> {
|
||||
log.info("connectionCircuitBreaker Circuit open");
|
||||
}).halfOpenHandler(v -> {
|
||||
log.info("connectionCircuitBreaker Circuit halfOpen");
|
||||
}).closeHandler(v -> {
|
||||
log.info("connectionCircuitBreaker Circuit close");
|
||||
});
|
||||
}
|
||||
|
||||
private static void loadVertxOptions(VertxOptions vertxOptions) {
|
||||
long blockedThreadCheckInterval = VERTX_CONFIG.getVertxOptionsConfig() == null ? -1
|
||||
: VERTX_CONFIG.getVertxOptionsConfig().getBlockedThreadCheckInterval();
|
||||
int workerPoolSize = VERTX_CONFIG == null || VERTX_CONFIG.getVertxOptionsConfig() == null ? -1
|
||||
: VERTX_CONFIG.getVertxOptionsConfig().getWorkerPoolSize();
|
||||
if (workerPoolSize != -1) {
|
||||
vertxOptions.setWorkerPoolSize(workerPoolSize);
|
||||
}
|
||||
|
||||
// TODO
|
||||
blockedThreadCheckInterval = 1000000L;
|
||||
if (blockedThreadCheckInterval != -1) {
|
||||
vertxOptions.setBlockedThreadCheckInterval(blockedThreadCheckInterval); // 不打印Thread blocked 阻塞日志
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String bodyEncrypt(String body, String appCode) {
|
||||
DataSecurity dataSecurity = getAppConfig(appCode).getDataSecurity();
|
||||
switch (dataSecurity.getAlgorithm()) {
|
||||
case "AES":
|
||||
return MainSecurity.aesEncrypt(body, dataSecurity.getPrivateKey());
|
||||
case "RSA":
|
||||
return MainSecurity.rsaEncrypt(body, dataSecurity.getPublicKey());
|
||||
default:
|
||||
break;
|
||||
}
|
||||
log.info(" appCode:{}, encrypt key config is error.", appCode);
|
||||
throw new HttpException(10011);
|
||||
}
|
||||
|
||||
public static String bodyDecrypt(String body, String appCode) {
|
||||
DataSecurity dataSecurity = getAppConfig(appCode).getDataSecurity();
|
||||
switch (dataSecurity.getAlgorithm()) {
|
||||
case "AES":
|
||||
return MainSecurity.aesDecrypt(body, dataSecurity.getPrivateKey());
|
||||
case "RSA":
|
||||
return MainSecurity.rsaDecrypt(body, dataSecurity.getPrivateKey());
|
||||
default:
|
||||
break;
|
||||
}
|
||||
log.info(" appCode:{}, decrypt key config is error.", appCode);
|
||||
throw new HttpException(10011);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import io.vertx.codegen.annotations.VertxGen;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
*/
|
||||
/***
|
||||
* 限流熔断, redis存储
|
||||
* @author xy
|
||||
*
|
||||
*//*
|
||||
|
||||
@VertxGen
|
||||
public interface AppRateLimitHandler extends Handler<RoutingContext> {
|
||||
|
||||
static AppRateLimitHandler create(String instance) {
|
||||
switch (instance) {
|
||||
case "redis":
|
||||
default:
|
||||
// 本地缓存
|
||||
return new AppRateLimitHandlerImpl();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import com.sf.vertx.constans.RedisKeyConfig;
|
||||
import com.sf.vertx.enums.GatewayError;
|
||||
import com.sf.vertx.exception.ServiceException;
|
||||
import com.sf.vertx.pojo.SacCurrentLimiting;
|
||||
|
||||
import io.github.resilience4j.core.functions.CheckedRunnable;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiter;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.handler.HttpException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
*/
|
||||
/***
|
||||
* 内存存储
|
||||
*
|
||||
* @author xy
|
||||
*
|
||||
*//*
|
||||
|
||||
@Slf4j
|
||||
public class AppRateLimitHandlerImpl implements AppRateLimitHandler {
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext rc) {
|
||||
log.info("Enter AppRateLimitHandlerImpl.handle()");
|
||||
String appCode = rc.request().headers().get(AppConfigHandler.getAppCodeHeaderKey());
|
||||
SacCurrentLimiting currentLimiting = AppConfigHandler.getGlobalAppCurrentLimitingConfig(appCode);
|
||||
|
||||
if (currentLimiting != null) {
|
||||
String key = RedisKeyConfig.APP_CURRENT_LIMITING_CONFIG_KEY + ":" + appCode;
|
||||
RateLimiter rateLimiter = currentLimiting.getRegistry().rateLimiter(key);
|
||||
CheckedRunnable restrictedCall = RateLimiter.decorateCheckedRunnable(rateLimiter, rc::next);
|
||||
try {
|
||||
restrictedCall.run();
|
||||
} catch (Throwable t) {
|
||||
//t.printStackTrace();
|
||||
log.info("app ratelimit:{}", key);
|
||||
rc.fail(new ServiceException(GatewayError.REQUEST_URL_RESTRICTED_BY_FLOW, currentLimiting.getStrategy().getDefaultResponse()));
|
||||
}
|
||||
} else {
|
||||
rc.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
*/
|
||||
/*
|
||||
* Copyright 2014 Red Hat, Inc.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*//*
|
||||
|
||||
|
||||
import io.vertx.codegen.annotations.Fluent;
|
||||
import io.vertx.codegen.annotations.VertxGen;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
*/
|
||||
/**
|
||||
* A handler which gathers the entire request body and sets it on the {@link RoutingContext}.
|
||||
* <p>
|
||||
* It also handles HTTP file uploads and can be used to limit body sizes.
|
||||
*
|
||||
* @author <a href="http://tfox.org">Tim Fox</a>
|
||||
*//*
|
||||
|
||||
@VertxGen
|
||||
public interface BodyHandler extends Handler<RoutingContext> {
|
||||
|
||||
*/
|
||||
/**
|
||||
* Default max size for a request body in bytes = {@code 10485760}, i.e. 10 megabytes
|
||||
*//*
|
||||
|
||||
long DEFAULT_BODY_LIMIT = 10 * 1024 * 1024;
|
||||
|
||||
*/
|
||||
/**
|
||||
* Default uploads directory on server for file uploads
|
||||
*//*
|
||||
|
||||
String DEFAULT_UPLOADS_DIRECTORY = "file-uploads";
|
||||
|
||||
*/
|
||||
/**
|
||||
* Default value of whether form attributes should be merged into request params
|
||||
*//*
|
||||
|
||||
boolean DEFAULT_MERGE_FORM_ATTRIBUTES = true;
|
||||
|
||||
*/
|
||||
/**
|
||||
* Default value of whether uploaded files should be removed after handling the request
|
||||
*//*
|
||||
|
||||
boolean DEFAULT_DELETE_UPLOADED_FILES_ON_END = false;
|
||||
|
||||
*/
|
||||
/**
|
||||
* Default value of whether to pre-allocate the body buffer size according to the content-length HTTP request header
|
||||
*//*
|
||||
|
||||
boolean DEFAULT_PREALLOCATE_BODY_BUFFER = false;
|
||||
|
||||
*/
|
||||
/**
|
||||
* Create a body handler with defaults.
|
||||
*
|
||||
* @return the body handler
|
||||
*//*
|
||||
|
||||
static BodyHandler create() {
|
||||
return new BodyHandlerImpl();
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* Create a body handler setting if it should handle file uploads.
|
||||
*
|
||||
* @param handleFileUploads true if files upload should be handled
|
||||
* @return the body handler
|
||||
*//*
|
||||
|
||||
static BodyHandler create(boolean handleFileUploads) {
|
||||
return new BodyHandlerImpl(handleFileUploads);
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* Create a body handler and use the given upload directory.
|
||||
*
|
||||
* @param uploadDirectory the uploads directory
|
||||
* @return the body handler
|
||||
*//*
|
||||
|
||||
static BodyHandler create(String uploadDirectory) {
|
||||
return new BodyHandlerImpl(uploadDirectory);
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* Set whether file uploads will be handled.
|
||||
*
|
||||
* @param handleFileUploads true if they should be handled
|
||||
* @return reference to this for fluency
|
||||
*//*
|
||||
|
||||
@Fluent
|
||||
BodyHandler setHandleFileUploads(boolean handleFileUploads);
|
||||
|
||||
*/
|
||||
/**
|
||||
* Set the maximum body size in bytes, {@code -1} means no limit.
|
||||
*
|
||||
* @param bodyLimit the max size in bytes
|
||||
* @return reference to this for fluency
|
||||
*//*
|
||||
|
||||
@Fluent
|
||||
BodyHandler setBodyLimit(long bodyLimit);
|
||||
|
||||
*/
|
||||
/**
|
||||
* Set the uploads directory to use.
|
||||
*
|
||||
* @param uploadsDirectory the uploads directory
|
||||
* @return reference to this for fluency
|
||||
*//*
|
||||
|
||||
@Fluent
|
||||
BodyHandler setUploadsDirectory(String uploadsDirectory);
|
||||
|
||||
*/
|
||||
/**
|
||||
* Set whether form attributes will be added to the request parameters.
|
||||
*
|
||||
* @param mergeFormAttributes true if they should be merged
|
||||
* @return reference to this for fluency
|
||||
*//*
|
||||
|
||||
@Fluent
|
||||
BodyHandler setMergeFormAttributes(boolean mergeFormAttributes);
|
||||
|
||||
*/
|
||||
/**
|
||||
* Set whether uploaded files should be removed after handling the request.
|
||||
*
|
||||
* @param deleteUploadedFilesOnEnd true if uploaded files should be removed after handling the request
|
||||
* @return reference to this for fluency
|
||||
*//*
|
||||
|
||||
@Fluent
|
||||
BodyHandler setDeleteUploadedFilesOnEnd(boolean deleteUploadedFilesOnEnd);
|
||||
|
||||
*/
|
||||
/**
|
||||
* Pre-allocate the body buffer according to the value parsed from content-length header.
|
||||
* The buffer is capped at 64KB
|
||||
* @param isPreallocateBodyBuffer {@code true} if body buffer is pre-allocated according to the size
|
||||
* read from content-length Header.
|
||||
* {code false} if body buffer is pre-allocated to 1KB, and is resized dynamically
|
||||
* @return reference to this for fluency
|
||||
*//*
|
||||
|
||||
@Fluent
|
||||
BodyHandler setPreallocateBodyBuffer(boolean isPreallocateBodyBuffer);
|
||||
|
||||
}
|
||||
*/
|
@ -0,0 +1,389 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
*/
|
||||
/*
|
||||
* Copyright 2014 Red Hat, Inc.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*//*
|
||||
|
||||
|
||||
import com.sf.vertx.api.pojo.ApiConfig;
|
||||
import com.sf.vertx.api.pojo.AppConfig;
|
||||
import com.sf.vertx.enums.RequestMethod;
|
||||
import com.sf.vertx.utils.AppUtils;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.buffer.Buffer;
|
||||
import io.vertx.core.file.FileSystem;
|
||||
import io.vertx.core.http.HttpHeaders;
|
||||
import io.vertx.core.http.HttpServerRequest;
|
||||
import io.vertx.core.http.HttpServerResponse;
|
||||
import io.vertx.core.http.HttpVersion;
|
||||
import io.vertx.core.impl.logging.Logger;
|
||||
import io.vertx.core.impl.logging.LoggerFactory;
|
||||
import io.vertx.ext.web.FileUpload;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.impl.FileUploadImpl;
|
||||
import io.vertx.ext.web.impl.RoutingContextInternal;
|
||||
|
||||
import static com.sf.vertx.constans.SACConstants.*;
|
||||
|
||||
*/
|
||||
/**
|
||||
* @author <a href="http://tfox.org">Tim Fox</a>
|
||||
*//*
|
||||
|
||||
public class BodyHandlerImpl implements BodyHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BodyHandlerImpl.class);
|
||||
|
||||
private long bodyLimit = DEFAULT_BODY_LIMIT;
|
||||
private boolean handleFileUploads;
|
||||
private String uploadsDir;
|
||||
private boolean mergeFormAttributes = DEFAULT_MERGE_FORM_ATTRIBUTES;
|
||||
private boolean deleteUploadedFilesOnEnd = DEFAULT_DELETE_UPLOADED_FILES_ON_END;
|
||||
private boolean isPreallocateBodyBuffer = DEFAULT_PREALLOCATE_BODY_BUFFER;
|
||||
private static final int DEFAULT_INITIAL_BODY_BUFFER_SIZE = 1024; // bytes
|
||||
|
||||
public BodyHandlerImpl() {
|
||||
this(true, DEFAULT_UPLOADS_DIRECTORY);
|
||||
}
|
||||
|
||||
public BodyHandlerImpl(boolean handleFileUploads) {
|
||||
this(handleFileUploads, DEFAULT_UPLOADS_DIRECTORY);
|
||||
}
|
||||
|
||||
public BodyHandlerImpl(String uploadDirectory) {
|
||||
this(true, uploadDirectory);
|
||||
}
|
||||
|
||||
private BodyHandlerImpl(boolean handleFileUploads, String uploadDirectory) {
|
||||
this.handleFileUploads = handleFileUploads;
|
||||
setUploadsDirectory(uploadDirectory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext context) {
|
||||
// =======源码流程
|
||||
final HttpServerRequest request = context.request();
|
||||
final HttpServerResponse response = context.response();
|
||||
//
|
||||
// we need to keep state since we can be called again on reroute
|
||||
if (!((RoutingContextInternal) context).seenHandler(RoutingContextInternal.BODY_HANDLER)) {
|
||||
((RoutingContextInternal) context).visitHandler(RoutingContextInternal.BODY_HANDLER);
|
||||
|
||||
// Check if a request has a request body.
|
||||
// A request with a body __must__ either have `transfer-encoding`
|
||||
// or `content-length` headers set.
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
|
||||
final long parsedContentLength = parseContentLengthHeader(request);
|
||||
// http2 never transmits a `transfer-encoding` as frames are chunks.
|
||||
final boolean hasTransferEncoding = request.version() == HttpVersion.HTTP_2
|
||||
|| request.headers().contains(HttpHeaders.TRANSFER_ENCODING);
|
||||
|
||||
if (!hasTransferEncoding && parsedContentLength == -1) {
|
||||
// there is no "body", so we can skip this handler
|
||||
context.next();
|
||||
return;
|
||||
}
|
||||
|
||||
// before parsing the body we can already discard a bad request just by
|
||||
// inspecting the content-length against
|
||||
// the body limit, this will reduce load, on the server by totally skipping
|
||||
// parsing the request body
|
||||
if (bodyLimit != -1 && parsedContentLength != -1) {
|
||||
if (parsedContentLength > bodyLimit) {
|
||||
context.fail(413);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// handle expectations
|
||||
// https://httpwg.org/specs/rfc7231.html#header.expect
|
||||
final String expect = request.getHeader(HttpHeaders.EXPECT);
|
||||
if (expect != null) {
|
||||
// requirements validation
|
||||
if (expect.equalsIgnoreCase("100-continue")) {
|
||||
// A server that receives a 100-continue expectation in an HTTP/1.0 request MUST
|
||||
// ignore that expectation.
|
||||
if (request.version() != HttpVersion.HTTP_1_0) {
|
||||
// signal the client to continue
|
||||
response.writeContinue();
|
||||
}
|
||||
} else {
|
||||
// the server cannot meet the expectation, we only know about 100-continue
|
||||
context.fail(417);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 改造了这个地方 在真正解析body之前验证是否需要继续解析
|
||||
AppConfig appConfig = AppUtils.getAppConfigFromRoutingContext(context);
|
||||
ApiConfig apiConfig = AppUtils.getApiConfigFromRoutingContext(context);
|
||||
String apiServiceType = context.get(API_SERVICE_TYPE);
|
||||
if (!AppUtils.isAnalysisBody(appConfig,apiConfig,apiServiceType)){
|
||||
context.next();
|
||||
return;
|
||||
}
|
||||
|
||||
final BHandler handler = new BHandler(context, isPreallocateBodyBuffer ? parsedContentLength : -1);
|
||||
boolean ended = request.isEnded();
|
||||
if (!ended) {
|
||||
request
|
||||
// resume the request (if paused)
|
||||
.handler(handler).endHandler(handler::end).resume();
|
||||
}
|
||||
} else {
|
||||
// on reroute we need to re-merge the form params if that was desired
|
||||
if (mergeFormAttributes && request.isExpectMultipart()) {
|
||||
request.params().addAll(request.formAttributes());
|
||||
}
|
||||
context.next();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyHandler setHandleFileUploads(boolean handleFileUploads) {
|
||||
this.handleFileUploads = handleFileUploads;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyHandler setBodyLimit(long bodyLimit) {
|
||||
this.bodyLimit = bodyLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyHandler setUploadsDirectory(String uploadsDirectory) {
|
||||
this.uploadsDir = uploadsDirectory;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyHandler setMergeFormAttributes(boolean mergeFormAttributes) {
|
||||
this.mergeFormAttributes = mergeFormAttributes;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyHandler setDeleteUploadedFilesOnEnd(boolean deleteUploadedFilesOnEnd) {
|
||||
this.deleteUploadedFilesOnEnd = deleteUploadedFilesOnEnd;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyHandler setPreallocateBodyBuffer(boolean isPreallocateBodyBuffer) {
|
||||
this.isPreallocateBodyBuffer = isPreallocateBodyBuffer;
|
||||
return this;
|
||||
}
|
||||
|
||||
private long parseContentLengthHeader(HttpServerRequest request) {
|
||||
String contentLength = request.getHeader(HttpHeaders.CONTENT_LENGTH);
|
||||
if (contentLength == null || contentLength.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
long parsedContentLength = Long.parseLong(contentLength);
|
||||
return parsedContentLength < 0 ? -1 : parsedContentLength;
|
||||
} catch (NumberFormatException ex) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private class BHandler implements Handler<Buffer> {
|
||||
private static final int MAX_PREALLOCATED_BODY_BUFFER_BYTES = 65535;
|
||||
|
||||
final RoutingContext context;
|
||||
final long contentLength;
|
||||
Buffer body;
|
||||
boolean failed;
|
||||
final AtomicInteger uploadCount = new AtomicInteger();
|
||||
boolean ended;
|
||||
long uploadSize = 0L;
|
||||
final boolean isMultipart;
|
||||
final boolean isUrlEncoded;
|
||||
|
||||
public BHandler(RoutingContext context, long contentLength) {
|
||||
this.context = context;
|
||||
this.contentLength = contentLength;
|
||||
// the request clearly states that there should
|
||||
// be a body, so we respect the client and ensure
|
||||
// that the body will not be null
|
||||
if (contentLength != -1) {
|
||||
initBodyBuffer();
|
||||
}
|
||||
|
||||
List<FileUpload> fileUploads = context.fileUploads();
|
||||
|
||||
final String contentType = context.request().getHeader(HttpHeaders.CONTENT_TYPE);
|
||||
if (contentType == null) {
|
||||
isMultipart = false;
|
||||
isUrlEncoded = false;
|
||||
} else {
|
||||
final String lowerCaseContentType = contentType.toLowerCase();
|
||||
isMultipart = lowerCaseContentType.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA.toString());
|
||||
isUrlEncoded = lowerCaseContentType
|
||||
.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString());
|
||||
}
|
||||
|
||||
if (isMultipart || isUrlEncoded) {
|
||||
context.request().setExpectMultipart(true);
|
||||
if (handleFileUploads) {
|
||||
makeUploadDir(context.vertx().fileSystem());
|
||||
}
|
||||
context.request().uploadHandler(upload -> {
|
||||
if (bodyLimit != -1 && upload.isSizeAvailable()) {
|
||||
// we can try to abort even before the upload starts
|
||||
long size = uploadSize + upload.size();
|
||||
if (size > bodyLimit) {
|
||||
failed = true;
|
||||
context.cancelAndCleanupFileUploads();
|
||||
context.fail(413);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (handleFileUploads) {
|
||||
// we actually upload to a file with a generated filename
|
||||
uploadCount.incrementAndGet();
|
||||
String uploadedFileName = new File(uploadsDir, UUID.randomUUID().toString()).getPath();
|
||||
FileUploadImpl fileUpload = new FileUploadImpl(context.vertx().fileSystem(), uploadedFileName,
|
||||
upload);
|
||||
fileUploads.add(fileUpload);
|
||||
Future<Void> fut = upload.streamToFileSystem(uploadedFileName);
|
||||
fut.onComplete(ar -> {
|
||||
if (fut.succeeded()) {
|
||||
uploadEnded();
|
||||
} else {
|
||||
context.cancelAndCleanupFileUploads();
|
||||
context.fail(ar.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
context.request().exceptionHandler(t -> {
|
||||
context.cancelAndCleanupFileUploads();
|
||||
int sc = 200;
|
||||
if (t instanceof DecoderException) {
|
||||
// bad request
|
||||
sc = 400;
|
||||
if (t.getCause() != null) {
|
||||
t = t.getCause();
|
||||
}
|
||||
}
|
||||
context.fail(sc, t);
|
||||
});
|
||||
}
|
||||
|
||||
private void initBodyBuffer() {
|
||||
int initialBodyBufferSize;
|
||||
if (contentLength < 0) {
|
||||
initialBodyBufferSize = DEFAULT_INITIAL_BODY_BUFFER_SIZE;
|
||||
} else if (contentLength > MAX_PREALLOCATED_BODY_BUFFER_BYTES) {
|
||||
initialBodyBufferSize = MAX_PREALLOCATED_BODY_BUFFER_BYTES;
|
||||
} else {
|
||||
initialBodyBufferSize = (int) contentLength;
|
||||
}
|
||||
|
||||
if (bodyLimit != -1) {
|
||||
initialBodyBufferSize = (int) Math.min(initialBodyBufferSize, bodyLimit);
|
||||
}
|
||||
|
||||
this.body = Buffer.buffer(initialBodyBufferSize);
|
||||
}
|
||||
|
||||
private void makeUploadDir(FileSystem fileSystem) {
|
||||
if (!fileSystem.existsBlocking(uploadsDir)) {
|
||||
fileSystem.mkdirsBlocking(uploadsDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Buffer buff) {
|
||||
if (failed) {
|
||||
return;
|
||||
}
|
||||
uploadSize += buff.length();
|
||||
if (bodyLimit != -1 && uploadSize > bodyLimit) {
|
||||
failed = true;
|
||||
context.cancelAndCleanupFileUploads();
|
||||
context.fail(413);
|
||||
} else {
|
||||
// multipart requests will not end up in the request body
|
||||
// url encoded should also not, however jQuery by default
|
||||
// post in urlencoded even if the payload is something else
|
||||
if (!isMultipart */
|
||||
/* && !isUrlEncoded *//*
|
||||
) {
|
||||
if (body == null) {
|
||||
initBodyBuffer();
|
||||
}
|
||||
body.appendBuffer(buff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uploadEnded() {
|
||||
int count = uploadCount.decrementAndGet();
|
||||
// only if parsing is done and count is 0 then all files have been processed
|
||||
if (ended && count == 0) {
|
||||
doEnd();
|
||||
}
|
||||
}
|
||||
|
||||
void end(Void v) {
|
||||
// this marks the end of body parsing, calling doEnd should
|
||||
// only be possible from this moment onwards
|
||||
ended = true;
|
||||
|
||||
// only if parsing is done and count is 0 then all files have been processed
|
||||
if (uploadCount.get() == 0) {
|
||||
doEnd();
|
||||
}
|
||||
}
|
||||
|
||||
void doEnd() {
|
||||
|
||||
if (failed || context.failed()) {
|
||||
context.cancelAndCleanupFileUploads();
|
||||
return;
|
||||
}
|
||||
|
||||
if (deleteUploadedFilesOnEnd) {
|
||||
context.addBodyEndHandler(x -> context.cancelAndCleanupFileUploads());
|
||||
}
|
||||
|
||||
HttpServerRequest req = context.request();
|
||||
if (mergeFormAttributes && req.isExpectMultipart()) {
|
||||
req.params().addAll(req.formAttributes());
|
||||
}
|
||||
((RoutingContextInternal) context).setBody(body);
|
||||
// release body as it may take lots of memory
|
||||
body = null;
|
||||
|
||||
context.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.ext.consul.ConsulClientOptions;
|
||||
import io.vertx.ext.consul.KeyValue;
|
||||
import io.vertx.ext.consul.KeyValueList;
|
||||
import io.vertx.ext.consul.Watch;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ConsulHandler {
|
||||
|
||||
public static void init(Vertx vertx) {
|
||||
ConsulClientOptions options = new ConsulClientOptions().setHost("127.0.0.1").setPort(8500);
|
||||
|
||||
Watch.keyPrefix("apiCode_", vertx, options)
|
||||
.setHandler(res -> {
|
||||
if (res.succeeded()) {
|
||||
//
|
||||
// 删除
|
||||
boolean isDel = (res.nextResult() == null || res.nextResult().getList() == null) && (res.prevResult() != null && res.prevResult().getList() != null);
|
||||
// if(isDel) {
|
||||
// log.info("isDel");
|
||||
// KeyValueList keyValueList = res.prevResult();
|
||||
// if(keyValueList != null && keyValueList.getList() != null) {
|
||||
// for(KeyValue keyValue : keyValueList.getList()) {
|
||||
// log.info("keyValue:{}", JSONObject.toJSONString(keyValue));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // 新增
|
||||
// boolean isAdd = (res.nextResult() != null && res.nextResult().getList() != null) && (res.prevResult() == null || res.prevResult().getList() == null);
|
||||
// if(isAdd) {
|
||||
// log.info("isAdd");
|
||||
// KeyValueList keyValueList = res.nextResult();
|
||||
// if(keyValueList != null && keyValueList.getList() != null) {
|
||||
// for(KeyValue keyValue : keyValueList.getList()) {
|
||||
// log.info("keyValue:{}", JSONObject.toJSONString(keyValue));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 修改
|
||||
// boolean isModify = (res.nextResult() != null && res.nextResult().getList() != null) && (res.prevResult() != null && res.prevResult().getList() != null);
|
||||
// if(isModify) {
|
||||
// log.info("isModify");
|
||||
// KeyValueList keyValueList = res.nextResult();
|
||||
// if(keyValueList != null && keyValueList.getList() != null) {
|
||||
// for(KeyValue keyValue : keyValueList.getList()) {
|
||||
// log.info("keyValue:{}", JSONObject.toJSONString(keyValue));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
res.cause().printStackTrace();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
public static void init1(Vertx vertx) {
|
||||
ConsulClientOptions options = new ConsulClientOptions().setHost("127.0.0.1").setPort(8500);
|
||||
|
||||
Watch.keyPrefix("apiCode_", vertx, options)
|
||||
.setHandler(res -> {
|
||||
if (res.succeeded()) {
|
||||
// 删除
|
||||
boolean isDel = (res.nextResult() == null || res.nextResult().getList() == null) && (res.prevResult() != null && res.prevResult().getList() != null);
|
||||
if(isDel) {
|
||||
log.info("isDel");
|
||||
KeyValueList keyValueList = res.prevResult();
|
||||
if(keyValueList != null && keyValueList.getList() != null) {
|
||||
for(KeyValue keyValue : keyValueList.getList()) {
|
||||
log.info("keyValue:{}", JSONObject.toJSONString(keyValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 新增
|
||||
boolean isAdd = (res.nextResult() != null && res.nextResult().getList() != null) && (res.prevResult() == null || res.prevResult().getList() == null);
|
||||
if(isAdd) {
|
||||
log.info("isAdd");
|
||||
KeyValueList keyValueList = res.nextResult();
|
||||
if(keyValueList != null && keyValueList.getList() != null) {
|
||||
for(KeyValue keyValue : keyValueList.getList()) {
|
||||
log.info("keyValue:{}", JSONObject.toJSONString(keyValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修改
|
||||
boolean isModify = (res.nextResult() != null && res.nextResult().getList() != null) && (res.prevResult() != null && res.prevResult().getList() != null);
|
||||
if(isModify) {
|
||||
log.info("isModify");
|
||||
KeyValueList keyValueList = res.nextResult();
|
||||
if(keyValueList != null && keyValueList.getList() != null) {
|
||||
for(KeyValue keyValue : keyValueList.getList()) {
|
||||
log.info("keyValue:{}", JSONObject.toJSONString(keyValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.cause().printStackTrace();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import com.sf.vertx.api.pojo.ApiConfig;
|
||||
import com.sf.vertx.enums.GatewayError;
|
||||
import com.sf.vertx.exception.ServiceException;
|
||||
|
||||
import com.sf.vertx.api.pojo.AppConfig;
|
||||
|
||||
import com.sf.vertx.utils.AppUtils;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.sf.vertx.constans.SACConstants.*;
|
||||
|
||||
@Slf4j
|
||||
public class OpenParameterCheckHandlerImpl implements ParameterCheckHandler {
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext rc) {
|
||||
try {
|
||||
log.info("Enter OPEN Route");
|
||||
// 判断OPEN模式, header不传递参数, 走域名映射
|
||||
String domain = rc.request().authority().host();
|
||||
log.info("request domain:{}", domain);
|
||||
AppConfig appConfig = AppConfigHandler.getAppConfigByDomain(domain);
|
||||
if (appConfig == null) {
|
||||
rc.fail(new ServiceException(GatewayError.APP_SERVICE_NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
String apiCacheKey = appConfig.getAppCode() + CACHE_KEY_CONNECTOR + domain;
|
||||
ApiConfig apiConfig = AppConfigHandler.getApicodeConfig(apiCacheKey);
|
||||
if (apiConfig == null) {
|
||||
rc.fail(new ServiceException(GatewayError.API_SERVICE_NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
// 设置应用配置、接口配置到上下文
|
||||
AppUtils.setAppConfigToRoutingContext(appConfig,rc);
|
||||
AppUtils.setApiConfigIntoRoutingContext(apiConfig,rc);
|
||||
rc.put(API_SERVICE_TYPE,AppConfigHandler.getServiceTypeOpen(apiCacheKey));
|
||||
// 将appcode和apicode设置到请求头,兼容原有逻辑
|
||||
rc.request().headers().add(AppConfigHandler.getAppCodeHeaderKey(), appConfig.getAppCode());
|
||||
rc.request().headers().add(AppConfigHandler.getApiCodeHeaderKey(), apiConfig.getApiCode());
|
||||
} catch (Exception e) {
|
||||
log.error("OpenParameterCheckHandlerImpl Error:",e);
|
||||
rc.fail(new ServiceException(GatewayError.DEFAULT_SERVICE_ERROR));
|
||||
return;
|
||||
}
|
||||
rc.next();
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import com.sf.vertx.enums.GatewayServiceType;
|
||||
import com.sf.vertx.enums.GatewayError;
|
||||
import com.sf.vertx.exception.ServiceException;
|
||||
import io.vertx.codegen.annotations.VertxGen;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
*/
|
||||
/***
|
||||
* 入口参数校验
|
||||
*
|
||||
* @author xy
|
||||
*
|
||||
*//*
|
||||
|
||||
@VertxGen
|
||||
public interface ParameterCheckHandler extends Handler<RoutingContext> {
|
||||
|
||||
static ParameterCheckHandler create(GatewayServiceType serviceType) {
|
||||
switch (serviceType) {
|
||||
case SAC:
|
||||
return new SACParameterCheckHandlerImpl();
|
||||
case OPEN:
|
||||
return new OpenParameterCheckHandlerImpl();
|
||||
default:
|
||||
throw new ServiceException(GatewayError.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,27 @@
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import io.vertx.codegen.annotations.VertxGen;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.httpproxy.HttpProxy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:emad.albloushi@gmail.com">Emad Alblueshi</a>
|
||||
*/
|
||||
|
||||
@VertxGen
|
||||
public interface ProxyHandler extends Handler<RoutingContext> {
|
||||
|
||||
static ProxyHandler create(WebClient mainWebClient, HttpProxy httpProxy) {
|
||||
return new ProxyHandlerImpl(mainWebClient, httpProxy);
|
||||
}
|
||||
|
||||
static ProxyHandler create(HttpProxy httpProxy) {
|
||||
return new ProxyHandlerImpl(httpProxy);
|
||||
}
|
||||
|
||||
static ProxyHandler create(HttpProxy httpProxy, int port, String host) {
|
||||
return new ProxyHandlerImpl(httpProxy, port, host);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.httpproxy.HttpProxy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:emad.albloushi@gmail.com">Emad Alblueshi</a>
|
||||
*/
|
||||
public class ProxyHandlerImpl implements ProxyHandler {
|
||||
|
||||
private final HttpProxy httpProxy;
|
||||
private WebClient mainWebClient;
|
||||
|
||||
public ProxyHandlerImpl(WebClient mainWebClient, HttpProxy httpProxy) {
|
||||
this.httpProxy = httpProxy;
|
||||
this.mainWebClient = mainWebClient;
|
||||
}
|
||||
|
||||
public ProxyHandlerImpl(HttpProxy httpProxy) {
|
||||
this.httpProxy = httpProxy;
|
||||
}
|
||||
|
||||
public ProxyHandlerImpl(HttpProxy httpProxy, int port, String host) {
|
||||
this.httpProxy = httpProxy.origin(port, host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext ctx) {
|
||||
// TODO 改造了这个地方
|
||||
// httpProxy.handle(mainWebClient, ctx);
|
||||
// 原始代码只有如下一句
|
||||
httpProxy.handle(ctx.request());
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
public interface RestfulFailureHandler extends Handler<RoutingContext> {
|
||||
static RestfulFailureHandlerImpl create() {
|
||||
return new RestfulFailureHandlerImpl();
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.sf.vertx.enums.GatewayError;
|
||||
import com.sf.vertx.exception.MockException;
|
||||
import com.sf.vertx.exception.ServiceException;
|
||||
import com.sf.vertx.utils.AppUtils;
|
||||
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.handler.HttpException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class RestfulFailureHandlerImpl implements RestfulFailureHandler {
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext frc) {
|
||||
int statusCode = 500;
|
||||
JsonObject errorJson;
|
||||
// 网关的业务码
|
||||
int gatewayServiceCode = GatewayError.DEFAULT_SERVICE_ERROR.getCode();
|
||||
try {
|
||||
Throwable failure = frc.failure();
|
||||
log.info("failure:", failure);
|
||||
if (failure instanceof HttpException) {
|
||||
HttpException httpException = (HttpException) failure;
|
||||
gatewayServiceCode = httpException.getStatusCode();
|
||||
errorJson = AppUtils.getResponseJsonByGatewayError(GatewayError.getByCode(statusCode));
|
||||
if (StrUtil.isNotBlank(httpException.getPayload())){
|
||||
errorJson.put("msg",httpException.getPayload());
|
||||
}
|
||||
} else if (failure instanceof MockException) {
|
||||
MockException mockException = (MockException) failure;
|
||||
gatewayServiceCode = mockException.getStatusCode();
|
||||
statusCode = mockException.getStatusCode();
|
||||
errorJson = new JsonObject(mockException.getPayload());
|
||||
}else if (failure instanceof ServiceException) {
|
||||
ServiceException serviceException = (ServiceException) failure;
|
||||
gatewayServiceCode = serviceException.getStatusCode();
|
||||
// 业务异常,为网关业务错误,响应码设置为500,
|
||||
GatewayError gatewayError = GatewayError.getByCode(serviceException.getStatusCode());
|
||||
// 如果是被限流或熔断,直接返回限流或熔断响应
|
||||
if (GatewayError.REQUEST_URL_RESTRICTED_BY_FLOW.equals(gatewayError)
|
||||
|| GatewayError.REQUEST_URL_IS_BROKEN.equals(gatewayError)){
|
||||
errorJson = new JsonObject(serviceException.getPayload());
|
||||
}else {
|
||||
errorJson = AppUtils.getResponseJsonByGatewayError(gatewayError);
|
||||
}
|
||||
} else {
|
||||
errorJson = AppUtils.getResponseJsonByGatewayError(GatewayError.getByCode(statusCode));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("RestfulFailureHandlerImpl.handle Error:",e);
|
||||
errorJson = AppUtils.getResponseJsonByGatewayError(GatewayError.DEFAULT_SERVICE_ERROR);
|
||||
}
|
||||
frc.response().setChunked(true).setStatusCode(statusCode).putHeader("Content-Type", "application/json")
|
||||
.putHeader(AppConfigHandler.sacResponseHeaderKey(), String.valueOf(gatewayServiceCode))
|
||||
.end(errorJson.toBuffer());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
*/
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
package com.sf.vertx.handle;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.sf.vertx.api.pojo.ApiConfig;
|
||||
import com.sf.vertx.api.pojo.AppConfig;
|
||||
import com.sf.vertx.enums.GatewayError;
|
||||
import com.sf.vertx.exception.ServiceException;
|
||||
import com.sf.vertx.utils.AppUtils;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.sf.vertx.constans.SACConstants.*;
|
||||
|
||||
@Slf4j
|
||||
public class SACParameterCheckHandlerImpl implements ParameterCheckHandler {
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext rc) {
|
||||
try {
|
||||
log.info("Enter SAC Route");
|
||||
// SAC规范要求Header必须要同时包含appCode和apiCode
|
||||
String appCode = rc.request().headers().get(AppConfigHandler.getAppCodeHeaderKey());
|
||||
String apiCode = rc.request().headers().get(AppConfigHandler.getApiCodeHeaderKey());
|
||||
if (StrUtil.isBlank(appCode) || StrUtil.isBlank(apiCode)) {
|
||||
rc.fail(new ServiceException(GatewayError.PARAMETER_TRANSFER_ERROR));
|
||||
return;
|
||||
}
|
||||
AppConfig appConfig = AppConfigHandler.getAppConfig(appCode);
|
||||
if (appConfig == null) {
|
||||
rc.fail(new ServiceException(GatewayError.APP_SERVICE_NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
String apiCacheKey = appConfig.getAppCode() + CACHE_KEY_CONNECTOR + apiCode;
|
||||
ApiConfig apiConfig = AppConfigHandler.getApicodeConfig(apiCacheKey);
|
||||
if (apiConfig == null) {
|
||||
rc.fail(new ServiceException(GatewayError.API_SERVICE_NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
// 设置应用配置、接口配置到上下文
|
||||
AppUtils.setAppConfigToRoutingContext(appConfig,rc);
|
||||
AppUtils.setApiConfigIntoRoutingContext(apiConfig,rc);
|
||||
rc.put(API_SERVICE_TYPE,AppConfigHandler.getServiceTypeOpen(apiCacheKey));
|
||||
} catch (Exception e) {
|
||||
log.error("SACParameterCheckHandlerImpl Error:",e);
|
||||
rc.fail(new ServiceException(GatewayError.DEFAULT_SERVICE_ERROR));
|
||||
return;
|
||||
}
|
||||
rc.next();
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
package com.sf.vertx.init;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.sf.vertx.constans.RedisKeyConfig;
|
||||
import com.sf.vertx.handle.AppConfigHandler;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
*/
|
||||
/***
|
||||
* 动态构建vertx服务
|
||||
*
|
||||
* @author xy
|
||||
*
|
||||
*//*
|
||||
|
||||
@Slf4j
|
||||
@Order(value = 10)
|
||||
@Component
|
||||
public class DynamicBuildServer implements ApplicationRunner {
|
||||
// TODO 后面可以和app挂钩
|
||||
@Autowired
|
||||
private SacVertxConfig sacVertxConfig;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private RedisKeyConfig redisKeyConfig;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
// 初始化redis key
|
||||
redisKeyConfig.init();
|
||||
AppConfigHandler.init(redisTemplate, sacVertxConfig);
|
||||
// 从redis同步vertx配置
|
||||
AppConfigHandler.initVertxConfig();
|
||||
|
||||
// 加载vertx、应用配置
|
||||
startVertxService();
|
||||
}
|
||||
|
||||
*/
|
||||
/***
|
||||
* 应用启动, 从redis读取配置,初始化vertx服务
|
||||
*
|
||||
* @throws Exception
|
||||
*//*
|
||||
|
||||
private void startVertxService() throws Exception {
|
||||
if(sacVertxConfig.getDeploymentMode() == 2) {
|
||||
// 集群
|
||||
AppConfigHandler.createHazelcastClusterVertx();
|
||||
} else {
|
||||
// 单机
|
||||
AppConfigHandler.createVertx();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
@ -0,0 +1,188 @@
|
||||
package com.sf.vertx.init;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.sf.vertx.enums.GatewayError;
|
||||
import io.vertx.config.ConfigRetriever;
|
||||
import io.vertx.config.ConfigRetrieverOptions;
|
||||
import io.vertx.config.ConfigStoreOptions;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.AsyncResult;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.http.HttpHeaders;
|
||||
import io.vertx.core.http.HttpServerOptions;
|
||||
import io.vertx.core.http.HttpServerResponse;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.core.net.JksOptions;
|
||||
import io.vertx.ext.web.Route;
|
||||
import io.vertx.ext.web.Router;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
import io.vertx.ext.web.handler.CorsHandler;
|
||||
import io.vertx.ext.web.handler.SessionHandler;
|
||||
import io.vertx.ext.web.sstore.ClusteredSessionStore;
|
||||
import io.vertx.ext.web.sstore.LocalSessionStore;
|
||||
import io.vertx.ext.web.sstore.SessionStore;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 网关配置同步与管理
|
||||
*/
|
||||
@Slf4j
|
||||
public class GatewayConfigVerticle extends AbstractVerticle {
|
||||
|
||||
/**
|
||||
* HTTP的路由服务
|
||||
*/
|
||||
private static Router httpRouter = null;
|
||||
|
||||
private static VertxProperties vertxProperties;
|
||||
|
||||
/**
|
||||
* 全局IP黑名单
|
||||
*/
|
||||
private Set<String> blackIpSet = new LinkedHashSet<>();
|
||||
|
||||
public void start() {
|
||||
ConfigStoreOptions store = new ConfigStoreOptions()
|
||||
.setType("file")
|
||||
.setFormat("yaml")
|
||||
.setConfig(new JsonObject()
|
||||
.put("path", "application.yaml")
|
||||
);
|
||||
|
||||
ConfigRetriever retriever = ConfigRetriever.create(vertx,
|
||||
new ConfigRetrieverOptions().addStore(store));
|
||||
retriever.getConfig(json -> {
|
||||
if (json.succeeded()) {
|
||||
doStart(json);
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void doStart(AsyncResult<JsonObject> json) {
|
||||
JsonObject config = json.result();
|
||||
log.info("配置读取成功");
|
||||
// 读取vertx配置
|
||||
JsonObject vertxConfig = config.getJsonObject("vertx");
|
||||
// 读取redis配置
|
||||
JsonObject redisConfig = config.getJsonObject("redis");
|
||||
|
||||
//先初始化再发布Http服务
|
||||
vertx.executeBlocking(() -> {
|
||||
//顺序不能乱
|
||||
try {
|
||||
//初始化Redis
|
||||
RedisManager.initRedisConfig(vertx, redisConfig, redisConnectionAsyncResult -> {
|
||||
if (redisConnectionAsyncResult.succeeded()) {
|
||||
log.info("GatewayConfig redis初始化成功");
|
||||
} else {
|
||||
log.error("GatewayConfig redis初始化失败", redisConnectionAsyncResult.cause());
|
||||
stop();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("GatewayConfig redis初始化失败:", e);
|
||||
stop();
|
||||
}
|
||||
return true;
|
||||
}).onComplete(ar -> {
|
||||
// 启动服务
|
||||
if (ar.succeeded()) {
|
||||
vertxProperties = vertxConfig.mapTo(VertxProperties.class);
|
||||
blackIpSet = new LinkedHashSet<>(List.of(vertxProperties.getBlackIP()));
|
||||
createHttpServer(createServerAR ->{
|
||||
if (createServerAR.succeeded()) {
|
||||
log.info("GatewayConfig 启动服务成功");
|
||||
// 添加路由
|
||||
Route addAppConfigRoute = httpRouter.post("/vertx/app/config");
|
||||
addAppConfigRoute.handler(BodyHandler.create(false))
|
||||
.handler(ctx ->{
|
||||
String s = ctx.body().asString();
|
||||
log.info(s);
|
||||
JSONObject jsonre = new JSONObject();
|
||||
jsonre.put("code", 200);
|
||||
jsonre.put("msg", "success");
|
||||
ctx.response()
|
||||
.putHeader("Content-Type", "application/json")
|
||||
.end(jsonre.toJSONString());
|
||||
});
|
||||
|
||||
} else {
|
||||
log.error("GatewayConfig 启动服务失败", createServerAR.cause());
|
||||
stop();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.error("GatewayConfig 启动服务失败:", ar.cause());
|
||||
stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void stop() {
|
||||
log.info("close GatewayConfigVerticle !");
|
||||
vertx.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建http服务器
|
||||
*
|
||||
* @param createHttp
|
||||
*/
|
||||
public void createHttpServer(Handler<AsyncResult<Void>> createHttp) {
|
||||
// 所有ip都能访问
|
||||
HttpServerOptions httpServerOptions = new HttpServerOptions().setHost("0.0.0.0");
|
||||
if (vertxProperties.isSSL()) {
|
||||
httpServerOptions.setSsl(true)
|
||||
.setKeyStoreOptions(new JksOptions().setPassword("changeit").setPath("keystore.jks"));
|
||||
}
|
||||
httpRouter = Router.router(vertx);
|
||||
httpRouter.route().handler(this::filterBlackIP);
|
||||
httpRouter.route().handler(CorsHandler.create().addRelativeOrigin(".*"));
|
||||
// 创建http服务器
|
||||
vertx.createHttpServer(httpServerOptions)
|
||||
.requestHandler(httpRouter)
|
||||
.listen(vertxProperties.getConfigPort(), res -> {
|
||||
if (res.succeeded()) {
|
||||
log.info("GatewayConfigServer Running on port {} by HTTP", vertxProperties.getConfigPort());
|
||||
createHttp.handle(Future.succeededFuture());
|
||||
} else {
|
||||
log.error("create HTTP Server (GatewayConfigServer) failed : GatewayConfigServer Running on port {} by HTTP", vertxProperties.getConfigPort());
|
||||
createHttp.handle(Future.failedFuture(res.cause()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤黑名单
|
||||
*
|
||||
* @param rct
|
||||
*/
|
||||
public void filterBlackIP(RoutingContext rct) {
|
||||
|
||||
String host = rct.request().remoteAddress().host();
|
||||
if (blackIpSet.contains(host)) {
|
||||
HttpServerResponse response = rct.response();
|
||||
response.putHeader(HttpHeaders.CONTENT_TYPE, "text/html");
|
||||
response.setStatusCode(GatewayError.FORBIDDEN.getCode());
|
||||
response.setStatusMessage(GatewayError.FORBIDDEN.getReasonPhrase());
|
||||
response.end("<html>" +
|
||||
"<body><h1>you can't access this service</h1></body>" +
|
||||
"</html>");
|
||||
} else {
|
||||
rct.next();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package com.sf.vertx.init;
|
||||
|
||||
import io.vertx.config.ConfigRetriever;
|
||||
import io.vertx.config.ConfigRetrieverOptions;
|
||||
import io.vertx.config.ConfigStoreOptions;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.AsyncResult;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.http.HttpServer;
|
||||
import io.vertx.core.http.HttpServerResponse;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.Router;
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
import io.vertx.ext.web.handler.StaticHandler;
|
||||
import io.vertx.redis.client.impl.RedisClient;
|
||||
|
||||
|
||||
/**
|
||||
* 网关RPC支持
|
||||
*/
|
||||
public class GatewayRPCVerticle extends AbstractVerticle {
|
||||
|
||||
|
||||
|
||||
|
||||
public void start() {
|
||||
ConfigStoreOptions store = new ConfigStoreOptions()
|
||||
.setType("file")
|
||||
.setFormat("yaml")
|
||||
.setConfig(new JsonObject()
|
||||
.put("path", "application.yaml")
|
||||
);
|
||||
|
||||
ConfigRetriever retriever = ConfigRetriever.create(vertx,
|
||||
new ConfigRetrieverOptions().addStore(store));
|
||||
// Create redisClient
|
||||
JsonObject cachedConfig = retriever.getCachedConfig();
|
||||
|
||||
//RedisOptions config = new RedisOptions()
|
||||
//.("127.0.0.1");
|
||||
|
||||
//redisClient = new RedisClient(vertx, config);
|
||||
}
|
||||
|
||||
private void startWebApp(Handler<AsyncResult<HttpServer>> next) {
|
||||
// Create a router object.
|
||||
Router router = Router.router(vertx);
|
||||
|
||||
// Bind "/" to our hello message.
|
||||
router.route("/").handler(routingContext -> {
|
||||
HttpServerResponse response = routingContext.response();
|
||||
response
|
||||
.putHeader("content-type", "text/html")
|
||||
.end("<h1>Hello from my first Vert.x 3 application</h1>");
|
||||
});
|
||||
|
||||
router.route("/assets/*").handler(StaticHandler.create("assets"));
|
||||
|
||||
/* router.get("/api/whiskies").handler(this::getAll);
|
||||
router.route("/api/whiskies*").handler(BodyHandler.create());
|
||||
router.post("/api/whiskies").handler(this::addOne);
|
||||
router.get("/api/whiskies/:id").handler(this::getOne);
|
||||
router.put("/api/whiskies/:id").handler(this::updateOne);
|
||||
router.delete("/api/whiskies/:id").handler(this::deleteOne);*/
|
||||
|
||||
|
||||
// Create the HTTP server and pass the "accept" method to the request handler.
|
||||
vertx
|
||||
.createHttpServer()
|
||||
.listen(
|
||||
// Retrieve the port from the configuration,
|
||||
// default to 8080.
|
||||
config().getInteger("http.port", 8080),
|
||||
next::handle
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.sf.vertx.init;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.vertx.core.AsyncResult;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.redis.client.*;
|
||||
import io.vertx.redis.client.impl.RedisClient;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 功能描述:
|
||||
*
|
||||
* @author a_kun
|
||||
* @date 2024/5/27 16:53
|
||||
*/
|
||||
public class RedisManager {
|
||||
|
||||
private static RedisAPI redisAPI;
|
||||
private static Redis redis;
|
||||
|
||||
private static RedisProperties redisProperties;
|
||||
|
||||
public static void initRedisConfig(Vertx vertx, JsonObject redisConfig, Handler<AsyncResult<RedisConnection>> handler) {
|
||||
try {
|
||||
if (redis == null) {
|
||||
redisProperties = redisConfig.mapTo(RedisProperties.class);
|
||||
//解析配置
|
||||
RedisOptions options = new RedisOptions()
|
||||
.setType(redisProperties.getClientType())
|
||||
.setPoolName(redisProperties.getPoolName())
|
||||
.setMaxPoolSize(redisProperties.getMaxPoolSize())
|
||||
.setMaxPoolWaiting(redisProperties.getMaxPoolWaiting())
|
||||
.setPoolCleanerInterval(redisProperties.getPoolCleanerInterval());
|
||||
// password
|
||||
if (StrUtil.isNotBlank(redisProperties.getPassword())) {
|
||||
options.setPassword(redisProperties.getPassword());
|
||||
}
|
||||
// connect address [redis://localhost:6379/0, redis://localhost:6779/1]
|
||||
for (String url : redisProperties.getUrls()) {
|
||||
options.addConnectionString(url);
|
||||
}
|
||||
// sentinel
|
||||
if (redisProperties.getClientType().equals(RedisClientType.SENTINEL)) {
|
||||
options.setRole(redisProperties.getRole()).setMasterName(redisProperties.getMasterName());
|
||||
}
|
||||
//创建redis实例
|
||||
redis = Redis.createClient(vertx, options);
|
||||
redisAPI = RedisAPI.api(redis);
|
||||
}
|
||||
handler.handle(Future.succeededFuture());
|
||||
} catch (Exception e) {
|
||||
handler.handle(Future.failedFuture(e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.sf.vertx.init;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import io.vertx.redis.client.RedisClientType;
|
||||
import io.vertx.redis.client.RedisRole;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Redis配置
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class RedisProperties {
|
||||
private RedisClientType clientType = RedisClientType.STANDALONE;
|
||||
private String[] urls;
|
||||
private String password;
|
||||
private String poolName = "vertx-redis";
|
||||
private int poolCleanerInterval = 30000;
|
||||
private int maxPoolSize = 8;
|
||||
private int maxPoolWaiting = 32;
|
||||
private String masterName = "redis_master";
|
||||
private RedisRole role = RedisRole.MASTER;
|
||||
|
||||
private Integer expireTime = 60 * 60;
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
package com.sf.vertx.init;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
*/
|
||||
/***
|
||||
* 配置文件
|
||||
* @author xy
|
||||
*
|
||||
*//*
|
||||
|
||||
//@Component
|
||||
@Data
|
||||
public class SacVertxConfig {
|
||||
@Value("${server.vertx.server.default.port:80}")
|
||||
private Integer port;
|
||||
|
||||
@Value("${server.vertx.cluster.ip:127.0.0.1}")
|
||||
private String clusterIp;
|
||||
|
||||
@Value("${server.vertx.cluster.networkPort:5701}")
|
||||
private Integer networkPort;
|
||||
|
||||
@Value("${server.vertx.cluster.portAutoIncrement:false}")
|
||||
private boolean portAutoIncrement;
|
||||
|
||||
@Value("${server.vertx.sacResponseHeaderKey:sacErrorCode}")
|
||||
private String sacResponseHeaderKey;
|
||||
|
||||
@Value("${server.vertx.rpcUri:/rpc}")
|
||||
private String rpcUri;
|
||||
|
||||
@Value("${server.vertx.isSSL:false}")
|
||||
private boolean isSSLs;
|
||||
|
||||
@Value("${server.vertx.deploymentMode:1}")
|
||||
private Integer deploymentMode;
|
||||
|
||||
@Value("${server.vertx.cluster.clusterName:sac}")
|
||||
private String clusterName;
|
||||
}
|
||||
*/
|
@ -0,0 +1,26 @@
|
||||
package com.sf.vertx.init;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Redis配置
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class VertxProperties {
|
||||
private int deploymentMode = 1;
|
||||
private boolean isSSL = false;
|
||||
private int rpcPort = 80;
|
||||
private String rpcUri;
|
||||
private String sacResponseHeaderKey;
|
||||
private String environment;
|
||||
private int configPort = 5566;
|
||||
private Map<String,Object> cluster;
|
||||
private String[] blackIP;// 黑名单
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.sf.vertx.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ClusterEventMsg implements Serializable{
|
||||
private static final long serialVersionUID = -3380175299815557039L;
|
||||
private int type; // 1: app
|
||||
private int operation; // 1:新增,修改,3:删除
|
||||
private String appCode;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.sf.vertx.pojo;
|
||||
|
||||
import com.sf.vertx.api.pojo.Strategy;
|
||||
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SacCurrentLimiting {
|
||||
|
||||
private RateLimiterRegistry registry;
|
||||
private Strategy strategy;
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package com.sf.vertx.security;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* 本类采用对称加密 加密算法:{@link #PADDINIG_MODE}
|
||||
* {@link #RESULT_TYPE} 密文结果:1=base64 2=hex
|
||||
*/
|
||||
public class AesUtils {
|
||||
/**
|
||||
* 整平方法
|
||||
*/
|
||||
private static final int RESULT_TYPE = 2;
|
||||
|
||||
/**
|
||||
* 加密方法
|
||||
*/
|
||||
private static final String AES_ALGORITHM = "AES";
|
||||
|
||||
/**
|
||||
* 填充方法
|
||||
*/
|
||||
private static final String PADDINIG_MODE = "AES/CBC/PKCS5Padding";
|
||||
|
||||
/**
|
||||
* 偏移量
|
||||
*/
|
||||
private static final byte[] IV = "0000000000000000".getBytes();
|
||||
|
||||
public static String encrypt(String s, String k) throws Exception {
|
||||
SecretKeySpec key = new SecretKeySpec(StringUtils.getBytes(k), AES_ALGORITHM);
|
||||
byte[] data = encrypt(StringUtils.getBytes(s), key);
|
||||
String result;
|
||||
switch (RESULT_TYPE) {
|
||||
case 1:
|
||||
result = Base64Utils.encode(data);
|
||||
break;
|
||||
case 2:
|
||||
result = HexUtils.bytes2Hex(data);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unsupport Result Type");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String decrypt(String s, String k) throws Exception {
|
||||
SecretKeySpec key = new SecretKeySpec(StringUtils.getBytes(k), AES_ALGORITHM);
|
||||
byte[] data;
|
||||
switch (RESULT_TYPE) {
|
||||
case 1:
|
||||
data = Base64Utils.decode(s);
|
||||
break;
|
||||
case 2:
|
||||
data = HexUtils.hex2Bytes(s);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unsupport Result Type");
|
||||
}
|
||||
return StringUtils.bytes2String(decrypt(data, key));
|
||||
}
|
||||
|
||||
private static byte[] encrypt(byte[] data, SecretKeySpec keySpec) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
|
||||
IvParameterSpec ivspec = new IvParameterSpec(IV);
|
||||
Cipher cipher = Cipher.getInstance(PADDINIG_MODE);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivspec);
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
private static byte[] decrypt(byte[] data, SecretKeySpec keySpec) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
|
||||
IvParameterSpec ivspec = new IvParameterSpec(IV);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivspec);
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.sf.vertx.security;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 将Base64字符串和字节数组互转
|
||||
*/
|
||||
public class Base64Utils {
|
||||
|
||||
/**
|
||||
* 将字节数组编码为base64字符串
|
||||
*
|
||||
* @param b 字节数组
|
||||
* @return String 字符串
|
||||
*/
|
||||
public static String encode(byte[] b) {
|
||||
return Base64.getEncoder().encodeToString(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串转base64为字节数组
|
||||
*
|
||||
* @param s 字符串
|
||||
* @return byte[] 字节数组
|
||||
*/
|
||||
public static byte[] decode(String s) {
|
||||
return Base64.getDecoder().decode(s);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.sf.vertx.security;
|
||||
|
||||
/**
|
||||
* 将字节数组和HEX字符串进行互转
|
||||
*/
|
||||
public class HexUtils {
|
||||
private static final char[] HEX = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||
|
||||
/**
|
||||
* 字节数组转字符串
|
||||
*
|
||||
* @param byteArr 字节数组
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String bytes2Hex(byte[] byteArr) {
|
||||
StringBuilder sb = new StringBuilder(byteArr.length);
|
||||
for (byte b : byteArr) {
|
||||
String sTemp = Integer.toHexString(255 & b);
|
||||
if (sTemp.length() < 2) {
|
||||
sb.append(0);
|
||||
}
|
||||
sb.append(sTemp.toUpperCase());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* HEX字符串转字节数组
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 字节数组
|
||||
*/
|
||||
public static byte[] hex2Bytes(String str) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
} else {
|
||||
char[] hex = str.toCharArray();
|
||||
int length = hex.length / 2;
|
||||
byte[] raw = new byte[length];
|
||||
|
||||
for (int i = 0; i < length; ++i) {
|
||||
int high = Character.digit(hex[i * 2], 16);
|
||||
int low = Character.digit(hex[i * 2 + 1], 16);
|
||||
int value = high << 4 | low;
|
||||
if (value > 127) {
|
||||
value -= 256;
|
||||
}
|
||||
raw[i] = (byte) value;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
package com.sf.vertx.security;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
*/
|
||||
/**
|
||||
* 本项目中真正对外提供服务的工具类
|
||||
*//*
|
||||
|
||||
public class MainSecurity {
|
||||
private static final Logger LOGGER = Logger.getLogger(MainSecurity.class.getName());
|
||||
|
||||
*/
|
||||
/**
|
||||
* 加密,当失败的时候,返回空字符串
|
||||
*
|
||||
* @param content
|
||||
* @param pubKey
|
||||
* @return
|
||||
*//*
|
||||
|
||||
public static String rsaEncrypt(String content, String pubKey) {
|
||||
try {
|
||||
return RSAUtil.encrypt(content, pubKey);
|
||||
} catch (Exception e) {
|
||||
LOGGER.info("RSA加密失败");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String rsaDecrypt(String content, String priKey) {
|
||||
try {
|
||||
return RSAUtil.decrypt(content, priKey);
|
||||
} catch (Exception e) {
|
||||
LOGGER.info("RSA解密失败");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String aesEncrypt(String content, String key) {
|
||||
try {
|
||||
return AesUtils.encrypt(content, key);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.info("AES加密失败");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String aesDecrypt(String content, String key) {
|
||||
try {
|
||||
return AesUtils.decrypt(content, key);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.info("AES解密失败");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String sign(String content) {
|
||||
try {
|
||||
return MessageDigest.md5(content);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.info("MD5加密失败");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// System.out.println(aesEncrypt("{\n"
|
||||
// + " \"errorCode\": \"中文\",\n"
|
||||
// + " \"result\": 0,\n"
|
||||
// + " \"data\": {\n"
|
||||
// + " \"username\" : \"测试\"\n"
|
||||
// + " }\n"
|
||||
// + "}", "dadddsdfadfadsfa33323223"));
|
||||
// System.out.println(aesDecrypt("59A69B6BBCF046C3CF9953C5CC078CC638602D454BBCE8CF8F0DA6AF1F3A4707686263C834A612C5C6F22D9F897B13B434A53E32AAD4036E12A5098565AB1AD352B400FC23354ECE977DDC670F793992D7F884264A9689B000E37157B4D41351", "dadddsdfadfadsfa33323223"));
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,47 @@
|
||||
package com.sf.vertx.security;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class MessageDigest {
|
||||
|
||||
public static String md5(String text) throws NoSuchAlgorithmException {
|
||||
java.security.MessageDigest digest = java.security.MessageDigest.getInstance("md5");
|
||||
byte[] buffer = digest.digest(text.getBytes());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : buffer) {
|
||||
int a = b & 0xff;
|
||||
String hex = Integer.toHexString(a);
|
||||
|
||||
if (hex.length() == 1) {
|
||||
hex = 0 + hex;
|
||||
}
|
||||
sb.append(hex);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String hmacSha1(String data, String key, int type) throws Exception {
|
||||
//根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
|
||||
SecretKeySpec signinKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
|
||||
//生成一个指定 Mac 算法 的 Mac 对象
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
//用给定密钥初始化 Mac 对象
|
||||
mac.init(signinKey);
|
||||
//完成 Mac 操作
|
||||
byte[] rawHmac = mac.doFinal(StringUtils.getBytes(data));
|
||||
String result;
|
||||
switch (type) {
|
||||
case 1:
|
||||
result = Base64Utils.encode(rawHmac);
|
||||
break;
|
||||
case 2:
|
||||
result = HexUtils.bytes2Hex(rawHmac);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unsupport Type");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,276 @@
|
||||
//package com.sf.vertx.security;
|
||||
//
|
||||
//
|
||||
//import javax.crypto.BadPaddingException;
|
||||
//import javax.crypto.Cipher;
|
||||
//import javax.crypto.IllegalBlockSizeException;
|
||||
//import javax.crypto.NoSuchPaddingException;
|
||||
//import java.io.ByteArrayOutputStream;
|
||||
//import java.io.IOException;
|
||||
//import java.io.UnsupportedEncodingException;
|
||||
//import java.security.*;
|
||||
//import java.security.spec.InvalidKeySpecException;
|
||||
//import java.security.spec.PKCS8EncodedKeySpec;
|
||||
//import java.security.spec.X509EncodedKeySpec;
|
||||
//import java.util.List;
|
||||
//
|
||||
///**
|
||||
// * 本类进行非对称加密,不推荐使用非对称加密对长字符串进行加密或者解密,徒增资源消耗,另外由于长度限制,过长的字符串的加密和解密会使用循环,对数据分段加密;本类采用的
|
||||
// * 密钥字符串均为Base64加密后的
|
||||
// * 另外所有异常都会抛出
|
||||
// * 下面将会列举几个可以自定义或者暴露出去的接口和参数
|
||||
// * {@link #IS_LONG_TEXT} 是否否对长文本处理
|
||||
// * {@link #RESULT_TYPE} 密文结果:1=base64 2=hex
|
||||
// * {@link #RSA_ALGORITHM} RSA算法
|
||||
// * {@link #encrypt(String, String)} 加密方法
|
||||
// * {@link #decrypt(String, String)} 解密方法
|
||||
// * {@link #getKeyPair} 解密方法
|
||||
// */
|
||||
//public class RSA2Utils {
|
||||
// /**
|
||||
// * 是否对长文本加密;请参照{@link #MAX_DECRYPT_BLOCK}和{@link #MAX_ENCRYPT_BLOCK}
|
||||
// */
|
||||
// private static final boolean IS_LONG_TEXT = true;
|
||||
// /**
|
||||
// * 结果类型
|
||||
// */
|
||||
// private static final int RESULT_TYPE = 2;
|
||||
// /**
|
||||
// * RSA 算法
|
||||
// */
|
||||
// private static final String RSA_ALGORITHM = "RSA";
|
||||
// /**
|
||||
// * 长文本解密块大小
|
||||
// */
|
||||
// private static final int MAX_DECRYPT_BLOCK = 128;
|
||||
// /**
|
||||
// * 长文本加密块大小
|
||||
// */
|
||||
// private static final int MAX_ENCRYPT_BLOCK = 117;
|
||||
// /**
|
||||
// * KyeSize
|
||||
// */
|
||||
// private static final int KEY_SIZE = 2048;
|
||||
//
|
||||
// /**
|
||||
// * 加密
|
||||
// *
|
||||
// * @param content 待加密的字符串
|
||||
// * @param pubKey 公钥字符串
|
||||
// * @return 加密后的文本
|
||||
// * @throws Exception 异常
|
||||
// */
|
||||
// public static String encrypt(String content, String pubKey) throws Exception {
|
||||
// byte[] data = StringUtils.getBytes(content);
|
||||
// PublicKey publicKey = string2PubKey(pubKey);
|
||||
// byte[] resultArr;
|
||||
// if (IS_LONG_TEXT) {
|
||||
// resultArr = encryptLongStr(data, publicKey);
|
||||
// } else {
|
||||
// resultArr = encrypt(data, publicKey);
|
||||
// }
|
||||
// String result;
|
||||
// switch (RESULT_TYPE) {
|
||||
// case 1:
|
||||
// result = Base64Utils.encode(resultArr);
|
||||
// break;
|
||||
// case 2:
|
||||
// result = HexUtils.bytes2Hex(resultArr);
|
||||
// break;
|
||||
// default:
|
||||
// throw new Exception("Unsupport result type");
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @param content 密文内容
|
||||
// * @param priKey 私钥
|
||||
// * @return 解密后的字符串
|
||||
// * @throws Exception 异常
|
||||
// */
|
||||
// public static String decrypt(String content, String priKey) throws Exception {
|
||||
// byte[] data;
|
||||
// switch (RESULT_TYPE) {
|
||||
// case 1:
|
||||
// data = Base64Utils.decode(content);
|
||||
// break;
|
||||
// case 2:
|
||||
// data = HexUtils.hex2Bytes(content);
|
||||
// break;
|
||||
// default:
|
||||
// throw new Exception("Unsupport result type");
|
||||
// }
|
||||
// PrivateKey privateKey = string2PrivateKey(priKey);
|
||||
// byte[] result;
|
||||
// if (IS_LONG_TEXT) {
|
||||
// result = decryptLongStr(data, privateKey);
|
||||
// } else {
|
||||
// result = decrypt(privateKey, data);
|
||||
// }
|
||||
// return StringUtils.bytes2String(result);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 响应公私钥对
|
||||
// *
|
||||
// * @return 0号 公钥 1号 私钥
|
||||
// * @throws NoSuchAlgorithmException 异常
|
||||
// */
|
||||
// public static List<String> getKeyPair() throws NoSuchAlgorithmException {
|
||||
// KeyPair keyPairObj = getKeyPairObj();
|
||||
// return List.of(Base64Utils.encode(keyPairObj.getPublic().getEncoded()), Base64Utils.encode(keyPairObj.getPrivate().getEncoded()));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 将公钥字符串转化为对象
|
||||
// *
|
||||
// * @param s base64字符串
|
||||
// * @return 公钥
|
||||
// * @throws NoSuchAlgorithmException 异常
|
||||
// * @throws UnsupportedEncodingException 异常
|
||||
// * @throws InvalidKeySpecException 异常
|
||||
// */
|
||||
// private static PublicKey string2PubKey(String s) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
|
||||
// KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
|
||||
// X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Utils.decode(s));
|
||||
// return keyFactory.generatePublic(keySpec);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 对段字符串进行加密
|
||||
// *
|
||||
// * @param bytes 字节数组
|
||||
// * @param publicKey 公钥
|
||||
// * @return 加密后的数组
|
||||
// * @throws InvalidKeyException 异常
|
||||
// * @throws BadPaddingException 异常
|
||||
// * @throws IllegalBlockSizeException 异常
|
||||
// * @throws NoSuchPaddingException 异常
|
||||
// * @throws NoSuchAlgorithmException 异常
|
||||
// */
|
||||
// private static byte[] encrypt(byte[] bytes, PublicKey publicKey) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException {
|
||||
// Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
|
||||
// cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
// return cipher.doFinal(bytes);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 对长字符串进行加密
|
||||
// *
|
||||
// * @param bytes 字节数组
|
||||
// * @param publicKey 公钥
|
||||
// * @return 加密后的数组
|
||||
// * @throws NoSuchPaddingException 异常
|
||||
// * @throws NoSuchAlgorithmException 异常
|
||||
// * @throws InvalidKeyException 异常
|
||||
// */
|
||||
// private static byte[] encryptLongStr(byte[] bytes, PublicKey publicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
|
||||
// Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
|
||||
// cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
// int inputLen = bytes.length;
|
||||
// byte[] encryptedData = new byte[0];
|
||||
// try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
// int offSet = 0;
|
||||
// byte[] cache;
|
||||
// int i = 0;
|
||||
// // 对数据分段加密
|
||||
// while (inputLen - offSet > 0) {
|
||||
// if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
|
||||
// cache = cipher.doFinal(bytes, offSet, MAX_ENCRYPT_BLOCK);
|
||||
// } else {
|
||||
// cache = cipher.doFinal(bytes, offSet, inputLen - offSet);
|
||||
// }
|
||||
// out.write(cache, 0, cache.length);
|
||||
// i++;
|
||||
// offSet = i * MAX_ENCRYPT_BLOCK;
|
||||
// }
|
||||
// encryptedData = out.toByteArray();
|
||||
// } catch (IOException | BadPaddingException | IllegalBlockSizeException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return encryptedData;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 私钥字符串转为私钥对象
|
||||
// *
|
||||
// * @param priStr 私钥字符串
|
||||
// * @return 私钥对象
|
||||
// * @throws NoSuchAlgorithmException 异常
|
||||
// * @throws InvalidKeySpecException 异常
|
||||
// */
|
||||
// private static PrivateKey string2PrivateKey(String priStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
// PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64Utils.decode(priStr));
|
||||
// KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
|
||||
// return keyFactory.generatePrivate(keySpec);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 解密
|
||||
// *
|
||||
// * @param privateKey 私钥
|
||||
// * @param bytes 字节数组
|
||||
// * @return 解密后的字节数组
|
||||
// * @throws NoSuchPaddingException 异常
|
||||
// * @throws NoSuchAlgorithmException 异常
|
||||
// * @throws BadPaddingException 异常
|
||||
// * @throws IllegalBlockSizeException 异常
|
||||
// * @throws InvalidKeyException 异常
|
||||
// */
|
||||
// public static byte[] decrypt(PrivateKey privateKey, byte[] bytes) throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException {
|
||||
// Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
|
||||
// cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
// return cipher.doFinal(bytes);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 解密
|
||||
// *
|
||||
// * @param data 解密前的字节数组
|
||||
// * @param privateKey 私钥
|
||||
// * @return 解密后的字节数组
|
||||
// * @throws InvalidKeyException 异常
|
||||
// * @throws NoSuchPaddingException 异常
|
||||
// * @throws NoSuchAlgorithmException 异常
|
||||
// */
|
||||
// public static byte[] decryptLongStr(byte[] data, PrivateKey privateKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
|
||||
// Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
|
||||
// cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
// int inputLen = data.length;
|
||||
// byte[] result = new byte[0];
|
||||
// try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
// int offSet = 0;
|
||||
// byte[] cache;
|
||||
// int i = 0;
|
||||
// // 对数据分段解密
|
||||
// while (inputLen - offSet > 0) {
|
||||
// if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
|
||||
// cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
|
||||
// } else {
|
||||
// cache = cipher.doFinal(data, offSet, inputLen - offSet);
|
||||
// }
|
||||
// out.write(cache, 0, cache.length);
|
||||
// i++;
|
||||
// offSet = i * MAX_DECRYPT_BLOCK;
|
||||
// }
|
||||
// result = out.toByteArray();
|
||||
// } catch (BadPaddingException | IllegalBlockSizeException | IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获得一堆公私钥
|
||||
// *
|
||||
// * @return KeyPair对象
|
||||
// * @throws NoSuchAlgorithmException 异常
|
||||
// */
|
||||
// private static KeyPair getKeyPairObj() throws NoSuchAlgorithmException {
|
||||
// KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
|
||||
// SecureRandom secureRandom = new SecureRandom(StringUtils.getBytes(String.valueOf(System.currentTimeMillis())));
|
||||
// keyPairGenerator.initialize(KEY_SIZE, secureRandom);
|
||||
// return keyPairGenerator.genKeyPair();
|
||||
// }
|
||||
//}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,53 @@
|
||||
package com.sf.vertx.security;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class StringUtils {
|
||||
/**
|
||||
* 编码方法
|
||||
*/
|
||||
private static final Charset CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* 根据本类的常量对字符串进行获取字节数组的操作
|
||||
*
|
||||
* @param s String:待处理的字符串
|
||||
* @return byte[] 形成的字节数组
|
||||
*/
|
||||
public static byte[] getBytes(String s) {
|
||||
return s.getBytes(CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组转为文本
|
||||
*
|
||||
* @param data 字节数组
|
||||
* @return 文本
|
||||
*/
|
||||
public static String bytes2String(byte[] data) {
|
||||
return new String(data, CHARSET);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 字符串是不是空的
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isBlank(String str) {
|
||||
int strLen;
|
||||
if (str != null && (strLen = str.length()) != 0) {
|
||||
for (int i = 0; i < strLen; ++i) {
|
||||
if (!Character.isWhitespace(str.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.sf.vertx.service;
|
||||
|
||||
import com.sf.vertx.api.pojo.AppConfig;
|
||||
import com.sf.vertx.api.pojo.VertxConfig;
|
||||
|
||||
public interface AppConfigService {
|
||||
|
||||
void saveAppConfig(AppConfig appConfig);
|
||||
|
||||
void deleteAppConfig(AppConfig appConfig);
|
||||
|
||||
void saveVertxConfig(VertxConfig vertxConfig);
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
package com.sf.vertx.service.impl;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.sf.vertx.api.pojo.AppConfig;
|
||||
import com.sf.vertx.api.pojo.VertxConfig;
|
||||
import com.sf.vertx.constans.RedisKeyConfig;
|
||||
import com.sf.vertx.handle.AppConfigHandler;
|
||||
import com.sf.vertx.pojo.ClusterEventMsg;
|
||||
import com.sf.vertx.service.AppConfigService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AppConfigServiceImpl implements AppConfigService {
|
||||
@Value("${server.vertx.environment}")
|
||||
private String vertxEnvironment;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
*/
|
||||
/***
|
||||
* 新增、修改
|
||||
*
|
||||
* @param appConfig
|
||||
*//*
|
||||
|
||||
public void saveAppConfig(AppConfig appConfig) {
|
||||
redisTemplate.opsForZSet().add(RedisKeyConfig.APP_CONFIG_SET_KEY, appConfig.getAppCode(), 0);
|
||||
String appCodeKey = RedisKeyConfig.APP_CONFIG_PREFIX_KEY + ":" + appConfig.getAppCode();
|
||||
redisTemplate.opsForValue().set(appCodeKey, JSONObject.toJSONString(appConfig));
|
||||
|
||||
// 发布集群消息
|
||||
ClusterEventMsg msg = new ClusterEventMsg();
|
||||
msg.setType(1);
|
||||
msg.setOperation(1);
|
||||
msg.setAppCode(appConfig.getAppCode());
|
||||
AppConfigHandler.publishClusterEventMsg(msg);
|
||||
}
|
||||
|
||||
*/
|
||||
/***
|
||||
* 删除
|
||||
*
|
||||
* @param appConfig
|
||||
*//*
|
||||
|
||||
public void deleteAppConfig(AppConfig appConfig) {
|
||||
redisTemplate.opsForZSet().remove(RedisKeyConfig.APP_CONFIG_SET_KEY, appConfig.getAppCode());
|
||||
String appCodeKey = RedisKeyConfig.APP_CONFIG_PREFIX_KEY + ":" + appConfig.getAppCode();
|
||||
redisTemplate.delete(appCodeKey);
|
||||
|
||||
// 发送集群消息
|
||||
ClusterEventMsg msg = new ClusterEventMsg();
|
||||
msg.setType(1);
|
||||
msg.setOperation(3);
|
||||
msg.setAppCode(appConfig.getAppCode());
|
||||
AppConfigHandler.publishClusterEventMsg(msg);
|
||||
}
|
||||
|
||||
*/
|
||||
/***
|
||||
* 新增、修改
|
||||
*
|
||||
* @param appConfig
|
||||
*//*
|
||||
|
||||
public void saveVertxConfig(VertxConfig vertxConfig) {
|
||||
String vertxConfigKey = RedisKeyConfig.VERTX_CONFIG_STRING_KEY;
|
||||
redisTemplate.opsForValue().set(vertxConfigKey, JSONObject.toJSONString(vertxConfig));
|
||||
AppConfigHandler.initVertxConfig();
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
@ -0,0 +1,269 @@
|
||||
/*
|
||||
package com.sf.vertx.utils;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import com.sf.vertx.api.pojo.*;
|
||||
import com.sf.vertx.enums.GatewayError;
|
||||
import com.sf.vertx.enums.GatewayServiceType;
|
||||
import com.sf.vertx.enums.MatchType;
|
||||
import com.sf.vertx.enums.RequestMethod;
|
||||
import com.sf.vertx.handle.AppConfigHandler;
|
||||
import io.vertx.circuitbreaker.CircuitBreaker;
|
||||
import io.vertx.core.http.HttpServerRequest;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import static com.sf.vertx.constans.SACConstants.*;
|
||||
|
||||
*/
|
||||
/**
|
||||
* 功能描述:
|
||||
*
|
||||
* @author a_kun
|
||||
* @date 2024/5/24 12:30
|
||||
*//*
|
||||
|
||||
@Slf4j
|
||||
@UtilityClass
|
||||
public class AppUtils {
|
||||
|
||||
*/
|
||||
/**
|
||||
* 将AppConfig设置到上下文中
|
||||
* 配合getAppConfigFromRoutingContext(RoutingContext rc)使用
|
||||
* @param appConfig
|
||||
* @param rc
|
||||
*//*
|
||||
|
||||
public static void setAppConfigToRoutingContext(AppConfig appConfig, RoutingContext rc) {
|
||||
rc.put(APP_CONFIG,appConfig);
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* 从路由上下文获取AppConfig
|
||||
* @param rc
|
||||
* @return AppConfig
|
||||
*//*
|
||||
|
||||
public static AppConfig getAppConfigFromRoutingContext(RoutingContext rc) {
|
||||
return rc.get(APP_CONFIG);
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* 将AppConfig设置到上下文中
|
||||
* 配合getApiConfigFromRoutingContext(RoutingContext rc)使用
|
||||
* @param apiConfig
|
||||
* @param rc
|
||||
*//*
|
||||
|
||||
public static void setApiConfigIntoRoutingContext(ApiConfig apiConfig, RoutingContext rc) {
|
||||
rc.put(API_CONFIG,apiConfig);
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* 从路由上下文获取ApiConfig
|
||||
* @param rc
|
||||
* @return ApiConfig
|
||||
*//*
|
||||
|
||||
public static ApiConfig getApiConfigFromRoutingContext(RoutingContext rc) {
|
||||
return rc.get(API_CONFIG);
|
||||
}
|
||||
|
||||
public static boolean isEnableMockApi(ApiConfig apiConfig) {
|
||||
if (apiConfig != null) {
|
||||
return apiConfig.getMockDefaultHttpStatus() != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* 判断API服务类型是否是SAC规范服务类型
|
||||
*
|
||||
* @param apiServiceType
|
||||
* @return
|
||||
*//*
|
||||
|
||||
public static boolean isSACServiceType(String apiServiceType) {
|
||||
return GatewayServiceType.SAC.getCode().equals(apiServiceType);
|
||||
}
|
||||
|
||||
public static boolean isEnableDataSecurity(AppConfig appConfig) {
|
||||
return appConfig != null && appConfig.getDataSecurity() != null;
|
||||
}
|
||||
|
||||
public static MockResponse mock(RoutingContext rc) {
|
||||
AppConfig appConfig = getAppConfigFromRoutingContext(rc);
|
||||
ApiConfig apiConfig = getApiConfigFromRoutingContext(rc);
|
||||
String apiServiceType = rc.get(API_SERVICE_TYPE);
|
||||
if (AppUtils.isEnableMockApi(apiConfig)) {
|
||||
// 如果是sac服务,query和body参数都从body中取
|
||||
Integer httpStatus = apiConfig.getMockDefaultHttpStatus();
|
||||
String mockResponse = apiConfig.getMockDefaultResponse();
|
||||
List<MockExpectation> mockExpectations = apiConfig.getMockExpectations();
|
||||
if (mockExpectations != null && !mockExpectations.isEmpty()) {
|
||||
String bodyStr = rc.body().asString();
|
||||
if (AppUtils.isEnableDataSecurity(appConfig)) {
|
||||
bodyStr = AppConfigHandler.bodyDecrypt(bodyStr, appConfig.getAppCode());
|
||||
}
|
||||
JsonObject jsonBody = new JsonObject(bodyStr);
|
||||
for (MockExpectation mockExpectation : mockExpectations) {
|
||||
// 匹配条件需要全部匹配成功
|
||||
boolean matchSuccess = true;
|
||||
for (MockMatchCondition matchCondition : mockExpectation.getMatchConditions()) {
|
||||
if (!matchSuccess) {
|
||||
break;
|
||||
}
|
||||
MatchType matchType = MatchType.getByCode(matchCondition.getMatchType());
|
||||
if (matchType == null) {
|
||||
matchSuccess = false;
|
||||
break;
|
||||
}
|
||||
String parameterPosition = matchCondition.getParameterPosition();
|
||||
String parameterKey = matchCondition.getParameterKey();
|
||||
List<String> parameterValue = matchCondition.getParameterValue();
|
||||
String requestParameterValue = getRequestParameterValue(AppUtils.isSACServiceType(apiServiceType), parameterPosition, parameterKey, rc.request(), jsonBody);
|
||||
if (!MatchType.IS_NULL.equals(matchType) && !MatchType.NOT_NULL.equals(matchType)) {
|
||||
// 需要值而没有设置值,直接匹配失败
|
||||
if (CollectionUtil.isEmpty(parameterValue) || requestParameterValue == null) {
|
||||
matchSuccess = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (matchType) {
|
||||
case EQ:
|
||||
matchSuccess = parameterValue.get(0).equals(requestParameterValue);
|
||||
break;
|
||||
case NOT_EQ:
|
||||
matchSuccess = !parameterValue.get(0).equals(requestParameterValue);
|
||||
break;
|
||||
case GT:
|
||||
if (NumberUtil.isNumber(requestParameterValue) && NumberUtil.isNumber(parameterValue.get(0))) {
|
||||
matchSuccess = new BigDecimal(requestParameterValue).compareTo(new BigDecimal(parameterValue.get(0))) > 0;
|
||||
} else {
|
||||
matchSuccess = requestParameterValue.compareTo(parameterValue.get(0)) > 0;
|
||||
}
|
||||
break;
|
||||
case GE:
|
||||
if (NumberUtil.isNumber(requestParameterValue) && NumberUtil.isNumber(parameterValue.get(0))) {
|
||||
matchSuccess = new BigDecimal(requestParameterValue).compareTo(new BigDecimal(parameterValue.get(0))) >= 0;
|
||||
} else {
|
||||
matchSuccess = requestParameterValue.compareTo(parameterValue.get(0)) >= 0;
|
||||
}
|
||||
break;
|
||||
case LT:
|
||||
if (NumberUtil.isNumber(requestParameterValue) && NumberUtil.isNumber(parameterValue.get(0))) {
|
||||
matchSuccess = new BigDecimal(requestParameterValue).compareTo(new BigDecimal(parameterValue.get(0))) < 0;
|
||||
} else {
|
||||
matchSuccess = requestParameterValue.compareTo(parameterValue.get(0)) < 0;
|
||||
}
|
||||
break;
|
||||
case LE:
|
||||
if (NumberUtil.isNumber(requestParameterValue) && NumberUtil.isNumber(parameterValue.get(0))) {
|
||||
matchSuccess = new BigDecimal(requestParameterValue).compareTo(new BigDecimal(parameterValue.get(0))) <= 0;
|
||||
} else {
|
||||
matchSuccess = requestParameterValue.compareTo(parameterValue.get(0)) <= 0;
|
||||
}
|
||||
break;
|
||||
case IN:
|
||||
matchSuccess = parameterValue.contains(requestParameterValue);
|
||||
break;
|
||||
case NOT_IN:
|
||||
matchSuccess = !parameterValue.contains(requestParameterValue);
|
||||
break;
|
||||
case IS_NULL:
|
||||
matchSuccess = requestParameterValue == null;
|
||||
break;
|
||||
case NOT_NULL:
|
||||
matchSuccess = requestParameterValue != null;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (matchSuccess) {
|
||||
httpStatus = mockExpectation.getHttpStatus();
|
||||
mockResponse = mockExpectation.getMockResponse();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new MockResponse(httpStatus, mockResponse);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getRequestParameterValue(Boolean isServiceTypeSac, String parameterPosition, String parameterKey, HttpServerRequest request, JsonObject jsonBody) {
|
||||
switch (parameterPosition) {
|
||||
case "query":
|
||||
if (isServiceTypeSac) {
|
||||
return jsonBody.getString(parameterKey);
|
||||
}
|
||||
return request.getParam(parameterKey);
|
||||
case "header":
|
||||
return request.getHeader(parameterKey);
|
||||
case "body":
|
||||
return jsonBody.getString(parameterKey);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getResponseMsgByGatewayError(GatewayError gatewayError) {
|
||||
if (gatewayError != null) {
|
||||
return gatewayError.getReasonPhrase();
|
||||
}
|
||||
return getResponseMsgByGatewayError(GatewayError.DEFAULT_SERVICE_ERROR);
|
||||
}
|
||||
|
||||
public static JsonObject getResponseJsonByGatewayError(GatewayError gatewayError) {
|
||||
if (gatewayError != null) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.put("code",gatewayError.getCode());
|
||||
jsonObject.put("msg",gatewayError.getReasonPhrase());
|
||||
return jsonObject;
|
||||
}
|
||||
return getResponseJsonByGatewayError(GatewayError.DEFAULT_SERVICE_ERROR);
|
||||
}
|
||||
|
||||
public static boolean isAnalysisBody(AppConfig appConfig, ApiConfig apiConfig,String apiServiceType) {
|
||||
// 不是Mock(是mock模式的话,存在body就解析。)
|
||||
if (AppUtils.isEnableMockApi(apiConfig)) {
|
||||
return true;
|
||||
}
|
||||
// SAC模式,实际API请求方式GET,DELETE,HEAD请求需要解析body,可能需要二次处理。(SAC模式请求方式参数统一使用body传递)
|
||||
// SAC模式,如果是POST或者PUT,并且没有设置数据加密则跳过
|
||||
if (AppUtils.isSACServiceType(apiServiceType)) {
|
||||
RequestMethod requestMethod = RequestMethod.getByCode(apiConfig.getMethod());
|
||||
if (RequestMethod.GET.equals(requestMethod)
|
||||
|| RequestMethod.DELETE.equals(requestMethod)
|
||||
|| RequestMethod.HEAD.equals(requestMethod)) {
|
||||
return true;
|
||||
}
|
||||
String keyCircuitBreaker = appConfig.getAppCode() + ":" + apiConfig.getApiCode() + ":" + "CIRCUIT_BREAKER";
|
||||
CircuitBreaker circuitBreaker = AppConfigHandler.getApiCodeCircuitBreaker(keyCircuitBreaker);
|
||||
boolean isDataSecurity = AppUtils.isEnableDataSecurity(appConfig);
|
||||
// 文件上传不走加解密
|
||||
return (isDataSecurity || circuitBreaker != null);
|
||||
}else {
|
||||
// 未启用Mock,OPEN模式都不会解析body
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
package com.sf.vertx.utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.sf.vertx.api.pojo.Node;
|
||||
import com.sf.vertx.api.pojo.RouteContent;
|
||||
import com.sf.vertx.arithmetic.roundRobin.SacLoadBalancing;
|
||||
import com.sf.vertx.arithmetic.roundRobin.WeightedRoundRobin;
|
||||
import com.sf.vertx.handle.AppConfigHandler;
|
||||
|
||||
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 static SocketAddress resolveOriginAddress(HttpServerRequest request) {
|
||||
String appCode = request.getHeader(AppConfigHandler.getAppCodeHeaderKey());
|
||||
String apiCode = request.getHeader(AppConfigHandler.getApiCodeHeaderKey());
|
||||
log.info("uri:{}, header appCode:{},apiCode:{}", request.uri(), appCode, apiCode);
|
||||
// 判断 "routeType": "WEIGHT_ROUTE", // 路由类型 WEIGHT_ROUTE ,HEADER_ROUTE
|
||||
String key = appCode + ":" + apiCode;
|
||||
Integer routerType = AppConfigHandler.routerType(key);
|
||||
SocketAddress socketAddress = null;
|
||||
switch (routerType) {
|
||||
case 1:
|
||||
SacLoadBalancing sacLoadBalancing = AppConfigHandler.getLoadBalancing(key);
|
||||
Node node = sacLoadBalancing.selectNode();
|
||||
socketAddress = SocketAddress.inetSocketAddress(node.getPort(), node.getIp());
|
||||
log.info("sacLoadBalancing address:{},port:{}", socketAddress.host(), socketAddress.port());
|
||||
return socketAddress;
|
||||
case 2:
|
||||
List<RouteContent> routeContentList = AppConfigHandler.routerHeaderConentList(key);
|
||||
if(routeContentList != null && routeContentList.size() > 0) {
|
||||
for (RouteContent routeContent : routeContentList) {
|
||||
String headerValue = request.getHeader(routeContent.getHeaderKey());
|
||||
List<String> headerValues = routeContent.getHeaderValues();
|
||||
// String matchType = routeContent.getMatchType();
|
||||
if(headerValues.contains(headerValue)) {
|
||||
socketAddress = SocketAddress.inetSocketAddress(routeContent.getServerAddress().getPort(), routeContent.getServerAddress().getHost());
|
||||
log.info("sacLoadBalancing address:{},port:{}", socketAddress.host(), socketAddress.port());
|
||||
return socketAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 抛出异常,无法找到负载均衡node节点
|
||||
throw new HttpException(10021);
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,2 @@
|
||||
Automatic-Module-Name: io.vertx.core
|
||||
|
@ -0,0 +1,38 @@
|
||||
[
|
||||
{
|
||||
"name": "java.lang.Thread$Builder",
|
||||
"condition": {
|
||||
"typeReachable": "io.vertx.core.impl.VertxImpl"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "factory",
|
||||
"parameterTypes": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "java.lang.Thread$Builder$OfVirtual",
|
||||
"condition": {
|
||||
"typeReachable": "io.vertx.core.impl.VertxImpl"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "name",
|
||||
"parameterTypes": ["java.lang.String", "long"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "java.lang.Thread",
|
||||
"condition": {
|
||||
"typeReachable": "io.vertx.core.impl.VertxImpl"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "ofVirtual",
|
||||
"parameterTypes": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,18 @@
|
||||
# Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
|
||||
#
|
||||
# This program and the accompanying materials are made available under the
|
||||
# terms of the Eclipse Public License 2.0 which is available at
|
||||
# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
#
|
||||
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
|
||||
# Core commands
|
||||
io.vertx.core.impl.launcher.commands.RunCommandFactory
|
||||
io.vertx.core.impl.launcher.commands.VersionCommandFactory
|
||||
io.vertx.core.impl.launcher.commands.BareCommandFactory
|
||||
|
||||
# Background application control
|
||||
io.vertx.core.impl.launcher.commands.ListCommandFactory
|
||||
io.vertx.core.impl.launcher.commands.StartCommandFactory
|
||||
io.vertx.core.impl.launcher.commands.StopCommandFactory
|
@ -0,0 +1 @@
|
||||
${project.version}
|
@ -0,0 +1,39 @@
|
||||
# 开发环境配置
|
||||
|
||||
vertx:
|
||||
deploymentMode: 1 # 1:单机 2:集群
|
||||
isSSL: false # vertx https或者http启动, ssl 证书有过期时间
|
||||
rpcPort: 80 # RPC调用使用的端口
|
||||
rpcUri: /rpc.sac # RPC调用使用的url
|
||||
sacResponseHeaderKey: sacErrorCode
|
||||
environment: dev
|
||||
configPort: 5566 # 注册配置的端口
|
||||
blackIP:
|
||||
- 192.168.1.20
|
||||
- 192.168.1.21
|
||||
cluster:
|
||||
ip: 127.0.0.1
|
||||
clusterName: sac-dev
|
||||
networkPort: 5701
|
||||
portAutoIncrement: false
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.sf: info
|
||||
io.vertx: debug
|
||||
org.springframework: warn
|
||||
|
||||
|
||||
# redis 配置
|
||||
redis:
|
||||
clientType: STANDALONE
|
||||
poolName: vertx-redis
|
||||
maxPoolSize: 8
|
||||
maxPoolWaiting: 32
|
||||
poolCleanerInterval: 30000
|
||||
urls:
|
||||
- redis://192.168.1.23:22002/1
|
||||
# 密码
|
||||
password:
|
||||
|
BIN
zt-vertx/zt-vertx-service/src/main/resources/cacerts.jks
Normal file
BIN
zt-vertx/zt-vertx-service/src/main/resources/cacerts.jks
Normal file
Binary file not shown.
30
zt-vertx/zt-vertx-service/src/main/resources/cluster.xml
Normal file
30
zt-vertx/zt-vertx-service/src/main/resources/cluster.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hazelcast
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-4.1.xsd"
|
||||
xmlns="http://www.hazelcast.com/schema/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
||||
<!-- 配置集群网络 -->
|
||||
<network>
|
||||
<!-- 配置Hazelcast节点之间通信的端口 -->
|
||||
<port auto-increment="true">5701</port>
|
||||
|
||||
<!-- 配置节点发现方式为TCP/IP -->
|
||||
<join>
|
||||
<tcp-ip enabled="true">
|
||||
<interface>127.0.0.1</interface>
|
||||
<!-- 添加集群中其他节点的IP地址 -->
|
||||
<member>127.0.0.1</member>
|
||||
|
||||
<!-- 添加更多节点的IP地址 -->
|
||||
</tcp-ip>
|
||||
</join>
|
||||
</network>
|
||||
|
||||
<!-- 管理中心的配置 -->
|
||||
<management-center enabled="true">
|
||||
<!-- 指定管理中心的 URL -->
|
||||
<url>http://127.0.0.1:8080/mancenter</url>
|
||||
</management-center>
|
||||
|
||||
</hazelcast>
|
@ -0,0 +1,38 @@
|
||||
#错误消息
|
||||
not.null=* 必须填写
|
||||
user.jcaptcha.error=验证码错误
|
||||
user.jcaptcha.expire=验证码已失效
|
||||
user.not.exists=用户不存在/密码错误
|
||||
user.password.not.match=用户不存在/密码错误
|
||||
user.password.retry.limit.count=密码输入错误{0}次
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
|
||||
user.password.delete=对不起,您的账号已被删除
|
||||
user.blocked=用户已封禁,请联系管理员
|
||||
role.blocked=角色已封禁,请联系管理员
|
||||
login.blocked=很遗憾,访问IP已被列入系统黑名单
|
||||
user.logout.success=退出成功
|
||||
|
||||
length.not.valid=长度必须在{min}到{max}个字符之间
|
||||
|
||||
user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
|
||||
user.password.not.valid=* 5-50个字符
|
||||
|
||||
user.email.not.valid=邮箱格式错误
|
||||
user.mobile.phone.number.not.valid=手机号格式错误
|
||||
user.login.success=登录成功
|
||||
user.register.success=注册成功
|
||||
user.notfound=请重新登录
|
||||
user.forcelogout=管理员强制退出,请重新登录
|
||||
user.unknown.error=未知错误,请重新登录
|
||||
|
||||
##文件上传消息
|
||||
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
|
||||
upload.filename.exceed.length=上传的文件名最长{0}个字符
|
||||
|
||||
##权限
|
||||
no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
|
||||
no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
|
||||
no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
|
||||
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
|
||||
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
|
||||
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
|
BIN
zt-vertx/zt-vertx-service/src/main/resources/keystore.jks
Normal file
BIN
zt-vertx/zt-vertx-service/src/main/resources/keystore.jks
Normal file
Binary file not shown.
123
zt-vertx/zt-vertx-service/src/main/resources/log4j2.xml
Normal file
123
zt-vertx/zt-vertx-service/src/main/resources/log4j2.xml
Normal file
@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration>
|
||||
<!--<Configuration status="WARN" monitorInterval="30"> -->
|
||||
<properties>
|
||||
<property name="LOG_HOME">./vertx-logs</property>
|
||||
</properties>
|
||||
<Appenders>
|
||||
<!--*********************控制台日志***********************-->
|
||||
<Console name="consoleAppender" target="SYSTEM_OUT">
|
||||
<!--设置日志格式及颜色-->
|
||||
<PatternLayout
|
||||
pattern="%style{%d{ISO8601}}{bright,green} %highlight{%-5level} [%style{%t}{bright,blue}] %style{%C{}}{bright,yellow}: %msg%n%style{%throwable}{red}"
|
||||
disableAnsi="false" noConsoleNoAnsi="false"/>
|
||||
</Console>
|
||||
|
||||
<!--*********************文件日志***********************-->
|
||||
<!--debug级别日志-->
|
||||
<RollingFile name="debugFileAppender"
|
||||
fileName="${LOG_HOME}/debug.log"
|
||||
filePattern="${LOG_HOME}/$${date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<Filters>
|
||||
<!--过滤掉info及更高级别日志-->
|
||||
<ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
|
||||
</Filters>
|
||||
<!--设置日志格式-->
|
||||
<PatternLayout>
|
||||
<pattern>%d %p %C{} [%t] %m%n</pattern>
|
||||
</PatternLayout>
|
||||
<Policies>
|
||||
<!-- 设置日志文件切分参数 -->
|
||||
<!--<OnStartupTriggeringPolicy/>-->
|
||||
<!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
|
||||
<SizeBasedTriggeringPolicy size="100 MB"/>
|
||||
<!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
|
||||
<TimeBasedTriggeringPolicy/>
|
||||
</Policies>
|
||||
<!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
|
||||
<DefaultRolloverStrategy max="100"/>
|
||||
</RollingFile>
|
||||
|
||||
<!--info级别日志-->
|
||||
<RollingFile name="infoFileAppender"
|
||||
fileName="${LOG_HOME}/info.log"
|
||||
filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<Filters>
|
||||
<!--过滤掉warn及更高级别日志-->
|
||||
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
|
||||
</Filters>
|
||||
<!--设置日志格式-->
|
||||
<PatternLayout>
|
||||
<pattern>%d %p %C{} [%t] %m%n</pattern>
|
||||
</PatternLayout>
|
||||
<Policies>
|
||||
<!-- 设置日志文件切分参数 -->
|
||||
<!--<OnStartupTriggeringPolicy/>-->
|
||||
<!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
|
||||
<SizeBasedTriggeringPolicy size="100 MB"/>
|
||||
<!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
|
||||
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
|
||||
</Policies>
|
||||
<!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
|
||||
<!--<DefaultRolloverStrategy max="100"/>-->
|
||||
</RollingFile>
|
||||
|
||||
<!--warn级别日志-->
|
||||
<RollingFile name="warnFileAppender"
|
||||
fileName="${LOG_HOME}/warn.log"
|
||||
filePattern="${LOG_HOME}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<Filters>
|
||||
<!--过滤掉error及更高级别日志-->
|
||||
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
|
||||
</Filters>
|
||||
<!--设置日志格式-->
|
||||
<PatternLayout>
|
||||
<pattern>%d %p %C{} [%t] %m%n</pattern>
|
||||
</PatternLayout>
|
||||
<Policies>
|
||||
<!-- 设置日志文件切分参数 -->
|
||||
<!--<OnStartupTriggeringPolicy/>-->
|
||||
<!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
|
||||
<SizeBasedTriggeringPolicy size="100 MB"/>
|
||||
<!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
|
||||
<TimeBasedTriggeringPolicy/>
|
||||
</Policies>
|
||||
<!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
|
||||
<DefaultRolloverStrategy max="100"/>
|
||||
</RollingFile>
|
||||
|
||||
<!--error及更高级别日志-->
|
||||
<RollingFile name="errorFileAppender"
|
||||
fileName="${LOG_HOME}/error.log"
|
||||
filePattern="${LOG_HOME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<!--设置日志格式-->
|
||||
<PatternLayout>
|
||||
<pattern>%d %p %C{} [%t] %m%n</pattern>
|
||||
</PatternLayout>
|
||||
<Policies>
|
||||
<!-- 设置日志文件切分参数 -->
|
||||
<!--<OnStartupTriggeringPolicy/>-->
|
||||
<!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
|
||||
<SizeBasedTriggeringPolicy size="100 MB"/>
|
||||
<!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
|
||||
<TimeBasedTriggeringPolicy/>
|
||||
</Policies>
|
||||
<!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
|
||||
<DefaultRolloverStrategy max="100"/>
|
||||
</RollingFile>
|
||||
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<!-- 根日志设置 -->
|
||||
<Root level="debug">
|
||||
<AppenderRef ref="consoleAppender" level="debug"/>
|
||||
<AppenderRef ref="debugFileAppender" level="debug"/>
|
||||
<AppenderRef ref="infoFileAppender" level="info"/>
|
||||
<AppenderRef ref="warnFileAppender" level="warn"/>
|
||||
<AppenderRef ref="errorFileAppender" level="error"/>
|
||||
</Root>
|
||||
|
||||
</Loggers>
|
||||
|
||||
</Configuration>
|
BIN
zt-vertx/zt-vertx-service/src/main/resources/server.cer
Normal file
BIN
zt-vertx/zt-vertx-service/src/main/resources/server.cer
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user