Vert.x网关改造

This commit is contained in:
akun 2024-05-28 17:48:52 +08:00
parent b20639a02c
commit 2efcb5a275
89 changed files with 5679 additions and 0 deletions

71
zt-vertx/pom.xml Normal file
View 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>

View 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>

View File

@ -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定时任务循环执行时间间隔
}

View File

@ -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;// 监控指标
}

View File

@ -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期望
}

View File

@ -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; // 高级配置
}

View File

@ -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; // 加密算法ECCRSA 和国密SM2
private String publicKey; // 公钥
private String privateKey; // 私钥 当加密算法为 ECC 或国密SM2时填写私钥内容当加密算法为 RSA 分别填写公私钥内容)
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,35 @@
package com.sf.vertx.api.pojo;
import lombok.Data;
import java.util.List;
/**
* 接口Mock匹配条件
*/
@Data
public class MockMatchCondition {
/**
* 参数位置headerquerybody
*/
private String parameterPosition;
/**
* 参数键值
*/
private String parameterKey;
/**
* 参数值包含不包含类型允许多个值所以使用数组其余类型都只有一个值
*/
private List<String> parameterValue;
/**
* 匹配类型
*/
private String matchType;
}

View File

@ -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;
}

View File

@ -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 + "}";
}
}

View File

@ -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
}

View File

@ -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; // 路由的配置信息
}

View File

@ -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; // 路由
}

View File

@ -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; // 前缀
}

View File

@ -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字符串。
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,7 @@
package com.sf.vertx.arithmetic.roundRobin;
import com.sf.vertx.api.pojo.Node;
public interface SacLoadBalancing {
Node selectNode();
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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"]

View 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>
&lt;!&ndash; for metrics &ndash;&gt;
<groupId>org.hdrhistogram</groupId>
<artifactId>HdrHistogram</artifactId>
<version>2.1.12</version>
<optional>true</optional>
</dependency>
&lt;!&ndash; For tests and examples &ndash;&gt;
<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>
&lt;!&ndash;<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>&ndash;&gt;
<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>

View File

@ -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());
}
}

View File

@ -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);
}
}
*/

View File

@ -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);";
}
}
*/

View File

@ -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";
}
}
*/

View File

@ -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";
}

View File

@ -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;
}
}
*/

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
*/

View File

@ -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();
}
}
*/

View File

@ -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();
}
}
}
*/

View File

@ -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();
}
}
}
*/

View File

@ -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/>
// 1serviceNodel="NORMAL", serviceNodel="ROUTE" and RouteType = "WEIGHT_ROUTE"
// <br/>
// return LOADBALANCING_MAP
// 2serviceNodel="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) {
// appapi默认限流
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);
// appapi默认限流
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);
}
}
*/

View File

@ -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();
}
}
}
*/

View File

@ -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();
}
}
}
*/

View File

@ -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);
}
*/

View File

@ -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();
}
}
}
*/

View File

@ -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();
}
}
*/

View File

@ -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();
}
}
*/

View File

@ -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);
}
}
}
*/

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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();
}
}
*/

View File

@ -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());
}
}
*/

View File

@ -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();
}
}
*/

View File

@ -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();
}
}
}
*/

View File

@ -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();
}
}
}

View File

@ -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
);
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}

View File

@ -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;
}
*/

View File

@ -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;// 黑名单
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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"));
}
}
*/

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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();
}
}
*/

View File

@ -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 {
// 未启用MockOPEN模式都不会解析body
return false;
}
}
}
*/

View File

@ -0,0 +1,91 @@
package com.sf.vertx.utils;
import java.util.concurrent.atomic.AtomicBoolean;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.streams.ReadStream;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Filter implements ReadStream<Buffer> {
private final AtomicBoolean paused = new AtomicBoolean();
private ReadStream<Buffer> stream;
private Buffer expected = Buffer.buffer();
private Handler<Buffer> dataHandler;
private Handler<Throwable> exceptionHandler;
private Handler<Void> endHandler;
/***
*
* @param s
* @param encryption true: 请求参数解密, false: 返回数据 加密
* @return
*/
public ReadStream<Buffer> init(String uri, ReadStream<Buffer> s, Boolean encryption) {
stream = s;
stream.handler(buff -> {
if (dataHandler != null) {
byte[] bytes = new byte[buff.length()];
for (int i = 0; i < bytes.length; i++) {
// bytes[i] = (byte) (('a' - 'A') + buff.getByte(i));
bytes[i] = (byte) (buff.getByte(i));
}
String res = new String(bytes);
log.info("request uri:{}, return data:{}", uri, res);
expected.appendBytes(bytes);
dataHandler.handle(Buffer.buffer(bytes));
}
});
stream.exceptionHandler(err -> {
if (exceptionHandler != null) {
exceptionHandler.handle(err);
}
});
stream.endHandler(v -> {
if (endHandler != null) {
endHandler.handle(v);
}
});
return this;
}
@Override
public ReadStream<Buffer> pause() {
paused.set(true);
stream.pause();
return this;
}
@Override
public ReadStream<Buffer> resume() {
stream.resume();
return this;
}
@Override
public ReadStream<Buffer> fetch(long amount) {
stream.fetch(amount);
return this;
}
@Override
public ReadStream<Buffer> exceptionHandler(Handler<Throwable> handler) {
exceptionHandler = handler;
return this;
}
@Override
public ReadStream<Buffer> handler(Handler<Buffer> handler) {
dataHandler = handler;
return this;
}
@Override
public ReadStream<Buffer> endHandler(Handler<Void> handler) {
endHandler = handler;
return this;
}
}

View File

@ -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;
}
}
*/

View File

@ -0,0 +1,2 @@
Automatic-Module-Name: io.vertx.core

View File

@ -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": []
}
]
}
]

View File

@ -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

View File

@ -0,0 +1 @@
${project.version}

View File

@ -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:

View 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>

View File

@ -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}]

View 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>