first commit ZZZzzzzzz

This commit is contained in:
QAIU
2023-04-20 17:42:39 +08:00
commit 28cb764c81
78 changed files with 5055 additions and 0 deletions

9
core/README.md Normal file
View File

@@ -0,0 +1,9 @@
TODO
- Interceptor重构 -> (0%)
- 可配置的反向代理服务器 -> (70%)
- SQL-gen/ORM/JSON-Model -> (1%)
- 注解式APO -> (0%)
- 注解式eventbus,sockjs-bridge -> (10%)
- Code-gen TemplateEngine -> (1%)
- HTML TemplateEngine -> (0%)

127
core/pom.xml Normal file
View File

@@ -0,0 +1,127 @@
<?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>
<artifactId>lz-cow-api</artifactId>
<groupId>cn.qaiu</groupId>
<version>0.0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>1.0.8</version>
<artifactId>core</artifactId>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<vertx.version>4.1.3</vertx.version>
<org.reflections.version>0.9.12</org.reflections.version>
<lombok.version>1.18.12</lombok.version>
<slf4j.version>2.0.5</slf4j.version>
<commons-lang3.version>3.8.1</commons-lang3.version>
<jackson.version>2.11.3</jackson.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-dependencies</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--logback日志实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-codegen</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-config</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-config-yaml</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-service-proxy</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-jdbc-client</artifactId>
</dependency>
<!-- SQL模板 -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-sql-client-templates</artifactId>
</dependency>
<!-- jwt鉴权 -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-proxy</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stomp</artifactId>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>${org.reflections.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,159 @@
package cn.com.yhinfo.core;
import cn.com.yhinfo.core.util.ConfigUtil;
import cn.com.yhinfo.core.util.VertxHolder;
import cn.com.yhinfo.core.verticle.ReverseProxyVerticle;
import cn.com.yhinfo.core.verticle.ServiceVerticle;
import cn.com.yhinfo.core.verticle.RouterVerticle;
import io.vertx.core.*;
import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.LocalMap;
import io.vertx.core.shareddata.SharedData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.management.ManagementFactory;
import java.util.Calendar;
import java.util.Date;
/**
* vertx启动类 需要在主启动类完成回调
* <br>Create date 2021-05-07 10:26:54
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public final class Deploy {
private static final Deploy INSTANCE = new Deploy();
private static final Logger LOGGER = LoggerFactory.getLogger(Deploy.class);
private static final long startTime = System.currentTimeMillis();
private final Vertx tempVertx = Vertx.vertx();
StringBuilder path = new StringBuilder("app");
private JsonObject customConfig;
private Handler<JsonObject> handle;
public static Deploy instance() {
return INSTANCE;
}
public void start(String[] args, Handler<JsonObject> handle) {
this.handle = handle;
if (args.length > 0) {
// 启动参数dev或者prod
path.append("-").append(args[0]);
}
// 读取yml配置
ConfigUtil.readYamlConfig(path.toString(), tempVertx)
.onSuccess(this::readConf)
.onFailure(Throwable::printStackTrace);
}
private void readConf(JsonObject conf) {
outLogo(conf);
String activeMode = conf.getString("active");
if ("dev".equals(activeMode)) {
LOGGER.info("---------------> development environment <--------------\n");
System.setProperty("vertxweb.environment","dev");
} else {
LOGGER.info("---------------> Production environment <--------------\n");
}
ConfigUtil.readYamlConfig(path + "-" + activeMode, tempVertx).onSuccess(this::deployVerticle);
}
/**
* 打印logo
*/
private void outLogo(JsonObject conf) {
Date date = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
int year = calendar.get(Calendar.YEAR);
String logoTemplete = "\nWeb Server powered by: \n" +
" ____ ____ _ _ _ \n" +
"|_^^_| |_^^_| / |_ | | | | \n" +
" \\ \\ / /.---. _ .--.`| |-' _ __ | |__| |_ \n" +
" \\ \\ / // /__\\\\[ `/'`\\]| | [ \\ [ ]|____ _| \n" +
" \\ V / | \\__., | | | |, _ > ' < _| |_ \n" +
" \\_/ '.__.'[___] \\__/(_)[__]`\\_] |_____| \n" +
" Version: %s; Framework version: %s; %s©%d.\n\n";
System.out.printf(logoTemplete,
conf.getString("version_app"),
conf.getString("version_vertx"),
conf.getString("copyright"),
year
);
}
/**
* 部署Verticle
*
* @param globalConfig 配置
*/
private void deployVerticle(JsonObject globalConfig) {
tempVertx.close();
LOGGER.info("配置读取成功");
customConfig = globalConfig.getJsonObject("custom");
VertxOptions vertxOptions = new VertxOptions(globalConfig.getJsonObject("vertx"));
Vertx vertx = Vertx.vertx(vertxOptions);
VertxHolder.init(vertx);
//配置保存在共享数据中
SharedData sharedData = vertx.sharedData();
LocalMap<String, Object> localMap = sharedData.getLocalMap("local");
localMap.put("globalConfig", globalConfig);
localMap.put("customConfig", customConfig);
localMap.put("server", globalConfig.getJsonObject("server"));
handle.handle(globalConfig);
Future<String> future1 = vertx.deployVerticle(RouterVerticle.class, getWorkDeploymentOptions("Router"));
Future<String> future2 = vertx.deployVerticle(ServiceVerticle.class, getWorkDeploymentOptions("Service"));
Future<String> future3 = vertx.deployVerticle(ReverseProxyVerticle.class, getWorkDeploymentOptions("proxy"));
CompositeFuture.all(future1, future2, future3)
.onSuccess(this::deployWorkVerticalSuccess)
.onFailure(this::deployVerticalFailed);
}
/**
* 部署失败
*
* @param throwable Exception信息
*/
private void deployVerticalFailed(Throwable throwable) {
LOGGER.error(throwable.getClass().getName() + ": " + throwable.getMessage());
System.exit(-1);
}
/**
* 启动时间信息
*
* @param compositeFuture future wraps a list
*/
private void deployWorkVerticalSuccess(CompositeFuture compositeFuture) {
double t1 = ((double) (System.currentTimeMillis() - startTime)) / 1000;
double t2 = ((double) System.currentTimeMillis() - ManagementFactory.getRuntimeMXBean().getStartTime()) / 1000;
LOGGER.info("web服务启动成功 -> 用时: {}s, jvm启动用时: {}s", t1, t2);
}
/**
* deploy Verticle Options
*
* @param name the worker pool name
* @return Deployment Options
*/
private DeploymentOptions getWorkDeploymentOptions(String name) {
return getWorkDeploymentOptions(name, customConfig.getInteger("asyncServiceInstances"));
}
private DeploymentOptions getWorkDeploymentOptions(String name, int ins) {
return new DeploymentOptions()
.setWorkerPoolName(name)
.setWorker(true)
.setInstances(ins);
}
}

View File

@@ -0,0 +1,18 @@
package cn.com.yhinfo.core.annotaions;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 日期参数格式化注解
* <br>Create date 2021-05-06 09:20:37
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface DateFormat {
String value() default "yyyy-MM-dd HH:mm:ss";
}

View File

@@ -0,0 +1,25 @@
package cn.com.yhinfo.core.annotaions;
import java.lang.annotation.*;
/**
* Web Router API类 标识注解
* <br>Create date 2021-04-30 09:22:18
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RouteHandler {
String value() default "";
boolean isOpen() default false;
/**
* 注册顺序,数字越大越先注册
*/
int order() default 0;
}

View File

@@ -0,0 +1,45 @@
package cn.com.yhinfo.core.annotaions;
import cn.com.yhinfo.core.enums.MIMEType;
import cn.com.yhinfo.core.enums.RouteMethod;
import java.lang.annotation.*;
/**
* Router API Method 标识注解
* <br>Create date 2021-05-06 09:20:37
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RouteMapping {
String value() default "";
/**
* 使用http method
*/
RouteMethod method() default RouteMethod.GET;
/**
* 接口description
*/
String description() default "";
/**
* 注册顺序,数字越大越先注册
*/
int order() default 0;
/**
* 响应类型
*/
MIMEType responseMimeType() default MIMEType.APPLICATION_JSON;
/**
* 请求类型
*/
MIMEType requestMIMEType() default MIMEType.ALL;
}

View File

@@ -0,0 +1,18 @@
package cn.com.yhinfo.core.annotaions;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 服务实现层注解(XXServiceImpl)
* <br>Create date 2021/8/25 15:57
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
String name() default "";
}

View File

@@ -0,0 +1,17 @@
package cn.com.yhinfo.core.annotaions;
import java.lang.annotation.*;
/**
* WebSocket api 注解类
* <br>Create date 2021/8/25 15:57
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SockRouteMapper {
String value() default "";
}

View File

@@ -0,0 +1,39 @@
package cn.com.yhinfo.core.base;
import cn.com.yhinfo.core.util.CastUtil;
import java.util.Arrays;
/**
* 反射获取父接口类名辅助类, 异步服务需要实现此接口
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public interface BaseAsyncService {
/**
* 获取异步服务接口地址
* @see BaseAsyncService#getAsyncInterfaceClass
* @return 服务接口类名
* @throws ClassNotFoundException 接口不存在
*/
default String getAddress() throws ClassNotFoundException {
return getAsyncInterfaceClass().getName();
}
/**
* 获取异步服务接口地址
* @return 服务接口类对象
* @throws ClassNotFoundException 接口不存在
*/
default Class<Object> getAsyncInterfaceClass() throws ClassNotFoundException {
// 获取实现接口 作为地址注册到EventBus
Class<Object> clazz = CastUtil.cast(Arrays.stream(this.getClass().getInterfaces()).filter(
clz-> Arrays.asList(clz.getInterfaces()).contains(BaseAsyncService.class)
).findFirst().orElse(null));
if (clazz == null) {
throw new ClassNotFoundException("No interface found: \""+this.getClass().getName()+"\" need to implement interface");
}
return clazz;
}
}

View File

@@ -0,0 +1,34 @@
package cn.com.yhinfo.core.base;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
/**
* 统一响应处理
* <br>Create date 2021-05-06 09:20:37
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public interface BaseHttpApi {
default void fireJsonResponse(RoutingContext ctx, JsonObject jsonResult) {
ctx.response().putHeader(CONTENT_TYPE, "application/json; charset=utf-8")
.setStatusCode(200)
.end(jsonResult.encode());
}
default <T> void fireJsonResponse(RoutingContext ctx, T jsonResult) {
JsonObject jsonObject = JsonObject.mapFrom(jsonResult);
fireJsonResponse(ctx, jsonObject);
}
default void fireTextResponse(RoutingContext ctx, String text) {
ctx.response().putHeader("content-type", "text/html; charset=utf-8").end(text);
}
default void sendError(int statusCode, RoutingContext ctx) {
ctx.response().setStatusCode(statusCode).end();
}
}

View File

@@ -0,0 +1,39 @@
package cn.com.yhinfo.core.enums;
/**
* MIMEType: request or response head
* <br>Create date 2021/8/30 4:35
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public enum MIMEType {
NULL(""),
ALL("*/*"),
TEXT_HTML("text/html"),
APPLICATION_POSTSCRIPT("application/postscript"),
TEXT_PLAIN("text/plain"),
APPLICATION_X_WWW_FORM_URLENCODED("application/x-www-form-urlencoded"),
APPLICATION_OCTET_STREAM("application/octet-stream"),
MULTIPART_FORM_DATA("multipart/form-data"),
APPLICATION_X_JAVA_AGENT("application/x-java-agent"),
MESSAGE_HTTP("message/http"),
TEXT_CSS("text/css"),
TEXT_XML("text/xml"),
TEXT("text/*"),
APPLICATION_RDF_XML("application/rdf+xml"),
APPLICATION_XHTML_XML("application/xhtml+xml"),
APPLICATION_XML("application/xml"),
APPLICATION_JSON("application/json");
public String getValue() {
return type;
}
private final String type;
MIMEType(String type) {
this.type = type;
}
}

View File

@@ -0,0 +1,11 @@
package cn.com.yhinfo.core.enums;
/**
* Router API 请求处理方式枚举
* <br>Create date 2021-04-30 09:22:18
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public enum RouteMethod {
OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT, PATCH, ROUTE
}

View File

@@ -0,0 +1,383 @@
package cn.com.yhinfo.core.handlerfactory;
import cn.com.yhinfo.core.annotaions.DateFormat;
import cn.com.yhinfo.core.annotaions.RouteHandler;
import cn.com.yhinfo.core.annotaions.RouteMapping;
import cn.com.yhinfo.core.annotaions.SockRouteMapper;
import cn.com.yhinfo.core.base.BaseHttpApi;
import cn.com.yhinfo.core.enums.MIMEType;
import cn.com.yhinfo.core.model.JsonResult;
import cn.com.yhinfo.core.util.*;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
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.sockjs.SockJSHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
import javassist.CtClass;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.vertx.core.http.HttpHeaders.*;
/**
* 路由映射, 参数绑定
* <br>Create date 2021-05-07 10:26:54
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public class RouterHandlerFactory implements BaseHttpApi {
private static final Logger LOGGER = LoggerFactory.getLogger(RouterHandlerFactory.class);
private static final Set<HttpMethod> httpMethods = new HashSet<HttpMethod>() {{
add(HttpMethod.GET);
add(HttpMethod.POST);
add(HttpMethod.OPTIONS);
add(HttpMethod.PUT);
add(HttpMethod.DELETE);
add(HttpMethod.HEAD);
}};
// 需要扫描注册的Router路径
private static volatile Reflections reflections;
private final String gatewayPrefix;
public RouterHandlerFactory(String routerScanAddress, String gatewayPrefix) {
Objects.requireNonNull(routerScanAddress, "The router package address scan is empty.");
Objects.requireNonNull(gatewayPrefix, "The gateway prefix is empty.");
reflections = ReflectionUtil.getReflections(routerScanAddress);
this.gatewayPrefix = gatewayPrefix;
}
/**
* 开始扫描并注册handler
*/
public Router createRouter() {
Router router = Router.router(VertxHolder.getVertxInstance());
router.route().handler(ctx -> {
LOGGER.debug("The HTTP service request address information ===>path:{}, uri:{}, method:{}",
ctx.request().path(), ctx.request().absoluteURI(), ctx.request().method());
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, OPTIONS, PUT, DELETE, HEAD");
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGOTHER, Origin,Content-Type, Accept, X-Requested-With, Dev, Authorization, Version, Token");
ctx.response().headers().add(ACCESS_CONTROL_MAX_AGE, "1728000");
ctx.next();
});
// 添加跨域的方法
router.route().handler(CorsHandler.create("*").allowCredentials(true).allowedMethods(httpMethods));
// 配置文件上传路径
router.route().handler(BodyHandler.create().setUploadsDirectory("uploads"));
try {
Set<Class<?>> handlers = reflections.getTypesAnnotatedWith(RouteHandler.class);
Comparator<Class<?>> comparator = (c1, c2) -> {
RouteHandler routeHandler1 = c1.getAnnotation(RouteHandler.class);
RouteHandler routeHandler2 = c2.getAnnotation(RouteHandler.class);
return Integer.compare(routeHandler2.order(), routeHandler1.order());
};
// 获取处理器类列表
List<Class<?>> sortedHandlers = handlers.stream().sorted(comparator).collect(Collectors.toList());
for (Class<?> handler : sortedHandlers) {
try {
// 注册请求处理方法
registerNewHandler(router, handler);
} catch (Throwable e) {
LOGGER.error("Error register {}, Error details", handler, e.getCause());
}
}
} catch (Exception e) {
LOGGER.error("Manually Register Handler Fail, Error details" + e.getMessage());
}
// 错误请求处理
router.errorHandler(405, ctx -> fireJsonResponse(ctx, JsonResult
.error("Method Not Allowed", 405)));
router.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true)
.end("Internal server error: 404 not found"));
return router;
}
/**
* 注册handler
*/
private void registerNewHandler(Router router, Class<?> handler) throws Throwable {
String root = getRootPath(handler);
Object instance = ReflectionUtil.newWithNoParam(handler);
Method[] methods = handler.getMethods();
// 注册处理方法排序
Comparator<Method> comparator = (m1, m2) -> {
RouteMapping mapping1 = m1.getAnnotation(RouteMapping.class);
RouteMapping mapping2 = m2.getAnnotation(RouteMapping.class);
return Integer.compare(mapping2.order(), mapping1.order());
};
List<Method> methodList = Stream.of(methods).filter(
method -> method.isAnnotationPresent(RouteMapping.class)
).sorted(comparator).collect(Collectors.toList());
methodList.addAll(Stream.of(methods).filter(
method -> method.isAnnotationPresent(SockRouteMapper.class)
).toList());
// 拦截器
Handler<RoutingContext> interceptor = getInterceptor();
// 依次注册处理方法
for (Method method : methodList) {
if (method.isAnnotationPresent(RouteMapping.class)) {
// 普通路由
RouteMapping mapping = method.getAnnotation(RouteMapping.class);
HttpMethod routeMethod = HttpMethod.valueOf(mapping.method().name());
String routeUrl = getRouteUrl(method.getName(), mapping.value());
String url = root.concat(routeUrl);
// 匹配方法
Route route = router.route(routeMethod, url);
String mineType = mapping.requestMIMEType().getValue();
LOGGER.info("route -> {}:{} -> {}", routeMethod.name(), url, mineType);
if (StringUtils.isNotEmpty(mineType)) {
route.consumes(mineType);
}
// 先执行拦截方法, 再进入业务请求
route.handler(interceptor);
route.handler(ctx -> handlerMethod(instance, method, ctx)).failureHandler(ctx -> {
if (ctx.response().ended()) return;
ctx.failure().printStackTrace();
fireJsonResponse(ctx, JsonResult.error(ctx.failure().getMessage(), 500));
});
} else if (method.isAnnotationPresent(SockRouteMapper.class)) {
// websocket 基于sockJs
SockRouteMapper mapping = method.getAnnotation(SockRouteMapper.class);
String routeUrl = getRouteUrl(method.getName(), mapping.value());
String url = root.concat(routeUrl);
LOGGER.info("Register New Websocket Handler -> {}", url);
SockJSHandlerOptions options = new SockJSHandlerOptions()
.setHeartbeatInterval(2000)
.setRegisterWriteHandler(true);
SockJSHandler sockJSHandler = SockJSHandler.create(VertxHolder.getVertxInstance(), options);
Router route = sockJSHandler.socketHandler(sock -> {
try {
ReflectionUtil.invokeWithArguments(method, instance, sock);
} catch (Throwable e) {
e.printStackTrace();
}
});
router.mountSubRouter(url, route);
}
}
}
/**
* 获取并处理路由URL分隔符
*
* @param methodName 路由method
* @return String
*/
private String getRouteUrl(String methodName, String mapperValue) {
String routeUrl;
if (mapperValue.startsWith("/:") || "/".equals(mapperValue)) {
routeUrl = (methodName + mapperValue);
} else if (mapperValue.startsWith("/")) {
routeUrl = mapperValue.substring(1);
} else {
routeUrl = mapperValue;
}
return routeUrl;
}
/**
* 配置拦截
*
* @return Handler
* @throws Throwable Throwable
*/
private Handler<RoutingContext> getInterceptor() throws Throwable {
// 配置拦截
Class<?> interceptorClass = Class.forName(SharedDataUtil.getValueForCustomConfig("interceptorClassPath"));
Object handleInstance = ReflectionUtil.newWithNoParam(interceptorClass);
Method doHandle = interceptorClass.getMethod("doHandle");
// 反射调用
return CastUtil.cast(ReflectionUtil.invoke(doHandle, handleInstance));
}
/**
* 获取请求根路径
*
* @param handler handler
* @return 根路径
*/
private String getRootPath(Class<?> handler) {
// 处理请求路径前缀和后缀
String root = gatewayPrefix;
if (!root.startsWith("/")) {
root = "/" + root;
}
if (!root.endsWith("/")) {
root = root + "/";
}
// 子路径
if (handler.isAnnotationPresent(RouteHandler.class)) {
RouteHandler routeHandler = handler.getAnnotation(RouteHandler.class);
String value = routeHandler.value();
root += ("/".equals(value) ? "" : value);
}
if (!root.endsWith("/")) {
root = root + "/";
}
return root;
}
/**
* 处理请求-参数绑定
*
* @param instance 类实例
* @param method 处理方法
* @param ctx 路由上下文
*/
private void handlerMethod(Object instance, Method method, RoutingContext ctx) {
// 方法参数名列表
Map<String, Pair<Annotation[], CtClass>> methodParameters = ReflectionUtil.getMethodParameter(method);
Map<String, Pair<Annotation[], CtClass>> methodParametersTemp = new LinkedHashMap<>(methodParameters);
Map<String, String> pathParamValues = ctx.pathParams();
// 参数名-参数值
Map<String, Object> parameterValueList = new LinkedHashMap<>();
methodParameters.forEach((k, v) -> parameterValueList.put(k, null));
//绑定rest路径变量
if (!pathParamValues.isEmpty()) {
methodParameters.forEach((k, v) -> {
if (pathParamValues.containsKey(k)) {
methodParametersTemp.remove(k);
if (ReflectionUtil.isBasicType(v.getRight())) {
String fmt = getFmt(v.getLeft(), v.getRight());
parameterValueList.put(k, ReflectionUtil.conversion(v.getRight(), pathParamValues.get(k), fmt));
} else if (ReflectionUtil.isBasicTypeArray(v.getRight())) {
parameterValueList.put(k, ReflectionUtil.conversionArray(v.getRight(), pathParamValues.get(k)));
} else {
throw new RuntimeException("参数绑定异常: 类型不匹配");
}
}
});
}
final MultiMap queryParams = ctx.queryParams();
if ("POST".equals(ctx.request().method().name())) {
queryParams.addAll(ctx.request().params());
}
JsonArray entityPackagesReg = SharedDataUtil.getJsonArrayForCustomConfig("entityPackagesReg");
// 绑定get或post请求头的请求参数
methodParametersTemp.forEach((k, v) -> {
if (ReflectionUtil.isBasicType(v.getRight())) {
String fmt = getFmt(v.getLeft(), v.getRight());
String value = queryParams.get(k);
parameterValueList.put(k, ReflectionUtil.conversion(v.getRight(), value, fmt));
} else if ("io.vertx.ext.web.RoutingContext".equals(v.getRight().getName())) {
parameterValueList.put(k, ctx);
} else if ("io.vertx.core.http.HttpServerRequest".equals(v.getRight().getName())) {
parameterValueList.put(k, ctx.request());
} else if ("io.vertx.core.http.HttpServerResponse".equals(v.getRight().getName())) {
parameterValueList.put(k, ctx.response());
} else if (CommonUtil.matchRegList(entityPackagesReg.getList(), v.getRight().getName())) {
// 绑定实体类
try {
Class<?> aClass = Class.forName(v.getRight().getName());
Object entity = ParamUtil.multiMapToEntity(queryParams, aClass);
parameterValueList.put(k, entity);
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 解析body-json参数
if ("application/json".equals(ctx.parsedHeaders().contentType().value()) && ctx.getBodyAsJson() != null) {
JsonObject body = ctx.getBodyAsJson();
if (body != null) {
methodParametersTemp.forEach((k, v) -> {
// 只解析已配置包名前缀的实体类
if (CommonUtil.matchRegList(entityPackagesReg.getList(), v.getRight().getName())) {
try {
Class<?> aClass = Class.forName(v.getRight().getName());
JsonObject data = CommonUtil.getSubJsonForEntity(body, aClass);
if (!data.isEmpty()) {
Object entity = data.mapTo(aClass);
parameterValueList.put(k, entity);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
});
}
}
// 调用handle 获取响应对象
Object[] parameterValueArray = parameterValueList.values().toArray(new Object[0]);
try {
// 反射调用
Object data = ReflectionUtil.invokeWithArguments(method, instance, parameterValueArray);
if (data != null) {
if (data instanceof JsonResult) {
fireJsonResponse(ctx, data);
} else if (data instanceof Future) { // 处理异步响应
((Future<?>) data).onSuccess(res -> {
if (res instanceof JsonObject) {
fireJsonResponse(ctx, res);
} else {
fireJsonResponse(ctx, JsonResult.data(res));
}
}).onFailure(e -> fireJsonResponse(ctx, JsonResult.error(e.getMessage())));
} else {
ctx.response().headers().set(CONTENT_TYPE, MIMEType.TEXT_HTML.getValue());
ctx.end(data.toString());
}
}
} catch (Throwable e) {
e.printStackTrace();
String err = e.getMessage();
if (e.getCause() != null) {
if (e.getCause() instanceof InvocationTargetException) {
err = ((InvocationTargetException)e.getCause()).getTargetException().getMessage();
} else {
err = e.getCause().getMessage();
}
}
fireJsonResponse(ctx, JsonResult.error(err));
}
}
/**
* 获取DateFormat注解值
*/
private String getFmt(Annotation[] parameterAnnotations, CtClass v) {
String fmt = "";
if ("java.util.Date".equals(v.getName())) {
for (Annotation annotation : parameterAnnotations) {
if (annotation instanceof DateFormat) {
fmt = ((DateFormat) annotation).value();
}
}
}
return fmt;
}
}

View File

@@ -0,0 +1,19 @@
package cn.com.yhinfo.core.interceptor;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
/**
* 拦截器接口
* <br>Create date 2021-05-06 09:20:37
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public interface Interceptor {
default Handler<RoutingContext> doHandle() {
return this::handle;
}
void handle(RoutingContext context);
}

View File

@@ -0,0 +1,160 @@
package cn.com.yhinfo.core.model;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
/**
* 响应实体 用于和前端交互
* <br>Create date 2021-05-06 09:20:37
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public class JsonResult<T> implements Serializable {
private static final long serialVersionUID = 1L;
private static final int SUCCESS_CODE = 200;
private static final int FAIL_CODE = 500;
private static final String SUCCESS_MESSAGE = "success";
private static final String FAIL_MESSAGE = "failed";
private int code = SUCCESS_CODE;//状态码
private String msg = SUCCESS_MESSAGE;//消息
private boolean success = true; //是否成功
private int count;
private T data;
private long timestamp = System.currentTimeMillis(); //时间戳
public JsonResult() {
}
public JsonResult(T data) {
this.data = data;
}
public JsonResult(int code, String msg, boolean success, T data) {
this.code = code;
this.msg = msg;
this.data = data;
this.success = success;
}
public JsonResult(int code, String msg, boolean success, T data, int count) {
this(code, msg, success, data);
this.count = count;
}
public int getCount() {
return count;
}
public JsonResult<T> setCount(int count) {
this.count = count;
return this;
}
public int getCode() {
return code;
}
public JsonResult<T> setCode(int code) {
this.code = code;
return this;
}
public String getMsg() {
return msg;
}
public JsonResult<T> setMsg(String msg) {
this.msg = msg;
return this;
}
public T getData() {
return data;
}
public boolean getSuccess() {
return success;
}
public long getTimestamp() {
return timestamp;
}
public JsonResult<T> setData(T data) {
this.data = data;
return this;
}
public JsonResult<T> setSuccess(boolean success) {
this.success = success;
return this;
}
public JsonResult<T> setTimestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
// 响应失败消息
public static <T> JsonResult<T> error() {
return new JsonResult<>(FAIL_CODE, FAIL_MESSAGE, false, null);
}
// 响应失败消息
public static <T> JsonResult<T> error(String msg) {
if (StringUtils.isEmpty(msg)) msg = FAIL_MESSAGE;
return new JsonResult<>(FAIL_CODE, msg, false, null);
}
// 响应失败消息和状态码
public static <T> JsonResult<T> error(String msg, int code) {
if (StringUtils.isEmpty(msg)) msg = FAIL_MESSAGE;
return new JsonResult<>(code, msg, false, null);
}
// 响应成功消息和数据实体
public static <T> JsonResult<T> data(String msg, T data) {
if (StringUtils.isEmpty(msg)) msg = SUCCESS_MESSAGE;
return new JsonResult<>(SUCCESS_CODE, msg, true, data);
}
// 响应成功消息和数据实体
public static <T> JsonResult<T> data(String msg, T data, int count) {
if (StringUtils.isEmpty(msg)) msg = SUCCESS_MESSAGE;
return new JsonResult<>(SUCCESS_CODE, msg, true, data, count);
}
// 响应数据实体
public static <T> JsonResult<T> data(T data) {
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, data, 0);
}
// 响应数据实体
public static <T> JsonResult<T> data(T data, int count) {
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, data, count);
}
// 响应成功消息
public static <T> JsonResult<T> success(String msg) {
if (StringUtils.isEmpty(msg)) msg = SUCCESS_MESSAGE;
return new JsonResult<>(SUCCESS_CODE, msg, true, null);
}
// 响应成功消息
public static <T> JsonResult<T> success() {
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, null);
}
}

View File

@@ -0,0 +1,20 @@
package cn.com.yhinfo.core.util;
import io.vertx.core.Vertx;
import io.vertx.serviceproxy.ServiceProxyBuilder;
/**
* @author Xu Haidong
* @date 2018/8/15
*/
public final class AsyncServiceUtil {
public static <T> T getAsyncServiceInstance(Class<T> asClazz, Vertx vertx) {
String address = asClazz.getName();
return new ServiceProxyBuilder(vertx).setAddress(address).build(asClazz);
}
public static <T> T getAsyncServiceInstance(Class<T> asClazz) {
return getAsyncServiceInstance(asClazz, VertxHolder.getVertxInstance());
}
}

View File

@@ -0,0 +1,18 @@
package cn.com.yhinfo.core.util;
/**
* 转换为任意类型 旨在消除泛型转换时的异常
*/
public interface CastUtil {
/**
* 泛型转换
* @param object 要转换的object
* @param <T> T
* @return T
*/
@SuppressWarnings("unchecked")
static <T> T cast(Object object) {
return (T) object;
}
}

View File

@@ -0,0 +1,129 @@
package cn.com.yhinfo.core.util;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Map;
/**
* CommonUtil
* <br>Create date 2021/5/8 14:52
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public class CommonUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(CommonUtil.class);
/**
* 匹配正则list
*
* @param regList 正则list
* @param destStr 要匹配的目标字符
* @return 匹配成功返回true 否则返回false
*/
public static boolean matchRegList(List<?> regList, String destStr) {
// 判断是否忽略
for (Object ignores : regList) {
if (destStr.matches(ignores.toString())) {
return true;
}
}
return false;
}
/**
* 测试本机端口是否被使用
*
* @param port port
* @return boolean
*/
public static boolean isPortUsing(int port) {
//如果该端口还在使用则返回true,否则返回false,127.0.0.1代表本机
return isPortUsing("127.0.0.1", port);
}
/**
* 测试主机Host的port端口是否被使用
*
* @param host host
* @param port port
* @throws UnknownHostException
*/
public static boolean isPortUsing(String host, int port) {
boolean flag = false;
InetAddress Address;
try {
Address = InetAddress.getByName(host);
} catch (UnknownHostException e) {
return false;
}
try(Socket ignored = new Socket(Address, port)) {
//建立一个Socket连接
flag = true;
} catch (IOException ignoredException) {}
return flag;
}
/**
* 获取实体类匹配的json字段组成的json
*
* @param jsonObject 要解析的json
* @param clazz 对应的实体类
* @return 子json
*/
public static JsonObject getSubJsonForEntity(JsonObject jsonObject, Class<?> clazz) {
JsonObject data = new JsonObject();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (jsonObject.containsKey(field.getName())) {
data.put(field.getName(), jsonObject.getValue(field.getName()));
}
}
return data;
}
/**
* 注册枚举转换器
*
* @param enums 枚举类
*/
@SafeVarargs
@SuppressWarnings({"unchecked", "rawtypes"})
public static void enumConvert(Class<? extends Enum>... enums) {
for (Class<? extends Enum> anEnum : enums) {
ConvertUtils.register(new Converter() {
public Object convert(Class type, Object value) {
return Enum.valueOf(anEnum, (String) value);
}
}, anEnum);
}
}
/**
* 处理其他配置
*
* @param configName configName
*/
public static void initConfig(String configName, Class<?> tClass) {
URL resource = tClass.getResource("/conf/" + configName);
if (resource == null) throw new RuntimeException("路径不存在");
Buffer buffer = VertxHolder.getVertxInstance().fileSystem().readFileBlocking(resource.getPath());
JsonObject entries = new JsonObject(buffer);
Map<String, Object> map = entries.getMap();
LocalConstant.put(configName, map);
LOGGER.info("读取配置{}成功", configName);
}
}

View File

@@ -0,0 +1,60 @@
package cn.com.yhinfo.core.util;
import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
/**
* 异步读取配置工具类
* <br>Create date 2021/9/2 1:23
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public class ConfigUtil {
/**
* 异步读取配置文件
*
* @param format 配置文件格式
* @param path 路径
* @param vertx vertx
* @return JsonObject的Future
*/
public static Future<JsonObject> readConfig(String format, String path, Vertx vertx) {
// 读取yml配置
ConfigStoreOptions store = new ConfigStoreOptions()
.setType("file")
.setFormat(format)
.setConfig(new JsonObject().put("path", path));
ConfigRetriever retriever = ConfigRetriever
.create(vertx, new ConfigRetrieverOptions().addStore(store));
return retriever.getConfig();
}
/**
* 异步读取Yaml配置文件
*
* @param path 路径
* @param vertx vertx
* @return JsonObject的Future
*/
synchronized public static Future<JsonObject> readYamlConfig(String path, Vertx vertx) {
return readConfig("yaml", path+".yml", vertx);
}
/**
* 异步读取Yaml配置文件
*
* @param path 路径
* @return JsonObject的Future
*/
synchronized public static Future<JsonObject> readYamlConfig(String path) {
return readConfig("yaml", path+".yml", VertxHolder.getVertxInstance());
}
}

View File

@@ -0,0 +1,43 @@
package cn.com.yhinfo.core.util;
import java.util.HashMap;
import java.util.Map;
/**
* vertx 上下文外的本地容器 为不在vertx线程的方法传递数据
* <br>Create date 2021-05-07 10:26:54
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public class LocalConstant {
private static final Map<String, Object> LOCAL_CONST = new HashMap<>();
public static Map<String, Object> put(String k, Object v) {
if (LOCAL_CONST.containsKey(k)) return LOCAL_CONST;
LOCAL_CONST.put(k, v);
return LOCAL_CONST;
}
public static Object get(String k) {
return LOCAL_CONST.get(k);
}
@SuppressWarnings("unchecked")
public static <T> T getWithCast(String k) {
return (T) LOCAL_CONST.get(k);
}
public static boolean containsKey(String k) {
return LOCAL_CONST.containsKey(k);
}
public static Map<?, ?> getMap(String k) {
return (Map<?, ?>) LOCAL_CONST.get(k);
}
public static String getString(String k) {
return LOCAL_CONST.get(k).toString();
}
}

View File

@@ -0,0 +1,45 @@
package cn.com.yhinfo.core.util;
import io.vertx.core.MultiMap;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
/**
* 参数 工具类
* <br>Create date 2021-04-30 09:22:18
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public final class ParamUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ParamUtil.class);
public static Map<String, String> multiMapToMap(MultiMap multiMap) {
if (multiMap == null) return null;
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, String> entry : multiMap.entries()) {
map.put(entry.getKey(), entry.getValue());
}
return map;
}
public static <T> T multiMapToEntity(MultiMap multiMap,Class<T> tClass) throws NoSuchMethodException {
Map<String,String> map = multiMapToMap(multiMap);
T obj = null;
try {
obj = tClass.getDeclaredConstructor().newInstance();
BeanUtils.populate(obj,map);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
LOGGER.error("实例化异常");
} catch (InvocationTargetException e2) {
e2.printStackTrace();
LOGGER.error("map2bean转换异常");
}
return obj;
}
}

View File

@@ -0,0 +1,270 @@
package cn.com.yhinfo.core.util;
import javassist.*;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.reflections.Reflections;
import org.reflections.scanners.*;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.text.ParseException;
import java.util.*;
/**
* 基于org.reflection和javassist的反射工具包
* 通过包扫描实现路由地址的注解映射
* <br>Create date 2021-05-07 10:26:54
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public final class ReflectionUtil {
/**
* 获取反射器
*
* @param packageAddress Package address String
* @return Reflections object
*/
public static Reflections getReflections(String packageAddress) {
List<String> packageAddressList;
if (packageAddress.contains(",")) {
packageAddressList = Arrays.asList(packageAddress.split(","));
} else if (packageAddress.contains(";")) {
packageAddressList = Arrays.asList(packageAddress.split(";"));
} else {
packageAddressList = Collections.singletonList(packageAddress);
}
return getReflections(packageAddressList);
}
/**
* 获取反射器
*
* @param packageAddresses Package address List
* @return Reflections object
*/
public static Reflections getReflections(List<String> packageAddresses) {
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
FilterBuilder filterBuilder = new FilterBuilder();
packageAddresses.forEach(str -> {
Collection<URL> urls = ClasspathHelper.forPackage(str.trim());
configurationBuilder.addUrls(urls);
filterBuilder.includePackage(str.trim());
});
// 采坑记录 2021-05-08
// 发现注解api层 没有继承父类时 这里反射一直有问题(Scanner SubTypesScanner was not configured)
// 因此这里需要手动配置各种Scanner扫描器 -- https://blog.csdn.net/qq_29499107/article/details/106889781
configurationBuilder.setScanners(
new SubTypesScanner(false), //允许getAllTypes获取所有Object的子类, 不设置为false则 getAllTypes 会报错.默认为true.
new MethodParameterNamesScanner(), //设置方法参数名称 扫描器,否则调用getConstructorParamNames 会报错
new MethodAnnotationsScanner(), //设置方法注解 扫描器, 否则getConstructorsAnnotatedWith,getMethodsAnnotatedWith 会报错
new MemberUsageScanner(), //设置 member 扫描器,否则 getMethodUsage 会报错, 不推荐使用,有可能会报错 Caused by: java.lang.ClassCastException: javassist.bytecode.InterfaceMethodrefInfo cannot be cast to javassist.bytecode.MethodrefInfo
new TypeAnnotationsScanner() //设置类注解 扫描器 ,否则 getTypesAnnotatedWith 会报错
);
configurationBuilder.filterInputsBy(filterBuilder);
return new Reflections(configurationBuilder);
}
/**
* 获取指定类指定方法的参数名和类型列表(忽略重载方法)
*
* @param method 方法名(不考虑重载)
* @return 参数名类型map
* @apiNote ..
*/
public static Map<String, Pair<Annotation[], CtClass>> getMethodParameter(Method method) {
Map<String, Pair<Annotation[], CtClass>> paramMap = new LinkedHashMap<>();
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get(method.getDeclaringClass().getName());
CtMethod cm = ctClass.getDeclaredMethod(method.getName());
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
MethodInfo methodInfo = cm.getMethodInfo();
CtClass[] parameterTypes = cm.getParameterTypes();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
boolean flag = true;
boolean flag2 = cm.getModifiers() - 1 != AccessFlag.STATIC;
for (int j = 0, k = 0; j < parameterTypes.length + k; j++) {
// 注意这里 只能从tableLength处获取 目前还没发现问题
String name = attr.variableName(j);
if (!"this".equals(name) && flag && flag2) {
k++;
continue;
}
flag = false;
paramMap.put(attr.variableName(j + (flag2 ? 1 : 0)), Pair.of(parameterAnnotations[j - k], parameterTypes[j - k]));
}
} catch (NotFoundException e) {
e.printStackTrace();
}
return paramMap;
}
/**
* 类型转换: 字符串转对应类型
*
* @param ctClass 目标类: javassist的ctClass类对象
* @param value 字符串值
* @param fmt 日期格式(如果value是日期的话这个字段将有用,否则置为null或空字符串)
* @return 基本类型或目标对象
*/
public static Object conversion(CtClass ctClass, String value, String fmt) {
if (StringUtils.isEmpty(value)) {
return null;
}
if (StringUtils.isEmpty(fmt)) {
fmt = "yyyy-MM-dd";
}
String name = ctClass.getName();
if (ctClass.isArray()) {
name = ctClass.getName().substring(0, ctClass.getName().length() - 2);
}
switch (name) {
case "java.lang.Boolean":
case "boolean":
return Boolean.valueOf(value);
case "java.lang.Character":
case "char":
return value.charAt(0);
case "java.lang.Byte":
case "byte":
return Byte.valueOf(value);
case "java.lang.Short":
case "short":
return Short.valueOf(value);
case "java.lang.Integer":
case "int":
return Integer.valueOf(value);
case "java.lang.Long":
case "long":
return Long.valueOf(value);
case "java.lang.Float":
case "float":
return Float.valueOf(value);
case "java.lang.Double":
case "double":
return Double.valueOf(value);
case "java.lang.String":
return value;
case "java.util.Date":
try {
return DateUtils.parseDate(value, fmt);
} catch (ParseException e) {
e.printStackTrace();
throw new ConversionException("无法将格式化日期");
}
default:
throw new ConversionException("无法将String类型" + value + "转为[" + name + "]");
}
}
/**
* 数组类型的转换
*
* @param ctClass 目标类: javassist的ctClass类对象
* @param value 字符串表示的数组(逗号分隔符)
* @return Array
*/
public static Object conversionArray(CtClass ctClass, String value) {
if (!isBasicTypeArray(ctClass)) throw new ConversionException("无法解析数组");
String[] strArr = value.split(",");
List<Object> obj = new ArrayList<>();
Arrays.stream(strArr).forEach(v -> obj.add(conversion(ctClass, v, null)));
try {
// 暂时这么处理
String name = "[" + ((CtPrimitiveType) ctClass.getComponentType()).getDescriptor();
Class<?> cls = Class.forName(name).getComponentType();
Object arr = Array.newInstance(cls, obj.size());//初始化对应类型的数组
for (int i = 0; i < obj.size(); i++) {
Array.set(arr, i, obj.get(i));
}
return arr;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 判断是否是基本类型 8种原始类型和包装类以及String类型 返回true
*
* @return bool
*/
public static boolean isBasicType(CtClass ctClass) {
if (ctClass.isPrimitive() || "java.util.Date".equals(ctClass.getName())) {
return true;
}
return ctClass.getName().matches("^java\\.lang\\.((Boolean)|(Character)|(Byte)|(Short)|(Integer)|(Long)|(Float)|(Double)|(String))$");
}
/**
* 判断是否是基本类型数组 8种原始数组类型和String数组 返回true
*
* @return bool
*/
public static boolean isBasicTypeArray(CtClass ctClass) {
if (!ctClass.isArray()) {
return false;
} else return (ctClass.getName().matches("^(boolen|char|byte|short|int|long|float|double|String)\\[]$"));
}
/**
* 反射通过无参构造创建对象
*
* @param handler 类对象
* @return 目标对象
* @throws NoSuchMethodException NoSuchMethodException
* @throws InvocationTargetException InvocationTargetException
* @throws InstantiationException InstantiationException
* @throws IllegalAccessException IllegalAccessException
*/
public static <T> T newWithNoParam(Class<T> handler) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
return handler.getConstructor().newInstance();
}
/**
* 反射调用有参方法
*
* @param method 方法类对象
* @param instance 方法所在的对象实例
* @param arguments 方法参数
* @return 方法返回值
* @throws Throwable Throwable
*/
public static Object invokeWithArguments(Method method, Object instance, Object... arguments) throws Throwable {
return MethodHandles.lookup().unreflect(method).bindTo(instance).invokeWithArguments(arguments);
}
/**
* 反射调用无参方法
*
* @param method 方法类对象
* @param instance 方法所在的对象实例
* @return 方法返回值
* @throws Throwable Throwable
*/
public static Object invoke(Method method, Object instance) throws Throwable {
return MethodHandles.lookup().unreflect(method).bindTo(instance).invoke();
}
}

View File

@@ -0,0 +1,67 @@
package cn.com.yhinfo.core.util;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.LocalMap;
import io.vertx.core.shareddata.SharedData;
/**
* vertx 共享数据
* <br>Create date 2021-05-07 10:26:54
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public class SharedDataUtil {
private static final SharedData sharedData = VertxHolder.getVertxInstance().sharedData();
public static SharedData shareData() {
return sharedData;
}
public static LocalMap<String, Object> getLocalMap(String key) {
return shareData().getLocalMap(key);
}
public static <T> LocalMap<String, T> getLocalMapWithCast(String key) {
return sharedData.getLocalMap(key);
}
public static JsonObject getJsonConfig(String key) {
LocalMap<String, Object> localMap = getLocalMap("local");
return (JsonObject) localMap.get(key);
}
public static JsonObject getJsonObjectForCustomConfig(String key) {
return getJsonConfig("customConfig").getJsonObject(key);
}
public static String getJsonStringForCustomConfig(String key) {
return getJsonConfig("customConfig").getString(key);
}
public static JsonArray getJsonArrayForCustomConfig(String key) {
return getJsonConfig("customConfig").getJsonArray(key);
}
public static <T> T getValueForCustomConfig(String key) {
return CastUtil.cast(getJsonConfig("customConfig").getValue(key));
}
public static JsonObject getJsonObjectForServerConfig(String key) {
return getJsonConfig("server").getJsonObject(key);
}
public static JsonArray getJsonArrayForServerConfig(String key) {
return getJsonConfig("server").getJsonArray(key);
}
public static String getJsonStringForServerConfig(String key) {
return getJsonConfig("server").getString(key);
}
public static <T> T getValueForServerConfig(String key) {
return CastUtil.cast(getJsonConfig("server").getValue(key));
}
}

View File

@@ -0,0 +1,243 @@
package cn.com.yhinfo.core.util;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
/**
* Twitter_Snowflake<br>
* SnowFlake的结构如下(每部分用-分开):<br>
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
* 1位标识由于long基本类型在Java中是带符号的最高位是符号位正数是0负数是1所以id一般是正数最高位是0<br>
* 41位时间截(毫秒级)注意41位时间截不是存储当前时间的时间截而是存储时间截的差值当前时间截 - 开始时间截)
* 得到的值这里的的开始时间截一般是我们的id生成器开始使用的时间由我们程序来指定的如下下面程序IdWorker类的startTime属性。41位的时间截可以使用69年年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
* 10位的数据机器位可以部署在1024个节点包括5位datacenterId和5位workerId<br>
* 12位序列毫秒内的计数12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
* 加起来刚好64位为一个Long型。<br>
* SnowFlake的优点是整体上按照时间自增排序并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分)并且效率较高经测试SnowFlake每秒能够产生26万ID左右。
*
* <br>Create date 2021-04-30 09:22:18
*
* @author Twitter, <a href="https://qaiu.top">QAIU</a>
*/
public class SnowflakeIdWorker {
private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdWorker.class);
// ==============================Fields===========================================
/**
* 机器id所占的位数
*/
private final long workerIdBits = 5L;
/**
* 数据标识id所占的位数
*/
private final long datacenterIdBits = 5L;
/**
* 工作机器ID(0~31)
*/
private final long workerId;
/**
* 数据中心ID(0~31)
*/
private final long datacenterId;
/**
* 毫秒内序列(0~4095)
*/
private long sequence = 0L;
/**
* 上次生成ID的时间截
*/
private long lastTimestamp = -1L;
//==============================Constructors=====================================
public SnowflakeIdWorker() {
this.datacenterId = getDatacenterId();
this.workerId = getMaxWorkerId(datacenterId);
}
/**
* 构造函数
*
* @param workerId 工作ID (0~31)
* @param datacenterId 数据中心ID (0~31)
*/
public SnowflakeIdWorker(long workerId, long datacenterId) {
// 支持的最大机器id结果是31 (这个移位算法可以很快地计算出几位二进制数所能表示的最大十进制数)
long maxWorkerId = ~(-1L << workerIdBits);
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
long maxDatacenterId = ~(-1L << datacenterIdBits);
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
// ==============================Methods==========================================
/**
* 获得下一个ID (该方法是线程安全的)
*
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一时间生成的,则进行毫秒内序列
//序列在id中占的位数
long sequenceBits = 12L;
if (lastTimestamp == timestamp) {
//生成序列的掩码这里为4095 (0b111111111111=0xfff=4095)
long sequenceMask = ~(-1L << sequenceBits);
sequence = (sequence + 1) & sequenceMask;
//毫秒内序列溢出
if (sequence == 0) {
//阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//时间戳改变,毫秒内序列重置
else {
sequence = 0L;
}
//上次生成ID的时间截
lastTimestamp = timestamp;
//移位并通过或运算拼到一起组成64位的ID
//机器ID向左移12位
//数据标识id向左移17位(12+5)
long datacenterIdShift = sequenceBits + workerIdBits;
//时间截向左移22位(5+5+12)
long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
//开始时间截 (2021-01-01)
long twepoch = 1609459200000L;
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << sequenceBits) //
| sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
*
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的当前时间
*
* @return 当前时间(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
/**
* <p>
* 获取 maxWorkerId
* </p>
*/
protected static long getMaxWorkerId(long datacenterId) {
StringBuilder mpid = new StringBuilder();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (StringUtils.isNotEmpty(name)) {
//GET jvmPid
mpid.append(name.split("@")[0]);
}
//MAC + PID 的 hashcode 获取16个低位
return (mpid.toString().hashCode() & 0xffff) % ((long) 31 + 1);
}
/**
* <p>
* 数据标识id部分
* </p>
*/
protected static long getDatacenterId() {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
if (null != mac) {
id = ((0x000000FF & (long) mac[mac.length - 1]) | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % ((long) 31 + 1);
}
}
} catch (Exception e) {
logger.warn(" getDatacenterId: " + e.getMessage());
}
return id;
}
private static final SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker();
private static volatile SnowflakeIdWorker snowflakeIdWorkerCluster = null;
synchronized public static SnowflakeIdWorker idWorker() {
return snowflakeIdWorker;
}
synchronized public static SnowflakeIdWorker idWorkerCluster(long workerId, long datacenterId) {
if (snowflakeIdWorkerCluster == null) {
snowflakeIdWorkerCluster = new SnowflakeIdWorker(workerId, datacenterId);
}
return snowflakeIdWorkerCluster;
}
//==============================Test=============================================
/**
* 测试
*/
public static void main(String[] args) {
final SnowflakeIdWorker snowflakeIdWorkerCluster = idWorkerCluster(0, 1);
final SnowflakeIdWorker idWorker = idWorker();
for (int i = 0; i < 100; i++) {
long id = idWorker.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
System.out.println("------------");
id = snowflakeIdWorkerCluster.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
System.out.println("------------\n");
}
}
}

View File

@@ -0,0 +1,110 @@
package cn.com.yhinfo.core.util;
import org.apache.commons.lang3.StringUtils;
/**
* 驼峰式下划线命名的字符串相互转换
*
* <br>Create date 2021/6/2 0:41
* @author <a href="https://qaiu.top">QAIU</a>
*/
public class StringCase {
/**
* 将驼峰式命名的字符串转换为下划线方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。<br>
* 例如:
*
* <pre>
* HelloWorld=》hello_world
* Hello_World=》hello_world
* HelloWorld_test=》hello_world_test
* </pre>
*
* @param str 转换前的驼峰式命名的字符串,也可以为下划线形式
* @return 转换后下划线方式命名的字符串
*/
public static String toUnderlineCase(String str) {
if (StringUtils.isEmpty(str)) return str;
StringBuilder sb = new StringBuilder();
for (String s : StringUtils.splitByCharacterTypeCamelCase(str)) {
if (!s.startsWith("_")) {
sb.append(s.toLowerCase()).append("_");
} else {
sb.append(s);
}
}
return sb.substring(0, sb.length() - 1);
}
/**
* @param str 转换前的驼峰式命名的字符串,也可以为下划线形式
* @return 转换后下划线方式命名的字符串(大写)
*/
public static String toUnderlineUpperCase(String str) {
return toUnderlineCase(str).toUpperCase();
}
public static String toCamelCase(String str, boolean isBigCamel) {
if (StringUtils.isEmpty(str)) {
return str;
}
StringBuilder sb = new StringBuilder();
String[] split = StringUtils.split(str, '_');
for (int i = 0; i < split.length; i++) {
String s = split[i];
char[] chars = s.toCharArray();
if ((i == 0 && isBigCamel) || (i > 0 && chars[0] >= 'a')) {
chars[0] -= ('a' - 'A');
}
sb.append(new String(chars));
}
return sb.toString();
}
/**
* 将下划线方式命名的字符串转换为大驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。<br>
* 例如hello_world=》HelloWorld
*
* @param str 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
public static String toBigCamelCase(String str) {
return toCamelCase(str, true);
}
/**
* 将下划线方式命名的字符串转换为小驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。<br>
* 例如hello_world=》helloWorld
*
* @param str 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
public static String toLittleCamelCase(String str) {
return toCamelCase(str, false);
}
public static void main(String[] args) {
// 下划线->驼峰
System.out.println(toLittleCamelCase("my_name_qaiu"));
System.out.println(toLittleCamelCase(null));
System.out.println(toLittleCamelCase(" "));
System.out.println(toLittleCamelCase(""));
// 大驼峰
System.out.println(toBigCamelCase("my_name_qaiu"));
System.out.println(toLittleCamelCase("____my_name_qaiu"));
// 驼峰 ->下划线
System.out.println(toUnderlineCase("MyNameQaiu"));
System.out.println(toUnderlineCase(null));
System.out.println(toUnderlineCase(" "));
System.out.println(toUnderlineCase(""));
System.out.println(toUnderlineCase("__my_nameQaiu___"));
// 大写下划线
System.out.println(toUnderlineUpperCase("MyNameQaiu"));
System.out.println(toUnderlineUpperCase("__my_nameQaiu___"));
}
}

View File

@@ -0,0 +1,26 @@
package cn.com.yhinfo.core.util;
import io.vertx.core.Vertx;
import java.util.Objects;
/**
* 保存vertx实例
* <br>Create date 2021-04-30 09:22:18
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public final class VertxHolder {
private static volatile Vertx singletonVertx;
public static synchronized void init(Vertx vertx) {
Objects.requireNonNull(vertx, "未初始化Vertx");
singletonVertx = vertx;
}
public static Vertx getVertxInstance() {
Objects.requireNonNull(singletonVertx, "未初始化Vertx");
return singletonVertx;
}
}

View File

@@ -0,0 +1,263 @@
package cn.com.yhinfo.core.verticle;
import cn.com.yhinfo.core.util.CastUtil;
import cn.com.yhinfo.core.util.ConfigUtil;
import cn.com.yhinfo.core.util.SharedDataUtil;
import cn.com.yhinfo.core.util.VertxHolder;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.WebSocket;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
import io.vertx.ext.web.proxy.handler.ProxyHandler;
import io.vertx.httpproxy.HttpProxy;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
/**
* <p>反向代理服务</p>
* <p>可以根据配置文件自动生成代理服务</p>
* <p>可以配置多个服务, 配置文件见示例</p>
* <br>Create date 2021/9/2 0:41
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public class ReverseProxyVerticle extends AbstractVerticle {
private static final Logger LOGGER = LoggerFactory.getLogger(ReverseProxyVerticle.class);
private static final String PATH_PROXY_CONFIG = SharedDataUtil.getJsonConfig("globalConfig").getString("proxyConf");
private static final Future<JsonObject> CONFIG = ConfigUtil.readYamlConfig(PATH_PROXY_CONFIG);
private static final String DEFAULT_PATH_404 = "webroot/err/404.html";
private static String serverName = "Vert.x-proxy-server"; //Server name in Http response header
@Override
public void start(Promise<Void> startPromise) throws Exception {
CONFIG.onSuccess(this::handleProxyConfList);
startPromise.complete();
}
/**
* 获取主配置文件
*
* @param config proxy config
*/
private void handleProxyConfList(JsonObject config) {
serverName = config.getString("server-name");
JsonArray proxyConfList = config.getJsonArray("proxy");
proxyConfList.forEach(proxyConf -> {
if (proxyConf instanceof JsonObject) {
handleProxyConf((JsonObject) proxyConf);
}
});
}
/**
* 处理单个反向代理配置
*
* @param proxyConf 代理配置
*/
private void handleProxyConf(JsonObject proxyConf) {
// 404 path
if (proxyConf.containsKey("404")) {
System.getProperty("user.dir");
String path = proxyConf.getString("404");
if (StringUtils.isEmpty(path)) {
proxyConf.put("404", DEFAULT_PATH_404);
} else {
if (!path.startsWith("/")) {
path = "/" + path;
}
if (!new File(System.getProperty("user.dir") + path).exists()) {
proxyConf.put("404", DEFAULT_PATH_404);
}
}
} else {
proxyConf.put("404", DEFAULT_PATH_404);
}
final HttpClient httpClient = VertxHolder.getVertxInstance().createHttpClient();
Router proxyRouter = Router.router(vertx);
// Add Server name header
proxyRouter.route().handler(ctx -> {
ctx.response().putHeader("Server", serverName);
ctx.next();
});
// http api proxy
if (proxyConf.containsKey("location")) {
handleLocation(proxyConf.getJsonArray("location"), httpClient, proxyRouter);
}
// static server
if (proxyConf.containsKey("static")) {
handleStatic(proxyConf.getJsonObject("static"), proxyRouter);
}
// static server
if (proxyConf.containsKey("sock")) {
handleSock(proxyConf.getJsonArray("sock"), httpClient, proxyRouter);
}
// Send 404 page
proxyRouter.errorHandler(404, ctx -> {
ctx.response().sendFile(proxyConf.getString("404"));
});
HttpServer server = vertx.createHttpServer();
server.requestHandler(proxyRouter);
Integer port = proxyConf.getInteger("listen");
LOGGER.info("proxy server start on {} port", port);
server.listen(port);
}
/**
* 处理静态资源配置
*
* @param staticConf 静态资源配置
* @param proxyRouter 代理路由
*/
private void handleStatic(JsonObject staticConf, Router proxyRouter) {
String path = staticConf.getString("path");
proxyRouter.route(path + "*").handler(ctx -> {
if (staticConf.containsKey("add-headers")) {
Map<String, String> headers = CastUtil.cast(staticConf.getJsonObject("add-headers").getMap());
headers.forEach(ctx.response()::putHeader);
}
ctx.next();
});
final StaticHandler staticHandler = StaticHandler.create();
if (staticConf.containsKey("root")) {
staticHandler.setWebRoot(staticConf.getString("root"));
}
if (staticConf.containsKey("directory-listing")) {
staticHandler.setDirectoryListing(staticConf.getBoolean("directory-listing"));
} else if (staticConf.containsKey("index")) {
staticHandler.setIndexPage(staticConf.getString("index"));
}
proxyRouter.route(path + "*").handler(staticHandler);
}
/**
* 处理Location配置 代理请求Location(和nginx类似?)
*
* @param locationsConf location配置
* @param httpClient 客户端
* @param proxyRouter 代理路由
*/
private void handleLocation(JsonArray locationsConf, HttpClient httpClient, Router proxyRouter) {
locationsConf.stream().map(e -> (JsonObject) e).forEach(location -> {
// 代理规则
String origin = location.getString("origin");
String path = location.getString("path");
try {
URL url = new URL("https://" + origin);
String host = url.getHost();
int port = url.getPort();
if (port == -1) {
port = 80;
}
String originPath = url.getPath();
LOGGER.debug("Conf(path, originPath, host, port) ----> {},{},{},{}", path, originPath, host, port);
// 注意这里不能origin多个代理地址, 一个实例只能代理一个origin
final HttpProxy httpProxy = HttpProxy.reverseProxy(httpClient);
httpProxy.origin(port, host);
if (StringUtils.isEmpty(path)) {
return;
}
// 代理目标路径为空 就像nginx一样路径穿透 (相对路径)
if (StringUtils.isEmpty(originPath) || path.equals(originPath)) {
proxyRouter.route(path + "*").handler(ProxyHandler.create(httpProxy));
} else {
proxyRouter.route(originPath + "*").handler(ProxyHandler.create(httpProxy));
proxyRouter.route(path + "*").handler(ctx -> {
String realPath = ctx.request().path();
if (realPath.startsWith(path)) {
// vertx web proxy暂不支持rewrite, 所以这里进行手动替换, 请求地址中的请求path前缀替换为originPath
String rePath = realPath.replaceAll("^" + path, originPath);
ctx.reroute(rePath);
} else {
ctx.next();
}
});
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
});
}
/**
* 处理websocket
*
* @param confList sock配置
* @param httpClient 客户端
* @param proxyRouter 代理路由
*/
private void handleSock(JsonArray confList, HttpClient httpClient, Router proxyRouter) {
// 代理规则
confList.stream().map(e -> (JsonObject) e).forEach(conf -> {
String origin = conf.getString("origin");
String path = conf.getString("path");
LOGGER.info("websocket proxy: {}, {}",origin,path);
SockJSHandlerOptions options = new SockJSHandlerOptions()
.setHeartbeatInterval(2000)
.setRegisterWriteHandler(true);
SockJSHandler sockJSHandler = SockJSHandler.create(VertxHolder.getVertxInstance(), options);
if (!path.startsWith("/")) {
path = "/" + path;
}
Router route = sockJSHandler.socketHandler(sock -> {
sock.handler(buffer -> {
Future<WebSocket> webSocketFuture = httpClient.webSocket(8086,"127.0.0.1",sock.uri());
webSocketFuture.onSuccess(s -> {
System.out.println(buffer.toString());
s.write(buffer).onSuccess(v -> {
s.handler(w->{
System.out.println("--------"+w.toString());
});
});
});
});
sock.endHandler(v -> {
});
sock.closeHandler(v -> {
});
});
proxyRouter.mountSubRouter("/real/serverApi/test", route);
});
}
}

View File

@@ -0,0 +1,74 @@
package cn.com.yhinfo.core.verticle;
import cn.com.yhinfo.core.handlerfactory.RouterHandlerFactory;
import cn.com.yhinfo.core.util.CommonUtil;
import cn.com.yhinfo.core.util.SharedDataUtil;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.stomp.StompServer;
import io.vertx.ext.stomp.StompServerHandler;
import io.vertx.ext.stomp.StompServerOptions;
import io.vertx.ext.web.Router;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Http服务 注册路由
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public class RouterVerticle extends AbstractVerticle {
private static final Logger LOGGER = LoggerFactory.getLogger(RouterVerticle.class);
private static final int port = SharedDataUtil.getValueForServerConfig("port");
private static final Router router = new RouterHandlerFactory(
SharedDataUtil.getJsonStringForCustomConfig("routerLocations"),
SharedDataUtil.getJsonStringForServerConfig("contextPath")).createRouter();
private static final JsonObject globalConfig = SharedDataUtil.getJsonConfig("globalConfig");
private HttpServer server;
static {
LOGGER.info("To start listening to port {} ......", port);
}
@Override
public void start(Promise<Void> startPromise) {
// 端口是否占用
if (CommonUtil.isPortUsing(port)) {
throw new RuntimeException("Start fail: the '" + port + "' port is already in use...");
}
HttpServerOptions options;
if (globalConfig.containsKey("http") && globalConfig.getValue("http") != null) {
options = new HttpServerOptions(globalConfig.getJsonObject("http"));
} else {
options = new HttpServerOptions();
}
options.setPort(port);
server = vertx.createHttpServer(options);
server.requestHandler(router).webSocketHandler(s->{}).listen()
.onSuccess(s -> startPromise.complete())
.onFailure(e -> startPromise.fail(e.getCause()));
}
@Override
public void stop(Promise<Void> stopPromise) {
if (server == null) {
stopPromise.complete();
return;
}
server.close(result -> {
if (result.failed()) {
stopPromise.fail(result.cause());
} else {
stopPromise.complete();
}
});
}
}

View File

@@ -0,0 +1,51 @@
package cn.com.yhinfo.core.verticle;
import cn.com.yhinfo.core.annotaions.Service;
import cn.com.yhinfo.core.base.BaseAsyncService;
import cn.com.yhinfo.core.util.ReflectionUtil;
import cn.com.yhinfo.core.util.SharedDataUtil;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.serviceproxy.ServiceBinder;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 服务注册到EventBus
* <br>Create date 2021-05-07 10:26:54
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public class ServiceVerticle extends AbstractVerticle {
Logger LOGGER = LoggerFactory.getLogger(ServiceVerticle.class);
private static final AtomicInteger ID = new AtomicInteger(1);
private static final Set<Class<?>> handlers;
static {
String handlerLocations = SharedDataUtil.getJsonStringForCustomConfig("handlerLocations");
Reflections reflections = ReflectionUtil.getReflections(handlerLocations);
handlers = reflections.getTypesAnnotatedWith(Service.class);
}
@Override
public void start(Promise<Void> startPromise) {
ServiceBinder binder = new ServiceBinder(vertx);
if (null != handlers && handlers.size() > 0) {
handlers.forEach(asyncService -> {
try {
BaseAsyncService asInstance = (BaseAsyncService) ReflectionUtil.newWithNoParam(asyncService);
binder.setAddress(asInstance.getAddress()).register(asInstance.getAsyncInterfaceClass(), asInstance);
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
});
LOGGER.info("registered async services -> id: {}", ID.getAndIncrement());
}
startPromise.complete();
}
}

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志自定义颜色 -->
<!-- https://logback.qos.ch/manual/layouts.html#coloring -->
<!--日志文件主目录:这里${user.home}为当前服务器用户主目录-->
<property name="LOG_HOME" value="logs"/>
<!--日志文件名称这里spring.application.name表示工程名称-->
<property name="LOGBACK_DEFAULT" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<property name="CUSTOMER_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%t]) %highlight(%-5p) %yellow(${PID:-}) %cyan(%-40.40logger{39}) : %green(%m%n)"/>
<property name="CUSTOMER_PATTERN2" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) -> %magenta([%15.15thread]) %cyan(%-40.40logger{39}) : %msg%n"/>
<!--配置日志文件(File)-->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--设置策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件路径:这里%d{yyyyMMdd}表示按天分类日志-->
<FileNamePattern>${LOG_HOME}/%d{yyyyMMdd}/run.log</FileNamePattern>
<!--日志保留天数-->
<MaxHistory>15</MaxHistory>
</rollingPolicy>
<!--设置格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!-- 或者使用默认配置 -->
<!--<pattern>${FILE_LOG_PATTERN}</pattern>-->
<charset>utf8</charset>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>100MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 将文件输出设置成异步输出 -->
<appender name="ASYNC-FILE" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>256</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="FILE"/>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符-->
<pattern>${CUSTOMER_PATTERN2}</pattern>
</encoder>
</appender>
<logger name="io.netty" level="warn"/>
<logger name="io.vertx" level="info"/>
<root level="debug">
<appender-ref ref="STDOUT"/>
</root>
</configuration>