第一个基本可用版本

This commit is contained in:
杨文
2022-06-16 17:42:19 +08:00
parent 132b7fed03
commit b4546301c2
114 changed files with 11592 additions and 63 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 傲世孤尘
Copyright (c) 2022 aoshiguchen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
-36
View File
@@ -1,36 +0,0 @@
# 中微子代理
#### Description
一款基于netty的内网穿透神器
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
+100 -26
View File
@@ -1,37 +1,111 @@
# 中微子代理
# 1、简介
中微子代理(neutrino-proxy)是一个基于netty的、开源的java内网穿透项目。遵循MIT许可,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
#### 介绍
一款基于netty的内网穿透神器
# 2、项目结构
- neutrino-proxy
- neutrino-core 与代理无关的基础封装
- neutrino-proxy-core 与代理相关的公共常量、编解码器
- neutrino-proxy-client 代理客户端项目
- neutrino-proxy-server 代理服务端项目
- neutrino-proxy-admin 代理监控项目(暂时空缺)
#### 软件架构
软件架构说明
# 3、运行
## 3.1、使用keytool工具生成ssl证书, 若不需要ssl加密可跳过
> keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
## 3.2、修改服务端配置(application.yml
```yml
application:
name: neutrino-proxy-server
#### 安装教程
proxy:
protocol:
max-frame-length: 2097152
length-field-offset: 0
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 60
write-idle-time: 40
all-idle-time-seconds: 0
server:
# 服务端端口,用于保持与客户端的连接,非SSL
port: 9000
# 服务端端口,用于保持与客户端的连接,SSL,需要jks证书文件,若不需要ssl支持,可不配置
ssl-port: 9002
# 证书密码
key-store-password: 123456
key-manager-password: 123456
# 证书存放路径,若不想打进jar包,可不带classpath:前缀
jks-path: classpath:/test.jks
# license配置, 客户端连接时需要用这个进行校验
license:
# license数为3表示用该license连接的客户端最多可代理3个端口,-1为不限
79419a1a8691413aa5e845b9e3e90051: 3
9352b1c25f564c81a5677131d7769876: 2
```
1. xxxx
2. xxxx
3. xxxx
## 3.3、启动服务端
> fun.asgc.neutrino.proxy.server.ProxyServer
#### 使用说明
## 3.4、修改客户端配置
```
application:
name: neutrino-proxy-client
1. xxxx
2. xxxx
3. xxxx
proxy:
protocol:
max-frame-length: 2097152
length-field-offset: 0
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 60
write-idle-time: 30
all-idle-time-seconds: 0
client:
# ssl证书密码
key-store-password: 123456
# ssl证书存放位置
jks-path: classpath:/test.jks
# 服务端ip,若部署到服务器,则配置服务器的ip
server-ip: localhost
# 服务端端口,若使用ssl,则需要配置为服务端的"ssl-port"
server-port: 9000
# 是否启用ssl,启用则必须配置ssl相关参数
ssl-enable: false
```
#### 参与贡献
## 3.5、准备代理信息配置文件 config.json
```
{
"environment": "我的Mac",
"clientKey": "79419a1a8691413aa5e845b9e3e90051", # 对应服务端配置license中的key
"proxy": [
{
"serverPort": 9100, # 外网服务器对外暴露的端口
"clientInfo": "127.0.0.1:3306" # 需要代理的本地端口(mysql)
},
{
"serverPort": 9101, # 外网服务器对外暴露的端口
"clientInfo": "rm-xxxx.mysql.rds.aliyuncs.com:3306" # 代理外网端口本身无意义,仅供测试
},
{
"serverPort": 9102, # 外网服务器对外暴露的端口
"clientInfo": "127.0.0.1:8080" # 需要代理的本地端口(http)
}
]
}
```
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
## 3.6、启动客户端
> fun.asgc.neutrino.proxy.client.ProxyClient
默认情况下,客户端会加载当前目录下的config.json文件作为代理配置,可通过命令行参数指定,如:java -jar neutrino-proxy-client.jar /xxx/proxy.json
# 4、未来迭代方向
- 优化代码、增强稳定性
- 服务端增加管理页面,提供报表、授权、限流等功能
- 从项目中分离、孵化出另一个开源项目(neutrino-framework)
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
# 5、联系我们
微信: yuyunshize
+18
View File
@@ -0,0 +1,18 @@
{
"environment": "我的Mac",
"clientKey": "79419a1a8691413aa5e845b9e3e90051",
"proxy": [
{
"serverPort": 9100,
"clientInfo": "127.0.0.1:3306"
},
{
"serverPort": 9101,
"clientInfo": "rm-uf63ggkfh7zrr08vw.mysql.rds.aliyuncs.com:3306"
},
{
"serverPort": 9102,
"clientInfo": "127.0.0.1:8080"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
<?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>neutrino-proxy</artifactId>
<groupId>fun.asgc.neutrino</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>neutrino-core</artifactId>
<dependencies>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
String value() default "";
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Bean {
String value() default "";
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
@Documented
public @interface Configuration {
/**
* 文件
* @return
*/
String file() default "";
/**
* 前缀
* @return
*/
String prefix() default "";
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Destroy {
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Init {
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Lazy {
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Match {
String type();
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface NeutrinoApplication {
String[] scanBasePackages() default {};
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
int value() default Integer.MAX_VALUE;
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.annotation;
import java.lang.annotation.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
String value() default "";
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.base;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@FunctionalInterface
public interface CodeBlock {
/**
* 执行
*/
void execute();
}
@@ -0,0 +1,112 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.base;
import com.alibaba.fastjson.JSONObject;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.core.util.Assert;
import fun.asgc.neutrino.core.util.CollectionUtil;
import fun.asgc.neutrino.core.util.StringUtil;
import fun.asgc.neutrino.core.util.TypeUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class DefaultDispatcher<Context, Data> implements Dispatcher<Context, Data> {
/**
* 调度器名称
*/
private String name;
/**
* 处理器映射
*/
private Map<String, Handler<Context,Data>> handlerMap;
/**
* 匹配器
*/
private Function<Data,String> matcher;
public DefaultDispatcher(String name, List<? extends Handler<Context,Data>> handlerList, Function<Data,String> matcher) {
Assert.notNull(name, "名称不能为空!");
Assert.notNull(matcher, "匹配器不能为空!");
if (CollectionUtil.isEmpty(handlerList)) {
log.error("{} 处理器列表为空.", name);
return;
}
this.name = name;
this.handlerMap = new HashMap<>();
this.matcher = matcher;
for (Handler handler : handlerList) {
Match match = handler.getClass().getAnnotation(Match.class);
if (null == match) {
log.warn("类: {} 缺失Match注解", handler.getClass().getName());
continue;
}
if (StringUtil.isEmpty(match.type())) {
log.warn("类: {} match注解缺失type参数!", handler.getClass().getName());
continue;
}
if (handlerMap.containsKey(match.type())) {
log.warn("类: {} match注解type值{} 存在重复!", handler.getClass().getName(), match.type());
continue;
}
handlerMap.put(match.type(), handler);
}
log.info("{} 处理器初始化完成", name);
}
@Override
public void dispatch(Context context, Data data) {
if (CollectionUtil.isEmpty(handlerMap)) {
return;
}
String type = matcher.apply(data);
if (null == type) {
log.warn("获取匹配类型失败 data:{]", JSONObject.toJSONString(data));
return;
}
Handler<Context,Data> handler = handlerMap.get(type);
if (null == handler) {
log.warn("找不到匹配的处理器 type:{}", type);
return;
}
String name = handler.name();
if (StringUtil.isEmpty(name)) {
name = TypeUtil.getSimpleName(handler.getClass());
}
log.debug("处理器[{}]执行.", name);
handler.handle(context, data);
}
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.base;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface Dispatcher<Context, Data> {
/**
* 调度
* @param context
* @param data
*/
void dispatch(Context context, Data data);
}
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.base;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface Handler<Context, Data> {
/**
* 处理
* @param context
* @param data
*/
void handle(Context context, Data data);
/**
* 名称
* @return
*/
default String name() {
return "";
};
}
@@ -0,0 +1,97 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.cache;
import java.util.Collection;
import java.util.Set;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface Cache<K,V> {
/**
* 设置缓存
* @param k
* @param v
*/
void set(K k, V v);
/**
* 获取缓存
* @param k
* @return
*/
V get(K k);
/**
* 判断缓存是否存在
* @param k
* @return
*/
boolean containsKey(K k);
/**
* 判断缓存值是否存在
* @param v
* @return
*/
boolean containsValue(V v);
/**
* key集合
* @return
*/
Set<K> keySet();
/**
* 值集合
* @return
*/
Collection<V> values();
/**
* 是否为空
* @return
*/
boolean isEmpty();
/**
* 清空缓存
*/
void clear();
/**
* 缓存大小
* @return
*/
int size();
/**
* 如果有且只有一个缓存,则返回该缓存值,否则返回空
* @return
*/
V isOnePeek();
}
@@ -0,0 +1,84 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.cache;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface CacheGroup<K,V> {
/**
* 获取缓存
* @param group
* @return
*/
Cache<K,V> getCache(String group);
/**
* 设置缓存
* @param group
* @param cache
*/
void setCache(String group, Cache<K, V> cache);
/**
* 判断缓存是否存在
* @param group
* @return
*/
boolean containsCache(String group);
/**
* 设置缓存值
* @param group
* @param k
* @param v
*/
void set(String group, K k, V v);
/**
* 获取缓存值
* @param group
* @param k
* @return
*/
V get(String group, K k);
/**
* 判断缓存值是否存在
* @param group
* @param k
* @return
*/
boolean containsKey(String group, K k);
/**
* 是否为空
* @param group
* @return
*/
boolean isEmpty(String group);
}
@@ -0,0 +1,274 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.cache;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.util.TypeUtils;
import fun.asgc.neutrino.core.util.FileUtil;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class LocalCache implements Cache<String,Object>, Preservable {
private JSONObject cache;
private String path;
public LocalCache(String path) {
this.cache = new JSONObject();
this.path = path;
FileUtil.makeDirs(path);
this.load();
}
@Override
public void set(String k, Object v) {
cache.put(k, v);
this.save();
}
@Override
public Object get(String k) {
return cache.getJSONObject(k);
}
@Override
public boolean containsKey(String k) {
return cache.containsKey(k);
}
@Override
public boolean containsValue(Object v) {
return cache.containsValue(v);
}
@Override
public Set<String> keySet() {
return cache.keySet();
}
@Override
public boolean isEmpty() {
return cache.isEmpty();
}
@Override
public void clear() {
cache.clear();
save();
}
@Override
public int size() {
return cache.size();
}
@Override
public Object isOnePeek() {
if (size() != 1) {
return null;
}
return cache.values().stream().findFirst().get();
}
@Override
public Collection<Object> values() {
return cache.values();
}
@Override
public synchronized void save() {
FileUtil.write(path, JSONObject.toJSONString(cache));
}
@Override
public synchronized void load() {
String content = FileUtil.readContentAsString(path);
if (StringUtils.isNotEmpty(content)) {
cache = JSONObject.parseObject(content);
}
}
public JSONObject getJSONObject(String key) {
Object value = this.cache.get(key);
if (value instanceof JSONObject) {
return (JSONObject)value;
} else if (value instanceof Map) {
return new JSONObject((Map)value);
} else {
return value instanceof String ? JSON.parseObject((String)value) : (JSONObject)JSON.toJSON(value);
}
}
public JSONArray getJSONArray(String key) {
Object value = this.cache.get(key);
if (value instanceof JSONArray) {
return (JSONArray)value;
} else if (value instanceof List) {
return new JSONArray((List)value);
} else {
return value instanceof String ? (JSONArray)JSON.parse((String)value) : (JSONArray)JSON.toJSON(value);
}
}
public <T> T getObject(String key, Class<T> clazz) {
Object obj = this.cache.get(key);
return TypeUtils.castToJavaBean(obj, clazz);
}
public <T> T getObject(String key, Type type) {
Object obj = this.cache.get(key);
return TypeUtils.cast(obj, type, ParserConfig.getGlobalInstance());
}
public <T> T getObject(String key, TypeReference typeReference) {
Object obj = this.cache.get(key);
return typeReference == null ? (T)obj : TypeUtils.cast(obj, typeReference.getType(), ParserConfig.getGlobalInstance());
}
public Boolean getBoolean(String key) {
Object value = this.get(key);
return value == null ? null : TypeUtils.castToBoolean(value);
}
public byte[] getBytes(String key) {
Object value = this.get(key);
return value == null ? null : TypeUtils.castToBytes(value);
}
public boolean getBooleanValue(String key) {
Object value = this.get(key);
Boolean booleanVal = TypeUtils.castToBoolean(value);
return booleanVal == null ? false : booleanVal;
}
public Byte getByte(String key) {
Object value = this.get(key);
return TypeUtils.castToByte(value);
}
public byte getByteValue(String key) {
Object value = this.get(key);
Byte byteVal = TypeUtils.castToByte(value);
return byteVal == null ? 0 : byteVal;
}
public Short getShort(String key) {
Object value = this.get(key);
return TypeUtils.castToShort(value);
}
public short getShortValue(String key) {
Object value = this.get(key);
Short shortVal = TypeUtils.castToShort(value);
return shortVal == null ? 0 : shortVal;
}
public Integer getInteger(String key) {
Object value = this.get(key);
return TypeUtils.castToInt(value);
}
public int getIntValue(String key) {
Object value = this.get(key);
Integer intVal = TypeUtils.castToInt(value);
return intVal == null ? 0 : intVal;
}
public Long getLong(String key) {
Object value = this.get(key);
return TypeUtils.castToLong(value);
}
public long getLongValue(String key) {
Object value = this.get(key);
Long longVal = TypeUtils.castToLong(value);
return longVal == null ? 0L : longVal;
}
public Float getFloat(String key) {
Object value = this.get(key);
return TypeUtils.castToFloat(value);
}
public float getFloatValue(String key) {
Object value = this.get(key);
Float floatValue = TypeUtils.castToFloat(value);
return floatValue == null ? 0.0F : floatValue;
}
public Double getDouble(String key) {
Object value = this.get(key);
return TypeUtils.castToDouble(value);
}
public double getDoubleValue(String key) {
Object value = this.get(key);
Double doubleValue = TypeUtils.castToDouble(value);
return doubleValue == null ? 0.0D : doubleValue;
}
public BigDecimal getBigDecimal(String key) {
Object value = this.get(key);
return TypeUtils.castToBigDecimal(value);
}
public BigInteger getBigInteger(String key) {
Object value = this.get(key);
return TypeUtils.castToBigInteger(value);
}
public String getString(String key) {
Object value = this.get(key);
return value == null ? null : value.toString();
}
public Date getDate(String key) {
Object value = this.get(key);
return TypeUtils.castToDate(value);
}
public java.sql.Date getSqlDate(String key) {
Object value = this.get(key);
return (java.sql.Date) TypeUtils.castToSqlDate(value);
}
public Timestamp getTimestamp(String key) {
Object value = this.get(key);
return (Timestamp) TypeUtils.castToTimestamp(value);
}
}
@@ -0,0 +1,88 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.cache;
import java.util.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class MemoryCache<K,V> implements Cache<K,V> {
private Map<K,V> cache = Collections.synchronizedMap(new HashMap<>());
@Override
public void set(K k, V v) {
cache.put(k, v);
}
@Override
public V get(K k) {IdentityHashMap a = new IdentityHashMap();
return (V)cache.get(k);
}
@Override
public boolean containsKey(K k) {
return cache.containsKey(k);
}
@Override
public boolean containsValue(V v) {
return cache.containsValue(v);
}
@Override
public Set<K> keySet() {
return cache.keySet();
}
@Override
public Collection<V> values() {
return cache.values();
}
@Override
public boolean isEmpty() {
return cache.isEmpty();
}
@Override
public void clear() {
cache.clear();
}
@Override
public int size() {
return cache.size();
}
@Override
public V isOnePeek() {
if (size() != 1) {
return null;
}
return cache.values().stream().findFirst().get();
}
}
@@ -0,0 +1,77 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.cache;
import fun.asgc.neutrino.core.util.LockUtil;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class MemoryCacheGroup<K,V> implements CacheGroup<K,V> {
/**
* 缓存
*/
private final Cache<String,Cache<K,V>> cacheGroup = new MemoryCache<>();
@Override
public Cache<K, V> getCache(String group) {
return cacheGroup.get(group);
}
@Override
public void setCache(String group, Cache<K, V> cache) {
cacheGroup.set(group, cache);
}
@Override
public boolean containsCache(String group) {
return cacheGroup.containsKey(group);
}
@Override
public void set(String group, K k, V v) {
LockUtil.doubleCheckProcess(() -> !cacheGroup.containsKey(group),
group,
() -> cacheGroup.set(group, new MemoryCache<>())
);
cacheGroup.get(group).set(k, v);
}
@Override
public V get(String group, K k) {
return (cacheGroup.containsKey(group) && cacheGroup.get(group).containsKey(k)) ? cacheGroup.get(group).get(k) : null;
}
@Override
public boolean containsKey(String group, K k) {
return cacheGroup.containsKey(group) && cacheGroup.get(group).containsKey(k);
}
@Override
public boolean isEmpty(String group) {
return !cacheGroup.containsKey(group) || cacheGroup.get(group).isEmpty();
}
}
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.cache;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface Preservable {
/**
* 保存到磁盘
*/
void save();
/**
* 从磁盘加载
*/
void load();
}
@@ -0,0 +1,143 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.config;
import fun.asgc.neutrino.core.annotation.Configuration;
import fun.asgc.neutrino.core.annotation.Value;
import fun.asgc.neutrino.core.constant.MetaDataConstant;
import fun.asgc.neutrino.core.exception.ConfigurationParserException;
import fun.asgc.neutrino.core.util.CollectionUtil;
import fun.asgc.neutrino.core.util.FileUtil;
import fun.asgc.neutrino.core.util.ReflectUtil;
import fun.asgc.neutrino.core.util.StringUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public abstract class AbstractConfigurationParser implements ConfigurationParser {
@Override
public <T> T parse(InputStream in, Class<T> clazz) throws ConfigurationParserException {
Map<String, Object> config = parse2Map(in);
return parse(config, clazz);
}
@Override
public <T> T parse(Class<T> clazz) throws ConfigurationParserException {
String fileName = defaultFileName();
Configuration configuration = clazz.getAnnotation(Configuration.class);
if (null != configuration && StringUtils.isNotEmpty(configuration.file())) {
fileName = configuration.file();
}
try {
InputStream in = FileUtil.getInputStream(MetaDataConstant.CLASSPATH_RESOURCE_IDENTIFIER.concat("/").concat(fileName));
return parse(in, clazz);
} catch (FileNotFoundException e) {
throw new ConfigurationParserException(String.format("配置文件[%s]不存在", fileName));
}
}
@Override
public <T> T parse(Map<String, Object> config, Class<T> clazz) throws ConfigurationParserException {
return parseProxy(config, clazz);
}
private <T> T parseProxy(Map<String, Object> config, Class<?> clazz) throws ConfigurationParserException {
T instance = null;
try {
instance = (T)clazz.newInstance();
} catch (InstantiationException e) {
throw new ConfigurationParserException(String.format("无法实例化类:%s", clazz.getName()));
} catch (IllegalAccessException e) {
throw new ConfigurationParserException(String.format("没有权限访问类:%s", clazz.getName()));
}
String prefix = "";
Configuration configuration = clazz.getAnnotation(Configuration.class);
if (null != configuration) {
prefix = configuration.prefix();
}
if (StringUtils.isNotEmpty(prefix)) {
String[] prefixNodes = prefix.split("\\.");
for (String prefixNode : prefixNodes) {
if (config.containsKey(prefixNode) && config.get(prefixNode) instanceof Map) {
config = (Map<String, Object>) config.get(prefixNode);
if (CollectionUtil.isEmpty(config)) {
return instance;
}
} else {
return instance;
}
}
}
Set<Field> fields = ReflectUtil.getInheritChainDeclaredFieldSet(clazz);
if (CollectionUtil.isEmpty(fields)) {
return instance;
}
for (Field field : fields) {
String key = field.getName();
Value value = field.getAnnotation(Value.class);
if (null != value && StringUtil.notEmpty(value.value())) {
key = value.value();
}
boolean success = ReflectUtil.setFieldValue(field, instance, config.get(key));
if (!success) {
try {
Object o = parseProxy((Map)config.get(key), field.getType());
if (null != o) {
ReflectUtil.setFieldValue(field, instance, o);
}
} catch (Exception e) {
// ignore
}
}
}
return instance;
}
/**
* 解析配置为map
* @param in
* @return
*/
protected abstract Map<String, Object> parse2Map(InputStream in) throws ConfigurationParserException;
/**
* 默认的文件名
* @return
*/
protected abstract String defaultFileName();
}
@@ -0,0 +1,63 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.config;
import fun.asgc.neutrino.core.exception.ConfigurationParserException;
import java.io.InputStream;
import java.util.Map;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface ConfigurationParser {
/**
* 解析
* @param in
* @param clazz
* @param <T>
* @return
*/
<T> T parse(InputStream in, Class<T> clazz) throws ConfigurationParserException;
/**
* 解析
* @param clazz
* @param <T>
* @return
*/
<T> T parse(Class<T> clazz) throws ConfigurationParserException;
/**
* 解析
* @param config
* @param clazz
* @param <T>
* @return
* @throws ConfigurationParserException
*/
<T> T parse(Map<String, Object> config, Class<T> clazz) throws ConfigurationParserException;
}
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.config.impl;
import fun.asgc.neutrino.core.config.AbstractConfigurationParser;
import fun.asgc.neutrino.core.constant.MetaDataConstant;
import fun.asgc.neutrino.core.exception.ConfigurationParserException;
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Map;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class YmlConfigurationParser extends AbstractConfigurationParser {
@Override
public Map<String, Object> parse2Map(InputStream in) throws ConfigurationParserException {
try {
return new Yaml().load(in);
} catch (Exception e) {
throw new ConfigurationParserException("yml配置解析异常!");
}
}
@Override
protected String defaultFileName() {
return MetaDataConstant.DEFAULT_YML_CONFIG_FILE_NAME;
}
}
@@ -0,0 +1,79 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.constant;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface MetaDataConstant {
String DEFAULT_BANNER = " _____ _____ _____ _____ _____ _____ _____ _______ \n" +
" /\\ \\ /\\ \\ /\\ \\ /\\ \\ /\\ \\ /\\ \\ /\\ \\ /::\\ \\ \n" +
" /::\\____\\ /::\\ \\ /::\\____\\ /::\\ \\ /::\\ \\ /::\\ \\ /::\\____\\ /::::\\ \\ \n" +
" /::::| | /::::\\ \\ /:::/ / \\:::\\ \\ /::::\\ \\ \\:::\\ \\ /::::| | /::::::\\ \\ \n" +
" /:::::| | /::::::\\ \\ /:::/ / \\:::\\ \\ /::::::\\ \\ \\:::\\ \\ /:::::| | /::::::::\\ \\ \n" +
" /::::::| | /:::/\\:::\\ \\ /:::/ / \\:::\\ \\ /:::/\\:::\\ \\ \\:::\\ \\ /::::::| | /:::/~~\\:::\\ \\ \n" +
" /:::/|::| | /:::/__\\:::\\ \\ /:::/ / \\:::\\ \\ /:::/__\\:::\\ \\ \\:::\\ \\ /:::/|::| | /:::/ \\:::\\ \\ \n" +
" /:::/ |::| | /::::\\ \\:::\\ \\ /:::/ / /::::\\ \\ /::::\\ \\:::\\ \\ /::::\\ \\ /:::/ |::| | /:::/ / \\:::\\ \\ \n" +
" /:::/ |::| | _____ /::::::\\ \\:::\\ \\ /:::/ / _____ /::::::\\ \\ /::::::\\ \\:::\\ \\ ____ /::::::\\ \\ /:::/ |::| | _____ /:::/____/ \\:::\\____\\ \n" +
" /:::/ |::| |/\\ \\ /:::/\\:::\\ \\:::\\ \\ /:::/____/ /\\ \\ /:::/\\:::\\ \\ /:::/\\:::\\ \\:::\\____\\ /\\ \\ /:::/\\:::\\ \\ /:::/ |::| |/\\ \\ |:::| | |:::| |\n" +
"/:: / |::| /::\\____\\/:::/__\\:::\\ \\:::\\____\\|:::| / /::\\____\\ /:::/ \\:::\\____\\/:::/ \\:::\\ \\:::| |/::\\ \\/:::/ \\:::\\____\\/:: / |::| /::\\____\\|:::|____| |:::| |\n" +
"\\::/ /|::| /:::/ /\\:::\\ \\:::\\ \\::/ /|:::|____\\ /:::/ / /:::/ \\::/ /\\::/ |::::\\ /:::|____|\\:::\\ /:::/ \\::/ /\\::/ /|::| /:::/ / \\:::\\ \\ /:::/ / \n" +
" \\/____/ |::| /:::/ / \\:::\\ \\:::\\ \\/____/ \\:::\\ \\ /:::/ / /:::/ / \\/____/ \\/____|:::::\\/:::/ / \\:::\\/:::/ / \\/____/ \\/____/ |::| /:::/ / \\:::\\ \\ /:::/ / \n" +
" |::|/:::/ / \\:::\\ \\:::\\ \\ \\:::\\ \\ /:::/ / /:::/ / |:::::::::/ / \\::::::/ / |::|/:::/ / \\:::\\ /:::/ / \n" +
" |::::::/ / \\:::\\ \\:::\\____\\ \\:::\\ /:::/ / /:::/ / |::|\\::::/ / \\::::/____/ |::::::/ / \\:::\\__/:::/ / \n" +
" |:::::/ / \\:::\\ \\::/ / \\:::\\__/:::/ / \\::/ / |::| \\::/____/ \\:::\\ \\ |:::::/ / \\::::::::/ / \n" +
" |::::/ / \\:::\\ \\/____/ \\::::::::/ / \\/____/ |::| ~| \\:::\\ \\ |::::/ / \\::::::/ / \n" +
" /:::/ / \\:::\\ \\ \\::::::/ / |::| | \\:::\\ \\ /:::/ / \\::::/ / \n" +
" /:::/ / \\:::\\____\\ \\::::/ / \\::| | \\:::\\____\\ /:::/ / \\::/____/ \n" +
" \\::/ / \\::/ / \\::/____/ \\:| | \\::/ / \\::/ / ~~ \n" +
" \\/____/ \\/____/ ~~ \\|___| \\/____/ \\/____/ \n" +
" ";
/**
* classpath标识符
*/
String CLASSPATH_IDENTIFIER = "classpath";
/**
* classpath资源标识
*/
String CLASSPATH_RESOURCE_IDENTIFIER = CLASSPATH_IDENTIFIER + ":";
/**
* 默认的yml配置文件名
*/
String DEFAULT_YML_CONFIG_FILE_NAME = "application.yml";
/**
* 应用banner路径
*/
String APP_BANNER_FILE_PATH = CLASSPATH_RESOURCE_IDENTIFIER.concat("/banner.txt");
/**
* 默认的http页面路径
*/
String DEFAULT_HTTP_PAGE_PATH = CLASSPATH_RESOURCE_IDENTIFIER.concat("/webapp/");
}
@@ -0,0 +1,107 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.constant;
/**
* 类型不匹配值(后期计划改为"类型距离")
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface TypeNotMatchingValueConstant {
/**
* 最小值,表示完全匹配
*/
int MIN = 0;
/**
* 最大值,表示完全不匹配
*/
int MAX = Integer.MAX_VALUE;
/**
* 完全匹配
*/
int COMPLETE = MIN;
/**
* 完全不匹配
*/
int NOT = MAX;
/**
* 包装匹配
*/
int WRAP = 1;
/**
* 解包装匹配
*/
int UNWRAP = 2;
/**
* 超类匹配起始值
* 每增加1层,值加1
*/
int SUPER_CLASS_MIN = 100000;
/**
* 超类最大层级(假定继承层级不会超过40万)
*/
int SUPER_CLASS_MAX_LEVEL = 400000;
/**
* 超类匹配结束值
*/
int SUPER_CLASS_MAX = SUPER_CLASS_MIN + SUPER_CLASS_MAX_LEVEL - 1;
/**
* 类型提升起始值
*/
int ASCENSION_MIN = 600000;
/**
* 类型提升层级
*/
int ASCENSION_LEVEL = 10000;
/**
* 类型提升最大值
*/
int ASCENSION_MAX = ASCENSION_MIN + ASCENSION_LEVEL - 1;
/**
* 自定义类型转换起始值
*/
int CUSTOM_MIN = 10000000;
/**
* 自定义类型转换层级
*/
int CUSTOM_LEVEL = 10000000;
/**
* 自定义类型转换结束值
*/
int CUSTOM_MAX = CUSTOM_MIN + CUSTOM_LEVEL - 1;
}
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.container;
/**
* 应用容器
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface ApplicationContainer extends Container {
}
@@ -0,0 +1,75 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.container;
import fun.asgc.neutrino.core.context.Bean;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface BeanContainer extends Container {
/**
* 是否存在bean实例
* @param clazz
* @return
*/
boolean hasBean(Class<?> clazz);
/**
* 是否存在bean实例
* @param name
* @return
*/
boolean hasBean(String name);
/**
* 获取bean实例
* @param clazz
* @return
*/
Bean getBean(Class<?> clazz);
/**
* 获取bean实例
* @param name
* @return
*/
Bean getBean(String name);
/**
* 获取bean集合
* @return
*/
List<Bean> beanList();
/**
* 添加bean
* @param bean
*/
void addBean(Bean bean);
}
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.container;
import java.util.Set;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface ClassContainer extends Container {
/**
* 判断指定类是否在容器中
* @param clazz
* @return
*/
boolean hasClass(Class<?> clazz);
/**
* 获取容器中所有的类
* @return
*/
Set<Class<?>> getClasses();
/**
* 获取容器中所有的组件
* @return
*/
Set<Class<?>> getComponentClasses();
}
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.container;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface Container extends LifeCycle {
}
@@ -0,0 +1,85 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.container;
import fun.asgc.neutrino.core.context.ApplicationContext;
import fun.asgc.neutrino.core.context.Environment;
import fun.asgc.neutrino.core.exception.ContainerInitException;
import fun.asgc.neutrino.core.runner.ApplicationRunner;
import fun.asgc.neutrino.core.util.BeanManager;
import lombok.extern.slf4j.Slf4j;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class DefaultApplicationContainer implements ApplicationContainer {
private Environment environment;
private BeanContainer beanContainer;
private ApplicationContext context;
public DefaultApplicationContainer(Environment environment) {
this.environment = environment;
this.init();
}
@Override
public void init() throws ContainerInitException {
this.beanContainer = new DefaultBeanContainer(environment);
this.context = new ApplicationContext()
.setEnvironment(environment)
.setBeanContainer(beanContainer)
.setApplicationConfig(environment.getConfig());
BeanManager.setContext(this.context);
this.beanContainer.beanList().forEach(bean -> {
if (bean.isBoot() || !bean.isLazy()) {
bean.newInstance(context);
}
});
log.info("应用容器初始化完成");
this.run();
}
private void run() {
this.beanContainer.beanList().forEach(bean -> {
if (bean.isBoot()) {
if (bean.hasInstance()) {
if (ApplicationRunner.class.isAssignableFrom(bean.getClazz())) {
ApplicationRunner runner = (ApplicationRunner)bean.getInstance();
runner.run(environment.getMainArgs());
}
}
}
});
}
@Override
public void destroy() {
this.beanContainer.destroy();
this.beanContainer.beanList().forEach(bean -> bean.destroy());
log.info("应用容器销毁");
}
}
@@ -0,0 +1,140 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.container;
import com.google.common.collect.Lists;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Order;
import fun.asgc.neutrino.core.context.Bean;
import fun.asgc.neutrino.core.cache.Cache;
import fun.asgc.neutrino.core.cache.MemoryCache;
import fun.asgc.neutrino.core.context.Environment;
import fun.asgc.neutrino.core.exception.ContainerInitException;
import fun.asgc.neutrino.core.runner.ApplicationRunner;
import fun.asgc.neutrino.core.util.ClassUtil;
import fun.asgc.neutrino.core.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class DefaultBeanContainer implements BeanContainer {
private Environment environment;
private ClassContainer classContainer;
private Cache<String, Bean> nameBeanCache;
private Cache<Class<?>, Bean> classBeanCache;
private List<Bean> beans;
public DefaultBeanContainer(Environment environment) {
this.environment = environment;
this.nameBeanCache = new MemoryCache<>();
this.classBeanCache = new MemoryCache<>();
this.beans = new ArrayList<>();
this.init();
}
@Override
public boolean hasBean(Class<?> clazz) {
return classBeanCache.containsKey(clazz);
}
@Override
public boolean hasBean(String name) {
return nameBeanCache.containsKey(name);
}
@Override
public Bean getBean(Class<?> clazz) {
return classBeanCache.get(clazz);
}
@Override
public Bean getBean(String name) {
return nameBeanCache.get(name);
}
@Override
public void init() throws ContainerInitException {
this.classContainer = new DefaultClassContainer(environment);
this.classContainer.getComponentClasses().forEach(clazz -> {
String name = clazz.getName();
Component component = ClassUtil.getAnnotation(clazz, Component.class);
if (null != component && StringUtil.notEmpty(component.value())) {
name = component.value();
}
Order order = ClassUtil.getAnnotation(clazz, Order.class);
int orderValue = Integer.MAX_VALUE;
if (null != order) {
orderValue = order.value();
}
addBean(new Bean()
.setClazz(clazz)
.setName(name)
.setComponent(component)
.setOrder(orderValue)
);
});
if (!classBeanCache.isEmpty()) {
beans = Lists.newArrayList(classBeanCache.values());
Collections.sort(beans, Comparator.comparingInt(Bean::getOrder));
}
log.info("bean容器初始化完成");
}
@Override
public void destroy() {
this.classContainer.destroy();
log.info("bean容器销毁");
}
@Override
public void addBean(Bean bean) {
if (null == bean) {
return;
}
// TODO 暂时由编码时规避名字冲突
if (ApplicationRunner.class.isAssignableFrom(bean.getClazz())) {
bean.setOrder(Integer.MIN_VALUE);
bean.setBoot(true);
}
classBeanCache.set(bean.getClazz(), bean);
nameBeanCache.set(bean.getName(), bean);
}
@Override
public List<Bean> beanList() {
return beans;
}
}
@@ -0,0 +1,89 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.container;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.context.Environment;
import fun.asgc.neutrino.core.exception.ContainerInitException;
import fun.asgc.neutrino.core.util.ClassUtil;
import fun.asgc.neutrino.core.util.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class DefaultClassContainer implements ClassContainer {
private Environment environment;
private Set<Class<?>> classes;
private Set<Class<?>> componentClasses;
public DefaultClassContainer(Environment environment) {
this.environment = environment;
this.classes = Collections.synchronizedSet(new HashSet<>());
this.componentClasses = Collections.synchronizedSet(new HashSet<>());
this.init();
}
@Override
public void init() throws ContainerInitException {
try {
if (CollectionUtil.isEmpty(environment.getScanBasePackages())) {
return;
}
this.classes = ClassUtil.scan(environment.getScanBasePackages());
this.componentClasses = this.classes.stream().filter(item -> ClassUtil.isAnnotateWith(item, Component.class)).collect(Collectors.toSet());
log.info("class容器初始化完成");
} catch (Exception e) {
log.error("class容器初始化异常!");
throw new ContainerInitException(this);
}
}
@Override
public boolean hasClass(Class<?> clazz) {
return classes.contains(clazz);
}
@Override
public Set<Class<?>> getClasses() {
return classes;
}
@Override
public Set<Class<?>> getComponentClasses() {
return componentClasses;
}
@Override
public void destroy() {
log.info("class容器销毁");
}
}
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.container;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface LifeCycle {
/**
* 初始化
*/
void init();
/**
* 销毁
*/
void destroy();
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.context;
import fun.asgc.neutrino.core.annotation.Configuration;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Configuration(prefix = "application")
public class ApplicationConfig {
/**
* 应用名称
*/
private String name;
}
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.context;
import fun.asgc.neutrino.core.container.BeanContainer;
import fun.asgc.neutrino.core.util.StringUtil;
import lombok.Data;
import lombok.experimental.Accessors;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Accessors(chain = true)
@Data
public class ApplicationContext {
/**
* 应用环境
*/
private Environment environment;
/**
* 应用配置
*/
private ApplicationConfig applicationConfig;
/**
* bean容器
*/
private BeanContainer beanContainer;
}
@@ -0,0 +1,227 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.context;
import fun.asgc.neutrino.core.annotation.*;
import fun.asgc.neutrino.core.container.LifeCycle;
import fun.asgc.neutrino.core.container.BeanContainer;
import fun.asgc.neutrino.core.util.*;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.stream.Stream;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
@Accessors(chain = true)
@Data
public class Bean implements LifeCycle {
private String name;
private Class<?> clazz;
private Object instance;
private Component component;
private int order;
private boolean isBoot;
private volatile boolean isInit;
public boolean hasInstance() {
return null != instance;
}
@Override
public void init() {
if (isInit) {
return;
}
if (!hasInstance()) {
return;
}
isInit = true;
Method[] methods = ReflectUtil.getMethods(clazz);
if (null != methods && methods.length > 0) {
for (Method method : methods) {
if (method.isAnnotationPresent(Init.class) && method.getParameters().length == 0) {
try {
method.invoke(instance);
} catch (Exception e) {
log.error(String.format("初始化方法执行异常 class:%s method:%s", clazz.getName(), method.getName()), e);
}
}
}
}
}
@Override
public void destroy() {
if (!hasInstance()) {
return;
}
Method[] methods = ReflectUtil.getMethods(clazz);
if (null != methods && methods.length > 0) {
for (Method method : methods) {
if (method.isAnnotationPresent(Destroy.class) && method.getParameters().length == 0) {
try {
method.invoke(instance);
} catch (Exception e) {
log.error(String.format("销毁方法执行异常 class:%s method:%s", clazz.getName(), method.getName()), e);
}
}
}
}
}
public boolean newInstance(ApplicationContext context) {
boolean result = newInstance0();
if (result) {
inject(context);
init();
}
return result;
}
private boolean newInstance0() {
return LockUtil.doubleCheckProcess(() -> !hasInstance(),
this,
() -> {
try {
// 暂时先只支持yml配置
Configuration configuration = clazz.getAnnotation(Configuration.class);
if (null != configuration) {
instance = ConfigUtil.getYmlConfig(clazz);
} else {
// 由编码规避没有无参构造器的问题
instance = clazz.newInstance();
}
} catch (Exception e) {
// ignore
}
},
() -> hasInstance()
);
}
private void inject(ApplicationContext context) {
Method[] methods = ReflectUtil.getMethods(clazz);
if (null != methods && methods.length > 0) {
Stream.of(methods).forEach(method -> {
fun.asgc.neutrino.core.annotation.Bean bean = method.getAnnotation(fun.asgc.neutrino.core.annotation.Bean.class);
if (null == bean) {
return;
}
String name = bean.value();
if (StringUtil.isEmpty(name)) {
name = method.getName();
}
// TODO 方法带参数的情况、class重复的问题,暂时由编码规避
if (method.getParameters().length == 0) {
try {
Object obj = method.invoke(this.instance);
if (null == obj) {
log.error("bean 实例不能为空!");
return;
}
context.getBeanContainer().addBean(new Bean()
.setClazz(obj.getClass())
.setBoot(false)
.setComponent(null)
.setInstance(obj)
.setName(name)
.setOrder(Integer.MAX_VALUE)
);
} catch (Exception e) {
log.error(String.format("bean [%s] 实例化失败!", name), e);
}
}
});
}
Set<Field> fieldSet = ReflectUtil.getInheritChainDeclaredFieldSet(clazz);
if (CollectionUtil.notEmpty(fieldSet)) {
fieldSet.forEach(field -> {
Autowired autowired = field.getAnnotation(Autowired.class);
if (null == autowired) {
return;
}
if (field.getType() == Environment.class) {
ReflectUtil.setFieldValue(field, instance, context.getEnvironment());
} else if(field.getType() == ApplicationConfig.class) {
ReflectUtil.setFieldValue(field, instance, context.getApplicationConfig());
} else if(field.getType() == BeanContainer.class) {
ReflectUtil.setFieldValue(field, instance, context.getBeanContainer());
} else if (field.getType() == ApplicationContext.class) {
ReflectUtil.setFieldValue(field, instance, context);
} else {
Class<?> autowiredType = field.getType();
Bean autowiredBean = null;
if (StringUtil.isEmpty(autowired.value())) {
autowiredBean = context.getBeanContainer().getBean(autowiredType);
} else {
autowiredBean = context.getBeanContainer().getBean(autowired.value());
}
if (null == autowiredBean) {
throw new RuntimeException(String.format("类 %s 自动装配字段:%s 依赖bean不存在!", clazz.getName(), field.getName()));
}
if (autowiredBean.isDependOn(clazz)) {
throw new RuntimeException(String.format("类[%s]与类[%s]存在循环依赖!", clazz.getName(), field.getType().getName()));
}
autowiredBean.newInstance(context);
ReflectUtil.setFieldValue(field, instance, autowiredBean.getInstance());
}
});
}
}
public boolean isLazy() {
Lazy lazy = ClassUtil.getAnnotation(clazz, Lazy.class);
return null != lazy;
}
private boolean isDependOn(Class<?> target) {
if (null == target) {
return false;
}
Set<Field> fieldSet = ReflectUtil.getInheritChainDeclaredFieldSet(clazz);
if (CollectionUtil.isEmpty(fieldSet)) {
return false;
}
for (Field field : fieldSet) {
Autowired autowired = field.getAnnotation(Autowired.class);
if (null == autowired) {
continue;
}
if (field.getType() == target) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.context;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Accessors(chain = true)
@Data
public class Environment {
/**
* 启动类
*/
private Class<?> mainClass;
/**
* 启动参数
*/
private String[] mainArgs;
/**
* 包扫描路径
*/
private List<String> scanBasePackages;
/**
* 横幅
*/
private String banner;
/**
* 应用配置
*/
private ApplicationConfig config;
}
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.exception;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ConfigurationParserException extends InternalException {
public ConfigurationParserException(String message) {
super(message);
}
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.exception;
import fun.asgc.neutrino.core.container.Container;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ContainerInitException extends RuntimeException {
public ContainerInitException(Container container) {
super(String.format("容器:[%s] 初始化异常", container.getClass().getName()));
}
}
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.exception;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class InternalException extends RuntimeException {
public InternalException(String message) {
super(message);
}
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.launcher;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface Launcher {
/**
* 启动
*/
void launch();
}
@@ -0,0 +1,151 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.launcher;
import com.google.common.collect.Lists;
import fun.asgc.neutrino.core.annotation.NeutrinoApplication;
import fun.asgc.neutrino.core.constant.MetaDataConstant;
import fun.asgc.neutrino.core.container.ApplicationContainer;
import fun.asgc.neutrino.core.container.DefaultApplicationContainer;
import fun.asgc.neutrino.core.context.ApplicationConfig;
import fun.asgc.neutrino.core.context.Environment;
import fun.asgc.neutrino.core.util.*;
import lombok.extern.slf4j.Slf4j;
import java.lang.management.ManagementFactory;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class NeutrinoLauncher implements Launcher {
private Environment environment;
private ApplicationContainer applicationContainer;
private static volatile boolean running = true;
private static Object lock = new Object();
public static void run(final Class<?> clazz, final String[] args) {
Assert.notNull(clazz, "启动类不能为空!");
ThreadUtil.run(() -> {
new NeutrinoLauncher(clazz, args).launch();
});
}
public static void runSync(final Class<?> clazz, final String[] args) {
run(clazz, args);
synchronized (lock) {
while (running) {
try {
lock.wait();
} catch (Exception e) {
// ignore
}
}
}
log.info("Application already stop.");
}
public NeutrinoLauncher(Class<?> clazz, String[] args) {
this.environment = new Environment()
.setMainClass(clazz)
.setMainArgs(args);
}
@Override
public void launch() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
environmentInit();
this.applicationContainer = new DefaultApplicationContainer(environment);
SystemUtil.addShutdownHook(() -> {
synchronized (lock) {
this.applicationContainer.destroy();
running = false;
lock.notify();
}
});
stopWatch.stop();
printLog(environment, stopWatch);
}
/**
* 环境初始化
*/
private void environmentInit() {
environment.setScanBasePackages(Lists.newArrayList(TypeUtil.getPackageName(environment.getMainClass())));
bannerProcess(environment);
NeutrinoApplication neutrinoApplication = environment.getMainClass().getAnnotation(NeutrinoApplication.class);
if (null != neutrinoApplication) {
String[] scanBasePackages = neutrinoApplication.scanBasePackages();
if (ArrayUtil.notEmpty(scanBasePackages)) {
environment.setScanBasePackages(Lists.newArrayList(scanBasePackages));
}
}
log.info("scanBasePackages: {}", environment.getScanBasePackages());
// 加载应用配置
environment.setConfig(ConfigUtil.getYmlConfig(ApplicationConfig.class));
log.info("load ApplicationConfig finished.");
}
/**
* 打印启动日志
* @param environment
* @param stopWatch
*/
private void printLog(Environment environment, StopWatch stopWatch) {
StringBuffer sb = new StringBuffer();
sb.append("Started ");
sb.append(environment.getMainClass().getName());
sb.append(" in ");
sb.append(stopWatch.getTotalTimeSeconds());
try {
double uptime = (double) ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0D;
sb.append(" seconds (JVM running for " + uptime + ")");
} catch (Throwable var5) {
}
log.info(sb.toString());
}
/**
* banner处理
* @param environment
*/
private void bannerProcess(Environment environment) {
environment.setBanner(MetaDataConstant.DEFAULT_BANNER);
String banner = FileUtil.readContentAsString(MetaDataConstant.APP_BANNER_FILE_PATH);
if (StringUtil.notEmpty(banner)) {
environment.setBanner(banner);
}
System.out.println(environment.getBanner());
}
}
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.runner;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface ApplicationRunner {
void run(String[] args);
}
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ArrayUtil {
/**
* 判断数组是否为空
* @param arr
* @return
*/
public static boolean isEmpty(Object[] arr) {
return null == arr || arr.length == 0;
}
/**
* 判断数组是否非空
* @param arr
* @return
*/
public static boolean notEmpty(Object[] arr) {
return !isEmpty(arr);
}
}
@@ -0,0 +1,412 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import org.apache.commons.lang3.ObjectUtils;
import java.util.Collection;
import java.util.Map;
import java.util.function.Supplier;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public abstract class Assert {
/**
*
* @param expression
* @param message
*/
public static void state(boolean expression, String message) {
if (!expression) {
throw new IllegalStateException(message);
}
}
/**
*
* @param expression
* @param messageSupplier
*/
public static void state(boolean expression, Supplier<String> messageSupplier) {
if (!expression) {
throw new IllegalStateException(nullSafeGet(messageSupplier));
}
}
/**
*
* @param expression
* @param message
*/
public static void isTrue(boolean expression, String message) {
if (!expression) {
throw new IllegalArgumentException(message);
}
}
/**
*
* @param expression
* @param messageSupplier
*/
public static void isTrue(boolean expression, Supplier<String> messageSupplier) {
if (!expression) {
throw new IllegalArgumentException(nullSafeGet(messageSupplier));
}
}
/**
*
* @param object
* @param message
*/
public static void isNull(Object object, String message) {
if (object != null) {
throw new IllegalArgumentException(message);
}
}
/**
*
* @param object
* @param messageSupplier
*/
public static void isNull(Object object, Supplier<String> messageSupplier) {
if (object != null) {
throw new IllegalArgumentException(nullSafeGet(messageSupplier));
}
}
/**
*
* @param object
* @param message
*/
public static void notNull(Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
/**
*
* @param object
* @param messageSupplier
*/
public static void notNull(Object object, Supplier<String> messageSupplier) {
if (object == null) {
throw new IllegalArgumentException(nullSafeGet(messageSupplier));
}
}
/**
*
* @param text
* @param message
*/
public static void hasLength(String text, String message) {
if (!StringUtil.hasLength(text)) {
throw new IllegalArgumentException(message);
}
}
/**
*
* @param text
* @param messageSupplier
*/
public static void hasLength(String text, Supplier<String> messageSupplier) {
if (!StringUtil.hasLength(text)) {
throw new IllegalArgumentException(nullSafeGet(messageSupplier));
}
}
/**
*
* @param text
* @param message
*/
public static void hasText(String text, String message) {
if (!StringUtil.hasText(text)) {
throw new IllegalArgumentException(message);
}
}
/**
*
* @param text
* @param messageSupplier
*/
public static void hasText(String text, Supplier<String> messageSupplier) {
if (!StringUtil.hasText(text)) {
throw new IllegalArgumentException(nullSafeGet(messageSupplier));
}
}
/**
*
* @param textToSearch
* @param substring
* @param message
*/
public static void doesNotContain(String textToSearch, String substring, String message) {
if (StringUtil.hasLength(textToSearch) && StringUtil.hasLength(substring) &&
textToSearch.contains(substring)) {
throw new IllegalArgumentException(message);
}
}
/**
*
* @param textToSearch
* @param substring
* @param messageSupplier
*/
public static void doesNotContain(String textToSearch, String substring, Supplier<String> messageSupplier) {
if (StringUtil.hasLength(textToSearch) && StringUtil.hasLength(substring) &&
textToSearch.contains(substring)) {
throw new IllegalArgumentException(nullSafeGet(messageSupplier));
}
}
/**
*
* @param array
* @param message
*/
public static void notEmpty(Object[] array, String message) {
if (ObjectUtils.isEmpty(array)) {
throw new IllegalArgumentException(message);
}
}
/**
*
* @param array
* @param messageSupplier
*/
public static void notEmpty(Object[] array, Supplier<String> messageSupplier) {
if (ObjectUtils.isEmpty(array)) {
throw new IllegalArgumentException(nullSafeGet(messageSupplier));
}
}
/**
*
* @param array
* @param message
*/
public static void noNullElements(Object[] array, String message) {
if (array != null) {
for (Object element : array) {
if (element == null) {
throw new IllegalArgumentException(message);
}
}
}
}
/**
*
* @param array
* @param messageSupplier
*/
public static void noNullElements(Object[] array, Supplier<String> messageSupplier) {
if (array != null) {
for (Object element : array) {
if (element == null) {
throw new IllegalArgumentException(nullSafeGet(messageSupplier));
}
}
}
}
/**
*
* @param collection
* @param message
*/
public static void notEmpty(Collection<?> collection, String message) {
if (CollectionUtil.isEmpty(collection)) {
throw new IllegalArgumentException(message);
}
}
/**
*
* @param collection
* @param messageSupplier
*/
public static void notEmpty(Collection<?> collection, Supplier<String> messageSupplier) {
if (CollectionUtil.isEmpty(collection)) {
throw new IllegalArgumentException(nullSafeGet(messageSupplier));
}
}
/**
*
* @param map
* @param message
*/
public static void notEmpty(Map<?, ?> map, String message) {
if (CollectionUtil.isEmpty(map)) {
throw new IllegalArgumentException(message);
}
}
/**
*
* @param map
* @param messageSupplier
*/
public static void notEmpty(Map<?, ?> map, Supplier<String> messageSupplier) {
if (CollectionUtil.isEmpty(map)) {
throw new IllegalArgumentException(nullSafeGet(messageSupplier));
}
}
/**
*
* @param type
* @param obj
* @param message
*/
public static void isInstanceOf(Class<?> type, Object obj, String message) {
notNull(type, "Type to check against must not be null");
if (!type.isInstance(obj)) {
instanceCheckFailed(type, obj, message);
}
}
/**
*
* @param type
* @param obj
* @param messageSupplier
*/
public static void isInstanceOf(Class<?> type, Object obj, Supplier<String> messageSupplier) {
notNull(type, "Type to check against must not be null");
if (!type.isInstance(obj)) {
instanceCheckFailed(type, obj, nullSafeGet(messageSupplier));
}
}
/**
*
* @param type
* @param obj
*/
public static void isInstanceOf(Class<?> type, Object obj) {
isInstanceOf(type, obj, "");
}
/**
*
* @param superType
* @param subType
* @param message
*/
public static void isAssignable(Class<?> superType, Class<?> subType, String message) {
notNull(superType, "Super type to check against must not be null");
if (subType == null || !superType.isAssignableFrom(subType)) {
assignableCheckFailed(superType, subType, message);
}
}
/**
*
* @param superType
* @param subType
* @param messageSupplier
*/
public static void isAssignable(Class<?> superType, Class<?> subType, Supplier<String> messageSupplier) {
notNull(superType, "Super type to check against must not be null");
if (subType == null || !superType.isAssignableFrom(subType)) {
assignableCheckFailed(superType, subType, nullSafeGet(messageSupplier));
}
}
/**
*
* @param superType
* @param subType
*/
public static void isAssignable(Class<?> superType, Class<?> subType) {
isAssignable(superType, subType, "");
}
private static void instanceCheckFailed(Class<?> type, Object obj, String msg) {
String className = (obj != null ? obj.getClass().getName() : "null");
String result = "";
boolean defaultMessage = true;
if (StringUtil.hasLength(msg)) {
if (endsWithSeparator(msg)) {
result = msg + " ";
}
else {
result = messageWithTypeName(msg, className);
defaultMessage = false;
}
}
if (defaultMessage) {
result = result + ("Object of class [" + className + "] must be an instance of " + type);
}
throw new IllegalArgumentException(result);
}
private static void assignableCheckFailed(Class<?> superType, Class<?> subType, String msg) {
String result = "";
boolean defaultMessage = true;
if (StringUtil.hasLength(msg)) {
if (endsWithSeparator(msg)) {
result = msg + " ";
}
else {
result = messageWithTypeName(msg, subType);
defaultMessage = false;
}
}
if (defaultMessage) {
result = result + (subType + " is not assignable to " + superType);
}
throw new IllegalArgumentException(result);
}
private static boolean endsWithSeparator(String msg) {
return (msg.endsWith(":") || msg.endsWith(";") || msg.endsWith(",") || msg.endsWith("."));
}
private static String messageWithTypeName(String msg, Object typeName) {
return msg + (msg.endsWith(" ") ? "" : ": ") + typeName;
}
private static String nullSafeGet(Supplier<String> messageSupplier) {
return (messageSupplier != null ? messageSupplier.get() : null);
}
}
@@ -0,0 +1,123 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import fun.asgc.neutrino.core.container.BeanContainer;
import fun.asgc.neutrino.core.context.ApplicationConfig;
import fun.asgc.neutrino.core.context.ApplicationContext;
import fun.asgc.neutrino.core.context.Bean;
import fun.asgc.neutrino.core.context.Environment;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class BeanManager {
private static volatile ApplicationContext context;
public static synchronized void setContext(ApplicationContext ctx) {
Assert.notNull(ctx, "ctx不能为空!");
context = ctx;
log.info("BeanManager初始化完成");
}
public static Object getBean(String name) {
if (StringUtil.isEmpty(name)) {
return null;
}
if ("environment".equals(name)) {
return context.getEnvironment();
} else if("applicationConfig".equals(name)) {
return context.getApplicationConfig();
} else if("beanContainer".equals(name)) {
return context.getBeanContainer();
} else if ("applicationContext".equals(name)) {
return context;
} else {
if (null != context.getBeanContainer()) {
Bean bean = context.getBeanContainer().getBean(name);
if (null != bean) {
if (!bean.hasInstance()) {
bean.newInstance(context);
}
return bean.getInstance();
}
}
}
return null;
}
public static <T> T getBean(Class<T> clazz) {
if (null == context) {
return null;
}
if (clazz == Environment.class) {
return (T)context.getEnvironment();
} else if(clazz == ApplicationConfig.class) {
return (T)context.getApplicationConfig();
} else if(clazz == BeanContainer.class) {
return (T)context.getBeanContainer();
} else if (clazz == ApplicationContext.class) {
return (T)context;
} else {
if (null != context.getBeanContainer()) {
Bean bean = context.getBeanContainer().getBean(clazz);
if (null != bean) {
if (!bean.hasInstance()) {
bean.newInstance(context);
}
return (T)bean.getInstance();
}
}
}
return null;
}
/**
* 根据超类获取bean集合
* @param superClass
* @param <T>
* @return
*/
public static <T> List<T> getBeanListBySuperClass(Class<T> superClass) {
if (null == context || null == context.getBeanContainer()) {
return null;
}
return context.getBeanContainer().beanList().stream()
.map(Bean::getClazz)
.filter(item -> superClass.isAssignableFrom(item))
.map(item -> (T)getBean(item))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
@@ -0,0 +1,213 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.cache.Cache;
import fun.asgc.neutrino.core.cache.MemoryCache;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class ClassUtil {
private static Cache<String, Set<Class<?>>> classesCache = new MemoryCache<>();
private static Cache<String, Set<Class<?>>> componentsCache = new MemoryCache<>();
/**
* 类扫描
* @param packageName
* @return
*/
public static Set<Class<?>> scanComponents(String packageName) throws IOException, ClassNotFoundException {
if (StringUtil.isEmpty(packageName)) {
return null;
}
if (componentsCache.containsKey(packageName)) {
componentsCache.get(packageName);
}
Set<Class<?>> result = new HashSet<>();
Set<Class<?>> classes = scan(packageName);
for (Class<?> clazz : classes) {
if (isAnnotateWith(clazz, Component.class)) {
if (!(clazz.isAnnotation() || clazz.isEnum())) {
log.debug("scan component {}", clazz.getName());
result.add(clazz);
}
}
}
componentsCache.set(packageName, result);
return result;
}
/**
* 类扫描
* @param packages
* @return
*/
public static Set<Class<?>> scanComponents(Collection<String> packages) throws IOException, ClassNotFoundException {
if (CollectionUtil.isEmpty(packages)) {
return null;
}
Set<Class<?>> classes = new HashSet<>();
for (String packageName : packages) {
Set<Class<?>> tmp = scanComponents(packageName);
if (CollectionUtil.notEmpty(tmp)) {
classes.addAll(tmp);
}
}
return classes;
}
public static Set<Class<?>> scan(List<String> packageNames) throws IOException, ClassNotFoundException{
Set<Class<?>> result = new HashSet<>();
if (null != packageNames && packageNames.size() > 0) {
for (String packageName : packageNames) {
Set<Class<?>> tmp = scan(packageName);
if (null != tmp) {
result.addAll(tmp);
}
}
}
return result;
}
/**
* 类扫描
* @param packageName
* @return
*/
public static Set<Class<?>> scan(String packageName) throws IOException, ClassNotFoundException {
if (StringUtil.isEmpty(packageName)) {
return null;
}
if (classesCache.containsKey(packageName)) {
return classesCache.get(packageName);
}
log.info("开始进行类扫描,扫描包:{}", packageName);
Set<Class<?>> classes = new HashSet<>();
String packagePath = packageName.replace(".", "/");
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("");
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
log.info(url.toString());
String protocol = url.getProtocol();
if ("jar".equals(protocol)) {
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
JarFile jarFile = jarURLConnection.getJarFile();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String name = jarEntry.getName();
int index = name.indexOf(packagePath);
if (index != -1 && name.endsWith(".class")) {
String replace = name.substring(index, name.length() - 6).replace("/", ".");
Class clazz = urlClassLoader.loadClass(replace);
log.debug("scan class {}", clazz.getName());
classes.add(clazz);
}
}
} else if ("file".endsWith(protocol)) {
String path = url.getPath();
String targetPath = path + "/" + packagePath;
addClasses(targetPath, classes, packageName);
}
}
log.info("扫描完毕,包:{}下一共有:{}个类", packageName, classes.size());
classesCache.set(packageName, classes);
return classes;
}
private static void addClasses(String path, Set<Class<?>> classes, String packageName) throws ClassNotFoundException {
File[] files = new File(path).listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
}
});
if (ArrayUtil.isEmpty(files)) {
return;
}
for (File file : files) {
String fileName = file.getName();
if (file.isFile()) {
String className = fileName.substring(0, fileName.lastIndexOf("."));
String fullClassName = packageName + "." + className;
Class clazz = Thread.currentThread().getContextClassLoader().loadClass(fullClassName);
log.debug("scan class {}", clazz.getName());
classes.add(clazz);
} else {
String subPackagePath = path + "/" + fileName;
String subPackageName = packageName + "." + fileName;
addClasses(subPackagePath, classes, subPackageName);
}
}
}
public static boolean isAnnotateWith(Class<?> clazz, Class targetAnnotation) {
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
Class<? extends Annotation> aClass = annotation.annotationType();
if (aClass.equals(targetAnnotation)) {
return true;
}
if (aClass.isAnnotationPresent(targetAnnotation)) {
return true;
}
}
return false;
}
public static <T extends Annotation> T getAnnotation(Class<?> clazz, Class<T> annotationType) {
T annotation = clazz.getAnnotation(annotationType);
if (null == annotation) {
Annotation[] annotations = clazz.getAnnotations();
for (Annotation item : annotations) {
Class<? extends Annotation> aClass = item.annotationType();
if (aClass.isAnnotationPresent(annotationType)) {
return aClass.getAnnotation(annotationType);
}
}
}
return annotation;
}
}
@@ -0,0 +1,341 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import org.apache.commons.lang3.ObjectUtils;
import java.util.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class CollectionUtil {
/**
*
* @param collection
* @return
*/
public static boolean isEmpty(Collection<?> collection) {
return (collection == null || collection.isEmpty());
}
/**
*
* @param map
* @return
*/
public static boolean isEmpty(Map<?, ?> map) {
return (map == null || map.isEmpty());
}
/**
*
* @param collection
* @return
*/
public static boolean notEmpty(Collection<?> collection) {
return !isEmpty(collection);
}
/**
*
* @param source
* @return
*/
@SuppressWarnings("rawtypes")
public static List arrayToList(Object source) {
return Arrays.asList(ObjectUtil.toObjectArray(source));
}
/**
*
* @param array
* @param collection
* @param <E>
*/
@SuppressWarnings("unchecked")
public static <E> void mergeArrayIntoCollection(Object array, Collection<E> collection) {
Object[] arr = ObjectUtil.toObjectArray(array);
for (Object elem : arr) {
collection.add((E) elem);
}
}
/**
*
* @param props
* @param map
* @param <K>
* @param <V>
*/
@SuppressWarnings("unchecked")
public static <K, V> void mergePropertiesIntoMap(Properties props, Map<K, V> map) {
if (props != null) {
for (Enumeration<?> en = props.propertyNames(); en.hasMoreElements();) {
String key = (String) en.nextElement();
Object value = props.get(key);
if (value == null) {
// Allow for defaults fallback or potentially overridden accessor...
value = props.getProperty(key);
}
map.put((K) key, (V) value);
}
}
}
/**
*
* @param iterator
* @param element
* @return
*/
public static boolean contains(Iterator<?> iterator, Object element) {
if (iterator != null) {
while (iterator.hasNext()) {
Object candidate = iterator.next();
if (ObjectUtil.nullSafeEquals(candidate, element)) {
return true;
}
}
}
return false;
}
/**
*
* @param enumeration
* @param element
* @return
*/
public static boolean contains(Enumeration<?> enumeration, Object element) {
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
Object candidate = enumeration.nextElement();
if (ObjectUtil.nullSafeEquals(candidate, element)) {
return true;
}
}
}
return false;
}
/**
*
* @param collection
* @param element
* @return
*/
public static boolean containsInstance(Collection<?> collection, Object element) {
if (collection != null) {
for (Object candidate : collection) {
if (candidate == element) {
return true;
}
}
}
return false;
}
/**
*
* @param source
* @param candidates
* @return
*/
public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
if (isEmpty(source) || isEmpty(candidates)) {
return false;
}
for (Object candidate : candidates) {
if (source.contains(candidate)) {
return true;
}
}
return false;
}
/**
*
* @param source
* @param candidates
* @param <E>
* @return
*/
@SuppressWarnings("unchecked")
public static <E> E findFirstMatch(Collection<?> source, Collection<E> candidates) {
if (isEmpty(source) || isEmpty(candidates)) {
return null;
}
for (Object candidate : candidates) {
if (source.contains(candidate)) {
return (E) candidate;
}
}
return null;
}
/**
*
* @param collection
* @param type
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T findValueOfType(Collection<?> collection, Class<T> type) {
if (isEmpty(collection)) {
return null;
}
T value = null;
for (Object element : collection) {
if (type == null || type.isInstance(element)) {
if (value != null) {
// More than one value found... no clear single value.
return null;
}
value = (T) element;
}
}
return value;
}
/**
*
* @param collection
* @param types
* @return
*/
public static Object findValueOfType(Collection<?> collection, Class<?>[] types) {
if (isEmpty(collection) || ObjectUtils.isEmpty(types)) {
return null;
}
for (Class<?> type : types) {
Object value = findValueOfType(collection, type);
if (value != null) {
return value;
}
}
return null;
}
/**
*
* @param collection
* @return
*/
public static boolean hasUniqueObject(Collection<?> collection) {
if (isEmpty(collection)) {
return false;
}
boolean hasCandidate = false;
Object candidate = null;
for (Object elem : collection) {
if (!hasCandidate) {
hasCandidate = true;
candidate = elem;
}
else if (candidate != elem) {
return false;
}
}
return true;
}
/**
*
* @param collection
* @return
*/
public static Class<?> findCommonElementType(Collection<?> collection) {
if (isEmpty(collection)) {
return null;
}
Class<?> candidate = null;
for (Object val : collection) {
if (val != null) {
if (candidate == null) {
candidate = val.getClass();
}
else if (candidate != val.getClass()) {
return null;
}
}
}
return candidate;
}
/**
*
* @param set
* @param <T>
* @return
*/
public static <T> T lastElement(Set<T> set) {
if (isEmpty(set)) {
return null;
}
if (set instanceof SortedSet) {
return ((SortedSet<T>) set).last();
}
// Full iteration necessary...
Iterator<T> it = set.iterator();
T last = null;
while (it.hasNext()) {
last = it.next();
}
return last;
}
/**
*
* @param list
* @param <T>
* @return
*/
public static <T> T lastElement(List<T> list) {
if (isEmpty(list)) {
return null;
}
return list.get(list.size() - 1);
}
/**
*
* @param enumeration
* @param array
* @param <A>
* @param <E>
* @return
*/
public static <A, E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) {
ArrayList<A> elements = new ArrayList<>();
while (enumeration.hasMoreElements()) {
elements.add(enumeration.nextElement());
}
return elements.toArray(array);
}
}
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import fun.asgc.neutrino.core.config.ConfigurationParser;
import fun.asgc.neutrino.core.config.impl.YmlConfigurationParser;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ConfigUtil {
private static final ConfigurationParser ymlParser = new YmlConfigurationParser();
public static <T> T getYmlConfig(Class<T> clazz) {
return ymlParser.parse(clazz);
}
}
@@ -0,0 +1,150 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import fun.asgc.neutrino.core.constant.MetaDataConstant;
import fun.asgc.neutrino.core.exception.InternalException;
import org.apache.commons.io.FileDeleteStrategy;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.util.stream.Collectors;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class FileUtil {
/**
* 根据文件路径获取输入流
* @param path
* @return
*/
public static InputStream getInputStream(String path) throws FileNotFoundException {
if (StringUtils.isEmpty(path)) {
throw new NullPointerException("path 不能为空!");
}
if (path.startsWith(MetaDataConstant.CLASSPATH_RESOURCE_IDENTIFIER)) {
String subPath = path.substring(MetaDataConstant.CLASSPATH_RESOURCE_IDENTIFIER.length());
return FileUtil.class.getResourceAsStream(subPath);
}
return new FileInputStream(path);
}
/**
* 根据文件路径获取输出流
* @param path
* @return
*/
public static OutputStream getOutputStream(String path) throws FileNotFoundException {
if (StringUtils.isEmpty(path)) {
throw new NullPointerException("path 不能为空!");
}
if (path.startsWith(MetaDataConstant.CLASSPATH_RESOURCE_IDENTIFIER)) {
throw new InternalException("不支持路径以'" + MetaDataConstant.CLASSPATH_IDENTIFIER + "'开头");
}
return new FileOutputStream(path);
}
/**
* 读取文件内容
* @param path
* @return
* @throws IOException
*/
public static String readContentAsString(String path) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(getInputStream(path)))){
return br.lines().collect(Collectors.joining("\n"));
} catch (Exception e) {
return null;
}
}
/**
* 读取字节内容
* @param path
* @return
*/
public static byte[] readBytes(String path) {
try (InputStream in = getInputStream(path)){
byte[] bytes = new byte[1024];
int length = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((length = in.read(bytes)) != -1) {
baos.write(bytes, 0, length);
}
return baos.toByteArray();
} catch (Exception e) {
return null;
}
}
public static void write(String path, String content) {
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(getOutputStream(path)))){
bw.write(content);
} catch (IOException e) {
e.printStackTrace();
}
}
public static File getDirectory(String path) {
if (path.endsWith("/./")) {
path = path.substring(0, path.length() - 2);
}
File file = new File(path);
if (file.isDirectory() || path.endsWith("/")) {
return file;
}
return file.getParentFile();
}
public static void makeDirs(String path) {
try {
File file = getDirectory(path);
if (null != file && !file.exists()) {
file.mkdirs();
}
} catch (Exception e) {
// ignore
}
}
public static boolean deleteDirs(String path) {
File file = getDirectory(path);
try {
FileDeleteStrategy.FORCE.delete(file);
} catch (Exception e) {
file = getDirectory(file.getPath());
try {
FileDeleteStrategy.FORCE.delete(file);
} catch (IOException ex) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import fun.asgc.neutrino.core.base.CodeBlock;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class LockUtil {
/**
* 双重校验处理
* @param isLock
* @param lock
* @param lockProcess
*/
public static void doubleCheckProcess(BooleanSupplier isLock, Object lock, CodeBlock lockProcess) {
if (isLock.getAsBoolean()) {
synchronized (lock) {
if (isLock.getAsBoolean()) {
lockProcess.execute();
}
}
}
}
/**
* 双重校验处理
* @param isLock
* @param lock
* @param lockProcess
* @param nonLockProcess
*/
public static <T> T doubleCheckProcess(BooleanSupplier isLock, Object lock, CodeBlock lockProcess, Supplier<T> nonLockProcess) {
doubleCheckProcess(isLock, lock, lockProcess);
return nonLockProcess.get();
}
}
@@ -0,0 +1,103 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import com.google.common.collect.Lists;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class NumberUtil {
/**
* 大小单位
*/
private static final List<String> SIZE_UINT = Lists.newArrayList("B", "K", "M", "G", "T");
/**
* 单位容量
*/
private static final int UINT_CAPACITY = 1024;
/**
* 大小转为描述
* @param size
* @return
*/
public static String sizeToDescription(long size, int decimals) {
double num = size * 1.0;
int unitIndex = 0;
while (unitIndex < SIZE_UINT.size() - 1 && num >= UINT_CAPACITY) {
num /= UINT_CAPACITY;
unitIndex++;
}
return trimZero(String.format("%." + decimals + "f", num)) + SIZE_UINT.get(unitIndex);
}
/**
* 大小转为描述
* @param size
* @return
*/
public static String sizeToDescription(long size) {
return sizeToDescription(size, 2);
}
public static long descriptionToSize(String description, long defaultValue) {
try {
description = description.trim().replaceAll(" ", "").toUpperCase();
double num = Double.valueOf(description.substring(0, description.length() - 1));
String unit = description.substring(description.length() - 1);
int unitIndex = SIZE_UINT.indexOf(unit);
if (unitIndex > 0) {
while (unitIndex-- > 0) {
num *= UINT_CAPACITY;
}
}
return (long) num;
} catch (Exception e) {
// ignore
}
return defaultValue;
}
public static long descriptionToSize(String description) {
return descriptionToSize(description, 0);
}
private static String trimZero(String s) {
if (s.indexOf(".") > 0) {
// 去掉多余的0
s = s.replaceAll("0+?$", "");
// 如最后一位是.则去掉
s = s.replaceAll("[.]$", "");
}
return s;
}
}
@@ -0,0 +1,843 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ObjectUtil {
private static final int INITIAL_HASH = 7;
private static final int MULTIPLIER = 31;
private static final String EMPTY_STRING = "";
private static final String NULL_STRING = "null";
private static final String ARRAY_START = "{";
private static final String ARRAY_END = "}";
private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END;
private static final String ARRAY_ELEMENT_SEPARATOR = ", ";
/**
*
* @param ex
* @return
*/
public static boolean isCheckedException(Throwable ex) {
return !(ex instanceof RuntimeException || ex instanceof Error);
}
/**
*
* @param ex
* @param declaredExceptions
* @return
*/
public static boolean isCompatibleWithThrowsClause(Throwable ex, Class<?>... declaredExceptions) {
if (!isCheckedException(ex)) {
return true;
}
if (declaredExceptions != null) {
for (Class<?> declaredException : declaredExceptions) {
if (declaredException.isInstance(ex)) {
return true;
}
}
}
return false;
}
/**
*
* @param obj
* @return
*/
public static boolean isArray(Object obj) {
return (obj != null && obj.getClass().isArray());
}
/**
*
* @param array
* @return
*/
public static boolean isEmpty(Object[] array) {
return (array == null || array.length == 0);
}
/**
*
* @param obj
* @return
*/
@SuppressWarnings("rawtypes")
public static boolean isEmpty(Object obj) {
if (obj == null) {
return true;
}
if (obj instanceof Optional) {
return !((Optional) obj).isPresent();
}
if (obj instanceof CharSequence) {
return ((CharSequence) obj).length() == 0;
}
if (obj.getClass().isArray()) {
return Array.getLength(obj) == 0;
}
if (obj instanceof Collection) {
return ((Collection) obj).isEmpty();
}
if (obj instanceof Map) {
return ((Map) obj).isEmpty();
}
// else
return false;
}
/**
*
* @param obj
* @return
*/
public static Object unwrapOptional(Object obj) {
if (obj instanceof Optional) {
Optional<?> optional = (Optional<?>) obj;
if (!optional.isPresent()) {
return null;
}
Object result = optional.get();
Assert.isTrue(!(result instanceof Optional), "Multi-level Optional usage not supported");
return result;
}
return obj;
}
/**
*
* @param array
* @param element
* @return
*/
public static boolean containsElement(Object[] array, Object element) {
if (array == null) {
return false;
}
for (Object arrayEle : array) {
if (nullSafeEquals(arrayEle, element)) {
return true;
}
}
return false;
}
/**
*
* @param enumValues
* @param constant
* @return
*/
public static boolean containsConstant(Enum<?>[] enumValues, String constant) {
return containsConstant(enumValues, constant, false);
}
/**
*
* @param enumValues
* @param constant
* @param caseSensitive
* @return
*/
public static boolean containsConstant(Enum<?>[] enumValues, String constant, boolean caseSensitive) {
for (Enum<?> candidate : enumValues) {
if (caseSensitive ? candidate.toString().equals(constant) :
candidate.toString().equalsIgnoreCase(constant)) {
return true;
}
}
return false;
}
/**
*
* @param enumValues
* @param constant
* @param <E>
* @return
*/
public static <E extends Enum<?>> E caseInsensitiveValueOf(E[] enumValues, String constant) {
for (E candidate : enumValues) {
if (candidate.toString().equalsIgnoreCase(constant)) {
return candidate;
}
}
throw new IllegalArgumentException("Constant [" + constant + "] does not exist in enum type " +
enumValues.getClass().getComponentType().getName());
}
/**
*
* @param array
* @param obj
* @param <A>
* @param <O>
* @return
*/
public static <A, O extends A> A[] addObjectToArray(A[] array, O obj) {
Class<?> compType = Object.class;
if (array != null) {
compType = array.getClass().getComponentType();
}
else if (obj != null) {
compType = obj.getClass();
}
int newArrLength = (array != null ? array.length + 1 : 1);
@SuppressWarnings("unchecked")
A[] newArr = (A[]) Array.newInstance(compType, newArrLength);
if (array != null) {
System.arraycopy(array, 0, newArr, 0, array.length);
}
newArr[newArr.length - 1] = obj;
return newArr;
}
/**
*
* @param source
* @return
*/
public static Object[] toObjectArray(Object source) {
if (source instanceof Object[]) {
return (Object[]) source;
}
if (source == null) {
return new Object[0];
}
if (!source.getClass().isArray()) {
throw new IllegalArgumentException("Source is not an array: " + source);
}
int length = Array.getLength(source);
if (length == 0) {
return new Object[0];
}
Class<?> wrapperType = Array.get(source, 0).getClass();
Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);
for (int i = 0; i < length; i++) {
newArray[i] = Array.get(source, i);
}
return newArray;
}
//---------------------------------------------------------------------
// Convenience methods for content-based equality/hash-code handling
//---------------------------------------------------------------------
/**
*
* @param o1
* @param o2
* @return
*/
public static boolean nullSafeEquals(Object o1, Object o2) {
if (o1 == o2) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
if (o1.equals(o2)) {
return true;
}
if (o1.getClass().isArray() && o2.getClass().isArray()) {
return arrayEquals(o1, o2);
}
return false;
}
/**
*
* @param o1
* @param o2
* @return
*/
private static boolean arrayEquals(Object o1, Object o2) {
if (o1 instanceof Object[] && o2 instanceof Object[]) {
return Arrays.equals((Object[]) o1, (Object[]) o2);
}
if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
return Arrays.equals((boolean[]) o1, (boolean[]) o2);
}
if (o1 instanceof byte[] && o2 instanceof byte[]) {
return Arrays.equals((byte[]) o1, (byte[]) o2);
}
if (o1 instanceof char[] && o2 instanceof char[]) {
return Arrays.equals((char[]) o1, (char[]) o2);
}
if (o1 instanceof double[] && o2 instanceof double[]) {
return Arrays.equals((double[]) o1, (double[]) o2);
}
if (o1 instanceof float[] && o2 instanceof float[]) {
return Arrays.equals((float[]) o1, (float[]) o2);
}
if (o1 instanceof int[] && o2 instanceof int[]) {
return Arrays.equals((int[]) o1, (int[]) o2);
}
if (o1 instanceof long[] && o2 instanceof long[]) {
return Arrays.equals((long[]) o1, (long[]) o2);
}
if (o1 instanceof short[] && o2 instanceof short[]) {
return Arrays.equals((short[]) o1, (short[]) o2);
}
return false;
}
/**
*
* @param obj
* @return
*/
public static int nullSafeHashCode(Object obj) {
if (obj == null) {
return 0;
}
if (obj.getClass().isArray()) {
if (obj instanceof Object[]) {
return nullSafeHashCode((Object[]) obj);
}
if (obj instanceof boolean[]) {
return nullSafeHashCode((boolean[]) obj);
}
if (obj instanceof byte[]) {
return nullSafeHashCode((byte[]) obj);
}
if (obj instanceof char[]) {
return nullSafeHashCode((char[]) obj);
}
if (obj instanceof double[]) {
return nullSafeHashCode((double[]) obj);
}
if (obj instanceof float[]) {
return nullSafeHashCode((float[]) obj);
}
if (obj instanceof int[]) {
return nullSafeHashCode((int[]) obj);
}
if (obj instanceof long[]) {
return nullSafeHashCode((long[]) obj);
}
if (obj instanceof short[]) {
return nullSafeHashCode((short[]) obj);
}
}
return obj.hashCode();
}
/**
*
* @param array
* @return
*/
public static int nullSafeHashCode(Object[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
for (Object element : array) {
hash = MULTIPLIER * hash + nullSafeHashCode(element);
}
return hash;
}
/**
*
* @param array
* @return
*/
public static int nullSafeHashCode(boolean[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
for (boolean element : array) {
hash = MULTIPLIER * hash + Boolean.hashCode(element);
}
return hash;
}
/**
*
* @param array
* @return
*/
public static int nullSafeHashCode(byte[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
for (byte element : array) {
hash = MULTIPLIER * hash + element;
}
return hash;
}
/**
*
* @param array
* @return
*/
public static int nullSafeHashCode(char[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
for (char element : array) {
hash = MULTIPLIER * hash + element;
}
return hash;
}
/**
*
* @param array
* @return
*/
public static int nullSafeHashCode(double[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
for (double element : array) {
hash = MULTIPLIER * hash + Double.hashCode(element);
}
return hash;
}
/**
*
* @param array
* @return
*/
public static int nullSafeHashCode(float[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
for (float element : array) {
hash = MULTIPLIER * hash + Float.hashCode(element);
}
return hash;
}
/**
*
* @param array
* @return
*/
public static int nullSafeHashCode(int[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
for (int element : array) {
hash = MULTIPLIER * hash + element;
}
return hash;
}
/**
*
* @param array
* @return
*/
public static int nullSafeHashCode(long[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
for (long element : array) {
hash = MULTIPLIER * hash + Long.hashCode(element);
}
return hash;
}
/**
*
* @param array
* @return
*/
public static int nullSafeHashCode(short[] array) {
if (array == null) {
return 0;
}
int hash = INITIAL_HASH;
for (short element : array) {
hash = MULTIPLIER * hash + element;
}
return hash;
}
//---------------------------------------------------------------------
// Convenience methods for toString output
//---------------------------------------------------------------------
/**
*
* @param obj
* @return
*/
public static String identityToString(Object obj) {
if (obj == null) {
return EMPTY_STRING;
}
return obj.getClass().getName() + "@" + getIdentityHexString(obj);
}
/**
*
* @param obj
* @return
*/
public static String getIdentityHexString(Object obj) {
return Integer.toHexString(System.identityHashCode(obj));
}
/**
*
* @param obj
* @return
*/
public static String getDisplayString(Object obj) {
if (obj == null) {
return EMPTY_STRING;
}
return nullSafeToString(obj);
}
/**
*
* @param obj
* @return
*/
public static String nullSafeClassName(Object obj) {
return (obj != null ? obj.getClass().getName() : NULL_STRING);
}
/**
*
* @param obj
* @return
*/
public static String nullSafeToString(Object obj) {
if (obj == null) {
return NULL_STRING;
}
if (obj instanceof String) {
return (String) obj;
}
if (obj instanceof Object[]) {
return nullSafeToString((Object[]) obj);
}
if (obj instanceof boolean[]) {
return nullSafeToString((boolean[]) obj);
}
if (obj instanceof byte[]) {
return nullSafeToString((byte[]) obj);
}
if (obj instanceof char[]) {
return nullSafeToString((char[]) obj);
}
if (obj instanceof double[]) {
return nullSafeToString((double[]) obj);
}
if (obj instanceof float[]) {
return nullSafeToString((float[]) obj);
}
if (obj instanceof int[]) {
return nullSafeToString((int[]) obj);
}
if (obj instanceof long[]) {
return nullSafeToString((long[]) obj);
}
if (obj instanceof short[]) {
return nullSafeToString((short[]) obj);
}
String str = obj.toString();
return (str != null ? str : EMPTY_STRING);
}
/**
*
* @param array
* @return
*/
public static String nullSafeToString(Object[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(String.valueOf(array[i]));
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
*
* @param array
* @return
*/
public static String nullSafeToString(boolean[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
*
* @param array
* @return
*/
public static String nullSafeToString(byte[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
*
* @param array
* @return
*/
public static String nullSafeToString(char[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append("'").append(array[i]).append("'");
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
*
* @param array
* @return
*/
public static String nullSafeToString(double[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
*
* @param array
* @return
*/
public static String nullSafeToString(float[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
*
* @param array
* @return
*/
public static String nullSafeToString(int[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
*
* @param array
* @return
*/
public static String nullSafeToString(long[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
/**
*
* @param array
* @return
*/
public static String nullSafeToString(short[] array) {
if (array == null) {
return NULL_STRING;
}
int length = array.length;
if (length == 0) {
return EMPTY_ARRAY;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0) {
sb.append(ARRAY_START);
}
else {
sb.append(ARRAY_ELEMENT_SEPARATOR);
}
sb.append(array[i]);
}
sb.append(ARRAY_END);
return sb.toString();
}
}
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import lombok.Data;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Data
public class Pair<K,V> {
/**
* 第一个值
*/
private K first;
/**
* 第二个值
*/
private V second;
public static <K,V> Pair<K,V> of(K first, V second) {
Pair<K,V> pair = new Pair<>();
pair.setFirst(first);
pair.setSecond(second);
return pair;
}
}
@@ -0,0 +1,312 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import com.google.common.collect.Sets;
import fun.asgc.neutrino.core.cache.Cache;
import fun.asgc.neutrino.core.cache.MemoryCache;
import fun.asgc.neutrino.core.constant.TypeNotMatchingValueConstant;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ReflectUtil {
private static Cache<Class<?>, Field[]> fieldsCache = new MemoryCache<>();
private static Cache<Class<?>, Field[]> declaredFieldsCache = new MemoryCache<>();
private static Cache<Class<?>, Set<Field>> inheritChainDeclaredFieldSetCache = new MemoryCache<>();
private static Cache<Class<?>, Method[]> methodsCache = new MemoryCache<>();
private static Cache<Class<?>, Method[]> declaredMethodsCache = new MemoryCache<>();
private static Cache<Field,Method> getMethodCache = new MemoryCache<>();
private static Cache<Field,Method> setMethodCache = new MemoryCache<>();
/**
* 获取字段列表
* @param clazz
* @return
*/
public static Field[] getFields(Class<?> clazz) {
return kvProcess(fieldsCache, clazz, c -> c.getFields());
}
/**
* 获取字段列表
* @param clazz
* @return
*/
public static Field[] getDeclaredFields(Class<?> clazz) {
return kvProcess(declaredFieldsCache, clazz, c -> c.getDeclaredFields());
}
/**
* 获取继承链字段列表
* @param clazz
* @return
*/
public static Set<Field> getInheritChainDeclaredFieldSet(Class<?> clazz) {
return getInheritChainDeclaredFieldSet(clazz, Sets.newHashSet(Object.class));
}
/**
* 获取继承链字段列表
* @param clazz
* @return
*/
public static Set<Field> getInheritChainDeclaredFieldSet(Class<?> clazz, Set<Class<?>> ignoreClasses) {
return kvProcess(inheritChainDeclaredFieldSetCache,
clazz,
(Class<?> c) -> {
Set<String> nameSet = new HashSet<>();
Set<Field> set = new HashSet<>();
Set<Class<?>> ignores = null == ignoreClasses ? new HashSet<>() : ignoreClasses;
while (null != c && !ignores.contains(c)) {
Field[] fields = getDeclaredFields(c);
if (null != fields && fields.length > 0) {
for (Field field : fields) {
if (field.getName().equals("this$0") || nameSet.contains(field.getName())) {
continue;
}
nameSet.add(field.getName());
set.add(field);
}
}
c = c.getSuperclass();
}
return set;
}
);
}
/**
* 获取方法列表
* @param clazz
* @return
*/
public static Method[] getMethods(Class<?> clazz) {
return kvProcess(methodsCache, clazz, c -> c.getMethods());
}
/**
* 获取方法列表
* @param clazz
* @return
*/
public static Method[] getDeclaredMethods(Class<?> clazz) {
return kvProcess(declaredMethodsCache, clazz, c -> c.getDeclaredMethods());
}
/**
* kv处理逻辑封装
* @param cache
* @param k
* @param fn
* @param <K>
* @param <V>
* @return
*/
private static <K,V> V kvProcess(Cache<K,V> cache, K k, Function<K,V>fn) {
return LockUtil.doubleCheckProcess(
() -> !cache.containsKey(k),
k,
() -> cache.set(k, fn.apply(k)),
() -> cache.get(k)
);
}
/**
* 获取Get方法名称
* @param field
* @return
*/
private static String getGetMethodName(Field field) {
String fieldName = field.getName();
String methodName = fieldName.substring(0, 1).toUpperCase().concat(fieldName.substring(1));
return TypeUtil.isBoolean(field) ? "is" + methodName : "get" + methodName;
}
/**
* 获取Set方法名称
* @param field
* @return
*/
private static String getSetMethodName(Field field) {
String fieldName = field.getName();
return "set" + fieldName.substring(0, 1).toUpperCase().concat(fieldName.substring(1));
}
/**
* 获取Get方法
* @param field
* @return
*/
public static Method getGetMethod(Field field) {
return kvProcess(getMethodCache, field, f -> {
String getMethodName = getGetMethodName(field);
return Stream.of(getMethods(field.getDeclaringClass()))
.filter(method -> method.getName().equals(getMethodName) && method.getParameters().length == 0)
.findFirst().get();
});
}
/**
* 获取valueof方法
* @param clazz
* @param params
* @return
*/
public static Method getValueOfMethod(Class<?> clazz, Class<?> ...params) {
try {
return clazz.getMethod("valueOf", params);
} catch (NoSuchMethodException e) {
// ignore
}
return null;
}
/**
* 获取set方法
* @param field
* @return
*/
public static Method getSetMethod(Field field) {
return kvProcess(setMethodCache, field, f -> {
String setMethodName = getSetMethodName(field);
Optional<Method> methodOptional = Stream.of(getMethods(field.getDeclaringClass()))
.filter(method -> method.getName().equals(setMethodName) && method.getParameters().length == 1 && method.getParameterTypes()[0] == field.getType())
.findFirst();
if (methodOptional.isPresent()) {
return methodOptional.get();
}
int level = TypeNotMatchingValueConstant.MAX;
Method method = null;
for (Method m : getMethods(field.getDeclaringClass())) {
if (m.getName().equals(setMethodName) && m.getParameters().length == 1) {
int curLevel = TypeUtil.typeMatch(m.getParameterTypes()[0], field.getType()).getNotMatchingValue();
if (curLevel < level) {
level = curLevel;
method = m;
}
}
}
if (level < TypeNotMatchingValueConstant.MAX && method != null) {
return method;
}
return null;
});
}
/**
* 通过方法设置字段的值
* @param field
* @param obj
* @param value
*/
public static boolean setFieldValueByMethod(Field field, Object obj, Object value) {
Assert.notNull(field, "field不能为空");
Assert.notNull(obj, "对象不能为空");
Assert.notNull(value, "值不能为空");
try {
Method setMethod = getSetMethod(field);
if (null == setMethod) {
return false;
}
if (null != value) {
value = TypeUtil.conversion(value, field.getType());
if (null == value) {
return false;
}
}
setMethod.invoke(obj, value);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 设置字段的值
* 先尝试通过set方法设置,若设置失败则通过字段直接设置
* @param field
* @param obj
* @param value
* @return
*/
public static boolean setFieldValue(Field field, Object obj, Object value) {
Assert.notNull(field, "field不能为空");
Assert.notNull(obj, "对象不能为空");
try {
return setFieldValueByMethod(field, obj , value) || setFieldValueByField(field, obj, value);
} catch (Exception e) {
return false;
}
}
/**
* 通过字段设置值
* @param field
* @param obj
* @param value
* @return
*/
public static boolean setFieldValueByField(Field field, Object obj, Object value) {
Assert.notNull(field, "field不能为空!");
Assert.notNull(obj, "对象不能为空!");
try {
Class<?> fieldType = field.getType();
if (!field.isAccessible()) {
field.setAccessible(true);
}
if (null == value) {
if (TypeUtil.isStrictBasicType(fieldType)) {
return false;
}
field.set(obj, null);
return true;
}
value = TypeUtil.conversion(value, fieldType);
if (null != value) {
field.set(obj, value);
return true;
}
} catch (Exception e) {
// ignore
}
return false;
}
}
@@ -0,0 +1,316 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class StopWatch {
/**
* Identifier of this stop watch.
* Handy when we have output from multiple stop watches
* and need to distinguish between them in log or console output.
*/
private final String id;
private boolean keepTaskList = true;
private final List<TaskInfo> taskList = new LinkedList<>();
/** Start time of the current task */
private long startTimeMillis;
/** Name of the current task */
private String currentTaskName;
private TaskInfo lastTaskInfo;
private int taskCount;
/** Total running time */
private long totalTimeMillis;
/**
* Construct a new stop watch. Does not start any task.
*/
public StopWatch() {
this("");
}
/**
* Construct a new stop watch with the given id.
* Does not start any task.
* @param id identifier for this stop watch.
* Handy when we have output from multiple stop watches
* and need to distinguish between them.
*/
public StopWatch(String id) {
this.id = id;
}
/**
* Return the id of this stop watch, as specified on construction.
* @return the id (empty String by default)
* @since 4.2.2
* @see #StopWatch(String)
*/
public String getId() {
return this.id;
}
/**
* Determine whether the TaskInfo array is built over time. Set this to
* "false" when using a StopWatch for millions of intervals, or the task
* info structure will consume excessive memory. Default is "true".
*/
public void setKeepTaskList(boolean keepTaskList) {
this.keepTaskList = keepTaskList;
}
/**
* Start an unnamed task. The results are undefined if {@link #stop()}
* or timing methods are called without invoking this method.
* @see #stop()
*/
public void start() throws IllegalStateException {
start("");
}
/**
* Start a named task. The results are undefined if {@link #stop()}
* or timing methods are called without invoking this method.
* @param taskName the name of the task to start
* @see #stop()
*/
public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) {
throw new IllegalStateException("Can't start StopWatch: it's already running");
}
this.currentTaskName = taskName;
this.startTimeMillis = System.currentTimeMillis();
}
/**
* Stop the current task. The results are undefined if timing
* methods are called without invoking at least one pair
* {@code start()} / {@code stop()} methods.
* @see #start()
*/
public void stop() throws IllegalStateException {
if (this.currentTaskName == null) {
throw new IllegalStateException("Can't stop StopWatch: it's not running");
}
long lastTime = System.currentTimeMillis() - this.startTimeMillis;
this.totalTimeMillis += lastTime;
this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
if (this.keepTaskList) {
this.taskList.add(this.lastTaskInfo);
}
++this.taskCount;
this.currentTaskName = null;
}
/**
* Return whether the stop watch is currently running.
* @see #currentTaskName()
*/
public boolean isRunning() {
return (this.currentTaskName != null);
}
/**
* Return the name of the currently running task, if any.
* @since 4.2.2
* @see #isRunning()
*/
public String currentTaskName() {
return this.currentTaskName;
}
/**
* Return the time taken by the last task.
*/
public long getLastTaskTimeMillis() throws IllegalStateException {
if (this.lastTaskInfo == null) {
throw new IllegalStateException("No tasks run: can't get last task interval");
}
return this.lastTaskInfo.getTimeMillis();
}
/**
* Return the name of the last task.
*/
public String getLastTaskName() throws IllegalStateException {
if (this.lastTaskInfo == null) {
throw new IllegalStateException("No tasks run: can't get last task name");
}
return this.lastTaskInfo.getTaskName();
}
/**
* Return the last task as a TaskInfo object.
*/
public TaskInfo getLastTaskInfo() throws IllegalStateException {
if (this.lastTaskInfo == null) {
throw new IllegalStateException("No tasks run: can't get last task info");
}
return this.lastTaskInfo;
}
/**
* Return the total time in milliseconds for all tasks.
*/
public long getTotalTimeMillis() {
return this.totalTimeMillis;
}
/**
* Return the total time in seconds for all tasks.
*/
public double getTotalTimeSeconds() {
return this.totalTimeMillis / 1000.0;
}
/**
* Return the number of tasks timed.
*/
public int getTaskCount() {
return this.taskCount;
}
/**
* Return an array of the data for tasks performed.
*/
public TaskInfo[] getTaskInfo() {
if (!this.keepTaskList) {
throw new UnsupportedOperationException("Task info is not being kept!");
}
return this.taskList.toArray(new TaskInfo[0]);
}
/**
* Return a short description of the total running time.
*/
public String shortSummary() {
return "StopWatch '" + getId() + "': running time (millis) = " + getTotalTimeMillis();
}
/**
* Return a string with a table describing all tasks performed.
* For custom reporting, call getTaskInfo() and use the task info directly.
*/
public String prettyPrint() {
StringBuilder sb = new StringBuilder(shortSummary());
sb.append('\n');
if (!this.keepTaskList) {
sb.append("No task info kept");
}
else {
sb.append("-----------------------------------------\n");
sb.append("ms % Task name\n");
sb.append("-----------------------------------------\n");
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumIntegerDigits(5);
nf.setGroupingUsed(false);
NumberFormat pf = NumberFormat.getPercentInstance();
pf.setMinimumIntegerDigits(3);
pf.setGroupingUsed(false);
for (TaskInfo task : getTaskInfo()) {
sb.append(nf.format(task.getTimeMillis())).append(" ");
sb.append(pf.format(task.getTimeSeconds() / getTotalTimeSeconds())).append(" ");
sb.append(task.getTaskName()).append("\n");
}
}
return sb.toString();
}
/**
* Return an informative string describing all tasks performed
* For custom reporting, call {@code getTaskInfo()} and use the task info directly.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder(shortSummary());
if (this.keepTaskList) {
for (TaskInfo task : getTaskInfo()) {
sb.append("; [").append(task.getTaskName()).append("] took ").append(task.getTimeMillis());
long percent = Math.round((100.0 * task.getTimeSeconds()) / getTotalTimeSeconds());
sb.append(" = ").append(percent).append("%");
}
}
else {
sb.append("; no task info kept");
}
return sb.toString();
}
/**
* Inner class to hold data about one task executed within the stop watch.
*/
public static final class TaskInfo {
private final String taskName;
private final long timeMillis;
TaskInfo(String taskName, long timeMillis) {
this.taskName = taskName;
this.timeMillis = timeMillis;
}
/**
* Return the name of this task.
*/
public String getTaskName() {
return this.taskName;
}
/**
* Return the time in milliseconds this task took.
*/
public long getTimeMillis() {
return this.timeMillis;
}
/**
* Return the time in seconds this task took.
*/
public double getTimeSeconds() {
return (this.timeMillis / 1000.0);
}
}
}
@@ -0,0 +1,1094 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import org.apache.commons.lang3.ObjectUtils;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.util.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class StringUtil {
private static final String FOLDER_SEPARATOR = "/";
private static final String WINDOWS_FOLDER_SEPARATOR = "\\";
private static final String TOP_PATH = "..";
private static final String CURRENT_PATH = ".";
private static final char EXTENSION_SEPARATOR = '.';
//---------------------------------------------------------------------
// General convenience methods for working with Strings
//---------------------------------------------------------------------
/**
*
* @param str
* @return
*/
public static boolean isEmpty(String str) {
return (str == null || "".equals(str));
}
/**
*
* @param str
* @return
*/
public static boolean notEmpty(String str) {
return !isEmpty(str);
}
/**
*
* @param str
* @return
*/
public static boolean hasLength(CharSequence str) {
return (str != null && str.length() > 0);
}
/**
*
* @param str
* @return
*/
public static boolean hasLength(String str) {
return (str != null && !str.isEmpty());
}
/**
*
* @param str
* @return
*/
public static boolean hasText(CharSequence str) {
return (str != null && str.length() > 0 && containsText(str));
}
/**
*
* @param str
* @return
*/
public static boolean hasText(String str) {
return (str != null && !str.isEmpty() && containsText(str));
}
private static boolean containsText(CharSequence str) {
int strLen = str.length();
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return true;
}
}
return false;
}
/**
*
* @param str
* @return
*/
public static boolean containsWhitespace(CharSequence str) {
if (!hasLength(str)) {
return false;
}
int strLen = str.length();
for (int i = 0; i < strLen; i++) {
if (Character.isWhitespace(str.charAt(i))) {
return true;
}
}
return false;
}
/**
*
* @param str
* @return
*/
public static boolean containsWhitespace(String str) {
return containsWhitespace((CharSequence) str);
}
/**
*
* @param str
* @return
*/
public static String trimWhitespace(String str) {
if (!hasLength(str)) {
return str;
}
int beginIndex = 0;
int endIndex = str.length() - 1;
while (beginIndex <= endIndex && Character.isWhitespace(str.charAt(beginIndex))) {
beginIndex++;
}
while (endIndex > beginIndex && Character.isWhitespace(str.charAt(endIndex))) {
endIndex--;
}
return str.substring(beginIndex, endIndex + 1);
}
/**
*
* @param str
* @return
*/
public static String trimAllWhitespace(String str) {
if (!hasLength(str)) {
return str;
}
int len = str.length();
StringBuilder sb = new StringBuilder(str.length());
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
if (!Character.isWhitespace(c)) {
sb.append(c);
}
}
return sb.toString();
}
/**
*
* @param str
* @return
*/
public static String trimLeadingWhitespace(String str) {
if (!hasLength(str)) {
return str;
}
StringBuilder sb = new StringBuilder(str);
while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) {
sb.deleteCharAt(0);
}
return sb.toString();
}
/**
*
* @param str
* @return
*/
public static String trimTrailingWhitespace(String str) {
if (!hasLength(str)) {
return str;
}
StringBuilder sb = new StringBuilder(str);
while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
/**
*
* @param str
* @param leadingCharacter
* @return
*/
public static String trimLeadingCharacter(String str, char leadingCharacter) {
if (!hasLength(str)) {
return str;
}
StringBuilder sb = new StringBuilder(str);
while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) {
sb.deleteCharAt(0);
}
return sb.toString();
}
/**
*
* @param str
* @param trailingCharacter
* @return
*/
public static String trimTrailingCharacter(String str, char trailingCharacter) {
if (!hasLength(str)) {
return str;
}
StringBuilder sb = new StringBuilder(str);
while (sb.length() > 0 && sb.charAt(sb.length() - 1) == trailingCharacter) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
/**
*
* @param str
* @param prefix
* @return
*/
public static boolean startsWithIgnoreCase(String str, String prefix) {
return (str != null && prefix != null && str.length() >= prefix.length() &&
str.regionMatches(true, 0, prefix, 0, prefix.length()));
}
/**
*
* @param str
* @param suffix
* @return
*/
public static boolean endsWithIgnoreCase(String str, String suffix) {
return (str != null && suffix != null && str.length() >= suffix.length() &&
str.regionMatches(true, str.length() - suffix.length(), suffix, 0, suffix.length()));
}
/**
*
* @param str
* @param index
* @param substring
* @return
*/
public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
if (index + substring.length() > str.length()) {
return false;
}
for (int i = 0; i < substring.length(); i++) {
if (str.charAt(index + i) != substring.charAt(i)) {
return false;
}
}
return true;
}
/**
*
* @param str
* @param sub
* @return
*/
public static int countOccurrencesOf(String str, String sub) {
if (!hasLength(str) || !hasLength(sub)) {
return 0;
}
int count = 0;
int pos = 0;
int idx;
while ((idx = str.indexOf(sub, pos)) != -1) {
++count;
pos = idx + sub.length();
}
return count;
}
/**
*
* @param inString
* @param oldPattern
* @param newPattern
* @return
*/
public static String replace(String inString, String oldPattern, String newPattern) {
if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) {
return inString;
}
int index = inString.indexOf(oldPattern);
if (index == -1) {
// no occurrence -> can return input as-is
return inString;
}
int capacity = inString.length();
if (newPattern.length() > oldPattern.length()) {
capacity += 16;
}
StringBuilder sb = new StringBuilder(capacity);
int pos = 0; // our position in the old string
int patLen = oldPattern.length();
while (index >= 0) {
sb.append(inString, pos, index);
sb.append(newPattern);
pos = index + patLen;
index = inString.indexOf(oldPattern, pos);
}
// append any characters to the right of a match
sb.append(inString, pos, inString.length());
return sb.toString();
}
/**
*
* @param inString
* @param pattern
* @return
*/
public static String delete(String inString, String pattern) {
return replace(inString, pattern, "");
}
/**
*
* @param inString
* @param charsToDelete
* @return
*/
public static String deleteAny(String inString, String charsToDelete) {
if (!hasLength(inString) || !hasLength(charsToDelete)) {
return inString;
}
StringBuilder sb = new StringBuilder(inString.length());
for (int i = 0; i < inString.length(); i++) {
char c = inString.charAt(i);
if (charsToDelete.indexOf(c) == -1) {
sb.append(c);
}
}
return sb.toString();
}
//---------------------------------------------------------------------
// Convenience methods for working with formatted Strings
//---------------------------------------------------------------------
/**
*
* @param str
* @return
*/
public static String quote(String str) {
return (str != null ? "'" + str + "'" : null);
}
/**
*
* @param obj
* @return
*/
public static Object quoteIfString(Object obj) {
return (obj instanceof String ? quote((String) obj) : obj);
}
/**
*
* @param qualifiedName
* @return
*/
public static String unqualify(String qualifiedName) {
return unqualify(qualifiedName, '.');
}
/**
*
* @param qualifiedName
* @param separator
* @return
*/
public static String unqualify(String qualifiedName, char separator) {
return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1);
}
/**
*
* @param str
* @return
*/
public static String capitalize(String str) {
return changeFirstCharacterCase(str, true);
}
/**
*
* @param str
* @return
*/
public static String uncapitalize(String str) {
return changeFirstCharacterCase(str, false);
}
private static String changeFirstCharacterCase(String str, boolean capitalize) {
if (!hasLength(str)) {
return str;
}
char baseChar = str.charAt(0);
char updatedChar;
if (capitalize) {
updatedChar = Character.toUpperCase(baseChar);
}
else {
updatedChar = Character.toLowerCase(baseChar);
}
if (baseChar == updatedChar) {
return str;
}
char[] chars = str.toCharArray();
chars[0] = updatedChar;
return new String(chars, 0, chars.length);
}
/**
*
* @param path
* @return
*/
public static String getFilename(String path) {
if (path == null) {
return null;
}
int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path);
}
/**
*
* @param path
* @return
*/
public static String getFilenameExtension(String path) {
if (path == null) {
return null;
}
int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
if (extIndex == -1) {
return null;
}
int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
if (folderIndex > extIndex) {
return null;
}
return path.substring(extIndex + 1);
}
/**
*
* @param path
* @return
*/
public static String stripFilenameExtension(String path) {
int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
if (extIndex == -1) {
return path;
}
int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
if (folderIndex > extIndex) {
return path;
}
return path.substring(0, extIndex);
}
/**
*
* @param path
* @param relativePath
* @return
*/
public static String applyRelativePath(String path, String relativePath) {
int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
if (separatorIndex != -1) {
String newPath = path.substring(0, separatorIndex);
if (!relativePath.startsWith(FOLDER_SEPARATOR)) {
newPath += FOLDER_SEPARATOR;
}
return newPath + relativePath;
}
else {
return relativePath;
}
}
/**
*
* @param path
* @return
*/
public static String cleanPath(String path) {
if (!hasLength(path)) {
return path;
}
String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
// Strip prefix from path to analyze, to not treat it as part of the
// first path element. This is necessary to correctly parse paths like
// "file:core/../core/io/Resource.class", where the ".." should just
// strip the first "core" directory while keeping the "file:" prefix.
int prefixIndex = pathToUse.indexOf(':');
String prefix = "";
if (prefixIndex != -1) {
prefix = pathToUse.substring(0, prefixIndex + 1);
if (prefix.contains(FOLDER_SEPARATOR)) {
prefix = "";
}
else {
pathToUse = pathToUse.substring(prefixIndex + 1);
}
}
if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
prefix = prefix + FOLDER_SEPARATOR;
pathToUse = pathToUse.substring(1);
}
String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
LinkedList<String> pathElements = new LinkedList<>();
int tops = 0;
for (int i = pathArray.length - 1; i >= 0; i--) {
String element = pathArray[i];
if (CURRENT_PATH.equals(element)) {
// Points to current directory - drop it.
}
else if (TOP_PATH.equals(element)) {
// Registering top path found.
tops++;
}
else {
if (tops > 0) {
// Merging path element with element corresponding to top path.
tops--;
}
else {
// Normal path element found.
pathElements.addFirst(element);
}
}
}
// Remaining top paths need to be retained.
for (int i = 0; i < tops; i++) {
pathElements.addFirst(TOP_PATH);
}
// If nothing else left, at least explicitly point to current path.
if (pathElements.size() == 1 && pathElements.getLast().isEmpty() && !prefix.endsWith(FOLDER_SEPARATOR)) {
pathElements.addFirst(CURRENT_PATH);
}
return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);
}
/**
*
* @param path1
* @param path2
* @return
*/
public static boolean pathEquals(String path1, String path2) {
return cleanPath(path1).equals(cleanPath(path2));
}
/**
*
* @param source
* @param charset
* @return
*/
public static String uriDecode(String source, Charset charset) {
int length = source.length();
if (length == 0) {
return source;
}
Assert.notNull(charset, "Charset must not be null");
ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
boolean changed = false;
for (int i = 0; i < length; i++) {
int ch = source.charAt(i);
if (ch == '%') {
if (i + 2 < length) {
char hex1 = source.charAt(i + 1);
char hex2 = source.charAt(i + 2);
int u = Character.digit(hex1, 16);
int l = Character.digit(hex2, 16);
if (u == -1 || l == -1) {
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
}
baos.write((char) ((u << 4) + l));
i += 2;
changed = true;
}
else {
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
}
}
else {
baos.write(ch);
}
}
return (changed ? new String(baos.toByteArray(), charset) : source);
}
/**
*
* @param localeValue
* @return
*/
public static Locale parseLocale(String localeValue) {
String[] tokens = tokenizeLocaleSource(localeValue);
if (tokens.length == 1) {
Locale resolved = Locale.forLanguageTag(localeValue);
if (resolved.getLanguage().length() > 0) {
return resolved;
}
}
return parseLocaleTokens(localeValue, tokens);
}
/**
*
* @param localeString
* @return
*/
public static Locale parseLocaleString(String localeString) {
return parseLocaleTokens(localeString, tokenizeLocaleSource(localeString));
}
private static String[] tokenizeLocaleSource(String localeSource) {
return tokenizeToStringArray(localeSource, "_ ", false, false);
}
private static Locale parseLocaleTokens(String localeString, String[] tokens) {
String language = (tokens.length > 0 ? tokens[0] : "");
String country = (tokens.length > 1 ? tokens[1] : "");
validateLocalePart(language);
validateLocalePart(country);
String variant = "";
if (tokens.length > 2) {
// There is definitely a variant, and it is everything after the country
// code sans the separator between the country code and the variant.
int endIndexOfCountryCode = localeString.indexOf(country, language.length()) + country.length();
// Strip off any leading '_' and whitespace, what's left is the variant.
variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode));
if (variant.startsWith("_")) {
variant = trimLeadingCharacter(variant, '_');
}
}
if (variant.isEmpty() && country.startsWith("#")) {
variant = country;
country = "";
}
return (language.length() > 0 ? new Locale(language, country, variant) : null);
}
private static void validateLocalePart(String localePart) {
for (int i = 0; i < localePart.length(); i++) {
char ch = localePart.charAt(i);
if (ch != ' ' && ch != '_' && ch != '#' && !Character.isLetterOrDigit(ch)) {
throw new IllegalArgumentException(
"Locale part \"" + localePart + "\" contains invalid characters");
}
}
}
/**
*
* @param timeZoneString
* @return
*/
public static TimeZone parseTimeZoneString(String timeZoneString) {
TimeZone timeZone = TimeZone.getTimeZone(timeZoneString);
if ("GMT".equals(timeZone.getID()) && !timeZoneString.startsWith("GMT")) {
// We don't want that GMT fallback...
throw new IllegalArgumentException("Invalid time zone specification '" + timeZoneString + "'");
}
return timeZone;
}
//---------------------------------------------------------------------
// Convenience methods for working with String arrays
//---------------------------------------------------------------------
/**
*
* @param collection
* @return
*/
public static String[] toStringArray(Collection<String> collection) {
return (collection != null ? collection.toArray(new String[0]) : new String[0]);
}
/**
*
* @param enumeration
* @return
*/
public static String[] toStringArray(Enumeration<String> enumeration) {
return (enumeration != null ? toStringArray(Collections.list(enumeration)) : new String[0]);
}
/**
*
* @param array
* @param str
* @return
*/
public static String[] addStringToArray(String[] array, String str) {
if (ObjectUtils.isEmpty(array)) {
return new String[] {str};
}
String[] newArr = new String[array.length + 1];
System.arraycopy(array, 0, newArr, 0, array.length);
newArr[array.length] = str;
return newArr;
}
/**
*
* @param array1
* @param array2
* @return
*/
public static String[] concatenateStringArrays(String[] array1, String[] array2) {
if (ObjectUtils.isEmpty(array1)) {
return array2;
}
if (ObjectUtils.isEmpty(array2)) {
return array1;
}
String[] newArr = new String[array1.length + array2.length];
System.arraycopy(array1, 0, newArr, 0, array1.length);
System.arraycopy(array2, 0, newArr, array1.length, array2.length);
return newArr;
}
/**
*
* @param array
* @return
*/
public static String[] sortStringArray(String[] array) {
if (ObjectUtils.isEmpty(array)) {
return new String[0];
}
Arrays.sort(array);
return array;
}
/**
*
* @param array
* @return
*/
public static String[] trimArrayElements(String[] array) {
if (ObjectUtils.isEmpty(array)) {
return new String[0];
}
String[] result = new String[array.length];
for (int i = 0; i < array.length; i++) {
String element = array[i];
result[i] = (element != null ? element.trim() : null);
}
return result;
}
/**
*
* @param array
* @return
*/
public static String[] removeDuplicateStrings(String[] array) {
if (ObjectUtils.isEmpty(array)) {
return array;
}
Set<String> set = new LinkedHashSet<>(Arrays.asList(array));
return toStringArray(set);
}
/**
*
* @param toSplit
* @param delimiter
* @return
*/
public static String[] split(String toSplit, String delimiter) {
if (!hasLength(toSplit) || !hasLength(delimiter)) {
return null;
}
int offset = toSplit.indexOf(delimiter);
if (offset < 0) {
return null;
}
String beforeDelimiter = toSplit.substring(0, offset);
String afterDelimiter = toSplit.substring(offset + delimiter.length());
return new String[] {beforeDelimiter, afterDelimiter};
}
/**
*
* @param array
* @param delimiter
* @return
*/
public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) {
return splitArrayElementsIntoProperties(array, delimiter, null);
}
/**
*
* @param array
* @param delimiter
* @param charsToDelete
* @return
*/
public static Properties splitArrayElementsIntoProperties(
String[] array, String delimiter, String charsToDelete) {
if (ObjectUtils.isEmpty(array)) {
return null;
}
Properties result = new Properties();
for (String element : array) {
if (charsToDelete != null) {
element = deleteAny(element, charsToDelete);
}
String[] splittedElement = split(element, delimiter);
if (splittedElement == null) {
continue;
}
result.setProperty(splittedElement[0].trim(), splittedElement[1].trim());
}
return result;
}
/**
*
* @param str
* @param delimiters
* @return
*/
public static String[] tokenizeToStringArray(String str, String delimiters) {
return tokenizeToStringArray(str, delimiters, true, true);
}
/**
*
* @param str
* @param delimiters
* @param trimTokens
* @param ignoreEmptyTokens
* @return
*/
public static String[] tokenizeToStringArray(
String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
if (str == null) {
return new String[0];
}
StringTokenizer st = new StringTokenizer(str, delimiters);
List<String> tokens = new ArrayList<>();
while (st.hasMoreTokens()) {
String token = st.nextToken();
if (trimTokens) {
token = token.trim();
}
if (!ignoreEmptyTokens || token.length() > 0) {
tokens.add(token);
}
}
return toStringArray(tokens);
}
/**
*
* @param str
* @param delimiter
* @return
*/
public static String[] delimitedListToStringArray(String str, String delimiter) {
return delimitedListToStringArray(str, delimiter, null);
}
/**
*
* @param str
* @param delimiter
* @param charsToDelete
* @return
*/
public static String[] delimitedListToStringArray(
String str, String delimiter, String charsToDelete) {
if (str == null) {
return new String[0];
}
if (delimiter == null) {
return new String[] {str};
}
List<String> result = new ArrayList<>();
if (delimiter.isEmpty()) {
for (int i = 0; i < str.length(); i++) {
result.add(deleteAny(str.substring(i, i + 1), charsToDelete));
}
}
else {
int pos = 0;
int delPos;
while ((delPos = str.indexOf(delimiter, pos)) != -1) {
result.add(deleteAny(str.substring(pos, delPos), charsToDelete));
pos = delPos + delimiter.length();
}
if (str.length() > 0 && pos <= str.length()) {
// Add rest of String, but not in case of empty input.
result.add(deleteAny(str.substring(pos), charsToDelete));
}
}
return toStringArray(result);
}
/**
*
* @param str
* @return
*/
public static String[] commaDelimitedListToStringArray(String str) {
return delimitedListToStringArray(str, ",");
}
/**
*
* @param str
* @return
*/
public static Set<String> commaDelimitedListToSet(String str) {
String[] tokens = commaDelimitedListToStringArray(str);
return new LinkedHashSet<>(Arrays.asList(tokens));
}
/**
*
* @param coll
* @param delim
* @param prefix
* @param suffix
* @return
*/
public static String collectionToDelimitedString(
Collection<?> coll, String delim, String prefix, String suffix) {
if (CollectionUtil.isEmpty(coll)) {
return "";
}
StringBuilder sb = new StringBuilder();
Iterator<?> it = coll.iterator();
while (it.hasNext()) {
sb.append(prefix).append(it.next()).append(suffix);
if (it.hasNext()) {
sb.append(delim);
}
}
return sb.toString();
}
/**
*
* @param coll
* @param delim
* @return
*/
public static String collectionToDelimitedString(Collection<?> coll, String delim) {
return collectionToDelimitedString(coll, delim, "", "");
}
/**
*
* @param coll
* @return
*/
public static String collectionToCommaDelimitedString(Collection<?> coll) {
return collectionToDelimitedString(coll, ",");
}
/**
*
* @param arr
* @param delim
* @return
*/
public static String arrayToDelimitedString(Object[] arr, String delim) {
if (ObjectUtils.isEmpty(arr)) {
return "";
}
if (arr.length == 1) {
return ObjectUtil.nullSafeToString(arr[0]);
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
if (i > 0) {
sb.append(delim);
}
sb.append(arr[i]);
}
return sb.toString();
}
/**
*
* @param arr
* @return
*/
public static String arrayToCommaDelimitedString(Object[] arr) {
return arrayToDelimitedString(arr, ",");
}
}
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class SystemUtil {
public static void addShutdownHook(Runnable runnable) {
Runtime.getRuntime().addShutdownHook(new Thread(runnable));
}
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ThreadUtil {
/**
* 执行一个线程
* @param runnable
*/
public static void run(Runnable runnable) {
new Thread(runnable).start();
}
}
@@ -0,0 +1,64 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import fun.asgc.neutrino.core.constant.TypeNotMatchingValueConstant;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class TypeMatchingInfo {
/**
* 字段类型
*/
private Class<?> fieldType;
/**
* 值类型
*/
private Class<?> valueType;
/**
* 不匹配值
*/
private Integer notMatchingValue;
public TypeMatchingInfo(Class<?> fieldType, Class<?> valueType) {
this.fieldType = fieldType;
this.valueType = valueType;
this.notMatchingValue = TypeNotMatchingValueConstant.NOT;
}
public boolean isNotMatch() {
return TypeNotMatchingValueConstant.NOT == notMatchingValue;
}
}
@@ -0,0 +1,587 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import fun.asgc.neutrino.core.constant.TypeNotMatchingValueConstant;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class TypeUtil {
/**
* 严格意义上的基本数据类型
*/
private static final Set<Class<?>> strictBasicType = new HashSet<Class<?>>() {
{
this.add(byte.class);
this.add(short.class);
this.add(int.class);
this.add(long.class);
this.add(float.class);
this.add(double.class);
this.add(char.class);
this.add(boolean.class);
}
};
/**
* 一般意义上的基本数据类型
*/
private static final Set<Class<?>> normalBasicType = new HashSet<Class<?>>() {
{
this.addAll(strictBasicType);
this.add(Byte.class);
this.add(Short.class);
this.add(Integer.class);
this.add(Long.class);
this.add(Float.class);
this.add(Double.class);
this.add(Character.class);
this.add(Boolean.class);
this.add(String.class);
}
};
/**
* 基本类型的映射
*/
public static final Map<Class<?>, Class<?>> basicTypeMap = new HashMap<Class<?>, Class<?>>() {
{
this.put(byte.class, byte.class);
this.put(short.class, short.class);
this.put(int.class, int.class);
this.put(long.class, long.class);
this.put(float.class, float.class);
this.put(double.class, double.class);
this.put(char.class, char.class);
this.put(boolean.class, boolean.class);
this.put(Byte.class, byte.class);
this.put(Short.class, short.class);
this.put(Integer.class, int.class);
this.put(Long.class, long.class);
this.put(Float.class, float.class);
this.put(Double.class, double.class);
this.put(Character.class, char.class);
this.put(Boolean.class, boolean.class);
}
};
/**
* 包装类型的映射
*/
public static final Map<Class<?>, Class<?>> wrapTypeMap = new HashMap<Class<?>, Class<?>>() {
{
this.put(byte.class, Byte.class);
this.put(short.class, Short.class);
this.put(int.class, Integer.class);
this.put(long.class, Long.class);
this.put(float.class, Float.class);
this.put(double.class, Double.class);
this.put(char.class, Character.class);
this.put(boolean.class, Boolean.class);
this.put(Byte.class, Byte.class);
this.put(Short.class, Short.class);
this.put(Integer.class, Integer.class);
this.put(Long.class, Long.class);
this.put(Float.class, Float.class);
this.put(Double.class, Double.class);
this.put(Character.class, Character.class);
this.put(Boolean.class, Boolean.class);
this.put(String.class, String.class);
}
};
/**
* 基本类型列表(用于计算类型提升距离)
*/
public static final List<Class<?>> basicTypeList = Lists.newArrayList(
boolean.class, byte.class, short.class, char.class, int.class, long.class, float.class, double.class,
Boolean.class, Byte.class, Short.class, Character.class, Integer.class, Long.class, Float.class, Double.class
);
/**
* 获取字段类型
* @param field
* @return
*/
public static Class<?> getFieldType(Field field) {
return field.getType();
}
/**
* 字段类型匹配
* @param field
* @param clazz
* @return
*/
public static boolean fieldTypeMatch(Field field, Class<?> clazz) {
return getFieldType(field) == clazz;
}
/**
* 是否是严格基本类型
* @param clazz
* @return
*/
public static boolean isStrictBasicType(Class<?> clazz) {
return strictBasicType.contains(clazz);
}
/**
* 是否是严格基本类型
* @param field
* @return
*/
public static boolean isStrictBasicType(Field field) {
return isStrictBasicType(getFieldType(field));
}
/**
* 是否是一般基本类型
* @param clazz
* @return
*/
public static boolean isNormalBasicType(Class<?> clazz) {
return normalBasicType.contains(clazz);
}
/**
* 是否是一般基本类型
* @param field
* @return
*/
public static boolean isNormalBasicType(Field field) {
return isNormalBasicType(getFieldType(field));
}
/**
* 是否是包装类型
* @param clazz
* @return
*/
public static boolean isWrapType(Class<?> clazz) {
return !isStrictBasicType(clazz);
}
/**
* 是否是包装类型
* @param field
* @return
*/
public static boolean isWrapType(Field field) {
return !isStrictBasicType(field);
}
/**
* 是否是byte类型
* @param field
* @return
*/
public static boolean isByte(Field field) {
Class<?> clazz = getFieldType(field);
return clazz == byte.class || clazz == Byte.class;
}
/**
* 是否是short类型
* @param field
* @return
*/
public static boolean isShort(Field field) {
Class<?> clazz = getFieldType(field);
return clazz == short.class || clazz == Short.class;
}
/**
* 是否是integer类型
* @param field
* @return
*/
public static boolean isInteger(Field field) {
Class<?> clazz = getFieldType(field);
return clazz == int.class || clazz == Integer.class;
}
/**
* 是否是long类型
* @param field
* @return
*/
public static boolean isLong(Field field) {
Class<?> clazz = getFieldType(field);
return clazz == long.class || clazz == Long.class;
}
/**
* 是否是float类型
* @param field
* @return
*/
public static boolean isFloat(Field field) {
Class<?> clazz = getFieldType(field);
return clazz == float.class || clazz == Float.class;
}
/**
* 是否是double类型
* @param field
* @return
*/
public static boolean isDouble(Field field) {
Class<?> clazz = getFieldType(field);
return clazz == double.class || clazz == Double.class;
}
/**
* 是否是char类型
* @param field
* @return
*/
public static boolean isChar(Field field) {
Class<?> clazz = getFieldType(field);
return clazz == char.class || clazz == Character.class;
}
/**
* 是否是boolean类型
* @param field
* @return
*/
public static boolean isBoolean(Field field) {
Class<?> clazz = getFieldType(field);
return clazz == boolean.class || clazz == Boolean.class;
}
/**
* 是否是String类型
* @param field
* @return
*/
public static boolean isString(Field field) {
Class<?> clazz = getFieldType(field);
return clazz == String.class;
}
/**
* 是否是布尔值
* @param clazz
* @return
*/
public static boolean isBoolean(Class<?> clazz) {
return clazz == boolean.class || clazz == Boolean.class;
}
/**
* 是否是字符
* @param clazz
* @return
*/
public static boolean isChar(Class<?> clazz) {
return clazz == char.class || clazz == Character.class;
}
/**
* 获取包装类型
* @param clazz
* @return
*/
public static Class<?> getWrapType(Class<?> clazz) {
if (isStrictBasicType(clazz)) {
return wrapTypeMap.get(clazz);
}
return clazz;
}
/**
* 获取包装类型
* @param field
* @return
*/
public static Class<?> getWrapType(Field field) {
return getWrapType(getFieldType(field));
}
/**
* 是否是超类
* @param superClass
* @param childClass
* @return
*/
public static boolean isSupper(Class<?> superClass, Class<?> childClass) {
return superClass.isAssignableFrom(childClass);
}
/**
* 获取简单类名
* @param clazz
* @return
*/
public static String getSimpleName(Class<?> clazz) {
return clazz.getSimpleName();
}
/**
* 获取包名
* @param clazz
* @return
*/
public static String getPackageName(Class<?> clazz) {
return clazz.getPackage().getName();
}
/**
* 获取默认变量名
* @param clazz
* @return
*/
public static String getDefaultVariableName(Class<?> clazz) {
String name = getSimpleName(clazz);
return name.substring(0, 1).toLowerCase() + name.substring(1);
}
/**
* 获取继承层级
* @param superClass
* @param childClass
* @return
*/
public static int getInheritLevel(Class<?> superClass, Class<?> childClass) {
if (!isSupper(superClass, childClass)) {
return -1;
}
List<Pair<Class<?>, Integer>> list = new LinkedList<>();
list.add(Pair.of(childClass, 0));
while (CollectionUtil.notEmpty(list) && list.get(0).getFirst() != superClass && list.get(0).getSecond() < TypeNotMatchingValueConstant.SUPER_CLASS_MAX) {
Pair<Class<?>, Integer> current = list.remove(0);
Class<?> clazz = current.getFirst();
int level = current.getSecond();
if (null != clazz.getSuperclass()) {
list.add(Pair.of(clazz.getSuperclass(), level + 1));
}
if (null != clazz.getInterfaces() && clazz.getInterfaces().length > 0) {
for (Class<?> inter : clazz.getInterfaces()) {
list.add(Pair.of(inter, level + 1));
}
}
}
if (list.isEmpty()) {
return TypeNotMatchingValueConstant.SUPER_CLASS_MAX;
}
return Math.min(list.get(0).getSecond(), TypeNotMatchingValueConstant.SUPER_CLASS_MAX);
}
/**
* 计算一个类型的值是否可以赋值给指定类型的匹配信息
* @param fieldType
* @param valueType
* @return
*/
public static TypeMatchingInfo typeMatch(Class<?> fieldType, Class<?> valueType) {
Assert.notNull(fieldType, "字段类型不能为空!");
Assert.notNull(valueType, "值类型不能为空!");
// 1、完全匹配
if (fieldType == valueType) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.COMPLETE);
}
// 2、包装匹配
if (isStrictBasicType(valueType) && getWrapType(valueType) == fieldType) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.WRAP);
}
// 3、解包装匹配
if (isStrictBasicType(fieldType) && getWrapType(fieldType) == valueType) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.UNWRAP);
}
// 4、超类匹配
if (fieldType.isAssignableFrom(valueType)) {
int inheritLevel = getInheritLevel(fieldType, valueType);
if (-1 == inheritLevel) {
throw new RuntimeException(String.format("超类匹配异常! fieldType:%s valueType:%s", fieldType.getName(), valueType.getName()));
}
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.SUPER_CLASS_MIN + inheritLevel - 1);
}
// 5、类型提升
if (isNormalBasicType(fieldType) && isNormalBasicType(valueType)) {
int index1 = basicTypeList.indexOf(fieldType);
int index2 = basicTypeList.indexOf(valueType);
if (index1 > index2 && index2 > 0) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.ASCENSION_MIN + (index1 - index2) - 1);
}
}
// 6、自定义转换
if (fieldType == Date.class || fieldType == java.sql.Date.class) {
if (valueType == long.class) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN);
} else if (valueType == long.class) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 1);
}
}
if (fieldType == Long.class || fieldType == long.class) {
if (valueType == Date.class) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 1000);
} else if (valueType == java.sql.Date.class) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 1001);
}
}
if (fieldType == String.class) {
if (valueType == char.class) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 2000);
} else if (valueType == Character.class) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 2001);
} else if (Number.class.isAssignableFrom(valueType)) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 2002);
}
}
if (Iterable.class.isAssignableFrom(fieldType) && Iterable.class.isAssignableFrom(valueType)) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 3000);
}
if (Iterable.class.isAssignableFrom(fieldType) && String[].class == valueType) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 4000);
}
if (Iterable.class.isAssignableFrom(valueType) && String[].class == fieldType) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 5000);
}
if (Iterable.class.isAssignableFrom(fieldType) && Integer[].class == valueType) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 6000);
}
if (Iterable.class.isAssignableFrom(valueType) && Integer[].class == fieldType) {
return new TypeMatchingInfo(fieldType, valueType, TypeNotMatchingValueConstant.CUSTOM_MIN + 7000);
}
return new TypeMatchingInfo(fieldType, valueType);
}
/**
* 类型提升
* @param value
* @param clazz
* @return
*/
public static Object ascensionType(Object value, Class<?> clazz) {
Class<?> valueType = value.getClass();
int index1 = basicTypeList.indexOf(valueType);
int index2 = basicTypeList.indexOf(clazz);
if (-1 == index1 || -1 == index2 || index1 >= index2) {
return value;
}
if (getWrapType(valueType) == clazz || getWrapType(clazz) == valueType) {
return value;
}
if (isChar(clazz)) {
return value;
}
String strValue;
if (isBoolean(valueType)) {
strValue = ((Boolean)value) ? "0" : "1";
} else if (isChar(valueType)){
strValue = (int)((Character)value) + "";
} else {
strValue = String.valueOf(value);
}
if (isBoolean(clazz)) {
return "0".equals(strValue);
}
Method method = ReflectUtil.getValueOfMethod(clazz, String.class);
if (null == method) {
return value;
}
try {
return method.invoke(null, strValue);
} catch (Exception e) {
// ignore
}
return value;
}
/**
* 类型转换
* @param value
* @param targetType
* @return
*/
public static Object conversion(Object value, Class<?> targetType) {
Assert.notNull(targetType, "目标类型不能为空!");
if (null == value) {
return null;
}
Class<?> valueType = value.getClass();
TypeMatchingInfo matchingInfo = typeMatch(targetType, valueType);
if (matchingInfo.isNotMatch()) {
return null;
}
// 超类
if (matchingInfo.getNotMatchingValue() <= TypeNotMatchingValueConstant.SUPER_CLASS_MAX) {
return value;
}
// 类型提升
if (matchingInfo.getNotMatchingValue() <= TypeNotMatchingValueConstant.ASCENSION_MAX) {
return TypeUtil.ascensionType(value, targetType);
}
if (matchingInfo.getNotMatchingValue() >= TypeNotMatchingValueConstant.CUSTOM_MIN && matchingInfo.getNotMatchingValue() <= TypeNotMatchingValueConstant.CUSTOM_MAX) {
// 自定义转换
if (targetType == Date.class) {
if (valueType == long.class || value == Long.class) {
return new Date((Long) value);
}
} else if (targetType == java.sql.Date.class) {
if (valueType == long.class || value == Long.class) {
return new java.sql.Date((Long) value);
}
} else if (targetType == Long.class || targetType == long.class) {
if (valueType == java.sql.Date.class || valueType == Date.class) {
return ((Date) value).getTime();
}
} else if (targetType == String.class) {
if (valueType == char.class || valueType == Character.class || Number.class.isAssignableFrom(valueType)) {
return String.valueOf(value);
}
} else if (Iterable.class.isAssignableFrom(targetType) && Iterable.class.isAssignableFrom(valueType)) {
if (Set.class.isAssignableFrom(targetType)) {
return Sets.newHashSet((Iterable) value);
}
} else if (Iterable.class.isAssignableFrom(valueType) && String[].class == targetType) {
if (List.class.isAssignableFrom(valueType)) {
return ((List<Object>) value).stream().map(String::valueOf).collect(Collectors.toList()).toArray(new String[]{});
}
} else if (Iterable.class.isAssignableFrom(valueType) && Integer[].class == targetType) {
if (List.class.isAssignableFrom(valueType)) {
return ((List<Object>) value).stream().map(String::valueOf).map(Integer::valueOf).collect(Collectors.toList()).toArray(new Integer[]{});
}
}
}
return null;
}
}
@@ -0,0 +1,126 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.config;
import com.alibaba.fastjson.JSONObject;
import fun.asgc.neutrino.core.annotation.Configuration;
import fun.asgc.neutrino.core.annotation.Value;
import fun.asgc.neutrino.core.config.impl.YmlConfigurationParser;
import fun.asgc.neutrino.core.util.FileUtil;
import lombok.Data;
import org.junit.Test;
import java.io.FileNotFoundException;
import java.util.List;
import java.util.Set;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class YmlConfigurationParserTest {
@Test
public void test1() throws FileNotFoundException {
ConfigurationParser parser = new YmlConfigurationParser();
Config1 config1 = parser.parse(FileUtil.getInputStream("classpath:/test1.yml"), Config1.class);
System.out.println(JSONObject.toJSONString(config1));
}
@Test
public void test2() throws FileNotFoundException {
ConfigurationParser parser = new YmlConfigurationParser();
Config1 config1 = parser.parse(Config1.class);
System.out.println(JSONObject.toJSONString(config1));
}
@Test
public void test3() throws FileNotFoundException {
ConfigurationParser parser = new YmlConfigurationParser();
Config2 config2 = parser.parse(Config2.class);
System.out.println(JSONObject.toJSONString(config2));
}
@Configuration(file = "test1.yml")
@Data
public static class Config1 {
@Value("server")
private Server serve;
private Integer a;
private String b;
private C c;
@Value("server")
private Object server;
@Value("c")
private Object cc;
@Data
public static class Server {
private String name;
private int port;
}
@Data
public static class C {
private D d;
public static class D {
private int e;
private Long f;
private String g;
private List<String> h;
@Value("f")
private Integer ff;
@Value("h")
private Set<String> hh;
@Value("h")
private String[] hhh;
private Integer[] i;
@Value("i")
private List<Integer> ii;
@Value("i")
private Object iii;
}
}
}
@Configuration(file = "test1.yml", prefix = "c.d")
public static class Config2 {
private int e;
private Long f;
private String g;
private List<String> h;
@Value("f")
private Integer ff;
@Value("h")
private Set<String> hh;
@Value("h")
private String[] hhh;
private Integer[] i;
@Value("i")
private List<Integer> ii;
@Value("i")
private Object iii;
}
}
@@ -0,0 +1,19 @@
server:
name: neutrino-configuration
port: 8080
a: 1
b: hello
c:
d:
e: 1
f: 2
g: ab
h:
- 1
- 2
- 3
- aaa
i:
- 10
- 20
- 30
+43
View File
@@ -0,0 +1,43 @@
<?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>neutrino-proxy</artifactId>
<groupId>fun.asgc.neutrino</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>neutrino-proxy-client</artifactId>
<dependencies>
<dependency>
<groupId>fun.asgc.neutrino</groupId>
<artifactId>neutrino-proxy-core</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.3.RELEASE</version>
<configuration>
<mainClass>fun.asgc.neutrino.proxy.client.ProxyClient</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client;
import fun.asgc.neutrino.core.annotation.NeutrinoApplication;
import fun.asgc.neutrino.core.launcher.NeutrinoLauncher;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@NeutrinoApplication
public class ProxyClient {
public static void main(String[] args) {
NeutrinoLauncher.runSync(ProxyClient.class, args);
}
}
@@ -0,0 +1,82 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.config;
import fun.asgc.neutrino.core.annotation.Configuration;
import fun.asgc.neutrino.core.annotation.Init;
import fun.asgc.neutrino.core.annotation.Value;
import fun.asgc.neutrino.proxy.core.ProxyClientConfig;
import lombok.Data;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Data
@Configuration(prefix = "proxy")
public class ProxyConfig {
public static ProxyConfig instance;
private Protocol protocol;
private Client client;
private ProxyClientConfig clientConfig;
@Data
public static class Protocol {
@Value("max-frame-length")
private Integer maxFrameLength;
@Value("length-field-offset")
private Integer lengthFieldOffset;
@Value("length-field-length")
private Integer lengthFieldLength;
@Value("initial-bytes-to-strip")
private Integer initialBytesToStrip;
@Value("length-adjustment")
private Integer lengthAdjustment;
@Value("read-idle-time")
private Integer readIdleTime;
@Value("write-idle-time")
private Integer writeIdleTime;
@Value("all-idle-time-seconds")
private Integer allIdleTimeSeconds;
}
@Data
public static class Client {
@Value("key-store-password")
private String keyStorePassword;
@Value("jks-path")
private String jksPath;
@Value("server-ip")
private String serverIp;
@Value("server-port")
private Integer serverPort;
@Value("ssl-enable")
private Boolean sslEnable;
}
@Init
public void init() {
instance = this;
}
}
@@ -0,0 +1,96 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.core;
import fun.asgc.neutrino.core.base.DefaultDispatcher;
import fun.asgc.neutrino.core.base.Dispatcher;
import fun.asgc.neutrino.core.util.BeanManager;
import fun.asgc.neutrino.core.util.LockUtil;
import fun.asgc.neutrino.proxy.client.util.ClientChannelMannager;
import fun.asgc.neutrino.proxy.core.*;
import io.netty.channel.*;
import lombok.extern.slf4j.Slf4j;
/**
* 处理与服务端之间的数据传输
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
private static volatile Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher;
public ClientChannelHandler() {
LockUtil.doubleCheckProcess(() -> null == dispatcher,
ClientChannelHandler.class,
() -> {
dispatcher = new DefaultDispatcher<>("消息调度器",
BeanManager.getBeanListBySuperClass(ProxyMessageHandler.class),
proxyMessage -> ProxyDataTypeEnum.of((int)proxyMessage.getType()) == null ? null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName());
});
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
log.info("recieved proxy message, type is {}", proxyMessage.getType());
dispatcher.dispatch(ctx, proxyMessage);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null) {
realServerChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 控制连接
if (ClientChannelMannager.getCmdChannel() == ctx.channel()) {
log.info("与服务端断开连接");
ClientChannelMannager.setCmdChannel(null);
ClientChannelMannager.clearRealServerChannels();
} else {
// 数据传输连接
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null && realServerChannel.isActive()) {
realServerChannel.close();
}
}
ClientChannelMannager.removeProxyChanel(ctx.channel());
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
cause.printStackTrace();
}
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.core;
import io.netty.channel.Channel;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface ProxyChannelBorrowListener {
void success(Channel channel);
void error(Throwable cause);
}
@@ -0,0 +1,184 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.core;
import com.alibaba.fastjson.JSONObject;
import fun.asgc.neutrino.core.annotation.Autowired;
import fun.asgc.neutrino.core.annotation.Bean;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.runner.ApplicationRunner;
import fun.asgc.neutrino.core.util.CollectionUtil;
import fun.asgc.neutrino.core.util.FileUtil;
import fun.asgc.neutrino.core.util.StringUtil;
import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
import fun.asgc.neutrino.proxy.client.util.ClientChannelMannager;
import fun.asgc.neutrino.proxy.core.*;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslHandler;
import lombok.extern.slf4j.Slf4j;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.InputStream;
import java.security.KeyStore;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
@Component
public class ProxyClientRunner implements ApplicationRunner {
@Autowired
private ProxyConfig proxyConfig;
@Autowired("bootstrap")
private static Bootstrap bootstrap;
@Autowired("realServerBootstrap")
private static Bootstrap realServerBootstrap;
private static NioEventLoopGroup workerGroup;
@Override
public void run(String[] args) {
ProxyClientConfig clientConfig = getClientConfig(args.length >= 1 ? args[args.length - 1] : null);
proxyConfig.setClientConfig(clientConfig);
connectProxyServer();
}
/**
* 连接代理服务器
*/
private void connectProxyServer() {
workerGroup = new NioEventLoopGroup();
realServerBootstrap.group(workerGroup);
realServerBootstrap.channel(NioSocketChannel.class);
realServerBootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new RealServerChannelHandler());
}
});
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (proxyConfig.getClient().getSslEnable()) {
ch.pipeline().addLast(createSslHandler());
}
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleCheckHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
ch.pipeline().addLast(new ClientChannelHandler());
}
});
bootstrap.connect(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort())
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// 连接成功,向服务器发送客户端认证信息(clientKey)
ClientChannelMannager.setCmdChannel(future.channel());
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(JSONObject.toJSONString(proxyConfig.getClientConfig())));
log.info("连接代理服务成功.");
} else {
log.info("连接代理服务失败!");
}
}
});
}
private ChannelHandler createSslHandler() {
try {
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getClient().getJksPath());
SSLContext clientContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, proxyConfig.getClient().getKeyStorePassword().toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
TrustManager[] trustManagers = tmf.getTrustManagers();
clientContext.init(null, trustManagers, null);
SSLEngine sslEngine = clientContext.createSSLEngine();
sslEngine.setUseClientMode(true);
return new SslHandler(sslEngine);
} catch (Exception e) {
log.error("创建SSL处理器失败", e);
e.printStackTrace();
}
return null;
}
@Bean
public Bootstrap bootstrap() {
return new Bootstrap();
}
@Bean
public Bootstrap realServerBootstrap() {
return new Bootstrap();
}
private ProxyClientConfig getClientConfig(String path) {
if (StringUtil.isEmpty(path)) {
path = "./config.json";
}
String content = FileUtil.readContentAsString(path);
if (StringUtil.isEmpty(content)) {
log.error("配置文件: {} 不存在或格式异常!", path);
System.exit(0);
return null;
}
try {
ProxyClientConfig clientConfig = JSONObject.parseObject(content, ProxyClientConfig.class);
if (StringUtil.isEmpty(clientConfig.getClientKey()) || CollectionUtil.isEmpty(clientConfig.getProxy())) {
log.error("配置异常!");
System.exit(0);
return null;
}
return clientConfig;
} catch (Exception e) {
log.error("解析配置文件异常!", e);
}
return null;
}
}
@@ -0,0 +1,90 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.core;
import fun.asgc.neutrino.proxy.client.util.ClientChannelMannager;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* 处理与被代理客户端的数据传输
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
Channel realServerChannel = ctx.channel();
Channel channel = realServerChannel.attr(Constants.NEXT_CHANNEL).get();
if (channel == null) {
// 代理客户端连接断开
ctx.channel().close();
} else {
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String userId = ClientChannelMannager.getRealServerChannelUserId(realServerChannel);
channel.writeAndFlush(ProxyMessage.buildTransferMessage(userId, bytes));
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel realServerChannel = ctx.channel();
String userId = ClientChannelMannager.getRealServerChannelUserId(realServerChannel);
ClientChannelMannager.removeRealServerChannel(userId);
Channel channel = realServerChannel.attr(Constants.NEXT_CHANNEL).get();
if (channel != null) {
channel.writeAndFlush(ProxyMessage.buildDisconnectMessage(userId));
}
super.channelInactive(ctx);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
Channel realServerChannel = ctx.channel();
Channel proxyChannel = realServerChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, realServerChannel.isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
@@ -0,0 +1,103 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.handler;
import fun.asgc.neutrino.core.annotation.Autowired;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
import fun.asgc.neutrino.proxy.client.core.ProxyChannelBorrowListener;
import fun.asgc.neutrino.proxy.client.util.ClientChannelMannager;
import fun.asgc.neutrino.proxy.core.*;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Match(type = Constants.ProxyDataTypeName.CONNECT)
@Component
public class ProxyMessageConnectHandler implements ProxyMessageHandler {
@Autowired("bootstrap")
private Bootstrap bootstrap;
@Autowired("realServerBootstrap")
private Bootstrap realServerBootstrap;
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
final Channel cmdChannel = ctx.channel();
final String userId = proxyMessage.getInfo();
String[] serverInfo = new String(proxyMessage.getData()).split(":");
String ip = serverInfo[0];
int port = Integer.parseInt(serverInfo[1]);
realServerBootstrap.connect(ip, port).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
// 连接后端服务器成功
if (future.isSuccess()) {
final Channel realServerChannel = future.channel();
realServerChannel.config().setOption(ChannelOption.AUTO_READ, false);
// 获取连接
ClientChannelMannager.borrowProxyChanel(bootstrap, new ProxyChannelBorrowListener() {
@Override
public void success(Channel channel) {
// 连接绑定
channel.attr(Constants.NEXT_CHANNEL).set(realServerChannel);
realServerChannel.attr(Constants.NEXT_CHANNEL).set(channel);
// 远程绑定
channel.writeAndFlush(ProxyMessage.buildConnectMessage(userId + "@" + ProxyConfig.instance.getClientConfig().getClientKey()));
realServerChannel.config().setOption(ChannelOption.AUTO_READ, true);
ClientChannelMannager.addRealServerChannel(userId, realServerChannel);
ClientChannelMannager.setRealServerChannelUserId(realServerChannel, userId);
}
@Override
public void error(Throwable cause) {
ProxyMessage proxyMessage = new ProxyMessage();
proxyMessage.setType(ProxyMessage.TYPE_DISCONNECT);
proxyMessage.setInfo(userId);
cmdChannel.writeAndFlush(proxyMessage);
}
});
} else {
cmdChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(userId));
}
}
});
}
@Override
public String name() {
return ProxyDataTypeEnum.CONNECT.getDesc();
}
}
@@ -0,0 +1,61 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.handler;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.proxy.client.util.ClientChannelMannager;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Match(type = Constants.ProxyDataTypeName.DISCONNECT)
@Component
public class ProxyMessageDisconnectHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null) {
ctx.channel().attr(Constants.NEXT_CHANNEL).remove();
ClientChannelMannager.returnProxyChanel(ctx.channel());
realServerChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public String name() {
return ProxyDataTypeEnum.DISCONNECT.getDesc();
}
}
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.handler;
import com.alibaba.fastjson.JSONObject;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.proxy.core.*;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
@Match(type = Constants.ProxyDataTypeName.ERROR)
@Component
public class ProxyMessageErrorHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
log.info("异常信息: {}", proxyMessage.getInfo());
JSONObject data = JSONObject.parseObject(proxyMessage.getInfo());
Integer code = data.getInteger("code");
if (ExceptionEnum.AUTH_FAILED.getCode().equals(code)) {
System.exit(0);
}
}
@Override
public String name() {
return ProxyDataTypeEnum.DISCONNECT.getDesc();
}
}
@@ -0,0 +1,59 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.handler;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Match(type = Constants.ProxyDataTypeName.TRANSFER)
@Component
public class ProxyMessageTransferHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null) {
ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
buf.writeBytes(proxyMessage.getData());
realServerChannel.writeAndFlush(buf);
}
}
@Override
public String name() {
return ProxyDataTypeEnum.TRANSFER.getDesc();
}
}
@@ -0,0 +1,139 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.util;
import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
import fun.asgc.neutrino.proxy.client.core.ProxyChannelBorrowListener;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyClientConfig;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.util.AttributeKey;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ClientChannelMannager {
private static final AttributeKey<Boolean> USER_CHANNEL_WRITEABLE = AttributeKey.newInstance("user_channel_writeable");
private static final AttributeKey<Boolean> CLIENT_CHANNEL_WRITEABLE = AttributeKey.newInstance("client_channel_writeable");
private static final int MAX_POOL_SIZE = 100;
private static Map<String, Channel> realServerChannels = new ConcurrentHashMap<String, Channel>();
private static ConcurrentLinkedQueue<Channel> proxyChannelPool = new ConcurrentLinkedQueue<Channel>();
private static volatile Channel cmdChannel;
public static void borrowProxyChanel(Bootstrap bootstrap, final ProxyChannelBorrowListener borrowListener) {
Channel channel = proxyChannelPool.poll();
if (channel != null) {
borrowListener.success(channel);
return;
}
bootstrap.connect(ProxyConfig.instance.getClient().getServerIp(), ProxyConfig.instance.getClient().getServerPort()).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
borrowListener.success(future.channel());
} else {
borrowListener.error(future.cause());
}
}
});
}
public static void returnProxyChanel(Channel proxyChanel) {
if (proxyChannelPool.size() > MAX_POOL_SIZE) {
proxyChanel.close();
} else {
proxyChanel.config().setOption(ChannelOption.AUTO_READ, true);
proxyChanel.attr(Constants.NEXT_CHANNEL).remove();
proxyChannelPool.offer(proxyChanel);
}
}
public static void removeProxyChanel(Channel proxyChanel) {
proxyChannelPool.remove(proxyChanel);
}
public static void setCmdChannel(Channel cmdChannel) {
ClientChannelMannager.cmdChannel = cmdChannel;
}
public static Channel getCmdChannel() {
return cmdChannel;
}
public static void setRealServerChannelUserId(Channel realServerChannel, String userId) {
realServerChannel.attr(Constants.USER_ID).set(userId);
}
public static String getRealServerChannelUserId(Channel realServerChannel) {
return realServerChannel.attr(Constants.USER_ID).get();
}
public static Channel getRealServerChannel(String userId) {
return realServerChannels.get(userId);
}
public static void addRealServerChannel(String userId, Channel realServerChannel) {
realServerChannels.put(userId, realServerChannel);
}
public static Channel removeRealServerChannel(String userId) {
return realServerChannels.remove(userId);
}
public static boolean isRealServerReadable(Channel realServerChannel) {
return realServerChannel.attr(CLIENT_CHANNEL_WRITEABLE).get() && realServerChannel.attr(USER_CHANNEL_WRITEABLE).get();
}
public static void clearRealServerChannels() {
Iterator<Entry<String, Channel>> ite = realServerChannels.entrySet().iterator();
while (ite.hasNext()) {
Channel realServerChannel = ite.next().getValue();
if (realServerChannel.isActive()) {
realServerChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
realServerChannels.clear();
}
}
@@ -0,0 +1,19 @@
application:
name: neutrino-proxy-client
proxy:
protocol:
max-frame-length: 2097152
length-field-offset: 0
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 60
write-idle-time: 30
all-idle-time-seconds: 0
client:
key-store-password: 123456
jks-path: classpath:/test.jks
server-ip: localhost
server-port: 9000
ssl-enable: false
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_FILE" value="/work/project/neutrino-proxy/client.log"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{50} - %msg%n"/>
<property name="ENCODE" value="utf8" />
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.zip</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
<logger name="com.example.logback" level="warn"/>
<logger name="io.netty" level="warn"/>
</configuration>
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
<?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>neutrino-proxy</artifactId>
<groupId>fun.asgc.neutrino</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>neutrino-proxy-core</artifactId>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>fun.asgc.neutrino</groupId>
<artifactId>neutrino-core</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface Constants {
AttributeKey<Channel> NEXT_CHANNEL = AttributeKey.newInstance("nxt_channel");
AttributeKey<String> USER_ID = AttributeKey.newInstance("user_id");
AttributeKey<String> CLIENT_KEY = AttributeKey.newInstance("client_key");
int HEADER_SIZE = 4;
int TYPE_SIZE = 1;
int SERIAL_NUMBER_SIZE = 8;
int INFO_LENGTH_SIZE = 4;
interface ProxyDataTypeName {
String HEARTBEAT = "HEARTBEAT";
String AUTH = "AUTH";
String CONNECT = "CONNECT";
String DISCONNECT = "DISCONNECT";
String TRANSFER = "TRANSFER";
String ERROR = "ERROR";
}
}
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Getter
@AllArgsConstructor
public enum ExceptionEnum {
AUTH_FAILED(1, "认证失败");
private Integer code;
private String msg;
}
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class IdleCheckHandler extends IdleStateHandler {
public IdleCheckHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {
super(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds);
}
@Override
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
if (IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT == evt) {
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
} else if (IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT == evt) {
ctx.channel().close();
}
super.channelIdle(ctx, evt);
}
}
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
import lombok.Data;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Data
public class ProxyClientConfig {
private String environment;
private String clientKey;
private List<Proxy> proxy;
@Data
public static class Proxy {
private Integer serverPort;
private String clientInfo;
}
}
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Getter
@AllArgsConstructor
public enum ProxyDataTypeEnum {
HEARTBEAT(0x01, Constants.ProxyDataTypeName.HEARTBEAT, "心跳"),
AUTH(0x02, Constants.ProxyDataTypeName.AUTH,"认证"),
CONNECT(0x03, Constants.ProxyDataTypeName.CONNECT,"连接"),
DISCONNECT(0x04, Constants.ProxyDataTypeName.DISCONNECT,"断开连接"),
TRANSFER(0x05, Constants.ProxyDataTypeName.TRANSFER,"数据传输"),
ERROR(0x06, Constants.ProxyDataTypeName.ERROR,"异常");
private static Map<Integer,ProxyDataTypeEnum> cache = Stream.of(values()).collect(Collectors.toMap(ProxyDataTypeEnum::getType, Function.identity()));
private int type;
private String name;
private String desc;
public static ProxyDataTypeEnum of(Integer type) {
return cache.get(type);
}
}
@@ -0,0 +1,136 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Arrays;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Accessors(chain = true)
@Data
public class ProxyMessage {
/**
* 心跳消息
*/
public static final byte TYPE_HEARTBEAT = 0x01;
/**
* 认证消息,检测clientKey是否正确
*/
public static final byte TYPE_AUTH = 0x02;
/**
* 代理后端服务器建立连接消息
*/
public static final byte TYPE_CONNECT = 0x03;
/**
* 代理后端服务器断开连接消息
*/
public static final byte TYPE_DISCONNECT = 0x04;
/**
* 代理数据传输
*/
public static final byte TYPE_TRANSFER = 0x05;
/**
* 通用异常信息
*/
public static final byte TYPE_ERROR = 0x06;
/**
* 消息类型
*/
private byte type;
/**
* 消息流水号
*/
private long serialNumber;
/**
* 消息命令请求信息
*/
private String info;
/**
* 消息传输数据
*/
private byte[] data;
@Override
public String toString() {
return "ProxyMessage [type=" + type + ", serialNumber=" + serialNumber + ", info=" + info + ", data=" + Arrays.toString(data) + "]";
}
public static ProxyMessage create() {
return new ProxyMessage();
}
public static ProxyMessage buildHeartbeatMessage() {
return create().setType(TYPE_HEARTBEAT);
}
public static ProxyMessage buildAuthMessage(String info) {
return create().setType(TYPE_AUTH)
.setInfo(info);
}
public static ProxyMessage buildConnectMessage(String info) {
return create().setType(TYPE_CONNECT)
.setInfo(info);
}
public static ProxyMessage buildDisconnectMessage(String info) {
return create().setType(TYPE_DISCONNECT)
.setInfo(info);
}
public static ProxyMessage buildTransferMessage(String info, byte[] data) {
return create().setType(TYPE_TRANSFER)
.setInfo(info)
.setData(data);
}
public static ProxyMessage buildErrMessage(ExceptionEnum exceptionEnum, String info) {
JSONObject data = new JSONObject();
data.put("code", exceptionEnum.getCode());
data.put("msg", exceptionEnum.getMsg());
data.put("info", info);
return create().setType(TYPE_ERROR)
.setInfo(data.toJSONString());
}
public static ProxyMessage buildErrMessage(ExceptionEnum exceptionEnum) {
return buildErrMessage(exceptionEnum, null);
}
}
@@ -0,0 +1,98 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import static fun.asgc.neutrino.proxy.core.Constants.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ProxyMessageDecoder extends LengthFieldBasedFrameDecoder {
/**
* @param maxFrameLength
* @param lengthFieldOffset
* @param lengthFieldLength
* @param lengthAdjustment
* @param initialBytesToStrip
*/
public ProxyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
}
/**
* @param maxFrameLength
* @param lengthFieldOffset
* @param lengthFieldLength
* @param lengthAdjustment
* @param initialBytesToStrip
* @param failFast
*/
public ProxyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip, boolean failFast) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
}
@Override
protected ProxyMessage decode(ChannelHandlerContext ctx, ByteBuf in2) throws Exception {
ByteBuf in = (ByteBuf) super.decode(ctx, in2);
if (in == null) {
return null;
}
if (in.readableBytes() < HEADER_SIZE) {
return null;
}
int frameLength = in.readInt();
if (in.readableBytes() < frameLength) {
return null;
}
ProxyMessage proxyMessage = new ProxyMessage();
byte type = in.readByte();
long sn = in.readLong();
proxyMessage.setSerialNumber(sn);
proxyMessage.setType(type);
int infoLength = in.readInt();
byte[] infoBytes = new byte[infoLength];
in.readBytes(infoBytes);
proxyMessage.setInfo(new String(infoBytes));
byte[] data = new byte[frameLength - TYPE_SIZE - SERIAL_NUMBER_SIZE - INFO_LENGTH_SIZE - infoLength];
in.readBytes(data);
proxyMessage.setData(data);
in.release();
return proxyMessage;
}
}
@@ -0,0 +1,71 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import static fun.asgc.neutrino.proxy.core.Constants.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ProxyMessageEncoder extends MessageToByteEncoder<ProxyMessage> {
public ProxyMessageEncoder() {
}
@Override
protected void encode(ChannelHandlerContext ctx, ProxyMessage msg, ByteBuf out) throws Exception {
int bodyLength = TYPE_SIZE + SERIAL_NUMBER_SIZE + INFO_LENGTH_SIZE;
byte[] infoBytes = null;
if (msg.getInfo() != null) {
infoBytes = msg.getInfo().getBytes();
bodyLength += infoBytes.length;
}
if (msg.getData() != null) {
bodyLength += msg.getData().length;
}
// write the total packet length but without length field's length.
out.writeInt(bodyLength);
out.writeByte(msg.getType());
out.writeLong(msg.getSerialNumber());
if (infoBytes != null) {
out.writeInt(infoBytes.length);
out.writeBytes(infoBytes);
} else {
out.writeInt(0x00);
}
if (msg.getData() != null) {
out.writeBytes(msg.getData());
}
}
}
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
import fun.asgc.neutrino.core.base.Handler;
import io.netty.channel.ChannelHandlerContext;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public interface ProxyMessageHandler extends Handler<ChannelHandlerContext, ProxyMessage> {
}
+44
View File
@@ -0,0 +1,44 @@
<?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>neutrino-proxy</artifactId>
<groupId>fun.asgc.neutrino</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>neutrino-proxy-server</artifactId>
<dependencies>
<dependency>
<groupId>fun.asgc.neutrino</groupId>
<artifactId>neutrino-proxy-core</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.3.RELEASE</version>
<configuration>
<mainClass>fun.asgc.neutrino.proxy.server.ProxyServer</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server;
import fun.asgc.neutrino.core.annotation.NeutrinoApplication;
import fun.asgc.neutrino.core.launcher.NeutrinoLauncher;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@NeutrinoApplication
public class ProxyServer {
public static void main(String[] args) {
NeutrinoLauncher.runSync(ProxyServer.class, args);
}
}
@@ -0,0 +1,78 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.config;
import fun.asgc.neutrino.core.annotation.Configuration;
import fun.asgc.neutrino.core.annotation.Value;
import lombok.Data;
import java.util.Map;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Data
@Configuration(prefix = "proxy")
public class ProxyConfig {
private Protocol protocol;
private Server server;
@Value("license")
private Map<String, Integer> licenseMap;
@Data
public static class Protocol {
@Value("max-frame-length")
private Integer maxFrameLength;
@Value("length-field-offset")
private Integer lengthFieldOffset;
@Value("length-field-length")
private Integer lengthFieldLength;
@Value("initial-bytes-to-strip")
private Integer initialBytesToStrip;
@Value("length-adjustment")
private Integer lengthAdjustment;
@Value("read-idle-time")
private Integer readIdleTime;
@Value("write-idle-time")
private Integer writeIdleTime;
@Value("all-idle-time-seconds")
private Integer allIdleTimeSeconds;
}
@Data
public static class Server {
@Value("port")
private Integer port;
@Value("ssl-port")
private Integer sslPort;
@Value("key-store-password")
private String keyStorePassword;
@Value("key-manager-password")
private String keyManagerPassword;
@Value("jks-path")
private String jksPath;
}
}
@@ -0,0 +1,125 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.config;
import fun.asgc.neutrino.proxy.core.ProxyClientConfig;
import lombok.Data;
import java.io.Serializable;
import java.util.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ProxyServerConfig implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 更新配置后保证在其他线程即时生效
*/
private static ProxyServerConfig instance = new ProxyServerConfig();;
/**
* 代理服务器为各个代理客户端(key)开启对应的端口列表(value)
*/
private volatile Map<String, List<Integer>> clientInetPortMapping = new HashMap<String, List<Integer>>();
/**
* 代理服务器上的每个对外端口(key)对应的代理客户端背后的真实服务器信息(value)
*/
private volatile Map<Integer, String> inetPortLanInfoMapping = new HashMap<Integer, String>();
public void addClientConfig(ProxyClientConfig clientConfig) {
String clientKey = clientConfig.getClientKey();
List<Integer> ports = new ArrayList<>();
for (ProxyClientConfig.Proxy proxy : clientConfig.getProxy()) {
ports.add(proxy.getServerPort());
inetPortLanInfoMapping.put(proxy.getServerPort(), proxy.getClientInfo());
}
clientInetPortMapping.put(clientKey, ports);
}
/**
* 获取代理客户端对应的代理服务器端口
*
* @param clientKey
* @return
*/
public List<Integer> getClientInetPorts(String clientKey) {
return clientInetPortMapping.get(clientKey);
}
/**
* 根据代理服务器端口获取后端服务器代理信息
*
* @param port
* @return
*/
public String getLanInfo(Integer port) {
return inetPortLanInfoMapping.get(port);
}
/**
* 返回需要绑定在代理服务器的端口(用于用户请求)
*
* @return
*/
public List<Integer> getUserPorts() {
List<Integer> ports = new ArrayList<Integer>();
Iterator<Integer> ite = inetPortLanInfoMapping.keySet().iterator();
while (ite.hasNext()) {
ports.add(ite.next());
}
return ports;
}
public static ProxyServerConfig getInstance() {
return instance;
}
/**
* 代理客户端与其后面真实服务器映射关系
*
* @author fengfei
*
*/
@Data
public static class ClientProxyMapping {
/**
* 代理服务器端口
*/
private Integer inetPort;
/**
* 需要代理的网络信息(代理客户端能够访问),格式 192.168.1.99:80 (必须带端口)
*/
private String lan;
}
}
@@ -0,0 +1,72 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.core;
import fun.asgc.neutrino.proxy.server.monitor.MetricsCollector;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import java.net.InetSocketAddress;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class BytesMetricsHandler extends ChannelDuplexHandler {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
MetricsCollector metricsCollector = MetricsCollector.getCollector(sa.getPort());
metricsCollector.incrementReadBytes(((ByteBuf) msg).readableBytes());
metricsCollector.incrementReadMsgs(1);
ctx.fireChannelRead(msg);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
MetricsCollector metricsCollector = MetricsCollector.getCollector(sa.getPort());
metricsCollector.incrementWroteBytes(((ByteBuf) msg).readableBytes());
metricsCollector.incrementWroteMsgs(1);
super.write(ctx, msg, promise);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
MetricsCollector.getCollector(sa.getPort()).getChannels().incrementAndGet();
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
MetricsCollector.getCollector(sa.getPort()).getChannels().decrementAndGet();
super.channelInactive(ctx);
}
}
@@ -0,0 +1,151 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.core;
import fun.asgc.neutrino.core.annotation.Autowired;
import fun.asgc.neutrino.core.annotation.Bean;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.runner.ApplicationRunner;
import fun.asgc.neutrino.core.util.FileUtil;
import fun.asgc.neutrino.proxy.core.IdleCheckHandler;
import fun.asgc.neutrino.proxy.core.ProxyMessageDecoder;
import fun.asgc.neutrino.proxy.core.ProxyMessageEncoder;
import fun.asgc.neutrino.proxy.server.config.ProxyConfig;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslHandler;
import lombok.extern.slf4j.Slf4j;
import javax.net.ssl.*;
import java.io.InputStream;
import java.security.KeyStore;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
@Component
public class ProxyServerRunner implements ApplicationRunner {
@Autowired
private ProxyConfig proxyConfig;
@Autowired("serverBossGroup")
private NioEventLoopGroup serverBossGroup;
@Autowired("serverWorkerGroup")
private NioEventLoopGroup serverWorkerGroup;
@Override
public void run(String[] args) {
startProxyServer();
startProxyServerForSSL();
}
/**
* 启动代理服务
*/
private void startProxyServer() {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
proxyServerCommonInitHandler(ch);
}
});
try {
bootstrap.bind(proxyConfig.getServer().getPort()).sync();
log.info("代理服务启动,端口:{}", proxyConfig.getServer().getPort());
} catch (Exception e) {
log.error("代理服务异常", e);
}
}
private void startProxyServerForSSL() {
if (null == proxyConfig.getServer().getSslPort()) {
return;
}
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup)
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(createSslHandler());
proxyServerCommonInitHandler(ch);
}
});
try {
bootstrap.bind(proxyConfig.getServer().getSslPort()).sync();
log.info("代理服务启动,SSL端口: {}", proxyConfig.getServer().getSslPort());
} catch (Exception e) {
log.error("代理服务异常", e);
}
}
private ChannelHandler createSslHandler() {
try {
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getServer().getJksPath());
SSLContext serverContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, proxyConfig.getServer().getKeyStorePassword().toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, proxyConfig.getServer().getKeyManagerPassword().toCharArray());
TrustManager[] trustManagers = null;
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
SSLEngine sslEngine = serverContext.createSSLEngine();
sslEngine.setUseClientMode(false);
sslEngine.setNeedClientAuth(false);
return new SslHandler(sslEngine);
} catch (Exception e) {
log.error("创建SSL处理器失败", e);
e.printStackTrace();
}
return null;
}
private void proxyServerCommonInitHandler(SocketChannel ch) {
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleCheckHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
ch.pipeline().addLast(new ServerChannelHandler());
}
@Bean
public NioEventLoopGroup serverBossGroup() {
return new NioEventLoopGroup();
}
@Bean
public NioEventLoopGroup serverWorkerGroup() {
return new NioEventLoopGroup();
}
}
@@ -0,0 +1,92 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.core;
import fun.asgc.neutrino.core.base.DefaultDispatcher;
import fun.asgc.neutrino.core.base.Dispatcher;
import fun.asgc.neutrino.core.util.BeanManager;
import fun.asgc.neutrino.core.util.LockUtil;
import fun.asgc.neutrino.proxy.core.*;
import fun.asgc.neutrino.proxy.server.util.ProxyChannelManager;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
private static volatile Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher;
public ServerChannelHandler() {
LockUtil.doubleCheckProcess(() -> null == dispatcher,
ServerChannelHandler.class,
() -> {
dispatcher = new DefaultDispatcher<>("消息调度器",
BeanManager.getBeanListBySuperClass(ProxyMessageHandler.class),
proxyMessage -> ProxyDataTypeEnum.of((int)proxyMessage.getType()) == null ? null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName());
});
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
dispatcher.dispatch(ctx, proxyMessage);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (userChannel != null) {
userChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (userChannel != null && userChannel.isActive()) {
String clientKey = ctx.channel().attr(Constants.CLIENT_KEY).get();
String userId = ctx.channel().attr(Constants.USER_ID).get();
Channel cmdChannel = ProxyChannelManager.getCmdChannel(clientKey);
if (cmdChannel != null) {
ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, userId);
}
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
userChannel.close();
} else {
ProxyChannelManager.removeCmdChannel(ctx.channel());
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
@@ -0,0 +1,153 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.core;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.server.config.ProxyServerConfig;
import fun.asgc.neutrino.proxy.server.util.ProxyChannelManager;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicLong;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class UserChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
private static AtomicLong userIdProducer = new AtomicLong(0);
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
// 通知代理客户端
Channel userChannel = ctx.channel();
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String userId = ProxyChannelManager.getUserChannelUserId(userChannel);
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(userId, bytes));
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel userChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
String userId = newUserId();
String lanInfo = ProxyServerConfig.getInstance().getLanInfo(sa.getPort());
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
userChannel.config().setOption(ChannelOption.AUTO_READ, false);
ProxyChannelManager.addUserChannelToCmdChannel(cmdChannel, userId, userChannel);
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(userId).setData(lanInfo.getBytes()));
}
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel userChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
// 用户连接断开,从控制连接中移除
String userId = ProxyChannelManager.getUserChannelUserId(userChannel);
ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, userId);
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null && proxyChannel.isActive()) {
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
proxyChannel.attr(Constants.CLIENT_KEY).remove();
proxyChannel.attr(Constants.USER_ID).remove();
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 通知客户端,用户连接已经断开
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(userId));
}
}
super.channelInactive(ctx);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel userChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, userChannel.isWritable());
}
}
super.channelWritabilityChanged(ctx);
}
/**
* 为用户连接产生ID
*
* @return
*/
private static String newUserId() {
return String.valueOf(userIdProducer.incrementAndGet());
}
}
@@ -0,0 +1,126 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.handler;
import com.alibaba.fastjson.JSONObject;
import fun.asgc.neutrino.core.annotation.Autowired;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.core.util.BeanManager;
import fun.asgc.neutrino.proxy.core.*;
import fun.asgc.neutrino.proxy.server.config.ProxyConfig;
import fun.asgc.neutrino.proxy.server.config.ProxyServerConfig;
import fun.asgc.neutrino.proxy.server.core.BytesMetricsHandler;
import fun.asgc.neutrino.proxy.server.core.UserChannelHandler;
import fun.asgc.neutrino.proxy.server.util.ProxyChannelManager;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.net.BindException;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
@Match(type = Constants.ProxyDataTypeName.AUTH)
@Component
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
@Autowired("serverBossGroup")
private NioEventLoopGroup serverBossGroup;
@Autowired("serverWorkerGroup")
private NioEventLoopGroup serverWorkerGroup;
@Autowired
private ProxyConfig proxyConfig;
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
ProxyClientConfig clientConfig = JSONObject.parseObject(proxyMessage.getInfo(), ProxyClientConfig.class);
String clientKey = clientConfig.getClientKey();
if (!proxyConfig.getLicenseMap().containsKey(clientKey)) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.AUTH_FAILED, "无效的clientKey"));
ctx.channel().close();
return;
}
if (proxyConfig.getLicenseMap().get(clientKey) != -1 && clientConfig.getProxy().size() > proxyConfig.getLicenseMap().get(clientKey)) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.AUTH_FAILED, "代理端口数超过license限制"));
ctx.channel().close();
return;
}
ProxyServerConfig.getInstance().addClientConfig(clientConfig);
List<Integer> ports = ProxyServerConfig.getInstance().getClientInetPorts(clientKey);
if (ports == null) {
ctx.channel().close();
return;
}
Channel channel = ProxyChannelManager.getCmdChannel(clientKey);
if (channel != null) {
ctx.channel().close();
return;
}
ProxyChannelManager.addCmdChannel(ports, clientKey, ctx.channel());
startUserPortServer(ports);
}
@Override
public String name() {
return ProxyDataTypeEnum.AUTH.getDesc();
}
private void startUserPortServer(List<Integer> ports) {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup)
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new UserChannelHandler());
}
});
for (int port : ports) {
try {
bootstrap.bind(port).get();
log.info("绑定用户端口: {}", port);
} catch (Exception ex) {
// BindException表示该端口已经绑定过
if (!(ex.getCause() instanceof BindException)) {
throw new RuntimeException(ex);
}
}
}
}
}
@@ -0,0 +1,80 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.handler;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.server.util.ProxyChannelManager;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Match(type = Constants.ProxyDataTypeName.CONNECT)
@Component
public class ProxyMessageConnectHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
String info = proxyMessage.getInfo();
if (info == null) {
ctx.channel().close();
return;
}
String[] tokens = info.split("@");
if (tokens.length != 2) {
ctx.channel().close();
return;
}
Channel cmdChannel = ProxyChannelManager.getCmdChannel(tokens[1]);
if (cmdChannel == null) {
ctx.channel().close();
return;
}
Channel userChannel = ProxyChannelManager.getUserChannel(cmdChannel, tokens[0]);
if (userChannel != null) {
ctx.channel().attr(Constants.USER_ID).set(tokens[0]);
ctx.channel().attr(Constants.CLIENT_KEY).set(tokens[1]);
ctx.channel().attr(Constants.NEXT_CHANNEL).set(userChannel);
userChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
// 代理客户端与后端服务器连接成功,修改用户连接为可读状态
userChannel.config().setOption(ChannelOption.AUTO_READ, true);
}
}
@Override
public String name() {
return ProxyDataTypeEnum.CONNECT.getDesc();
}
}
@@ -0,0 +1,81 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.handler;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.server.util.ProxyChannelManager;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Match(type = Constants.ProxyDataTypeName.DISCONNECT)
@Component
public class ProxyMessageDisconnectHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
String clientKey = ctx.channel().attr(Constants.CLIENT_KEY).get();
// 代理连接没有连上服务器由控制连接发送用户端断开连接消息
if (clientKey == null) {
String userId = proxyMessage.getInfo();
Channel userChannel = ProxyChannelManager.removeUserChannelFromCmdChannel(ctx.channel(), userId);
if (userChannel != null) {
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
return;
}
Channel cmdChannel = ProxyChannelManager.getCmdChannel(clientKey);
if (cmdChannel == null) {
return;
}
Channel userChannel = ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, ctx.channel().attr(Constants.USER_ID).get());
if (userChannel != null) {
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
ctx.channel().attr(Constants.NEXT_CHANNEL).remove();
ctx.channel().attr(Constants.CLIENT_KEY).remove();
ctx.channel().attr(Constants.USER_ID).remove();
}
}
@Override
public String name() {
return ProxyDataTypeEnum.DISCONNECT.getDesc();
}
}
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.handler;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import io.netty.channel.ChannelHandlerContext;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Match(type = Constants.ProxyDataTypeName.HEARTBEAT)
@Component
public class ProxyMessageHeartbeatHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
}
@Override
public String name() {
return ProxyDataTypeEnum.HEARTBEAT.getDesc();
}
}
@@ -0,0 +1,59 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.handler;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Match(type = Constants.ProxyDataTypeName.TRANSFER)
@Component
public class ProxyMessageTransferHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (userChannel != null) {
ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
buf.writeBytes(proxyMessage.getData());
userChannel.writeAndFlush(buf);
}
}
@Override
public String name() {
return ProxyDataTypeEnum.TRANSFER.getDesc();
}
}
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.monitor;
import lombok.Data;
import java.io.Serializable;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Data
public class Metrics implements Serializable {
private static final long serialVersionUID = 1L;
private int port;
private long readBytes;
private long wroteBytes;
private long readMsgs;
private long wroteMsgs;
private int channels;
private long timestamp;
}
@@ -0,0 +1,148 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.monitor;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class MetricsCollector {
private static Map<Integer, MetricsCollector> metricsCollectors = new ConcurrentHashMap<Integer, MetricsCollector>();
private Integer port;
private AtomicLong readBytes = new AtomicLong();
private AtomicLong wroteBytes = new AtomicLong();
private AtomicLong readMsgs = new AtomicLong();
private AtomicLong wroteMsgs = new AtomicLong();
private AtomicInteger channels = new AtomicInteger();
private MetricsCollector() {
}
public static MetricsCollector getCollector(Integer port) {
MetricsCollector collector = metricsCollectors.get(port);
if (collector == null) {
synchronized (metricsCollectors) {
collector = metricsCollectors.get(port);
if (collector == null) {
collector = new MetricsCollector();
collector.setPort(port);
metricsCollectors.put(port, collector);
}
}
}
return collector;
}
public static List<Metrics> getAndResetAllMetrics() {
List<Metrics> allMetrics = new ArrayList<Metrics>();
Iterator<Entry<Integer, MetricsCollector>> ite = metricsCollectors.entrySet().iterator();
while (ite.hasNext()) {
allMetrics.add(ite.next().getValue().getAndResetMetrics());
}
return allMetrics;
}
public static List<Metrics> getAllMetrics() {
List<Metrics> allMetrics = new ArrayList<Metrics>();
Iterator<Entry<Integer, MetricsCollector>> ite = metricsCollectors.entrySet().iterator();
while (ite.hasNext()) {
allMetrics.add(ite.next().getValue().getMetrics());
}
return allMetrics;
}
public Metrics getAndResetMetrics() {
Metrics metrics = new Metrics();
metrics.setChannels(channels.get());
metrics.setPort(port);
metrics.setReadBytes(readBytes.getAndSet(0));
metrics.setWroteBytes(wroteBytes.getAndSet(0));
metrics.setTimestamp(System.currentTimeMillis());
metrics.setReadMsgs(readMsgs.getAndSet(0));
metrics.setWroteMsgs(wroteMsgs.getAndSet(0));
return metrics;
}
public Metrics getMetrics() {
Metrics metrics = new Metrics();
metrics.setChannels(channels.get());
metrics.setPort(port);
metrics.setReadBytes(readBytes.get());
metrics.setWroteBytes(wroteBytes.get());
metrics.setTimestamp(System.currentTimeMillis());
metrics.setReadMsgs(readMsgs.get());
metrics.setWroteMsgs(wroteMsgs.get());
return metrics;
}
public void incrementReadBytes(long bytes) {
readBytes.addAndGet(bytes);
}
public void incrementWroteBytes(long bytes) {
wroteBytes.addAndGet(bytes);
}
public void incrementReadMsgs(long msgs) {
readMsgs.addAndGet(msgs);
}
public void incrementWroteMsgs(long msgs) {
wroteMsgs.addAndGet(msgs);
}
public AtomicInteger getChannels() {
return channels;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
}
@@ -0,0 +1,201 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.util;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.server.config.ProxyServerConfig;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class ProxyChannelManager {
private static final AttributeKey<Map<String, Channel>> USER_CHANNELS = AttributeKey.newInstance("user_channels");
private static final AttributeKey<String> REQUEST_LAN_INFO = AttributeKey.newInstance("request_lan_info");
private static final AttributeKey<List<Integer>> CHANNEL_PORT = AttributeKey.newInstance("channel_port");
private static final AttributeKey<String> CHANNEL_CLIENT_KEY = AttributeKey.newInstance("channel_client_key");
private static Map<Integer, Channel> portCmdChannelMapping = new ConcurrentHashMap<Integer, Channel>();
private static Map<String, Channel> cmdChannels = new ConcurrentHashMap<String, Channel>();
/**
* 增加代理服务器端口与代理控制客户端连接的映射关系
*
* @param ports
* @param channel
*/
public static void addCmdChannel(List<Integer> ports, String clientKey, Channel channel) {
if (ports == null) {
throw new IllegalArgumentException("port can not be null");
}
// 客户端(proxy-client)相对较少,这里同步的比较重
// 保证服务器对外端口与客户端到服务器的连接关系在临界情况时调用removeChannel(Channel channel)时不出问题
synchronized (portCmdChannelMapping) {
for (int port : ports) {
portCmdChannelMapping.put(port, channel);
}
}
channel.attr(CHANNEL_PORT).set(ports);
channel.attr(CHANNEL_CLIENT_KEY).set(clientKey);
channel.attr(USER_CHANNELS).set(new ConcurrentHashMap<String, Channel>());
cmdChannels.put(clientKey, channel);
}
/**
* 代理客户端连接断开后清除关系
*
* @param channel
*/
public static void removeCmdChannel(Channel channel) {
if (channel.attr(CHANNEL_PORT).get() == null) {
return;
}
String clientKey = channel.attr(CHANNEL_CLIENT_KEY).get();
Channel channel0 = cmdChannels.remove(clientKey);
if (channel != channel0) {
cmdChannels.put(clientKey, channel);
}
List<Integer> ports = channel.attr(CHANNEL_PORT).get();
for (int port : ports) {
Channel proxyChannel = portCmdChannelMapping.remove(port);
if (proxyChannel == null) {
continue;
}
// 在执行断连之前新的连接已经连上来了
if (proxyChannel != channel) {
portCmdChannelMapping.put(port, proxyChannel);
}
}
if (channel.isActive()) {
channel.close();
}
Map<String, Channel> userChannels = getUserChannels(channel);
Iterator<String> ite = userChannels.keySet().iterator();
while (ite.hasNext()) {
Channel userChannel = userChannels.get(ite.next());
if (userChannel.isActive()) {
userChannel.close();
}
}
}
public static Channel getCmdChannel(Integer port) {
return portCmdChannelMapping.get(port);
}
public static Channel getCmdChannel(String clientKey) {
return cmdChannels.get(clientKey);
}
/**
* 增加用户连接与代理客户端连接关系
*
* @param userId
* @param userChannel
*/
public static void addUserChannelToCmdChannel(Channel cmdChannel, String userId, Channel userChannel) {
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
String lanInfo = ProxyServerConfig.getInstance().getLanInfo(sa.getPort());
userChannel.attr(Constants.USER_ID).set(userId);
userChannel.attr(REQUEST_LAN_INFO).set(lanInfo);
cmdChannel.attr(USER_CHANNELS).get().put(userId, userChannel);
}
/**
* 删除用户连接与代理客户端连接关系
*
* @param userId
* @return
*/
public static Channel removeUserChannelFromCmdChannel(Channel cmdChannel, String userId) {
if (cmdChannel.attr(USER_CHANNELS).get() == null) {
return null;
}
synchronized (cmdChannel) {
return cmdChannel.attr(USER_CHANNELS).get().remove(userId);
}
}
/**
* 根据代理客户端连接与用户编号获取用户连接
*
* @param userId
* @return
*/
public static Channel getUserChannel(Channel cmdChannel, String userId) {
return cmdChannel.attr(USER_CHANNELS).get().get(userId);
}
/**
* 获取用户编号
*
* @param userChannel
* @return
*/
public static String getUserChannelUserId(Channel userChannel) {
return userChannel.attr(Constants.USER_ID).get();
}
/**
* 获取用户请求的内网IP端口信息
*
* @param userChannel
* @return
*/
public static String getUserChannelRequestLanInfo(Channel userChannel) {
return userChannel.attr(REQUEST_LAN_INFO).get();
}
/**
* 获取代理控制客户端连接绑定的所有用户连接
*
* @param cmdChannel
* @return
*/
public static Map<String, Channel> getUserChannels(Channel cmdChannel) {
return cmdChannel.attr(USER_CHANNELS).get();
}
}
@@ -0,0 +1,22 @@
application:
name: neutrino-proxy-server
proxy:
protocol:
max-frame-length: 2097152
length-field-offset: 0
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 60
write-idle-time: 40
all-idle-time-seconds: 0
server:
port: 9000
ssl-port: 9002
key-store-password: 123456
key-manager-password: 123456
jks-path: classpath:/test.jks
license:
79419a1a8691413aa5e845b9e3e90051: 3
9352b1c25f564c81a5677131d7769876: 2
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_FILE" value="/work/project/neutrino-proxy/server.log"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{50} - %msg%n"/>
<property name="ENCODE" value="utf8" />
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.zip</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
<logger name="com.example.logback" level="warn"/>
<logger name="io.netty" level="warn"/>
</configuration>
Binary file not shown.
+169
View File
@@ -0,0 +1,169 @@
<?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>fun.asgc.neutrino</groupId>
<artifactId>neutrino-proxy</artifactId>
<packaging>pom</packaging>
<version>${revision}</version>
<modules>
<module>neutrino-core</module>
<module>neutrino-proxy-core</module>
<module>neutrino-proxy-client</module>
<module>neutrino-proxy-server</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<revision>1.0-SNAPSHOT</revision>
<java.version>1.8</java.version>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<maven-flatten.version>1.1.0</maven-flatten.version>
<org.projectlombok.version>1.16.14</org.projectlombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.22.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.25</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- 添加flatten-maven-plugin插件 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>${maven-flatten.version}</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>