Compare commits

..
93 Commits
Author SHA1 Message Date
aoshiguchen 4c828f814c 服务端、客户端认证逻辑调整 2023-06-03 03:19:45 +08:00
aoshiguchen 5a65eebfd3 服务端、客户端认证逻辑调整 2023-06-03 01:34:22 +08:00
aoshiguchen 5a3375ce80 服务端、客户端认证逻辑调整. 2023-06-03 01:30:52 +08:00
aoshiguchen eb277b414a 服务端、客户端认证逻辑调整. 2023-06-03 00:47:30 +08:00
aoshiguchen fc4097529f 1、服务端、客户端认证逻辑调整.
2、解决端口映射被禁用时,客户端启动依然能访问的问题
2023-06-02 23:26:13 +08:00
aoshiguchen 375687aa5f 1、服务端、客户端认证逻辑调整.
2、解决端口映射被禁用时,客户端启动依然能访问的问题.
2023-06-02 22:46:15 +08:00
aoshiguchen 32504d33af 1、服务端、客户端认证逻辑调整.
2、解决端口映射被禁用时,客户端启动依然能访问的问题
2023-06-02 22:36:39 +08:00
aoshiguchen bcd429d54f 客户端连接时认证失败,直接断开连接 2023-06-02 22:24:20 +08:00
aoshiguchen 2ec87abda9 客户端重连机制调整,启动首次连接失败也进行重连 2023-06-02 22:09:01 +08:00
aoshiguchen 8c2e1b8f6c 客户端重连优化. 2023-06-02 21:20:19 +08:00
aoshiguchen e2195ab966 客户端重连优化 2023-06-01 22:34:41 +08:00
aoshiguchen 2d0b9132e9 协议枚举调整,维护协议关联的下层基础协议 2023-05-31 22:42:16 +08:00
aoshiguchen 5a502115b1 代码优化,避免新增端口映射时不断创建新的ServerBootstrap实例. 2023-05-31 22:32:22 +08:00
aoshiguchen a972e0f309 代码优化,避免新增端口映射时不断创建新的ServerBootstrap实例 2023-05-31 22:29:44 +08:00
aoshiguchen b52225937c http、https代理线程池分开 2023-05-31 22:23:12 +08:00
aoshiguchen 98737f0e9d 服务端代理相关命名调整,使得表义更加准确. 2023-05-31 21:41:22 +08:00
aoshiguchen ea67c2bce0 服务端代理相关命名调整,使得表义更加准确 2023-05-31 21:34:53 +08:00
aoshiguchen 7dda047d51 解决新增报不支持的协议HTTPS的问题 2023-05-29 22:57:16 +08:00
aoshiguchen b518dbfd58 更新官网 2023-05-29 22:23:19 +08:00
aoshiguchen 7054a1df0e 官网文档调整. 2023-05-27 12:05:43 +08:00
aoshiguchen da0d1946c6 更新README 2023-05-27 11:10:27 +08:00
aoshiguchen afff036641 新增HTTPS支持 2023-05-27 00:47:01 +08:00
aoshiguchen 9944e60825 启动脚本调整 2023-05-23 20:09:44 +08:00
aoshiguchen 74ff0c3b44 Merge branch 'feature/20230520' into dev 2023-05-20 14:28:53 +08:00
aoshiguchen 5bb6a09ce2 更改日志配置 2023-05-20 14:21:10 +08:00
aoshiguchen 3e9e7cb370 server端代码优化,将代理隧道配置分离出来 2023-05-03 18:07:12 +08:00
zCans 3cf6bcbe6e 首页图表剔除冗余 2023-04-24 22:52:27 +08:00
aoshiguchen 8f9375152e 管理后台首页折线图展示问题修复 2023-04-24 22:46:57 +08:00
aoshiguchen 501012d9bc 更新快速上手文档. 2023-04-22 15:44:50 +08:00
aoshiguchen f2b560fa1f 客户端优化,解决长时间数据写入,导致指令通道被关闭的问题 2023-04-20 23:04:39 +08:00
aoshiguchen 2b95967820 配置调整&服务端代码优化 2023-04-20 22:22:50 +08:00
aoshiguchen 9b1fafd379 更新初始化sql 2023-04-20 19:03:02 +08:00
qiuyue e41e346dff feat: 百度统计 2023-04-19 20:35:27 +08:00
aoshiguchen fff5c0121a 移除_solon_plugin模块,引入solon-plugins外部依赖 2023-04-15 03:43:14 +08:00
aoshiguchen 406edaf172 新增docker-compose.yml 2023-04-14 10:24:12 +08:00
aoshiguchen b6cd8ab3a2 解决通过映射域名,代理文件上传接口失效的问题 2023-04-13 21:34:03 +08:00
aoshiguchen 18653410f9 更新官网文档. 2023-04-13 16:40:31 +08:00
傲世孤尘 6d6e58af52 !25 删除配置
Merge pull request !25 from Tany/dev
2023-04-11 07:43:29 +00:00
谭勇 e9e9ce8c90 干掉配置数据库驱动,根据数据库类型直接添加 2023-04-11 15:41:10 +08:00
傲世孤尘 1e1b376883 !24 干掉配置数据库驱动,根据数据库类型直接添加
Merge pull request !24 from Tany/dev
2023-04-11 07:26:10 +00:00
aoshiguchen 9cb5eb3225 logback编码调整。 2023-04-11 15:24:40 +08:00
谭勇 5b38bb6b22 干掉配置数据库驱动,根据数据库类型直接添加 2023-04-11 15:20:03 +08:00
aoshiguchen 2b28d4eff5 调整Dockerfile文件位置. 2023-04-10 17:18:54 +08:00
aoshiguchen 42f39ddb0f 更新docker脚本. 2023-04-09 16:36:08 +08:00
aoshiguchen fabc575c9e 调整docker脚本位置 2023-04-09 16:04:39 +08:00
aoshiguchen 3e1f14917b 更新README.MD。 2023-04-07 20:14:09 +08:00
aoshiguchen f17c341325 更新README.MD 2023-04-07 20:09:10 +08:00
aoshiguchen 5bf21b7c2c 异常处理优化 2023-04-07 15:20:48 +08:00
aoshiguchen 252a6ac7c1 新增性能测试文档 2023-04-07 15:14:12 +08:00
aoshiguchen 0b78367b9f 新增性能测试文档. 2023-04-07 13:19:54 +08:00
aoshiguchen bb91abae00 域名映射逻辑优化,支持websocket. 2023-04-07 12:46:15 +08:00
aoshiguchen 1e4312fcb1 域名映射逻辑优化,支持websocket 2023-04-07 10:48:35 +08:00
傲世孤尘 f9009bb869 !23 脚本层级变更导致路径问题修复
Merge pull request !23 from 霓虹深处/dev
2023-04-06 09:05:38 +00:00
谢飞 c4e1ae2a17 bugfix: 脚本层级变更导致路径问题修复 2023-04-06 16:59:30 +08:00
aoshiguchen d2fc7b6d45 更新快速使用文档. 2023-04-06 16:25:30 +08:00
傲世孤尘 4d4c06e4b1 !22 client dockerfile
Merge pull request !22 from Tany/dev
2023-04-06 05:10:40 +00:00
谭勇 98cc7ead6e client dockerfile 2023-04-06 13:02:50 +08:00
aoshiguchen e1be4523ed http代理配置优化 2023-04-06 12:05:18 +08:00
aoshiguchen 99ddfa65f3 更新官网文档. 2023-04-06 11:00:14 +08:00
aoshiguchen 31e36d1939 新增宣传文档. 2023-04-06 00:18:34 +08:00
aoshiguchen f46ae98867 http代理优化. 2023-04-05 23:34:59 +08:00
aoshiguchen f87370397e http代理优化. 2023-04-05 22:55:00 +08:00
aoshiguchen ce6634d51c http代理优化. 2023-04-05 21:11:10 +08:00
aoshiguchen f43d447ea0 更新todolist 2023-04-05 21:07:14 +08:00
aoshiguchen baafc9bf21 更新快速上手文档 2023-04-05 20:59:26 +08:00
aoshiguchen 56226831f9 更新快速上手文档. 2023-04-05 20:56:24 +08:00
aoshiguchen b3e15225b3 更新SQL脚本 2023-04-05 20:15:28 +08:00
Yohanes e725cdbb28 端口映射增加描述字段 2023-04-05 19:59:52 +08:00
Yohanes 480867e655 sqlite初始化脚本及增量脚本 增加描述字段 2023-04-05 19:52:39 +08:00
Yohanes a5c568392b 参数检查工具类增加最大长度检查方法,异常常量池增加超出长度提示模板 2023-04-05 19:49:49 +08:00
aoshiguchen bbb0e5dca3 新增官网文档。 2023-04-05 19:07:38 +08:00
aoshiguchen 2b80279256 新增官网文档 2023-04-05 19:04:28 +08:00
傲世孤尘 b7b4432d09 !21 update域名空字符未判空
Merge pull request !21 from 陈賝/dev
2023-04-05 09:47:35 +00:00
chenchen a7269229b0 update域名空字符未判空 2023-04-05 17:42:10 +08:00
aoshiguchen f84695d665 配置调整. 2023-04-05 17:41:17 +08:00
aoshiguchen 4258845985 脚本调整. 2023-04-05 17:36:52 +08:00
aoshiguchen 87e6c066ed 更新贡献者列表. 2023-04-04 23:17:07 +08:00
aoshiguchen 2ea8a5890f 更新贡献者列表 2023-04-04 23:16:30 +08:00
傲世孤尘 89ea802245 !18 feat: admin打包脚本适配windows
Merge pull request !18 from 霓虹深处/dev
2023-04-04 15:10:41 +00:00
aoshiguchen 88387003e3 更新初始化SQL 2023-04-04 23:09:45 +08:00
aoshiguchen b1797686f1 首页6大特性调整. 2023-04-04 23:05:34 +08:00
aoshiguchen 9a08a7c72e 首页6大特性调整 2023-04-04 23:02:46 +08:00
aoshiguchen 0f15bbc94d 首页6大特性调整. 2023-04-04 22:59:15 +08:00
aoshiguchen e454510967 官网首页增加特别赞助栏目 2023-04-04 22:50:21 +08:00
aoshiguchen 090c129f60 首页二维码图片调整 2023-04-04 22:45:23 +08:00
aoshiguchen 8b41038d13 官网首页增加友情开源项目Solon 2023-04-04 22:40:30 +08:00
aoshiguchen 980d6d1a46 去除多余的文案 2023-04-04 17:56:34 +08:00
傲世孤尘 6b805089ba !20 app.yml增加对容器环境变量的支持
Merge pull request !20 from Tany/dev
2023-04-04 09:55:16 +00:00
谭勇 d81cbb3f7b app.yml增加对容器环境变量的支持,可不挂载config直接定义配置 2023-04-04 17:53:49 +08:00
谭勇 b6b91b69ab Merge remote-tracking branch 'me/dev' into dev
# Conflicts:
#	neutrino-proxy-server/src/main/resources/app.yml
2023-04-04 17:52:51 +08:00
谭勇 3185934ff9 app.yml增加对容器环境变量的支持,可不挂载config直接定义配置 2023-04-04 17:44:08 +08:00
谢飞 238eb64c04 feat: admin打包脚本适配windows 2023-04-04 10:59:45 +08:00
aoshiguchen 31e3d56f9b . 2023-04-04 10:20:14 +08:00
126 changed files with 1890 additions and 1518 deletions
+2 -1
View File
@@ -54,4 +54,5 @@ hs_err_pid*
**/memo/**
neutrino-proxy-vuepress/deploy.sh
neutrino-proxy-vuepress/deploy.sh
.NEUTRINO_PROXY_CLIENT_ID
+55 -29
View File
@@ -4,12 +4,21 @@
<p align="center">
<a href='https://gitee.com/dromara/neutrino-proxy/stargazers'><img src='https://gitee.com/dromara/neutrino-proxy/badge/star.svg?theme=dark' alt='star'></img></a>
<a href='https://gitee.com/dromara/neutrino-proxy/members'><img src='https://gitee.com/dromara/neutrino-proxy/badge/fork.svg?theme=dark' alt='fork'></img></a>
<a target="_blank" href="https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html">
<img src="https://img.shields.io/badge/JDK-8+-red.svg" />
</a>
<a href="./LICENSE">
<img src="https://img.shields.io/badge/license-MIT-red" alt="license MIT">
</a>
</p>
# 1、简介
- 中微子代理(neutrino-proxy)是一个基于netty的、开源的java内网穿透项目。
- 技术栈:Solon、MybatisPlus、Netty
- 遵循MIT许可,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
- 官网地址1https://neutrino-proxy.dromara.org
- 官网地址2https://dromara.gitee.io/neutrino-proxy
- 快速上手请[点击这里](https://dromara.gitee.io/neutrino-proxy/pages/793dcb)
# 2、名称由来
中微子,是轻子的一种,是组成自然界的最基本的粒子之一。它十分微小、不带电,可自由穿过地球,以接近光速运动,与其他物质的相互作用十分微弱,号称宇宙间的“隐身人”。
@@ -19,11 +28,11 @@
因此以中微子命名,寓意着中微子代理拥有中微子"安全"、"快速"、"穿透力强"的特点。
# 3、运行示例
![首页](assets/run-example/home.png)
![用户管理](assets/run-example/user-manager1.png)
![端口池管理](assets/run-example/port-pool1.png)
![License管理](assets/run-example/license1.png)
![端口映射管理](assets/run-example/port-mapping1.png)
![客户端启动示例](assets/run-example/client-run1.png)
# 4、项目结构
- neutrino-proxy
@@ -38,38 +47,52 @@
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
```
## 5.2、修改服务端配置(application.yml
## 5.2、修改服务端配置(app.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
data:
# 数据库配置(支持mysql
type: sqlite
url: jdbc:sqlite:data.db
driver-class: org.sqlite.JDBC
username:
password:
neutrino:
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
tunnel:
boss-thread-count: 2
work-thread-count: 10
# 服务端端口,用于保持与客户端的连接,非SSL
port: ${OPEN_PORT:9000}
# 服务端端口,用于保持与客户端的连接,SSL,需要jks证书文件,若不需要ssl支持,可不配置
ssl-port: ${SSL_PORT:9002}
# 证书配置,用于隧道通信SSL加密
key-store-password: ${STORE_PASS:123456}
key-manager-password: ${MGR_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
server:
boss-thread-count: 5
work-thread-count: 20
# HTTP代理端口,默认80,也可以用其他端口,走nginx转发
http-proxy-port: ${HTTP_PROXY_PORT:80}
# HTTPS代理端口,默认443,也可以用其他端口,走nginx转发
https-proxy-port: ${HTTPS_PROXY_PORT:443}
# 如果不配置,则不支持域名映射
domain-name: ${DOMAIN_NAME:}
# 证书配置,用于支持HTTPS
key-store-password: ${HTTPS_STORE_PASS:}
jks-path: ${HTTPS_JKS_PATH:}
data:
# 数据库配置(支持mysql
type: sqlite
url: jdbc:sqlite:data.db
driver-class: org.sqlite.JDBC
username:
password:
```
## 5.3、启动服务端
@@ -183,6 +206,9 @@ neutrino.proxy.client.license-key=b0a907332b474b25897c4dcb31fc7eb6
<a href="https://gitee.com/ixxxk" target="_blank">
<img src="assets/developer/tany.png" width="12%">
</a>
<a href="https://gitee.com/zoo-plus" target="_blank">
<img src="assets/developer/zoo-plus.png" width="12%">
</a>
</p>
# ❤️ 感谢
-39
View File
@@ -1,39 +0,0 @@
<?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>
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>2.2.2</version>
</parent>
<groupId>fun.asgc</groupId>
<artifactId>job-solon-plugin</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--quartz-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.1</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.15</version>
</dependency>
</dependencies>
</project>
@@ -1,34 +0,0 @@
package fun.asgc.solon.extend.job;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
public class CustomThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public CustomThreadFactory(String prefix) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = prefix + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon()) {
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
@@ -1,17 +0,0 @@
package fun.asgc.solon.extend.job;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
public interface IJobCallback {
/**
* 执行日志
* @param jobInfo
* @param param
* @param throwable
*/
void executeLog(JobInfo jobInfo, String param, Throwable throwable);
}
@@ -1,34 +0,0 @@
package fun.asgc.solon.extend.job;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
public interface IJobExecutor {
/**
* 初始化
* @throws JobException
*/
void init() throws Exception;
/**
* 新增job
* @param jobInfo
*/
void add(JobInfo jobInfo);
/**
* 删除job
* @param jobName
*/
void remove(String jobName);
/**
* 触发
* @param jobName
* @param param
*/
void trigger(String jobName, String param);
}
@@ -1,16 +0,0 @@
package fun.asgc.solon.extend.job;
/**
* @author: aoshiguchen
* @date: 2022/9/4
*/
public interface IJobHandler {
/**
* job执行
* @param param
* @throws Exception
*/
void execute(String param) throws Exception;
}
@@ -1,17 +0,0 @@
package fun.asgc.solon.extend.job;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
public interface IJobSource {
/**
* 获取所有job列表
* @return
*/
List<JobInfo> sourceList();
}
@@ -1,26 +0,0 @@
package fun.asgc.solon.extend.job;
import fun.asgc.solon.extend.job.impl.JobExecutor;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.Solon;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
@Slf4j
public class JobBean implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobExecutor jobExecutor = Solon.context().getBean(JobExecutor.class);
if (null != jobExecutor) {
jobExecutor.execute(jobExecutionContext);
}
}
}
@@ -1,23 +0,0 @@
package fun.asgc.solon.extend.job;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Map;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
@Accessors(chain = true)
@Data
public class JobInfo {
private String id;
private String name;
private String desc;
private String cron;
private String param;
private boolean enable;
private Map<String, Object> extension;
}
@@ -1,45 +0,0 @@
package fun.asgc.solon.extend.job;
import fun.asgc.solon.extend.job.annotation.EnableJob;
import fun.asgc.solon.extend.job.impl.DefaultJobCallback;
import fun.asgc.solon.extend.job.impl.DefaultJobSource;
import fun.asgc.solon.extend.job.impl.JobExecutor;
import org.noear.solon.Solon;
import org.noear.solon.core.AopContext;
import org.noear.solon.core.Plugin;
import org.noear.solon.core.event.AppLoadEndEvent;
/**
* @author: aoshiguchen
* @date: 2023/3/11
*/
public class XPluginImp implements Plugin {
@Override
public void start(AopContext context) throws Throwable {
EnableJob enableJob = Solon.app().source().getAnnotation(EnableJob.class);
if (null == enableJob || !enableJob.value()) {
return;
}
//应用加载完后,再启动任务
Solon.app().onEvent(AppLoadEndEvent.class, e -> {
IJobSource jobSource = context.getBean(IJobSource.class);
IJobCallback jobCallback = context.getBean(IJobCallback.class);
if (null == jobSource) {
jobSource = new DefaultJobSource();
}
if (null == jobCallback) {
jobCallback = new DefaultJobCallback();
}
JobExecutor jobExecutor = new JobExecutor();
jobExecutor.setJobSource(jobSource);
jobExecutor.setJobCallback(jobCallback);
jobExecutor.start();
context.wrapAndPut(JobExecutor.class, jobExecutor);
});
}
}
@@ -1,16 +0,0 @@
package fun.asgc.solon.extend.job.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: aoshiguchen
* @date: 2023/3/12
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableJob {
boolean value() default true;
}
@@ -1,20 +0,0 @@
package fun.asgc.solon.extend.job.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/9/4
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JobHandler {
String name();
String desc() default "";
String cron();
String param() default "";
}
@@ -1,23 +0,0 @@
package fun.asgc.solon.extend.job.impl;
import fun.asgc.solon.extend.job.IJobCallback;
import fun.asgc.solon.extend.job.JobInfo;
import lombok.extern.slf4j.Slf4j;
/**
* @author: aoshiguchen
* @date: 2023/3/12
*/
@Slf4j
public class DefaultJobCallback implements IJobCallback {
@Override
public void executeLog(JobInfo jobInfo, String param, Throwable throwable) {
if (null == throwable) {
log.debug("[Solon Plugin Job] Job执行 id:{} name:{} desc:{} param:{}", jobInfo.getId(), jobInfo.getName(), jobInfo.getDesc(), param);
} else {
log.error("[Solon Plugin Job] Job执行 id:{} name:{} desc:{} param:{}", jobInfo.getId(), jobInfo.getName(), jobInfo.getDesc(), param, throwable);
}
}
}
@@ -1,44 +0,0 @@
package fun.asgc.solon.extend.job.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.IJobSource;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import fun.asgc.solon.extend.job.JobInfo;
import org.noear.solon.Solon;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
public class DefaultJobSource implements IJobSource {
@Override
public List<JobInfo> sourceList() {
List<IJobHandler> jobHandlerList = Solon.context().getBeansOfType(IJobHandler.class);
if (CollectionUtil.isEmpty(jobHandlerList)) {
return CollectionUtil.newArrayList();
}
List<JobInfo> jobInfoList = CollectionUtil.newArrayList();
for (IJobHandler jobHandler : jobHandlerList) {
JobHandler handler = jobHandler.getClass().getAnnotation(JobHandler.class);
if (null == handler || StrUtil.isEmpty(handler.name()) || StrUtil.isEmpty(handler.cron())) {
continue;
}
jobInfoList.add(new JobInfo()
.setId(handler.name())
.setName(handler.name())
.setDesc(handler.desc())
.setCron(handler.cron())
.setParam(handler.param())
.setEnable(true)
);
}
return jobInfoList;
}
}
@@ -1,175 +0,0 @@
package fun.asgc.solon.extend.job.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import fun.asgc.solon.extend.job.*;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import org.noear.solon.Solon;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Job执行器
* @author: aoshiguchen
* @date: 2022/9/4
*/
public class JobExecutor implements IJobExecutor {
private IJobSource jobSource;
private ThreadPoolExecutor threadPoolExecutor;
private Map<String, JobInfo> jobInfoMap = new ConcurrentHashMap<>();
private SchedulerFactory schedulerFactory;
private Scheduler scheduler;
private Map<String, IJobHandler> jobHandlerMap = new ConcurrentHashMap<>();
private Set<String> runJobSet = CollectionUtil.newHashSet();
private IJobCallback jobCallback;
private Map<String, TriggerKey> triggerKeyMap = new ConcurrentHashMap<>();
public void start() {
if (null == threadPoolExecutor) {
threadPoolExecutor = new ThreadPoolExecutor(5, 20, 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new CustomThreadFactory("SolonJob"));
}
List<IJobHandler> jobHandlerList = Solon.context().getBeansOfType(IJobHandler.class);
if (!CollectionUtil.isEmpty(jobHandlerList)) {
for (IJobHandler item : jobHandlerList) {
JobHandler jobHandler = item.getClass().getAnnotation(JobHandler.class);
if (null == jobHandler) {
continue;
}
jobHandlerMap.put(jobHandler.name(), item);
}
}
try {
this.schedulerFactory = new StdSchedulerFactory();
this.scheduler = schedulerFactory.getScheduler();
this.init();
} catch (Exception e){
throw new RuntimeException("job初始化异常");
}
}
public void setJobSource(IJobSource jobSource) {
this.jobSource = jobSource;
}
public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
this.threadPoolExecutor = threadPoolExecutor;
}
public void setJobCallback(IJobCallback jobCallback) {
this.jobCallback = jobCallback;
}
@Override
public void init() throws Exception {
List<JobInfo> jobInfoList = jobSource.sourceList();
if (CollectionUtil.isEmpty(jobInfoList)) {
return;
}
for (JobInfo jobInfo : jobInfoList) {
add(jobInfo);
}
}
@Override
public void add(JobInfo jobInfo) {
if (null == jobInfo || StrUtil.isEmpty(jobInfo.getId()) || StrUtil.isEmpty(jobInfo.getName()) ||
StrUtil.isEmpty(jobInfo.getCron())) {
return;
}
synchronized (jobInfo.getId()) {
runJobSet.add(jobInfo.getName());
jobInfoMap.put(jobInfo.getId(), jobInfo);
TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getId());
triggerKeyMap.put(jobInfo.getId(), triggerKey);
JobKey jobKey = new JobKey(jobInfo.getName());
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getCron()).withMisfireHandlingInstructionDoNothing();
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
JobDetail jobDetail = JobBuilder.newJob(JobBean.class).withIdentity(jobKey).build();
try {
if (jobInfo.isEnable()) {
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
}
} catch (Exception e) {
throw new RuntimeException(String.format("新增job[name=%s]异常", jobInfo.getName()));
}
}
}
@Override
public void remove(String jobId) {
JobInfo jobInfo = jobInfoMap.get(jobId);
if (null == jobInfo) {
return;
}
synchronized (jobId) {
runJobSet.remove(jobInfo.getName());
unscheduleJob(jobId);
}
}
@Override
public void trigger(String jobId, String param) {
doExecute(jobId, param);
}
public void execute(JobExecutionContext context) throws JobExecutionException {
if (null == context || null == context.getTrigger()) {
return;
}
String jobId = context.getTrigger().getKey().getName();
JobInfo jobInfo = jobInfoMap.get(jobId);
if (null == jobInfo) {
unscheduleJob(jobId);
return;
}
doExecute(jobId, jobInfo.getParam());
}
private void unscheduleJob(String jobId) {
TriggerKey triggerKey = triggerKeyMap.get(jobId);
if (null != triggerKey) {
try {
scheduler.unscheduleJob(triggerKey);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void doExecute(String jobId, String param) {
JobInfo jobInfo = jobInfoMap.get(jobId);
if (null == jobInfo) {
return;
}
IJobHandler jobHandler =jobHandlerMap.get(jobInfo.getName());
if (null == jobHandler) {
return;
}
threadPoolExecutor.submit(() -> {
try {
jobHandler.execute(param);
if (null != jobCallback) {
jobCallback.executeLog(jobInfo, param, null);
}
} catch (Throwable e) {
jobCallback.executeLog(jobInfo, param, e);
}
});
}
}
@@ -1 +0,0 @@
package fun.asgc.solon.extend.job;
@@ -1,2 +0,0 @@
solon.plugin=fun.asgc.solon.extend.job.XPluginImp
solon.plugin.priority=2
-30
View File
@@ -1,30 +0,0 @@
<?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>
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>2.2.2</version>
</parent>
<groupId>fun.asgc</groupId>
<artifactId>orika-solon-plugin</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon</artifactId>
</dependency>
<!--orika-->
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.4</version>
</dependency>
</dependencies>
</project>
@@ -1,33 +0,0 @@
package fun.asgc.solon.extend.orika;
import ma.glasnost.orika.CustomConverter;
import ma.glasnost.orika.Mapper;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.ClassMapBuilder;
import org.noear.solon.core.AopContext;
import org.noear.solon.core.Plugin;
/**
* @author: aoshiguchen
* @date: 2023/3/11
*/
public class XPluginImp implements Plugin {
@Override
public void start(AopContext context) throws Throwable {
DefaultMapperFactory factory = new DefaultMapperFactory.Builder().build();
context.subBeansOfType(CustomConverter.class, bean -> {
factory.getConverterFactory().registerConverter(bean);
});
context.subBeansOfType(Mapper.class, bean -> {
factory.registerMapper(bean);
});
context.subBeansOfType(ClassMapBuilder.class, bean -> {
factory.registerClassMap((ClassMapBuilder<? extends Object, ? extends Object>) bean);
});
context.wrapAndPut(MapperFactory.class, factory);
context.wrapAndPut(MapperFacade.class, factory.getMapperFacade());
}
}
@@ -1 +0,0 @@
package fun.asgc.solon.extend.orika;
@@ -1,2 +0,0 @@
solon.plugin=fun.asgc.solon.extend.orika.XPluginImp
solon.plugin.priority=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

+113
View File
@@ -0,0 +1,113 @@
# 内网穿透神器NeutrinoProxy 1.8.0版本发布
## 项目简介
- [中微子代理(neutrino-proxy)](https://gitee.com/dromara/neutrino-proxy) 是一款基于netty的内网穿透神器。该项目采用最为宽松的MIT协议,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
- Gitee地址:https://gitee.com/dromara/neutrino-proxy
- 官网地址:http://neutrino-proxy.dromara.org
- 服务端管理后台截图:
<img src="./home.png" width="100%"/>
## 主要特点:
- 1、流量监控:首页图表、报表管理多维度流量监控。全方位掌握实时、历史代理数据。
- 2、用户/License:支持多用户、多客户端使用。后台禁用实时生效。
- 3、端口池:对外端口统一管理,支持用户、License独占端口。
- 4、端口映射:新增、编辑、删除、禁用实时生效。
- 5、Docker:服务端支持Docker一键部署。
- 6、SSL证书:支持SSL,保护您的信息安全。
- 7、域名映射:支持绑定子域名,方便本地调试三方回调
- 8、采用最为宽松的MIT协议,免去你的后顾之忧
## 快速使用
> 更多使用姿势、细节请通过官网或结尾微信二维码加我备注"中微子代理"入群交流。
### 1、 部署服务端
#### 1.1、 Docker一键部署
> 当前最新版本为1.8.0,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.0` 指定版本安装,推荐使用`latest`直接安装最新版。
##### 使用默认sqlite数据库
```shell
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
-d --restart=always --name neutrino-proxy \
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
```
##### 指定自己的mysql数据库
- 在服务器上创建目录:/root/neutrino-proxy/config
- 在该目录下创建`app.yml`文本文件,并配置如下内容:
```yml
neutrino:
data:
db:
type: mysql
# 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化
url: jdbc:mysql://xxxx:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
driver-class: com.mysql.jdbc.Driver
# 数据库帐号
username: xxx
# 数据库密码
password: xxx
```
- 然后执行如下命令:
```shell
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
-v /root/neutrino-proxy/config:/root/neutrino-proxy/config \
-d --restart=always --name neutrino \
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
```
#### 1.2、使用jar包自行部署
- 首先确保服务器上已安装java8运行环境
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-server.jar``neutrino-proxy-admin.zip`
- 在服务器上新建部署目录:`/work/projects/neutrino-proxy-server`
-` neutrino-proxy-server.jar``neutrino-proxy-admin.zip`上传至服务器部署目录。
- 解压`neutrino-proxy-admin.zip`文件
- 执行命令`java -jar neutrino-proxy-server.jar`启动服务端完成部署,默认使用sqlite数据库。
- 若需要指定自己的mysql数据库,同样的需要在当前目录下新建`app.yml`文件,文件内容同上。执行命令`java -jar neutrino-proxy-server.jar config=app.yml`启动服务端完成部署
- 可参照 https://gitee.com/dromara/neutrino-proxy/blob/master/bin/server_start.sh 使用shell脚本启动服务端。
### 2、管理后台配置
- 服务端部署成功后,访问`http://{服务端IP}:8888`打开后台管理页面。
- 使用默认的管理员帐号登录:admin/123456
- 打开`代理配置>License管理`页面,可以看到系统已经自动为管理员初始化了一条License记录,复制该`LicenseKey`备用,后续客户端配置需要。
- 打开`代理配置>端口映射`页面,可以看到系统已经自动为初始化了几条端口映射。可根据需要自行添加、修改。这里我们以`9101 -> 127.0.0.1:8080`映射为例
### 3、启动客户端
- 首先确保本地已安装java8运行环境
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-client.jar`
- 在本地`neutrino-proxy-client.jar`同级别目录下新建`app.yml`文件,并配置如下内容:
```yml
neutrino:
proxy:
client:
# ssl证书密钥(使用jjar包内自带的证书,则此处无需修改)
key-store-password: 123456
# ssl证书管理密钥(使用jjar包内自带的证书,则此处无需修改。自定义证书,则此处配置对应的路径)
jks-path: classpath:/test.jks
# 代理服务端IP
server-ip: localhost
# 代理服务端IP, 若是非ssl端口,则ssl-enable需要配置为false
server-port: 9002
# 是否启用ssl
ssl-enable: true
# licenseKey,客户端凭证。此处需要配置刚刚从管理后台复制的LicenseKey
license-key: xxxx
```
- 执行命令`java -jar neutrino-proxy-client.jar`启动客户端
- 查看服务端License管理,刷新页面,对应的License在线状态为`在线`,则表明客户端已正常连接。
### 4、代理验证
- 本地启动被代理服务,如:redis、本地web项目、本地mysql等等
- 先确保本地能正常访问被代理服务,如果本地都不能访问,不用想代理更不可能!!!
- 通过服务端IP+9101(上面License配置的端口映射重的服务端端口)访问本地被代理服务
## 联系我们
笔者时间、能力有限,且开源项目非一朝一夕之事,存在众多问题亦在所难免。使用、学习过程中有任何问题欢迎大家与我联系。
对项目有什么想法或者建议,可以加我微信拉交流群,或者创建[issues](https://gitee.com/dromara/neutrino-proxy/issues),一起完善项目
- 微信号:yuyunshize
- Email: aoshiguchen@dromara.org
- 中微子代理官网:http://neutrino-proxy.dromara.org
- 中微子代理仓库:https://gitee.com/dromara/neutrino-proxy
- 微信二维码:
<img src="MyWeChatQRCode.jpeg" width="100%"/>
+38
View File
@@ -0,0 +1,38 @@
# 本地启动SpringBoot,提供hello接口以供测试:
## 1、10线程 100连接直接测试
> wrk -t 10 -c 100 -d 1s http://localhost:8080/hello
测试结果:
```
10 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 10.51ms 5.82ms 53.28ms 83.61%
Req/Sec 0.99k 123.01 1.21k 81.82%
10797 requests in 1.10s, 1.35MB read
Requests/sec: 9799.85
Transfer/sec: 1.23MB
```
## 2、10线程 100连接走端口转发测试
> wrk -t 10 -c 100 -d 1s http://localhost:9105/hello
测试结果:
```
10 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 126.37ms 163.69ms 971.32ms 81.43%
Req/Sec 24.04 21.06 80.00 84.62%
70 requests in 1.02s, 8.96KB read
Requests/sec: 68.46
Transfer/sec: 8.76K
```
## 3、10线程 100连接走域名转发测试
> wrk -t 10 -c 100 -d 1s http://test1.neutrino-proxy.asgc.fun/hello
测试结果:
```
10 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 327.86ms 218.93ms 903.28ms 56.76%
Req/Sec 30.65 20.46 88.00 62.67%
259 requests in 1.02s, 44.77KB read
Requests/sec: 253.75
Transfer/sec: 43.86KB
```
+1
View File
@@ -16,6 +16,7 @@ import './icons' // icon
import './errorLog'// error log
import './permission' // permission control
import './mock' // simulation data
import './plugins/baiduhm' // 百度统计
import * as filters from './filters' // global filters
@@ -0,0 +1,7 @@
const _hmt = _hmt || [];
(function() {
const hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?173e771eef816c412396d2cb4fe2d632";
const s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
@@ -50,12 +50,11 @@ export default {
this.chartDom = document.getElementById(this.chartId)
this.myChart = echarts.init(this.chartDom)
const seriesList = []
const legendList = []
this.data.list && this.data.list.forEach((item, index) => {
seriesList.push({
name: item.name,
type: 'line',
stack: 'Total',
// stack: 'Total',
data: item.value,
areaStyle: {
normal: {
@@ -74,7 +73,6 @@ export default {
},
smooth: true
})
legendList.push(item.name)
})
const option = {
@@ -87,13 +85,13 @@ export default {
formatter: (value) => {
let title = this.data.text + '<br/>'
value.forEach(item => {
title = title + item.marker + item.seriesName + ' : ' + this.data.list[item.seriesIndex].label[item.dataIndex] + '<br/>'
title = title + item.marker + item.seriesName + ' : ' + getSizeDescByByteCount(item.data) + '<br/>'
})
return title
}
},
legend: {
data: legendList,
data: this.data.legendList,
left: 'right'
},
grid: {
@@ -70,37 +70,19 @@ export default {
})
},
getChartData(last7dFlow) {
const title = []
const downFlowDesc = []
const totalFlowDesc = []
const upFlowDesc = []
last7dFlow.dataList.forEach(item => {
title.push(item.dateStr)
totalFlowDesc.push(item.totalFlowDesc)
downFlowDesc.push(item.downFlowDesc)
upFlowDesc.push(item.upFlowDesc)
})
const list = []
last7dFlow.seriesList.forEach(item => {
let label = []
if (item.seriesName.indexOf('上') > -1) {
label = upFlowDesc
} else if (item.seriesName.indexOf('下') > -1) {
label = downFlowDesc
} else if (item.seriesName.indexOf('总') > -1) {
label = totalFlowDesc
}
list.push({
name: item.seriesName,
value: item.seriesData,
label: label
value: item.seriesData
})
})
return {
text: '流量监控',
subtext: `最近${last7dFlow.dataList.length || 0}天流量监控`,
title: title,
list: list
title: last7dFlow.xDate,
list: list,
legendList: last7dFlow.legendData
}
}
}
@@ -1,16 +1,17 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container" style="display:flex">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable style="margin-right:10px">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable style="margin-right:10px;width: 120px;">
<el-option v-for="item in userList" :key="item.loginName" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.licenseId" placeholder="请选择license" clearable style="margin-right:10px">
<el-select v-model="listQuery.licenseId" placeholder="请选择license" clearable style="margin-right:10px;width: 135px;">
<el-option v-for="item in licenseList" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.protocal" placeholder="请选择协议" clearable style="margin-right:10px">
<el-option v-for="item in protocalList" :key="item.name" :label="item.name" :value="item.name" :disabled="!item.enable"/>
<el-select v-model="listQuery.protocal" placeholder="请选择协议" clearable style="margin-right:10px;width: 120px;">
<el-option v-for="item in protocalList" :key="item.name" :label="item.name" :value="item.name"
:disabled="!item.enable" />
</el-select>
<el-input v-model="listQuery.serverPort" type="text" style="width:150px;margin-right:10px" class="filter-item"
<el-input v-model="listQuery.serverPort" type="text" style="width:145px;margin-right:10px" class="filter-item"
placeholder="请输入服务端端口" :maxlength="5" show-word-limit />
<el-select v-model="listQuery.isOnline" placeholder="请选择在线状态" clearable style="width:145px;margin-right:10px">
<el-option v-for="item in selectObj.onlineOptions" :key="item.value" :label="item.label" :value="item.value" />
@@ -18,6 +19,8 @@
<el-select v-model="listQuery.enable" placeholder="请选择启用状态" clearable style="width:145px;margin-right:10px">
<el-option v-for="item in selectObj.statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-input v-model="listQuery.description" type="text" style="width:145px;margin-right:10px" class="filter-item"
placeholder="请输入描述详情" show-word-limit />
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{
$t('table.search') }}</el-button>
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary"
@@ -61,6 +64,12 @@
<span>{{ scope.row.clientIp }}:{{ scope.row.clientPort }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.desc')" width="120">
<template slot-scope="scope">
<span>{{ scope.row.description }}</span>
</template>
</el-table-column>
<el-table-column width="150px" align="center" :label="$t('table.createTime')">
<template slot-scope="scope">
<span>{{ scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
@@ -128,7 +137,8 @@
</el-form-item>
<el-form-item :label="$t('协议')" prop="protocal">
<el-select style="width: 280px;" class="filter-item" v-model="temp.protocal" placeholder="请选择">
<el-option v-for="item in protocalList" :key="item.name" :label="item.name" :value="item.name" :disabled="!item.enable">
<el-option v-for="item in protocalList" :key="item.name" :label="item.name" :value="item.name"
:disabled="!item.enable">
</el-option>
</el-select>
</el-form-item>
@@ -144,11 +154,14 @@
<el-form-item :label="$t('客户端端口')" prop="clientPort">
<el-input v-model="temp.clientPort"></el-input>
</el-form-item>
<el-form-item :label="$t('域名')" prop="subdomain" v-if="temp.protocal === 'HTTP'">
<el-form-item :label="$t('域名')" prop="subdomain" v-if="temp.protocal === 'HTTP' && domainName && domainName != ''">
<el-input v-model="temp.subdomain">
<template slot="append">.{{domainName}}</template>
<template slot="append">.{{ domainName }}</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('描述')" prop="description">
<el-input v-model="temp.description"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="
@@ -222,7 +235,8 @@ export default {
license: undefined,
port: undefined,
isOnline: undefined,
enable: undefined
enable: undefined,
description: undefined
},
importanceOptions: [1, 2, 3],
@@ -261,7 +275,8 @@ export default {
licenseId: [{ required: true, message: '请选择License', trigger: 'blur,change' }],
serverPort: [{ required: true, message: '请输入服务端端口', trigger: 'blur' }],
clientIp: [{ required: true, message: '请输入客户端IP', trigger: 'blur' }],
clientPort: [{ required: true, message: '请输入客户端端口', trigger: 'blur' }]
clientPort: [{ required: true, message: '请输入客户端端口', trigger: 'blur' }],
protocal: [{ required: true, message: '请选择协议', trigger: 'blur' }]
},
downloadLoading: false,
countryColumns: [
+13
View File
@@ -0,0 +1,13 @@
FROM openjdk:8-jdk-alpine
#同步时间
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
apk update && apk add wget unzip vim && apk add -U tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
RUN mkdir -p /root/neutrino-proxy/config
WORKDIR /root/neutrino-proxy
COPY ../neutrino-proxy-client/target/neutrino-proxy-client.jar /root/neutrino-proxy/neutrino-proxy-client.jar
COPY ../neutrino-proxy-client/src/main/resources/app.yml /root/neutrino-proxy/config
#VOLUME ["/root/neutrino-proxy"]
ENTRYPOINT ["java","-jar","neutrino-proxy-client.jar","config=./config/app.yml"]
#docker run -it -d --restart=always --name np_client -e SERVER_IP=127.0.0.1
+1 -1
View File
@@ -3,7 +3,7 @@
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-parent</artifactId>
<artifactId>neutrino-proxy</artifactId>
<groupId>org.dromara.neutrino-proxy</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
@@ -1,37 +0,0 @@
/**
* 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 org.dromara.neutrinoproxy.client.config;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/10/18
*/
@Data
public class CustomConfig {
private String jksPath;
private String serverIp;
private Integer serverPort;
private Boolean sslEnable;
private String licenseKey;
}
@@ -39,5 +39,6 @@ public class ProxyConfig {
private Integer obtainLicenseInterval;
private String licenseKey;
private Integer threadCount;
private String clientId;
}
}
@@ -31,8 +31,13 @@ public class ProxyConfiguration implements LifecycleBean {
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
}
@Bean("bootstrap")
public Bootstrap bootstrap() {
@Bean("cmdTunnelBootstrap")
public Bootstrap cmdTunnelBootstrap() {
return new Bootstrap();
}
@Bean("proxyTunnelBootstrap")
public Bootstrap proxyTunnelBootstrap() {
return new Bootstrap();
}
@@ -0,0 +1,77 @@
package org.dromara.neutrinoproxy.client.core;
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.Solon;
/**
* 处理与服务端之间的数据传输
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class CmdChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
log.info("Client CmdChannel recieved proxy message, type is {}", proxyMessage.getType());
}
Solon.context().getBean(Dispatcher.class).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 {
log.info("Client CmdChannel 与服务端断开连接");
ProxyUtil.setCmdChannel(null);
ProxyUtil.clearRealServerChannels();
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("Client CmdChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
ctx.close();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent)evt;
switch (event.state()) {
case READER_IDLE:
// 读超时,断开连接
// log.info("读超时");
// ctx.channel().close();
break;
case WRITER_IDLE:
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
break;
case ALL_IDLE:
log.info("Client CmdChannel 读写超时");
ctx.close();
break;
}
}
}
}
@@ -1,15 +1,15 @@
package org.dromara.neutrinoproxy.client.core;
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
import org.noear.solon.Solon;
/**
@@ -18,13 +18,13 @@ import org.noear.solon.Solon;
* @date: 2022/6/16
*/
@Slf4j
public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
public class ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
log.info("recieved proxy message, type is {}", proxyMessage.getType());
log.info("Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType());
}
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
}
@@ -41,17 +41,10 @@ public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 控制连接
if (ProxyUtil.getCmdChannel() == ctx.channel()) {
log.info("与服务端断开连接");
ProxyUtil.setCmdChannel(null);
ProxyUtil.clearRealServerChannels();
} else {
// 数据传输连接
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null && realServerChannel.isActive()) {
realServerChannel.close();
}
// 数据传输连接
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null && realServerChannel.isActive()) {
realServerChannel.close();
}
ProxyUtil.removeProxyChanel(ctx.channel());
@@ -60,8 +53,8 @@ public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
cause.printStackTrace();
log.error("Client ProxyChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
ctx.close();
}
@Override
@@ -71,13 +64,15 @@ public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
switch (event.state()) {
case READER_IDLE:
// 读超时断开连接
log.info("读超时");
ctx.channel().close();
// log.info("读超时");
// ctx.channel().close();
break;
case WRITER_IDLE:
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
break;
case ALL_IDLE:
log.info("读写超时");
ctx.close();
break;
}
}
@@ -25,13 +25,14 @@ import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 客户端服务
* 代理客户端服务
* @author: aoshiguchen
* @date: 2022/6/16
*/
@@ -40,8 +41,10 @@ import java.util.concurrent.TimeUnit;
public class ProxyClientService {
@Inject
private ProxyConfig proxyConfig;
@Inject("bootstrap")
private Bootstrap bootstrap;
@Inject("cmdTunnelBootstrap")
private Bootstrap cmdTunnelBootstrap;
@Inject("proxyTunnelBootstrap")
private Bootstrap proxyTunnelBootstrap;
@Inject("realServerBootstrap")
private Bootstrap realServerBootstrap;
private volatile Channel channel;
@@ -53,10 +56,11 @@ public class ProxyClientService {
* 重连次数
*/
private volatile int reconnectCount = 0;
/**
* 启用重连服务
*/
private volatile boolean reconnectServiceEnable = false;
// /**
// * 启用重连服务
// */
// private volatile boolean reconnectServiceEnable = false;
private NioEventLoopGroup workerGroup;
/**
* 重连服务执行器
*/
@@ -64,9 +68,9 @@ public class ProxyClientService {
@Init
public void init() {
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 0, RECONNECT_INTERVAL_SECONDS, TimeUnit.SECONDS);
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 10, RECONNECT_INTERVAL_SECONDS, TimeUnit.SECONDS);
this.workerGroup = new NioEventLoopGroup(proxyConfig.getClient().getThreadCount());
NioEventLoopGroup workerGroup = new NioEventLoopGroup(proxyConfig.getClient().getThreadCount());
realServerBootstrap.group(workerGroup);
realServerBootstrap.channel(NioSocketChannel.class);
realServerBootstrap.handler(new ChannelInitializer<SocketChannel>() {
@@ -77,25 +81,60 @@ public class ProxyClientService {
}
});
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
proxyTunnelBootstrap.group(workerGroup);
proxyTunnelBootstrap.channel(NioSocketChannel.class);
proxyTunnelBootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort()));
proxyTunnelBootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (proxyConfig.getClient().getSslEnable()) {
ch.pipeline().addLast(createSslHandler());
}
// ch.pipeline().addFirst(new LoggingHandler(ProxyClientService.class));
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 IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
ch.pipeline().addLast(new ClientChannelHandler());
ch.pipeline().addLast(new ProxyChannelHandler());
}
});
this.start();
cmdTunnelBootstrap.group(workerGroup);
cmdTunnelBootstrap.channel(NioSocketChannel.class);
// cmdTunnelBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
// cmdTunnelBootstrap.option(ChannelOption.SO_KEEPALIVE, true);
// /**
// * TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。
// * Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
// */
// cmdTunnelBootstrap.option(ChannelOption.TCP_NODELAY, true);
cmdTunnelBootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort()));
cmdTunnelBootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (proxyConfig.getClient().getSslEnable()) {
ch.pipeline().addLast(createSslHandler());
}
// ch.pipeline().addFirst(new LoggingHandler(ProxyClientService.class));
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 IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
ch.pipeline().addLast(new CmdChannelHandler());
}
});
try {
this.start();
} catch (Exception e) {
// 启动连不上也做一下重连,因此先catch异常
log.error("[客户端指令隧道] 启动异常", e);
}
}
public void start() {
@@ -110,7 +149,7 @@ public class ProxyClientService {
return;
}
if (null != proxyConfig.getClient().getSslEnable() && proxyConfig.getClient().getSslEnable()
&& StrUtil.isEmpty(proxyConfig.getClient().getJksPath())) {
&& StrUtil.isEmpty(proxyConfig.getClient().getJksPath())) {
log.error("not found jks-path config.");
Solon.stop();
return;
@@ -127,7 +166,7 @@ public class ProxyClientService {
log.error("client start error", e);
}
} else {
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey()));
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey(), ProxyUtil.getClientId()));
}
}
@@ -135,7 +174,7 @@ public class ProxyClientService {
* 连接代理服务器
*/
private void connectProxyServer() throws InterruptedException {
bootstrap.connect(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort())
cmdTunnelBootstrap.connect()
.addListener(new ChannelFutureListener() {
@Override
@@ -144,13 +183,13 @@ public class ProxyClientService {
channel = future.channel();
// 连接成功,向服务器发送客户端认证信息(licenseKey)
ProxyUtil.setCmdChannel(future.channel());
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey()));
log.info("连接代理服务成功. channelId:{}", future.channel().id().asLongText());
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey(), ProxyUtil.getClientId()));
log.info("[客户端指令隧道] 连接代理服务成功. channelId:{}", future.channel().id().asLongText());
reconnectServiceEnable = true;
// reconnectServiceEnable = true;
reconnectCount = 0;
} else {
log.info("连接代理服务失败!");
log.info("[客户端指令隧道] 连接代理服务失败!");
}
}
}).sync();
@@ -180,17 +219,21 @@ public class ProxyClientService {
}
protected synchronized void reconnect() {
if (!reconnectServiceEnable) {
return;
// if (!reconnectServiceEnable) {
// return;
// }
if (null != channel) {
if (channel.isActive()) {
return;
}
channel.close();
}
if (null != channel && channel.isActive()) {
return;
}
log.info("客户端重连 seq:{}", ++reconnectCount);
log.info("[客户端指令隧道] 客户端重连 seq:{}", ++reconnectCount);
try {
connectProxyServer();
} catch (Exception e) {
log.error("重连异常", e);
log.error("[客户端指令隧道] 重连异常", e);
}
}
}
@@ -22,6 +22,7 @@
package org.dromara.neutrinoproxy.client.core;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
@@ -36,6 +37,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
@@ -85,6 +87,6 @@ public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBu
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
log.error("Client ProxyChannel Error", cause);
}
}
@@ -2,11 +2,13 @@ package org.dromara.neutrinoproxy.client.handler;
import com.alibaba.fastjson.JSONObject;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ExceptionEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
/**
@@ -24,5 +26,15 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
JSONObject data = JSONObject.parseObject(info);
Integer code = data.getInteger("code");
log.info("认证结果:{}", info);
if (ExceptionEnum.AUTH_FAILED.getCode().equals(code)) {
// 客户端认证失败,直接停止服务
log.info("client auth failed , client stop.");
context.channel().close();
Solon.stop();
} else if (ExceptionEnum.CONNECT_FAILED.getCode().equals(code) ||
ExceptionEnum.LICENSE_CANNOT_REPEAT_CONNECT.getCode().equals(code)
){
context.channel().close();
}
}
}
@@ -21,8 +21,8 @@ import org.noear.solon.annotation.Inject;
@Match(type = Constants.ProxyDataTypeName.CONNECT)
@Component
public class ProxyMessageConnectHandler implements ProxyMessageHandler {
@Inject("bootstrap")
private Bootstrap bootstrap;
@Inject("proxyTunnelBootstrap")
private Bootstrap proxyTunnelBootstrap;
@Inject("realServerBootstrap")
private Bootstrap realServerBootstrap;
@Inject
@@ -48,7 +48,7 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
realServerChannel.config().setOption(ChannelOption.AUTO_READ, false);
// 获取连接
ProxyUtil.borrowProxyChanel(bootstrap, new ProxyChannelBorrowListener() {
ProxyUtil.borrowProxyChanel(proxyTunnelBootstrap, new ProxyChannelBorrowListener() {
@Override
public void success(Channel channel) {
@@ -21,6 +21,8 @@
*/
package org.dromara.neutrinoproxy.client.util;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
import org.dromara.neutrinoproxy.client.core.ProxyChannelBorrowListener;
import org.dromara.neutrinoproxy.core.Constants;
import io.netty.bootstrap.Bootstrap;
@@ -29,10 +31,12 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.util.AttributeKey;
import org.dromara.neutrinoproxy.core.util.FileUtil;
import org.noear.solon.Solon;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -54,16 +58,17 @@ public class ProxyUtil {
private static volatile Channel cmdChannel;
public static void borrowProxyChanel(Bootstrap bootstrap, final ProxyChannelBorrowListener borrowListener) {
private static String clientId;
private static final String CLIENT_ID_FILE = ".NEUTRINO_PROXY_CLIENT_ID";
public static void borrowProxyChanel(Bootstrap proxyTunnelBootstrap, final ProxyChannelBorrowListener borrowListener) {
Channel channel = proxyChannelPool.poll();
if (null != channel) {
borrowListener.success(channel);
return;
}
String serverIp = Solon.cfg().get("neutrino.proxy.client.server-ip");
Integer serverPort = Solon.cfg().getInt("neutrino.proxy.client.server-port", 9000);
bootstrap.connect(serverIp, serverPort).addListener((ChannelFutureListener) future -> {
proxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
borrowListener.success(future.channel());
} else {
@@ -129,4 +134,24 @@ public class ProxyUtil {
realServerChannels.clear();
}
public static String getClientId() {
if (StringUtils.isNotBlank(clientId)) {
return clientId;
}
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
if (StringUtils.isNotBlank(proxyConfig.getClient().getClientId())) {
clientId = proxyConfig.getClient().getClientId();
return clientId;
}
String id = FileUtil.readContentAsString(CLIENT_ID_FILE);
if (StringUtils.isNotBlank(id)) {
clientId = id;
return id;
}
id = UUID.randomUUID().toString().replace("-", "");
FileUtil.write(CLIENT_ID_FILE, id);
clientId = id;
return id;
}
}
@@ -9,15 +9,16 @@ neutrino:
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 40
write-idle-time: 8
all-idle-time-seconds: 0
read-idle-time: 8
write-idle-time: 3
all-idle-time-seconds: 8
client:
thread-count: 50
key-store-password: 123456
jks-path: classpath:/test.jks
server-ip: localhost
server-port: 9002
ssl-enable: true
key-store-password: ${STORE_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
server-ip: ${SERVER_IP:localhost}
server-port: ${SERVER_PORT:9002}
ssl-enable: ${SSL_ENABLE:true}
obtain-license-interval: 5
license-key: b0a907332b474b25897c4dcb31fc7eb6
license-key: ${LICENSE_KEY:}
client-id: ${CLIENT_ID:}
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_FILE" value="/work/projects/neutrino-proxy-client/app.log"/>
<property name="LOG_FILE" value="./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" />
<!-- <property name="ENCODE" value="utf8" />-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
@@ -10,7 +10,7 @@
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
@@ -22,7 +22,7 @@
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
+1 -1
View File
@@ -3,7 +3,7 @@
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-parent</artifactId>
<artifactId>neutrino-proxy</artifactId>
<groupId>org.dromara.neutrino-proxy</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
@@ -35,7 +35,10 @@ import lombok.Getter;
public enum ExceptionEnum {
SUCCESS(0, "成功"),
AUTH_FAILED(1, "认证失败"),
CONNECT_FAILED(2, "连接失败");
CONNECT_FAILED(2, "连接失败"),
@Deprecated
LICENSE_CANNOT_REPEAT_CONNECT(3, "license不能多个客户端同时使用"),
;
private Integer code;
private String msg;
@@ -100,9 +100,9 @@ public class ProxyMessage {
return create().setType(TYPE_HEARTBEAT);
}
public static ProxyMessage buildAuthMessage(String info) {
public static ProxyMessage buildAuthMessage(String info, String clientId) {
return create().setType(TYPE_AUTH)
.setInfo(info);
.setInfo(info + "," + clientId);
}
public static ProxyMessage buildAuthResultMessage(Integer code, String msg, String licenseKey) {
@@ -6,9 +6,9 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN mkdir -p /root/neutrino-proxy/config
RUN mkdir -p /root/neutrino-proxy/neutrino-proxy-admin
WORKDIR /root/neutrino-proxy
COPY neutrino-proxy-server/target/neutrino-proxy-server.jar /root/neutrino-proxy/neutrino-proxy-server.jar
COPY neutrino-proxy-server/src/main/resources/app.yml /root/neutrino-proxy/config
COPY neutrino-proxy-admin/dist /root/neutrino-proxy/neutrino-proxy-admin/dist
COPY ../neutrino-proxy-server/target/neutrino-proxy-server.jar /root/neutrino-proxy/neutrino-proxy-server.jar
COPY ../neutrino-proxy-server/src/main/resources/app.yml /root/neutrino-proxy/config
COPY ../neutrino-proxy-admin/dist /root/neutrino-proxy/neutrino-proxy-admin/dist
#VOLUME ["/root/neutrino-proxy"]
ENTRYPOINT ["java","-jar","neutrino-proxy-server.jar","config=./config/app.yml"]
+18
View File
@@ -0,0 +1,18 @@
version: '3.1'
services:
neutrino-proxy11:
container_name: 'np-server'
restart: always
image: registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.0
ports:
- "9000-9200:9000-9200"
- "8888:8888"
environment:
- TZ=Asia/Shanghai
volumes:
- /root/neutrino-proxy/config:/root/neutrino-proxy/config
privileged: true
+6 -10
View File
@@ -3,7 +3,7 @@
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-parent</artifactId>
<artifactId>neutrino-proxy</artifactId>
<groupId>org.dromara.neutrino-proxy</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
@@ -30,17 +30,13 @@
<groupId>org.noear</groupId>
<artifactId>activerecord-solon-plugin</artifactId>
</dependency>
<!--orika-->
<dependency>
<groupId>fun.asgc</groupId>
<artifactId>orika-solon-plugin</artifactId>
<version>2.2.2</version>
</dependency>
<!--job-->
<dependency>
<groupId>fun.asgc</groupId>
<groupId>org.dromara.solon-plugins</groupId>
<artifactId>job-solon-plugin</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.dromara.solon-plugins</groupId>
<artifactId>orika-solon-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.neutrino-proxy</groupId>
@@ -1,6 +1,6 @@
package org.dromara.neutrinoproxy.server;
import fun.asgc.solon.extend.job.annotation.EnableJob;
import org.dromara.solonplugins.job.annotation.EnableJob;
import org.noear.solon.Solon;
import org.noear.solon.annotation.SolonMain;
import org.noear.solon.web.cors.CrossFilter;
@@ -64,8 +64,7 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
// TODO 该事件有50%的概率不触发
System.out.println("11");
}
/**
@@ -45,11 +45,6 @@ public class DbConfig {
*/
@Inject("${neutrino.data.db.url}")
private String url;
/**
* 驱动类
*/
@Inject("${neutrino.data.db.driver-class}")
private String driverClass;
/**
* 用户名
@@ -5,8 +5,8 @@ import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.zaxxer.hikari.HikariDataSource;
import org.dromara.neutrinoproxy.server.constant.DbTypeEnum;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.constant.DbTypeEnum;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
@@ -32,7 +32,14 @@ public class DbConfiguration {
return dataSource;
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(dbConfig.getDriverClass());
String driver = "com.mysql.cj.jdbc.Driver";
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
// 对类名的判断,异常则说明不存在:
driver = "com.mysql.jdbc.Driver";
}
dataSource.setDriverClassName(driver);
dataSource.setJdbcUrl(dbConfig.getUrl());
dataSource.setMinimumIdle(5);
dataSource.setMaximumPoolSize(20);
@@ -57,13 +64,13 @@ public class DbConfiguration {
}
@Bean
public void db1_ext2(@Db("db") MybatisConfiguration config){
public void db1_ext2(@Db("db") MybatisConfiguration config) {
config.getTypeHandlerRegistry().register("fun.asgc.neutrino.proxy.server.dal");
config.setDefaultEnumTypeHandler(null);
}
@Bean
public MybatisSqlSessionFactoryBuilder factoryBuilderNew(){
public MybatisSqlSessionFactoryBuilder factoryBuilderNew() {
return new MybatisSqlSessionFactoryBuilderImpl();
}
@@ -18,10 +18,15 @@ public class ProxyConfig {
@Inject("${neutrino.proxy.protocol}")
private Protocol protocol;
/**
* 服务配置
* 代理服务配置
*/
@Inject("${neutrino.proxy.server}")
private Server server;
/**
* 代理隧道配置
*/
@Inject("${neutrino.proxy.tunnel}")
private Tunnel tunnel;
@Data
public static class Protocol {
@@ -37,14 +42,24 @@ public class ProxyConfig {
@Data
public static class Server {
private Integer bossThreadCount;
private Integer workThreadCount;
private String domainName;
private Integer httpProxyPort;
private Integer httpsProxyPort;
private String keyStorePassword;
private String jksPath;
}
@Data
public static class Tunnel {
private Integer bossThreadCount;
private Integer workThreadCount;
private Integer port;
private Integer sslPort;
private String keyStorePassword;
private String keyManagerPassword;
private String jksPath;
private Integer bossThreadCount;
private Integer workThreadCount;
private String domainName;
}
}
@@ -1,5 +1,9 @@
package org.dromara.neutrinoproxy.server.base.proxy;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
@@ -7,6 +11,8 @@ import org.dromara.neutrinoproxy.core.dispatcher.DefaultDispatcher;
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.nio.NioEventLoopGroup;
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
import org.dromara.neutrinoproxy.server.proxy.core.TcpVisitorChannelHandler;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
@@ -43,4 +49,30 @@ public class ProxyConfiguration implements LifecycleBean {
return new NioEventLoopGroup(proxyConfig.getServer().getWorkThreadCount());
}
@Bean("tcpServerBootstrap")
public ServerBootstrap tcpServerBootstrap(@Inject("serverBossGroup") NioEventLoopGroup serverBossGroup,
@Inject("serverWorkerGroup") NioEventLoopGroup serverWorkerGroup) {
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 TcpVisitorChannelHandler());
}
});
return bootstrap;
}
@Bean("tunnelBossGroup")
public NioEventLoopGroup tunnelBossGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getTunnel().getBossThreadCount());
}
@Bean("tunnelWorkerGroup")
public NioEventLoopGroup tunnelWorkerGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getTunnel().getWorkThreadCount());
}
}
@@ -41,6 +41,8 @@ public enum ExceptionConstant {
PARAMS_NOT_NULL(10, "参数[{}]不能为空"),
PARAMS_NOT_EMPTY(11, "参数[{}]不能为空"),
FILED_LENGTH_OUT(12 ,"{}不能超出长度{}"),
// 用户管理(11000)
// license管理(12000)
LICENSE_NAME_CANNOT_REPEAT(12000, "license名称不能重复"),
@@ -2,6 +2,7 @@ package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.function.Function;
@@ -15,14 +16,21 @@ import java.util.stream.Stream;
@Getter
@AllArgsConstructor
public enum NetworkProtocolEnum {
TCP("TCP"),
UDP("UDP"),
HTTP("HTTP"),
TCP("TCP", "TCP"),
UDP("UDP", "UDP"),
HTTP("HTTP", "TCP"),
;
private String desc;
private String baseProtocol;
private static final Map<String, NetworkProtocolEnum> map = Stream.of(NetworkProtocolEnum.values()).collect(Collectors.toMap(NetworkProtocolEnum::getDesc, Function.identity()));
public static NetworkProtocolEnum of(String desc) {
if (StringUtils.isBlank(desc)) {
return null;
}
if (desc.startsWith("HTTP")) {
return NetworkProtocolEnum.HTTP;
}
return map.get(desc);
}
}
@@ -32,24 +32,25 @@ public class PortMappingController {
return portMappingService.page(pageQuery, req);
}
@Post
@Mapping("/create")
public PortMappingCreateRes create(PortMappingCreateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
if (StringUtils.isBlank(req.getClientIp())) {
// 没传客户端ip默认为127.0.0.1
req.setClientIp("127.0.0.1");
}
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
// 目前仅HTTP支持绑定域名
req.setSubdomain("");
}
@Post
@Mapping("/create")
public PortMappingCreateRes create(PortMappingCreateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
ParamCheckUtil.checkMaxLength(req.getDescription(), 50, "描述", "50");
if (StringUtils.isBlank(req.getClientIp())) {
// 没传客户端ip默认为127.0.0.1
req.setClientIp("127.0.0.1");
}
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
// 目前仅HTTP支持绑定域名
req.setSubdomain(null);
}
return portMappingService.create(req);
}
@@ -62,6 +63,7 @@ public class PortMappingController {
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
ParamCheckUtil.checkMaxLength(req.getDescription(), 50, "描述", "50");
if (StringUtils.isBlank(req.getClientIp())) {
// 没传客户端ip默认为127.0.0.1
req.setClientIp("127.0.0.1");
@@ -70,7 +72,7 @@ public class PortMappingController {
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
// 目前仅HTTP支持绑定域名
req.setSubdomain("");
req.setSubdomain(null);
}
return portMappingService.update(req);
@@ -54,4 +54,8 @@ public class PortMappingCreateReq {
* 客户端端口
*/
private Integer clientPort;
/**
* 描述
*/
private String description;
}
@@ -48,6 +48,11 @@ public class PortMappingListReq {
* 服务端口号
*/
private Integer serverPort;
/**
* 描述
*/
private String description;
/**
* 是否在线
@@ -58,4 +58,8 @@ public class PortMappingUpdateReq {
* 客户端端口
*/
private Integer clientPort;
/**
* 描述
*/
private String description;
}
@@ -89,6 +89,10 @@ public class PortMappingListRes {
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
* 描述
*/
private String description;
/**
* 创建时间
*/
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingListReq;
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
@@ -48,7 +49,7 @@ public interface PortMappingMapper extends BaseMapper<PortMappingDO> {
* @return
*/
default Boolean checkRepeatBySubdomain(String subdomain, Set<Integer> excludeIds) {
return this.selectCount(new LambdaQueryWrapper<PortMappingDO>()
return StringUtils.isEmpty(subdomain)? Boolean.FALSE : this.selectCount(new LambdaQueryWrapper<PortMappingDO>()
.eq(PortMappingDO::getSubdomain, subdomain)
.notIn(!CollectionUtil.isEmpty(excludeIds), PortMappingDO::getId, excludeIds)
).intValue() > 0;
@@ -68,6 +68,11 @@ public class PortMappingDO {
* 客户端端口
*/
private Integer clientPort;
/**
* 描述
*/
private String description;
/**
* 是否在线
* {@link OnlineStatusEnum}
@@ -3,12 +3,12 @@ package org.dromara.neutrinoproxy.server.job;
import com.alibaba.fastjson.JSONObject;
import org.dromara.neutrinoproxy.core.util.DateUtil;
import org.dromara.neutrinoproxy.server.dal.*;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -1,8 +1,8 @@
package org.dromara.neutrinoproxy.server.job;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import lombok.extern.slf4j.Slf4j;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
/**
@@ -30,10 +30,10 @@ import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportDayDO;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportHourDO;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -8,10 +8,10 @@ import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportHourDO;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMinuteDO;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -7,9 +7,9 @@ import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMinuteDO;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import lombok.extern.slf4j.Slf4j;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -6,10 +6,10 @@ import org.dromara.neutrinoproxy.server.dal.*;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportDayDO;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMonthDO;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -1,192 +0,0 @@
package org.dromara.neutrinoproxy.server.proxy.core;
import cn.hutool.core.util.StrUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
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 org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.event.AppLoadEndEvent;
import org.noear.solon.core.event.EventListener;
import java.net.InetSocketAddress;
/**
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Slf4j
@Component
public class HttpProxy implements EventListener<AppLoadEndEvent> {
@Inject("serverBossGroup")
private NioEventLoopGroup serverBossGroup;
@Inject("serverWorkerGroup")
private NioEventLoopGroup serverWorkerGroup;
@Inject
private ProxyConfig proxyConfig;
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
log.info("no config domain name,nonsupport http proxy.");
return;
}
this.start();
}
private void start() {
try {
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 VisitorChannelHandler());
}
});
bootstrap.bind("0.0.0.0", 80).sync();
log.info("Http代理服务启动成功!");
} catch (Exception e) {
log.error("http proxy start err!", e);
}
}
private class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
ctx.channel().close();
return;
}
String host = getHost(byteBuf);
if (StringUtils.isBlank(host)) {
ctx.channel().close();
return;
}
log.debug("HttpProxy host: {}", host);
if (!host.endsWith(proxyConfig.getServer().getDomainName())) {
ctx.channel().close();
return;
}
int index = host.lastIndexOf("." + proxyConfig.getServer().getDomainName());
String subdomain = host.substring(0, index);
// 根据域名拿到绑定的映射对应的cmdChannel
Integer serverPort = ProxyUtil.getServerPortBySubdomain(subdomain);
if (null == serverPort) {
ctx.channel().close();
return;
}
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(serverPort);
if (null == cmdChannel) {
ctx.channel().close();
return;
}
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(serverPort);
if (StringUtils.isBlank(lanInfo)) {
ctx.channel().close();
return;
}
String visitorId = ProxyUtil.newVisitorId();
Channel visitorChannel = ctx.channel();
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
ProxyAttachment proxyAttachment = new ProxyAttachment(ctx.channel(), bytes, (channel, buf) -> {
Channel proxyChannel = channel.attr(Constants.NEXT_CHANNEL).get();
if (null == proxyChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
return;
}
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
// 增加流量计数
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
});
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, serverPort);
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel visitorChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
// 用户连接断开从控制连接中移除
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
// 删除代理附加对象
ProxyUtil.remoteProxyConnectAttachment(visitorId);
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null && proxyChannel.isActive()) {
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
proxyChannel.attr(Constants.LICENSE_ID).remove();
proxyChannel.attr(Constants.VISITOR_ID).remove();
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 通知客户端用户连接已经断开
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
}
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
}
private String getHost(ByteBuf byteBuf) {
byte[] buf = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(buf);
byteBuf.resetReaderIndex();
String req = new String(buf);
String[] lines = req.split("\r\n");
String firstLine = lines[0];
if (!(firstLine.endsWith("HTTP/1.1") || firstLine.endsWith("HTTP/1.0"))) {
return null;
}
for (int i = 1; i < lines.length; i++) {
String line = lines[i];
if (!line.startsWith("Host: ")) {
continue;
}
// 域名
String domain = line.substring(6);
return domain;
}
return null;
}
}
}
@@ -47,10 +47,10 @@ import java.util.Date;
* @date: 2022/6/16
*/
@Slf4j
public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
private static volatile Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher;
public ServerChannelHandler() {
public ProxyTunnelChannelHandler() {
dispatcher = Solon.context().getBean(Dispatcher.class);
}
@@ -71,33 +71,42 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (userChannel != null && userChannel.isActive()) {
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (null != visitorChannel) {
Integer licenseId = ctx.channel().attr(Constants.LICENSE_ID).get();
String visitorId = ctx.channel().attr(Constants.VISITOR_ID).get();
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseId);
if (cmdChannel != null) {
if (null != cmdChannel) {
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
}
// 数据发送完成后再关闭连接解决http1.0数据传输问题
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
userChannel.close();
if (visitorChannel.isActive()) {
// 数据发送完成后再关闭连接解决http1.0数据传输问题
visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
visitorChannel.close();
}
} else {
CmdChannelAttachInfo cmdChannelAttachInfo = ProxyUtil.getAttachInfo(ctx.channel());
if (null != cmdChannelAttachInfo) {
Solon.context().getBean(ProxyMutualService.class).offline(cmdChannelAttachInfo);
Solon.context().getBean(ClientConnectRecordService.class).add(new ClientConnectRecordDO()
.setIp(((InetSocketAddress)ctx.channel().remoteAddress()).getAddress().getHostAddress())
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
.setType(ClientConnectTypeEnum.DISCONNECT.getType())
.setMsg("")
.setCode(SuccessCodeEnum.SUCCESS.getCode())
.setCreateTime(new Date())
);
Channel curCmdChannel = ProxyUtil.getCmdChannelByLicenseId(cmdChannelAttachInfo.getLicenseId());
// 客户端切换网络后连接断开但服务端还未触发断开事件此时客户端重连上了然后服务端触发了断开此时不应该更新在线状态
if (curCmdChannel == ctx.channel()) {
Solon.context().getBean(ProxyMutualService.class).offline(cmdChannelAttachInfo);
ProxyUtil.removeCmdChannel(ctx.channel());
// 防止下次换一个客户端无法连接的情况
ProxyUtil.removeClientIdByLicenseId(cmdChannelAttachInfo.getLicenseId());
}
}
ProxyUtil.removeCmdChannel(ctx.channel());
// 即便是因为上述原因断开断开的日志依然要记录方便排查问题
Solon.context().getBean(ClientConnectRecordService.class).add(new ClientConnectRecordDO()
.setIp(((InetSocketAddress)ctx.channel().remoteAddress()).getAddress().getHostAddress())
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
.setType(ClientConnectTypeEnum.DISCONNECT.getType())
.setMsg("")
.setCode(SuccessCodeEnum.SUCCESS.getCode())
.setCreateTime(new Date())
);
}
super.channelInactive(ctx);
@@ -106,6 +115,9 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
if (ctx.channel().isActive()) {
ctx.channel().close();
}
}
@Override
@@ -119,7 +131,6 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
ctx.channel().close();
break;
case WRITER_IDLE:
log.info("写超时");
break;
case ALL_IDLE:
break;
@@ -26,29 +26,19 @@ import java.io.InputStream;
import java.security.KeyStore;
/**
*
* 代理隧道服务
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
@Component
public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
@Inject
private ProxyConfig proxyConfig;
@Inject("serverBossGroup")
@Inject("tunnelBossGroup")
private NioEventLoopGroup serverBossGroup;
@Inject("serverWorkerGroup")
@Inject("tunnelWorkerGroup")
private NioEventLoopGroup serverWorkerGroup;
@Inject("${neutrino.proxy.server.port}")
private Integer port;
@Inject("${neutrino.proxy.server.ssl-port}")
private Integer sslPort;
@Inject("${neutrino.proxy.server.jks-path}")
private String jksPath;
@Inject("${neutrino.proxy.server.key-store-password}")
private String keyStorePassword;
@Inject("${neutrino.proxy.server.key-manager-password}")
private String keyManagerPassword;
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
startProxyServer();
@@ -67,15 +57,15 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
}
});
try {
bootstrap.bind(port).sync();
log.info("代理服务启动,端口:{}", port);
bootstrap.bind(proxyConfig.getTunnel().getPort()).sync();
log.info("代理服务启动,端口:{}", proxyConfig.getTunnel().getPort());
} catch (Exception e) {
log.error("代理服务异常", e);
}
}
private void startProxyServerForSSL() {
if (null == sslPort) {
if (null == proxyConfig.getTunnel().getSslPort()) {
return;
}
ServerBootstrap bootstrap = new ServerBootstrap();
@@ -88,8 +78,8 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
}
});
try {
bootstrap.bind(sslPort).sync();
log.info("代理服务启动,SSL端口: {}", sslPort);
bootstrap.bind(proxyConfig.getTunnel().getSslPort()).sync();
log.info("代理服务启动,SSL端口: {}", proxyConfig.getTunnel().getSslPort());
} catch (Exception e) {
log.error("代理服务异常", e);
}
@@ -97,13 +87,13 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
private ChannelHandler createSslHandler() {
try {
InputStream jksInputStream = FileUtil.getInputStream(jksPath);
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getTunnel().getJksPath());
SSLContext serverContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, keyStorePassword.toCharArray());
ks.load(jksInputStream, proxyConfig.getTunnel().getKeyStorePassword().toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyManagerPassword.toCharArray());
kmf.init(ks, proxyConfig.getTunnel().getKeyManagerPassword().toCharArray());
TrustManager[] trustManagers = null;
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
@@ -121,11 +111,12 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
}
private void proxyServerCommonInitHandler(SocketChannel ch) {
// ch.pipeline().addFirst(new LoggingHandler(ProxyServerRunner.class));
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 IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
ch.pipeline().addLast(new ServerChannelHandler());
ch.pipeline().addLast(new ProxyTunnelChannelHandler());
}
}
@@ -4,7 +4,6 @@ import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
@@ -23,7 +22,7 @@ import java.net.InetSocketAddress;
* @date: 2022/6/16
*/
@Slf4j
public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
@@ -91,8 +90,7 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
if (null == cmdChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
@@ -127,12 +125,12 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
if (null == cmdChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null) {
if (null != proxyChannel) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
}
}
@@ -0,0 +1,53 @@
package org.dromara.neutrinoproxy.server.proxy.enhance;
import cn.hutool.core.util.StrUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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 org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.event.AppLoadEndEvent;
import org.noear.solon.core.event.EventListener;
/**
* HTTP代理
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Slf4j
@Component
public class HttpProxy implements EventListener<AppLoadEndEvent> {
@Inject
private ProxyConfig proxyConfig;
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName()) || null == proxyConfig.getServer().getHttpProxyPort()) {
log.info("no config domain name,nonsupport http proxy.");
return;
}
this.start();
}
private void start() {
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName()));
}
});
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpProxyPort()).sync();
log.info("Http代理服务启动成功!");
} catch (Exception e) {
log.error("http proxy start err!", e);
}
}
}
@@ -0,0 +1,170 @@
package org.dromara.neutrinoproxy.server.proxy.enhance;
import cn.hutool.core.util.StrUtil;
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 lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import org.noear.solon.Solon;
import java.net.InetSocketAddress;
/**
* @author: aoshiguchen
* @date: 2023/5/27
*/
@Slf4j
public class HttpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 域名
*/
private String domainName;
public HttpVisitorChannelHandler(String domainName) {
this.domainName = domainName;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
if (StrUtil.isBlank(domainName)) {
ctx.channel().close();
return;
}
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
byteBuf.resetReaderIndex();
ProxyAttachment proxyAttachment = new ProxyAttachment(ctx.channel(), bytes, (channel, buf) -> {
Channel proxyChannel = channel.attr(Constants.NEXT_CHANNEL).get();
if (null == proxyChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
return;
}
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(ProxyUtil.getVisitorIdByChannel(channel), bytes));
// 增加流量计数
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(channel);
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
});
String visitorId = ProxyUtil.getVisitorIdByChannel(ctx.channel());
if (StringUtils.isNotBlank(visitorId)) {
proxyAttachment.execute();
return;
}
// 用户连接到代理服务器时设置用户连接不可读等待代理后端服务器连接成功后再改变为可读状态
ctx.channel().config().setOption(ChannelOption.AUTO_READ, false);
String host = getHost(bytes);
if (StringUtils.isBlank(host)) {
ctx.channel().close();
return;
}
log.debug("HttpProxy host: {}", host);
if (!host.endsWith(domainName)) {
ctx.channel().close();
return;
}
int index = host.lastIndexOf("." + domainName);
String subdomain = host.substring(0, index);
// 根据域名拿到绑定的映射对应的cmdChannel
Integer serverPort = ProxyUtil.getServerPortBySubdomain(subdomain);
if (null == serverPort) {
ctx.channel().close();
return;
}
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(serverPort);
if (null == cmdChannel) {
ctx.channel().close();
return;
}
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(serverPort);
if (StringUtils.isBlank(lanInfo)) {
ctx.channel().close();
return;
}
visitorId = ProxyUtil.newVisitorId();
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, ctx.channel(), serverPort);
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel visitorChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
// 用户连接断开从控制连接中移除
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
// 删除代理附加对象
ProxyUtil.remoteProxyConnectAttachment(visitorId);
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null && proxyChannel.isActive()) {
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
proxyChannel.attr(Constants.LICENSE_ID).remove();
proxyChannel.attr(Constants.VISITOR_ID).remove();
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 通知客户端用户连接已经断开
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
}
}
super.channelInactive(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
}
private String getHost(byte[] buf) {
String req = new String(buf);
String[] lines = req.split("\r\n");
String firstLine = lines[0];
if (!(firstLine.endsWith("HTTP/1.1") || firstLine.endsWith("HTTP/1.0"))) {
return null;
}
for (int i = 1; i < lines.length; i++) {
String line = lines[i];
if (!line.startsWith("Host: ")) {
continue;
}
// 域名
String domain = line.substring(6);
return domain;
}
return null;
}
}
@@ -0,0 +1,87 @@
package org.dromara.neutrinoproxy.server.proxy.enhance;
import cn.hutool.core.util.StrUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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 org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.util.FileUtil;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.event.AppLoadEndEvent;
import org.noear.solon.core.event.EventListener;
import javax.net.ssl.*;
import java.io.InputStream;
import java.security.KeyStore;
/**
* HTTPS代理
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Slf4j
@Component
public class HttpsProxy implements EventListener<AppLoadEndEvent> {
@Inject
private ProxyConfig proxyConfig;
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName()) || null == proxyConfig.getServer().getHttpsProxyPort() ||
StringUtils.isEmpty(proxyConfig.getServer().getJksPath()) || StringUtils.isEmpty(proxyConfig.getServer().getKeyStorePassword())) {
log.info("no config domain name,nonsupport https proxy.");
return;
}
this.start();
}
private void start() {
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(createSslHandler());
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName()));
}
});
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpsProxyPort()).sync();
log.info("Https代理服务启动成功!");
} catch (Exception e) {
log.error("https proxy start err!", 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().getKeyStorePassword().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;
}
}
@@ -79,9 +79,19 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
String ip = ((InetSocketAddress)ctx.channel().remoteAddress()).getAddress().getHostAddress();
Date now = new Date();
String info = proxyMessage.getInfo();
String[] tmp = info.split(",");
String licenseKey = proxyMessage.getInfo();
String clientId = "";
if (tmp.length == 2) {
licenseKey = tmp[0];
clientId = tmp[1];
}
if (StrUtil.isEmpty(licenseKey)) {
log.warn("[客户端连接] license不能为空 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不能为空!", licenseKey));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setType(ClientConnectTypeEnum.CONNECT.getType())
@@ -94,7 +104,9 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
}
LicenseDO licenseDO = licenseService.findByKey(licenseKey);
if (null == licenseDO) {
log.warn("[客户端连接] license不存在 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不存在!", licenseKey));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setType(ClientConnectTypeEnum.CONNECT.getType())
@@ -106,7 +118,9 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
return;
}
if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) {
log.warn("[客户端连接] 当前license已被禁用 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被禁用!", licenseKey));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setLicenseId(licenseDO.getId())
@@ -119,7 +133,9 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
}
UserDO userDO = userService.findById(licenseDO.getUserId());
if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) {
log.warn("[客户端连接] 当前license无效 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license无效!", licenseKey));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setLicenseId(licenseDO.getId())
@@ -132,16 +148,21 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
}
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseDO.getId());
if (null != cmdChannel) {
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被另一节点使用!", licenseKey));
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setLicenseId(licenseDO.getId())
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setCode(SuccessCodeEnum.FAIL.getCode())
.setErr("当前license已被另一节点使用!")
.setCreateTime(now));
return;
String _clientId = ProxyUtil.getClientIdByLicenseId(licenseDO.getId());
if (!clientId.equals(_clientId)) {
log.warn("[客户端连接] 当前license已被另一节点使用 info:{} _clientId:{}", info, _clientId);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被另一节点使用!", licenseKey));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setLicenseId(licenseDO.getId())
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setCode(SuccessCodeEnum.FAIL.getCode())
.setErr("当前license已被另一节点使用!")
.setCreateTime(now));
return;
}
}
// 发送认证成功消息
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.SUCCESS.getCode(), "认证成功!", licenseKey));
@@ -150,10 +171,15 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
.setIp(ip)
.setLicenseId(licenseDO.getId())
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setMsg(info)
.setCode(SuccessCodeEnum.SUCCESS.getCode())
.setCreateTime(now));
// 设置当前licenseId对应的客户端ID
ProxyUtil.setLicenseIdToClientIdMap(licenseDO.getId(), clientId);
log.warn("[客户端连接] 认证成功 info:{} ", info);
// 更新license在线状态
licenseMapper.updateOnlineStatus(licenseDO.getId(), OnlineStatusEnum.ONLINE.getStatus(), now);
// 初始化VisitorChannel
@@ -1,5 +1,6 @@
package org.dromara.neutrinoproxy.server.proxy.handler;
import org.checkerframework.checker.units.qual.C;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
@@ -29,12 +30,17 @@ public class ProxyMessageDisconnectHandler implements ProxyMessageHandler {
return;
}
// 代理连接没有连上服务器由控制连接发送用户端断开连接消息
String visitorId = proxyMessage.getInfo();
Channel userChannel = ProxyUtil.removeVisitorChannelFromCmdChannel(ctx.channel(), visitorId);
if (null != userChannel) {
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (null != visitorChannel) {
// 数据发送完成后再关闭连接解决http1.0数据传输问题
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
// String visitorId = proxyMessage.getInfo();
// Channel visitorChannel = ProxyUtil.removeVisitorChannelFromCmdChannel(ctx.channel(), visitorId);
// if (null != visitorChannel) {
// // 数据发送完成后再关闭连接解决http1.0数据传输问题
// visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
// }
}
@Override
@@ -31,6 +31,9 @@ public class ProxyMessageTransferHandler implements ProxyMessageHandler {
buf.writeBytes(proxyMessage.getData());
visitorChannel.writeAndFlush(buf);
// 关闭http响应通道
// ProxyUtil.closeHttpProxyResponseChannel(visitorChannel);
// 增加流量计数
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
Solon.context().getBean(FlowReportService.class).addReadByte(visitorChannelAttachInfo.getLicenseId(), proxyMessage.getData().length);
@@ -19,19 +19,24 @@ import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateEnabl
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateRes;
import org.dromara.neutrinoproxy.server.dal.JobInfoMapper;
import org.dromara.neutrinoproxy.server.dal.entity.JobInfoDO;
import org.dromara.neutrinoproxy.server.job.*;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import fun.asgc.solon.extend.job.IJobSource;
import fun.asgc.solon.extend.job.JobInfo;
import fun.asgc.solon.extend.job.impl.JobExecutor;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.IJobSource;
import org.dromara.solonplugins.job.JobInfo;
import org.dromara.solonplugins.job.impl.JobExecutor;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Init;
import org.noear.solon.annotation.Inject;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
@@ -45,6 +50,29 @@ public class JobInfoService implements IJobSource {
private MapperFacade mapperFacade;
@Db
private JobInfoMapper jobInfoMapper;
@Inject
private DataCleanJob dataCleanJob;
@Inject
private DemoJob demoJob;
@Inject
private FlowReportForDayJob flowReportForDayJob;
@Inject
private FlowReportForHourJob flowReportForHourJob;
@Inject
private FlowReportForMinuteJob flowReportForMinuteJob;
@Inject
private FlowReportForMonthJob flowReportForMonthJob;
private Map<String, IJobHandler> jobHandlerMap = new HashMap<>();
@Init
public void init() {
jobHandlerMap.put("DataCleanJob", dataCleanJob);
jobHandlerMap.put("DemoJob", demoJob);
jobHandlerMap.put("FlowReportForDayJob", flowReportForDayJob);
jobHandlerMap.put("FlowReportForHourJob", flowReportForHourJob);
jobHandlerMap.put("FlowReportForMinuteJob", flowReportForMinuteJob);
jobHandlerMap.put("FlowReportForMonthJob", flowReportForMonthJob);
}
public PageInfo<JobInfoListRes> page(PageQuery pageQuery, JobInfoListReq req) {
Page<JobInfoListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
@@ -65,22 +93,15 @@ public class JobInfoService implements IJobSource {
ParamCheckUtil.checkNotNull(jobInfoDO, ExceptionConstant.JOB_INFO_NOT_EXIST);
jobInfoMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
if (EnableStatusEnum.ENABLE.getStatus().equals(req.getEnable())) {
Solon.context().getBean(JobExecutor.class).add(new JobInfo()
.setId(String.valueOf(jobInfoDO.getId()))
.setName(jobInfoDO.getHandler())
.setDesc(jobInfoDO.getDesc())
.setCron(jobInfoDO.getCron())
.setParam(jobInfoDO.getParam())
.setEnable(true)
);
Solon.context().getBean(JobExecutor.class).startById(String.valueOf(req.getId()));
} else {
Solon.context().getBean(JobExecutor.class).remove(String.valueOf(req.getId()));
Solon.context().getBean(JobExecutor.class).stopById(String.valueOf(req.getId()));
}
return new JobInfoUpdateEnableStatusRes();
}
public JobInfoExecuteRes execute(JobInfoExecuteReq req) {
Solon.context().getBean(JobExecutor.class).trigger(String.valueOf(req.getId()), req.getParam());
Solon.context().getBean(JobExecutor.class).triggerById(String.valueOf(req.getId()), req.getParam());
return new JobInfoExecuteRes();
}
@@ -99,6 +120,7 @@ public class JobInfoService implements IJobSource {
.setCron(item.getCron())
.setParam(item.getParam())
.setEnable(EnableStatusEnum.ENABLE.getStatus().equals(item.getEnable()))
.setJobHandler(jobHandlerMap.get(item.getHandler()))
);
}
@@ -30,12 +30,12 @@ import org.dromara.neutrinoproxy.server.controller.req.log.JobLogListReq;
import org.dromara.neutrinoproxy.server.controller.res.log.JobLogListRes;
import org.dromara.neutrinoproxy.server.dal.JobLogMapper;
import org.dromara.neutrinoproxy.server.dal.entity.JobLogDO;
import fun.asgc.solon.extend.job.IJobCallback;
import fun.asgc.solon.extend.job.JobInfo;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.solonplugins.job.IJobCallback;
import org.dromara.solonplugins.job.JobInfo;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.collect.Sets;
@@ -45,7 +46,6 @@ import java.util.function.Function;
import java.util.stream.Collectors;
/**
*
* @author: aoshiguchen
* @date: 2022/8/8
*/
@@ -71,246 +71,255 @@ public class PortMappingService implements LifecycleBean {
@Inject
private DBInitialize dbInitialize;
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
if (StringUtils.isNotEmpty(req.getDescription())) {
//描述字段为模糊查询在应用层处理否则sqlite不支持
req.setDescription("%" + req.getDescription() + "%");
}
List<PortMappingDO> list = portMappingMapper.selectPortMappingByCondition(req);
List<PortMappingListRes> respList = mapperFacade.mapAsList(list, PortMappingListRes.class);
if (CollectionUtils.isEmpty(list)) {
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
List<PortMappingDO> list = portMappingMapper.selectPortMappingByCondition(req);
List<PortMappingListRes> respList = mapperFacade.mapAsList(list, PortMappingListRes.class);
if (CollectionUtils.isEmpty(list)) {
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
Set<Integer> licenseIds = respList.stream().map(PortMappingListRes::getLicenseId).collect(Collectors.toSet());
List<LicenseDO> licenseList = licenseMapper.findByIds(licenseIds);
if (CollectionUtil.isEmpty(licenseList)) {
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
Set<Integer> userIds = licenseList.stream().map(LicenseDO::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
Map<Integer, LicenseDO> licenseMap = licenseList.stream().collect(Collectors.toMap(LicenseDO::getId, Function.identity()));
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
Set<Integer> licenseIds = respList.stream().map(PortMappingListRes::getLicenseId).collect(Collectors.toSet());
List<LicenseDO> licenseList = licenseMapper.findByIds(licenseIds);
if (CollectionUtil.isEmpty(licenseList)) {
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
Set<Integer> userIds = licenseList.stream().map(LicenseDO::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
Map<Integer, LicenseDO> licenseMap = licenseList.stream().collect(Collectors.toMap(LicenseDO::getId, Function.identity()));
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
respList.forEach(item -> {
LicenseDO license = licenseMap.get(item.getLicenseId());
if (null == license) {
return;
}
item.setLicenseName(license.getName());
item.setUserId(license.getUserId());
UserDO user = userMap.get(license.getUserId());
if (null == user) {
return;
}
item.setUserName(user.getName());
if (StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(item.getSubdomain())) {
item.setDomain(item.getSubdomain() + "." + proxyConfig.getServer().getDomainName());
}
if (NetworkProtocolEnum.HTTP.getDesc().equals(item.getProtocal())) {
item.setProtocal("HTTP(S)");
}
});
//sorted [userId asc] [licenseId asc] [createTime asc]
respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId)
.thenComparing(PortMappingListRes::getLicenseId)
.thenComparing(PortMappingListRes::getCreateTime))
.collect(Collectors.toList());
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
respList.forEach(item -> {
LicenseDO license = licenseMap.get(item.getLicenseId());
if (null == license) {
return;
}
item.setLicenseName(license.getName());
item.setUserId(license.getUserId());
UserDO user = userMap.get(license.getUserId());
if (null == user) {
return;
}
item.setUserName(user.getName());
if (StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(item.getSubdomain())) {
item.setDomain(item.getSubdomain() + "." + proxyConfig.getServer().getDomainName());
}
});
//sorted [userId asc] [licenseId asc] [createTime asc]
respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId)
.thenComparing(PortMappingListRes::getLicenseId)
.thenComparing(PortMappingListRes::getCreateTime))
.collect(Collectors.toList());
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
public PortMappingCreateRes create(PortMappingCreateReq req) {
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
// 临时处理如果当前用户不是管理员则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), null), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), null), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
public PortMappingCreateRes create(PortMappingCreateReq req) {
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
// 临时处理如果当前用户不是管理员则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), null), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), null), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
Date now = new Date();
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setLicenseId(req.getLicenseId());
portMappingDO.setProtocal(req.getProtocal());
portMappingDO.setSubdomain(req.getSubdomain());
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setDescription(req.getDescription());
portMappingDO.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portMappingDO.setCreateTime(now);
portMappingDO.setUpdateTime(now);
portMappingMapper.insert(portMappingDO);
// 更新VisitorChannel
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
// 更新域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) &&
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
}
return new PortMappingCreateRes();
}
Date now = new Date();
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setLicenseId(req.getLicenseId());
portMappingDO.setProtocal(req.getProtocal());
portMappingDO.setSubdomain(req.getSubdomain());
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portMappingDO.setCreateTime(now);
portMappingDO.setUpdateTime(now);
portMappingMapper.insert(portMappingDO);
// 更新VisitorChannel
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
// 更新域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) &&
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
}
return new PortMappingCreateRes();
}
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
// 临时处理如果当前用户不是管理员则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
// 临时处理如果当前用户不是管理员则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
// 查询原端口映射
PortMappingDO oldPortMappingDO = portMappingMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(oldPortMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
// 查询原端口映射
PortMappingDO oldPortMappingDO = portMappingMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(oldPortMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setId(req.getId());
portMappingDO.setProtocal(req.getProtocal());
portMappingDO.setSubdomain(req.getSubdomain());
portMappingDO.setLicenseId(req.getLicenseId());
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setDescription(req.getDescription());
portMappingDO.setUpdateTime(new Date());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portMappingMapper.updateById(portMappingDO);
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
// 删除老的域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(oldPortMappingDO.getProtocal()) &&
StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) {
ProxyUtil.removeSubdomainToServerPort(oldPortMappingDO.getSubdomain());
}
// 更新域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) &&
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
}
return new PortMappingUpdateRes();
}
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setId(req.getId());
portMappingDO.setProtocal(req.getProtocal());
portMappingDO.setSubdomain(req.getSubdomain());
portMappingDO.setLicenseId(req.getLicenseId());
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setUpdateTime(new Date());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portMappingMapper.updateById(portMappingDO);
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
// 删除老的域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(oldPortMappingDO.getProtocal()) &&
StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) {
ProxyUtil.removeSubdomainToServerPort(oldPortMappingDO.getSubdomain());
}
// 更新域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) &&
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
}
return new PortMappingUpdateRes();
}
public PortMappingDetailRes detail(Integer id) {
PortMappingDO portMappingDO = portMappingMapper.findById(id);
if (null == portMappingDO) {
return null;
}
PortMappingDetailRes res = new PortMappingDetailRes()
.setId(portMappingDO.getId())
.setLicenseId(portMappingDO.getLicenseId())
.setServerPort(portMappingDO.getServerPort())
.setClientIp(portMappingDO.getClientIp())
.setClientPort(portMappingDO.getClientPort())
.setIsOnline(portMappingDO.getIsOnline())
.setEnable(portMappingDO.getEnable())
.setCreateTime(portMappingDO.getCreateTime())
.setUpdateTime(portMappingDO.getUpdateTime());
public PortMappingDetailRes detail(Integer id) {
PortMappingDO portMappingDO = portMappingMapper.findById(id);
if (null == portMappingDO) {
return null;
}
PortMappingDetailRes res = new PortMappingDetailRes()
.setId(portMappingDO.getId())
.setLicenseId(portMappingDO.getLicenseId())
.setServerPort(portMappingDO.getServerPort())
.setClientIp(portMappingDO.getClientIp())
.setClientPort(portMappingDO.getClientPort())
.setIsOnline(portMappingDO.getIsOnline())
.setEnable(portMappingDO.getEnable())
.setCreateTime(portMappingDO.getCreateTime())
.setUpdateTime(portMappingDO.getUpdateTime());
LicenseDO license = licenseMapper.findById(portMappingDO.getLicenseId());
if (null != license) {
res.setLicenseName(license.getName());
res.setUserId(license.getUserId());
UserDO user = userMapper.findById(license.getUserId());
if (null != user) {
res.setUserName(user.getName());
}
}
LicenseDO license = licenseMapper.findById(portMappingDO.getLicenseId());
if (null != license) {
res.setLicenseName(license.getName());
res.setUserId(license.getUserId());
UserDO user = userMapper.findById(license.getUserId());
if (null != user) {
res.setUserName(user.getName());
}
}
return res;
}
return res;
}
public PortMappingUpdateEnableStatusRes updateEnableStatus(PortMappingUpdateEnableStatusReq req) {
PortMappingDO portMappingDO = portMappingMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
public PortMappingUpdateEnableStatusRes updateEnableStatus(PortMappingUpdateEnableStatusReq req) {
PortMappingDO portMappingDO = portMappingMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
portMappingMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
portMappingMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新VisitorChannel
portMappingDO.setEnable(req.getEnable());
if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(req.getEnable())) {
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
} else {
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
}
// 更新VisitorChannel
portMappingDO.setEnable(req.getEnable());
if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(req.getEnable())) {
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
} else {
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
}
return new PortMappingUpdateEnableStatusRes();
}
return new PortMappingUpdateEnableStatusRes();
}
public void delete(Integer id) {
PortMappingDO portMappingDO = portMappingMapper.findById(id);
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
public void delete(Integer id) {
PortMappingDO portMappingDO = portMappingMapper.findById(id);
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
if (null != licenseDO && !SystemContextHolder.isAdmin()) {
// 临时处理如果当前用户不是管理员则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
if (null != licenseDO && !SystemContextHolder.isAdmin()) {
// 临时处理如果当前用户不是管理员则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
portMappingMapper.deleteById(id);
portMappingMapper.deleteById(id);
// 更新VisitorChannel
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
// 更新域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain());
}
}
// 更新VisitorChannel
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
// 更新域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain());
}
}
/**
* 根据license查询可用的端口映射列表
*
* @param licenseId
* @return
*/
public List<PortMappingDO> findEnableListByLicenseId(Integer licenseId) {
return portMappingMapper.findEnableListByLicenseId(licenseId);
}
/**
* 根据license查询可用的端口映射列表
* @param licenseId
* @return
*/
public List<PortMappingDO> findEnableListByLicenseId(Integer licenseId) {
return portMappingMapper.findEnableListByLicenseId(licenseId);
}
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Init
public void init() {
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Init
public void init() {
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
// 未配置域名则不需要处理域名映射逻辑
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
return;
}
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>()
.eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc())
.isNotNull(PortMappingDO::getSubdomain)
);
if (CollectionUtil.isEmpty(portMappingDOList)) {
return;
}
portMappingDOList.forEach(item -> {
if (StrUtil.isBlank(item.getSubdomain())) {
return;
}
ProxyUtil.setSubdomainToServerPort(item.getSubdomain(), item.getServerPort());
});
}
// 未配置域名则不需要处理域名映射逻辑
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
return;
}
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>()
.eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc())
.isNotNull(PortMappingDO::getSubdomain)
);
if (CollectionUtil.isEmpty(portMappingDOList)) {
return;
}
portMappingDOList.forEach(item -> {
if (StrUtil.isBlank(item.getSubdomain())) {
return;
}
ProxyUtil.setSubdomainToServerPort(item.getSubdomain(), item.getServerPort());
});
}
@Override
public void start() throws Throwable {
@Override
public void start() throws Throwable {
}
}
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Override
public void stop() throws Throwable {
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Override
public void stop() throws Throwable {
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
}
@@ -2,6 +2,7 @@ package org.dromara.neutrinoproxy.server.service;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
import org.noear.solon.annotation.Component;
@@ -22,7 +23,7 @@ public class ProtocalService {
public List<ProtocalListRes> list() {
return Lists.newArrayList(
new ProtocalListRes().setName("TCP").setEnable(Boolean.TRUE).setRemark("支持一切TCP之上的协议"),
new ProtocalListRes().setName("HTTP").setEnable(Boolean.TRUE).setRemark("支持绑定子域名,未绑定时等价于时使用TCP"),
new ProtocalListRes().setName("HTTP(S)").setEnable(Boolean.TRUE).setRemark("支持绑定子域名,未绑定时等价于时使用TCP。 若配置了证书,则同时支持HTTPS。"),
new ProtocalListRes().setName("UDP").setEnable(Boolean.FALSE).setRemark("暂不支持")
);
}
@@ -14,7 +14,7 @@ import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
import org.dromara.neutrinoproxy.server.proxy.core.VisitorChannelHandler;
import org.dromara.neutrinoproxy.server.proxy.core.TcpVisitorChannelHandler;
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
@@ -41,10 +41,8 @@ import java.util.stream.Collectors;
@Slf4j
@Component
public class VisitorChannelService {
@Inject("serverBossGroup")
private NioEventLoopGroup serverBossGroup;
@Inject("serverWorkerGroup")
private NioEventLoopGroup serverWorkerGroup;
@Inject("tcpServerBootstrap")
private ServerBootstrap tcpServerBootstrap;
@Inject
private ProxyMutualService proxyMutualService;
@Db
@@ -218,20 +216,16 @@ public class VisitorChannelService {
if (CollectionUtil.isEmpty(portMappingList)) {
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().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new VisitorChannelHandler());
}
});
for (PortMappingDO portMapping : portMappingList) {
if (EnableStatusEnum.DISABLE.getStatus().equals(portMapping.getEnable())) {
// 端口映射被禁用了忽略 TODO 端口被禁用了也需要处理
continue;
}
// TODO 此处切入TCP/UDP代理
try {
proxyMutualService.bindServerPort(cmdChannelAttachInfo, portMapping.getServerPort());
bootstrap.bind(portMapping.getServerPort()).get();
tcpServerBootstrap.bind(portMapping.getServerPort()).get();
log.info("绑定用户端口: {}", portMapping.getServerPort());
} catch (Exception ex) {
// BindException表示该端口已经绑定过
@@ -9,82 +9,98 @@ import java.util.Map;
import java.util.Set;
/**
*
* @author: aoshiguchen
* @date: 2022/8/1
*/
public class ParamCheckUtil {
public static void checkNotNull(Object obj, String name) {
if (null == obj) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_NULL, name);
}
}
public static void checkNotNull(Object obj, String name) {
if (null == obj) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_NULL, name);
}
}
public static void checkNotEmpty(String str, String name) {
if (StrUtil.isEmpty(str)) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(String str, String name) {
if (StrUtil.isEmpty(str)) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Collection collection, String name) {
if (null == collection || collection.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Collection collection, String name) {
if (null == collection || collection.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Map map, String name) {
if (null == map || map.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Map map, String name) {
if (null == map || map.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Set set, String name) {
if (null == set || set.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Set set, String name) {
if (null == set || set.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkMustNull(Object obj, ExceptionConstant constant, Object... params) {
if (null != obj) {
throw ServiceException.create(constant, params);
}
}
public static void checkMustNull(Object obj, ExceptionConstant constant, Object... params) {
if (null != obj) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotNull(Object obj, ExceptionConstant constant, Object... params) {
if (null == obj) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotNull(Object obj, ExceptionConstant constant, Object... params) {
if (null == obj) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(String str, ExceptionConstant constant, Object... params) {
if (StrUtil.isEmpty(str)) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(String str, ExceptionConstant constant, Object... params) {
if (StrUtil.isEmpty(str)) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Collection collection, ExceptionConstant constant, Object... params) {
if (null == collection || collection.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Collection collection, ExceptionConstant constant, Object... params) {
if (null == collection || collection.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Map map, ExceptionConstant constant, Object... params) {
if (null == map || map.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Map map, ExceptionConstant constant, Object... params) {
if (null == map || map.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Set set, ExceptionConstant constant, Object... params) {
if (null == set || set.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Set set, ExceptionConstant constant, Object... params) {
if (null == set || set.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkExpression(boolean expression, ExceptionConstant constant, Object... params) {
if (!expression) {
throw ServiceException.create(constant, params);
}
}
/**
* check max length , if the string length exceeded ,an exception is thrown
* if the string is empty , it's not checked
* 检查最大长度,超出长度,则抛出异常 . 如果字符串本身为空,则不进行检查
*
* @param str String
* @param maxLength Maximum length
* @param params Exception parameters
* @throws {@link ServiceException}
*/
public static void checkMaxLength(String str, int maxLength, Object... params) {
if (!StrUtil.isEmpty(str) && maxLength < str.length()) {
throw ServiceException.create(ExceptionConstant.FILED_LENGTH_OUT, params);
}
}
public static void checkExpression(boolean expression, ExceptionConstant constant, Object... params) {
if (!expression) {
throw ServiceException.create(constant, params);
}
}
}
@@ -2,7 +2,9 @@ package org.dromara.neutrinoproxy.server.util;
import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.ChannelAttribute;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
@@ -61,6 +63,10 @@ public class ProxyUtil {
* 子域名 - 服务端端口映射
*/
private static Map<String, Integer> subdomainToServerPort = new HashMap<>();
/**
* licenseId - 客户端Id映射
*/
private static Map<Integer, String> licenseIdToClientIdMap = new HashMap<>();
/**
* 初始化代理信息
@@ -353,4 +359,62 @@ public class ProxyUtil {
public static Integer getServerPortBySubdomain(String subdomain) {
return subdomainToServerPort.get(subdomain);
}
/**
* 关闭http响应channel
* @param channel
* @return
*/
public static void closeHttpProxyResponseChannel(Channel channel) {
if (null == channel) {
return;
}
String visitorId = getVisitorIdByChannel(channel); // channel.attr(Constants.VISITOR_ID).get();
if (StringUtils.isBlank(visitorId)) {
return;
}
ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(visitorId);
if (null != proxyAttachment) {
tryClose(channel);
}
}
/**
* 关闭channel
* @param channel
*/
public static void tryClose(Channel channel) {
try {
channel.close();
} catch (Exception e) {
// ignore
}
}
/**
* 设置licenseId - clientId映射
* @param licenseId
* @param clientId
*/
public static void setLicenseIdToClientIdMap(Integer licenseId, String clientId) {
licenseIdToClientIdMap.put(licenseId, clientId);
}
/**
* 根据licenseId获取clientId
* @param licenseId
* @return
*/
public static String getClientIdByLicenseId(Integer licenseId) {
return licenseIdToClientIdMap.get(licenseId);
}
/**
* 根据licenseId删除clientId
* @param licenseId
*/
public static void removeClientIdByLicenseId(Integer licenseId) {
licenseIdToClientIdMap.remove(licenseId);
}
}
@@ -1,34 +1,40 @@
server:
port: 8888
port: ${WEB_PORT:8888}
neutrino:
proxy:
protocol:
max-frame-length: 2097152
max-frame-length: ${MAX_FRAME_LENGTH:2097152}
length-field-offset: 0
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 40
write-idle-time: 10
read-idle-time: 30
write-idle-time: 5
all-idle-time-seconds: 0
tunnel:
boss-thread-count: 2
work-thread-count: 10
port: ${OPEN_PORT:9000}
ssl-port: ${SSL_PORT:9002}
key-store-password: ${STORE_PASS:123456}
key-manager-password: ${MGR_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
server:
boss-thread-count: 10
work-thread-count: 60
port: 9000
ssl-port: 9002
key-store-password: 123456
key-manager-password: 123456
jks-path: classpath:/test.jks
# 如果不配置,则不支持代理http
domain-name:
boss-thread-count: 5
work-thread-count: 20
http-proxy-port: ${HTTP_PROXY_PORT:80}
https-proxy-port: ${HTTPS_PROXY_PORT:443}
# 如果不配置,则不支持域名映射
domain-name: ${DOMAIN_NAME:}
key-store-password: ${HTTPS_STORE_PASS:}
jks-path: ${HTTPS_JKS_PATH:}
data:
db:
type: sqlite
url: jdbc:sqlite:data.db
driver-class: org.sqlite.JDBC
username:
password:
type: ${DB_TYPE:sqlite}
url: ${DB_URL:jdbc:sqlite:data.db}
username: ${DB_USER:}
password: ${DB_PASSWORD:}
#添加MIME印射(如果有需要?)
#是否启用静态文件服务。(可不配,默认为启用)
@@ -50,4 +56,4 @@ mybatis.db:
cacheEnabled: false
mapUnderscoreToCamelCase: true
globalConfig: #全局配置(要与 GlobalConfig 类的属性一一对应)
banner: true
banner: true
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_FILE" value="/work/projects/neutrino-proxy-server/app.log"/>
<property name="LOG_FILE" value="./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" />
<!-- <property name="ENCODE" value="utf8" />-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
@@ -10,7 +10,7 @@
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
@@ -22,7 +22,7 @@
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
@@ -23,6 +23,9 @@
<if test="req.protocal != null and req.protocal != ''">
AND pm.protocal = #{req.protocal}
</if>
<if test="req.description != null and req.description != '' ">
AND pm.description like #{req.description}
</if>
</where>
order by pm.id asc
</select>
@@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS `user` (
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_user_login_name` (`login_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#token表
CREATE TABLE IF NOT EXISTS `user_token` (
@@ -24,7 +24,7 @@ CREATE TABLE IF NOT EXISTS `user_token` (
KEY `I_user_token_user_id` (`user_id`),
KEY `I_user_token_token` (`token`),
KEY `I_user_token_expiration_time` (`expiration_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `port_pool` (
@@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS `port_pool` (
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_port_pool_port` (`port`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `port_group` (
@@ -48,7 +48,7 @@ CREATE TABLE IF NOT EXISTS `port_group` (
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='端口分组';
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
##########################################################
#license表
@@ -63,7 +63,7 @@ CREATE TABLE IF NOT EXISTS `license` (
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `I_license_key` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `port_mapping` (
@@ -75,12 +75,13 @@ CREATE TABLE IF NOT EXISTS `port_mapping` (
`client_ip` varchar(20) NOT NULL COMMENT '客户端IP',
`client_port` int NOT NULL COMMENT '客户端端口',
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
`description` varchar(100) DEFAULT NULL COMMENT '描述',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_port_mapping_server_port` (`server_port`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
##########################################################
#
CREATE TABLE IF NOT EXISTS `user_login_record` (
@@ -91,11 +92,11 @@ CREATE TABLE IF NOT EXISTS `user_login_record` (
`type` int NOT NULL COMMENT '类型(1、登录 2、登出)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `client_connect_record` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'IP',
`ip` varchar(50) NOT NULL COMMENT 'IP',
`license_id` int NOT NULL COMMENT 'licenseId',
`type` int NOT NULL COMMENT '类型(1、连接 2、断开连接)',
`msg` varchar(512) DEFAULT NULL COMMENT '消息',
@@ -103,7 +104,7 @@ CREATE TABLE IF NOT EXISTS `client_connect_record` (
`err` text DEFAULT NULL COMMENT '异常信息',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
##########################################################
#
CREATE TABLE IF NOT EXISTS `job_info` (
@@ -119,7 +120,7 @@ CREATE TABLE IF NOT EXISTS `job_info` (
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_job_info_handler` (`handler`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `job_log` (
@@ -134,7 +135,7 @@ CREATE TABLE IF NOT EXISTS `job_log` (
PRIMARY KEY (`id`) USING BTREE,
KEY `I_job_log_create_time` (`create_time`) USING BTREE,
KEY `I_job_log_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
##########################################################
#-(24)
@@ -152,7 +153,7 @@ CREATE TABLE IF NOT EXISTS `flow_report_minute` (
KEY `I_flow_report_minute_date` (`date`) USING BTREE,
KEY `I_flow_report_minute_user_id` (`user_id`),
KEY `I_flow_report_minute_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#-(60)
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
@@ -162,14 +163,14 @@ CREATE TABLE IF NOT EXISTS `flow_report_hour` (
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM-dd HH',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_hour_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_hour_date` (`date`) USING BTREE,
KEY `I_flow_report_hour_user_id` (`user_id`),
KEY `I_flow_report_hour_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#-(1)
CREATE TABLE IF NOT EXISTS `flow_report_day` (
@@ -179,14 +180,14 @@ CREATE TABLE IF NOT EXISTS `flow_report_day` (
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM-dd',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_day_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_day_date` (`date`) USING BTREE,
KEY `I_flow_report_day_user_id` (`user_id`),
KEY `I_flow_report_day_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#-()
CREATE TABLE IF NOT EXISTS `flow_report_month` (
@@ -196,11 +197,11 @@ CREATE TABLE IF NOT EXISTS `flow_report_month` (
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_month_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_month_date` (`date`) USING BTREE,
KEY `I_flow_report_month_user_id` (`user_id`),
KEY `I_flow_report_month_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -10,4 +10,4 @@ CREATE TABLE IF NOT EXISTS `port_group` (
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='端口分组';
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='端口分组';
@@ -1,2 +1,3 @@
ALTER TABLE port_mapping ADD `protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议';
ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)';
ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)';
ALTER TABLE port_mapping ADD `description` varchar(100) DEFAULT NULL COMMENT '描述';
@@ -69,6 +69,7 @@ CREATE TABLE IF NOT EXISTS `port_mapping` (
`client_ip` VARCHAR(20) NOT NULL,
`client_port` INTEGER NOT NULL,
`is_online` INTEGER(2) NOT NULL,
`description` VARCHAR(100) DEFAULT NULL,
`enable` INTEGER(2) NOT NULL,
`create_time` INTEGER(20) NOT NULL,
`update_time` INTEGER(20) NOT NULL
@@ -1,7 +1,7 @@
#port_mapping
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`,`client_ip`, `client_port`, `is_online`, `enable`,`description`,`create_time`, `update_time`) VALUES
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1,'test1', STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`,`description`, `create_time`, `update_time`) VALUES
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1,'test2', STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`,`description`, `create_time`, `update_time`) VALUES
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1,'test3', STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
@@ -1,2 +1,3 @@
ALTER TABLE port_mapping ADD `protocal` varchar(10) NOT NULL DEFAULT 'TCP';
ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL;
ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL;
ALTER TABLE port_mapping ADD `description` varchar(100);
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

@@ -5,14 +5,15 @@ permalink: /pages/793dcb
article: false
---
接下来您讲学习到如何快速上手
## 1、 部署服务端
### 1.1、 Docker一键部署
> 当前最新版本为1.8.2,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.2` 指定版本安装,推荐使用`latest`直接安装最新版。
#### 使用默认sqlite数据库
```shell
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
-d --restart=always --name neutrino-proxy \
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.7.1
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
```
#### 指定自己的mysql数据库
@@ -29,14 +30,14 @@ neutrino:
# 数据库帐号
username: xxx
# 数据库密码
password: wHCvf3@hmw^D*
password: xxx
```
- 然后执行如下命令:
```shell
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
-v /root/neutrino-proxy/config:/root/neutrino-proxy/config \
-d --restart=always --name neutrino \
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.7.1
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
```
### 1.2、使用jar包自行部署
@@ -45,8 +46,8 @@ registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.7.1
- 在服务器上新建部署目录:`/work/projects/neutrino-proxy-server`
- 将` neutrino-proxy-server.jar``neutrino-proxy-admin.zip`上传至服务器部署目录。
- 解压`neutrino-proxy-admin.zip`文件
- 执行命令`java -jar neutrino-proxy-server.jar`启动服务端完成部署,默认使用sqlite数据库。
- 若需要指定自己的mysql数据库,同样的需要在当前目录下新建`app.yml`文件,文件内容同上。执行命令`java -jar neutrino-proxy-server.jar config=app.yml`启动服务端完成部署
- 执行命令`java -Dfile.encoding=utf-8 -jar neutrino-proxy-server.jar`启动服务端完成部署,默认使用sqlite数据库。
- 若需要指定自己的mysql数据库,同样的需要在当前目录下新建`app.yml`文件,文件内容同上。执行命令`java -Dfile.encoding=utf-8 -jar neutrino-proxy-server.jar config=app.yml`启动服务端完成部署
- 可参照 https://gitee.com/dromara/neutrino-proxy/blob/master/bin/server_start.sh 使用shell脚本启动服务端。
## 2、管理后台配置
@@ -76,7 +77,7 @@ neutrino:
# licenseKey,客户端凭证。此处需要配置刚刚从管理后台复制的LicenseKey
license-key: xxxx
```
- 执行命令`java -jar neutrino-proxy-client.jar`启动客户端
- 执行命令`java -jar neutrino-proxy-client.jar config=app.yml`启动客户端
- 查看服务端License管理,刷新页面,对应的License在线状态为`在线`,则表明客户端已正常连接。
## 4、代理验证
@@ -114,4 +115,4 @@ module.exports = {
}
```
:::
-->
-->
@@ -0,0 +1,31 @@
---
title: 关于端口
date: 2023-04-05 18:20:18
permalink: /pages/68badb/
---
# 端口池
> 端口池用于统一、集中管理服务端代理对外开放的代理端口。
- 1、这里不包含服务端启动本身所需要的一些特殊端口,如:WEB端口、对客户端开放的端口(9000、9002)、HTTP代理端口。
- 2、端口映射里所有的服务端端口,都包含在端口池内。方便了统一管理、安全组/防火墙设置。
# 端口池分组
> 顾名思义,它是对端口池下的端口做了一个划分。
- 1、允许同一个分组内的端口拥有特定的归属。比如:全局分组下的端口,进行代理时所有用户共享,这里极端情况下涉及到端口抢占问题。而用户分组下的端口,归属于某一个特定的用户。
License分组下的端口,归属于某一个特定的License。
- 2、该划分,使得特定的用户、License能够独占一部分端口。为后续更多场景下代理,避免端口被占用提供了支持。
# 端口映射
> 当前版本代理功能的基本单元。
- 1、该映射由一个四元组唯一标识(代理服务端IP、代理服务端端口、被代理服务局域网IP、被代理服务端口)。
- 2、由于本项目为个人版,代理服务端仅支持一个节点,所以四元组中的服务端IP可以省略,不在端口映射管理中体现。
- 3、代理成功的情况下,通过访问代理服务端IP+端口,可以达到访问内网被代理服务IP+端口的同等效果
- 4、例如:本地启动一个socket服务,需要和异地的同事联调。可以通过中微子代理jetbrains客户端插件(后续会推出)将本地socket端口代理至公网。同事通过访问代理服务端公网IP+端口进行调试。
# 基础端口说明
- WEB端口:服务端API、后台管理访问端口,默认为:8888
- 服务端等待客户端连接的端口,非SSL:默认9000,SSL端口:默认9002(若不需要SSL支持,可不配置SSL端口)
- HTTP代理端口:默认80,用于域名映射,若无需域名映射,可以忽略。
@@ -0,0 +1,62 @@
---
title: 域名映射
date: 2023-04-05 18:46:12
permalink: /pages/269a2e/
---
# 什么是域名映射?
域名映射是将指定端口映射绑定到某一个子域名上,通过直接访问域名的方式,达到访问被代理服务的目的。
# 域名映射能解决什么问题?
- 1、当服务端端口需要经常变动的时候,可以通过固定域名的方式,使得开发调试能够长期稳定的进行,不用每次更改访问地址。
- 2、使用域名可以使用有意义的名称方便记忆
- 3、调试某些第三方回调时,回调地址不能填写IP+端口的形式,此时需要域名来完成
# 域名映射的大致流程?
> 例如你有一个已备案的域名:asgc.fun,以下是使用域名映射的大致流程
## 情况1:代理服务端使用80端口
- 1、DNS解析配置泛域名解析 proxy.asgc.fun 指向代理服务端IP
- 2、代理服务端app.yml配置域名:proxy.asgc.fun,部署
- 3、服务端管理后台新建端口映射,协议选择:HTTP,设置好子域名,如:test1
- 4、配置好客户端并启动,代理映射完成
- 5、通过访问: http://test1.proxy.asgc.fun 实现访问被代理服务
## 情况2:代理服务端不使用80端口,80端口被nginx占用
> 假设代理服务端使用82作为HTTP代理端口
- 1、DNS解析配置泛域名解析 proxy.asgc.fun 指向代理服务端IP
- 2、nginx 80端口下将 *.proxy.asgc.fun 请求转到82端口,并附带原有请求头(特别是`Host`
- 3、代理服务端app.yml配置域名:proxy.asgc.fun,部署
- 4、服务端管理后台新建端口映射,协议选择:HTTP,设置好子域名,如:test1
- 5、配置好客户端并启动,代理映射完成
- 6、通过访问: http://test1.proxy.asgc.fun 实现访问被代理服务
# 所有的代理映射都能绑定域名吗?
不行的。之所以能实现域名绑定,原因是HTTP请求附带了Host请求头,包含了请求的域名信息,代理服务端能根据请求的域名做分发。而TCP协议本身并不包含域名,因此无法实现。
所以,目前只有HTTP(涵盖HTTPS)协议能支持。
# 代理服务端使用非80端口时,nginx配置示例
```
server {
listen 80;
# 此处配不配貌似不影响
server_name localhost *.neutrino-proxy.asgc.fun;
location / {
if ($http_host ~* "(.*?).neutrino-proxy.asgc.fun") {
# 转发到代理服务端HTTP代理端口
proxy_pass http://localhost:8899;
}
tcp_nodelay on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#以上三行,目的是将代理服务器收到的用户的信息传到真实服务器上
root html;
index index.html index.htm;
}
}
```
@@ -0,0 +1,25 @@
---
title: HTTPS配置
date: 2023-05-27 11:40:12
permalink: /pages/99a300/
---
## 基础说明
- 在中微子代理中,HTTP的定位是:为ip+端口的访问形式增加域名访问的支持。因此:
- 如果端口映射正确配置了域名,那么该映射同时支持IP+端口、域名2种访问形式。
- 如果端口映射未配置域名,那么只能以ip+端口的形式访问,此时等价于选择TCP协议。
- 在中微子代理中,HTTPS作为HTTP的一种增强行为,不作为一个单独的协议。因此:
- 在端口映射中只需要选择HTTP即可
- 若端口映射未正确配置域名,那么同理,此时等价于选择TCP协议。
- 若端口映射配置了域名,但是未正确配置HTTPS端口、证书,那么此时可以通过域名访问,但不支持HTTPS
- 若端口映射配置了域名,且正确配置了HTTPS端口、证书,那么此时可以通过IP+端口、http域名、https域名3种形式访问
## HTTPS配置流程
- 按照域名映射流程,完成域名的配置
- 在服务端配置域名的HTTPS证书信息(此处应该是通配符证书)
## HTTPS端口问题
- 与HTTP类似,HTTPS也有一个默认端口:443,默认情况下需要占用服务端443端口
- 如果代理服务端不能独占443端口,此处可以指定任意端口,但需要在nginx中配置443端口的转发规则。此处与域名映射中代理服务端不使用80端口的情况类似
@@ -0,0 +1,59 @@
---
title: 服务端配置
date: 2023-05-27 11:41:44
permalink: /pages/f2d0f1/
---
::: tip
1.8.2版本,服务端配置格式有所调整。从1.8.2之前的版本升级到1.8.2之后的版本,需要注意
:::
# 以下是最新的服务端配置格式(app.yml)
```yml
server:
# 服务端web端口,用于支持HTTP接口,管理后台页面访问
port: ${WEB_PORT:8888}
neutrino:
proxy:
# 隧道相关配置-用于维持服务端与客户端的通信
tunnel:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 2
work-thread-count: 10
# 隧道非SSL端口
port: ${OPEN_PORT:9000}
# 隧道SSL端口
ssl-port: ${SSL_PORT:9002}
# 隧道SSL证书配置
key-store-password: ${STORE_PASS:123456}
key-manager-password: ${MGR_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
# 代理服务相关配置
server:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# http代理端口,默认80
http-proxy-port: ${HTTP_PROXY_PORT:80}
# https代理端口,默认443 (需要配置域名、证书)
https-proxy-port: ${HTTPS_PROXY_PORT:443}
# 如果不配置,则不支持域名映射
domain-name: ${DOMAIN_NAME:}
# https证书配置
key-store-password: ${HTTPS_STORE_PASS:}
jks-path: ${HTTPS_JKS_PATH:}
data:
db:
# 数据库类型,目前支持sqlite、mysql两种
type: ${DB_TYPE:sqlite}
# 数据库连接URL
url: ${DB_URL:jdbc:sqlite:data.db}
# 数据库用户名
username: ${DB_USER:}
# 数据库密码
password: ${DB_PASSWORD:}
```
+46 -31
View File
@@ -1,7 +1,7 @@
---
home: true
heroImage: /img/logo.png
heroImage: /img/logo-0.75x.png
heroText: Neutrino-Proxy
tagline: 🚀一个基于Netty的开源内网穿透神器
actionText: 开始使用 →
@@ -11,6 +11,8 @@ bannerBg: none # auto => 网格纹背景(有bodyBgImg时无背景),默认 | no
features: # 可选的
- title: 流量监控
details: 首页图表、报表管理多维度流量监控。全方位掌握实时、历史代理数据。
- title: 域名映射
details: HTTP代理绑定子域名,开发调试三方回调更方便。
- title: 用户/License
details: 支持多用户、多客户端使用。后台禁用实时生效。
- title: 端口池
@@ -19,8 +21,12 @@ features: # 可选的
details: 新增、编辑、删除、禁用实时生效。
- title: Docker
details: 服务端支持Docker一键部署。
- title: SSL证书
details: 支持SSL,保护您的信息安全
- title: 隧道SSL加密
details: 隧道通信支持SSL,保护您的数据安全
- title: HTTPS
details: 支持HTTPS
- title: 多客户端支持
details: 暂未支持
# 文章列表显示方式: detailed 默认,显示详细版文章列表(包括作者、分类、标签、摘要、分页等)| simple => 显示简约版文章列表(仅标题和日期)| none 不显示文章列表
postList: none
@@ -129,35 +135,34 @@ postList: none
你也可以在Issue查看别人提的问题和给出解决方案。
或者加入我们的交流群:
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<img :src="$withBase('/img/qrcode/wxq.png')" class="no-zoom" style="width:120px;margin: 10px;">
<p>中微子 微信群(添加我微信备注"进群")</p>
</td>
<!--
<td align="center" valign="middle">
<img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/0.4pp7r95mdai0.jpeg" class="no-zoom" style="width:120px;margin: 10px;">
<p>vdoing微信群(添加我微信备注"进群")</p>
</td>
-->
</tr>
</tbody>
</table>
<br/>
<!--
### 🐞提供bug反馈或建议
提交问题反馈请说明正在使用环境以及相关问题
- [Gitee issue](https://gitee.com/dromara/neutrino-proxy/issues)
[//]: # (- [Github issue]&#40;https://github.com/dromara/hutool/issues&#41;)
<div align="center">
<img :src="$withBase('/img/qrcode/wxq.png')" class="no-zoom" style="width:200px;margin: 10px;">
<p>中微子 微信群(添加我微信备注"进群")</p>
</div>
<br/>
<h2 id="🌈特别赞助"><a href="#🌈特别赞助" class="header-anchor">#</a> 🌈特别赞助</h2>
<p align="center">等待有缘人赞助</p>
<!--
<div class="cardListContainer"><div class="card-list"><a href="https://datayi.cn/w/nPN4JJe9" target="_blank" class="card-item row-3" style="background-color:#102863;--random-color:#102863;color:#FFFFFF;"><img src="/images/friends/ad/postcat.png" class="no-zoom"> <div><p class="name">Postcat</p> <p class="desc">开源 API 管理工具,接口文档、接口测试、Mock...</p></div></a> <a href="https://gitee.com/dromara/MaxKey" target="_blank" class="card-item row-3" style="background-color:#9ed3e5;--random-color:#9ed3e5;color:#1f2328;"><img src="/images/friends/ad/maxkey.png" class="no-zoom"> <div><p class="name">MaxKey</p> <p class="desc"><br>业界领先的单点登录</p></div></a> <a href="https://www.xiaonuo.vip" target="_blank" class="card-item row-3" style="background-color:#1373CC;--random-color:#1373CC;color:#ffffff;"><img src="/images/friends/ad/snowy.png" class="no-zoom"> <div><p class="name">Snowy</p> <p class="desc">国内首个国密前后端分离快速开发平台,中国式工作流、独家双模式租户</p></div></a></div><div class="language-yaml line-numbers-mode"><pre class="language-yaml codecopy-enabled"><code><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Postcat
<span class="token key atrule">desc</span><span class="token punctuation">:</span> 开源 API 管理工具,接口文档、接口测试、Mock<span class="token punctuation">...</span>
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/postcat.png
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//datayi.cn/w/nPN4JJe9
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#102863'</span>
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#FFFFFF'</span>
<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> MaxKey
<span class="token key atrule">desc</span><span class="token punctuation">:</span> &lt;/br<span class="token punctuation">&gt;</span>业界领先的单点登录
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/maxkey.png
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//gitee.com/dromara/MaxKey
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#9ed3e5'</span>
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#1f2328'</span>
<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Snowy
<span class="token key atrule">desc</span><span class="token punctuation">:</span> 国内首个国密前后端分离快速开发平台,中国式工作流、独家双模式租户
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/snowy.png
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//www.xiaonuo.vip
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#1373CC'</span>
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#ffffff'</span>
</code><i class="code-copy" title="Copy to clipboard"><svg style="color:#aaa;font-size:14px" t="1572422231464" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3201" width="14" height="14"><path d="M866.461538 39.384615H354.461538c-43.323077 0-78.769231 35.446154-78.76923 78.769231v39.384616h472.615384c43.323077 0 78.769231 35.446154 78.769231 78.76923v551.384616h39.384615c43.323077 0 78.769231-35.446154 78.769231-78.769231V118.153846c0-43.323077-35.446154-78.769231-78.769231-78.769231z m-118.153846 275.692308c0-43.323077-35.446154-78.769231-78.76923-78.769231H157.538462c-43.323077 0-78.769231 35.446154-78.769231 78.769231v590.769231c0 43.323077 35.446154 78.769231 78.769231 78.769231h512c43.323077 0 78.769231-35.446154 78.76923-78.769231V315.076923z m-354.461538 137.846154c0 11.815385-7.876923 19.692308-19.692308 19.692308h-157.538461c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h157.538461c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z m157.538461 315.076923c0 11.815385-7.876923 19.692308-19.692307 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h315.076923c11.815385 0 19.692308 7.876923 19.692307 19.692308v39.384615z m78.769231-157.538462c0 11.815385-7.876923 19.692308-19.692308 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h393.846153c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z" p-id="3202"></path></svg></i></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div></div>
-->
<h2 id="🤝-dromara-组织项目"><a href="#🤝-dromara-组织项目" class="header-anchor">#</a> 🤝 dromara 组织项目</h2>
@@ -285,6 +290,16 @@ postList: none
</a>
</p>
<h2>🤝 友情开源项目</h2>
<p>
<a href="https://gitee.com/noear/solon" target="_blank" class="friends-item" >
<img class="no-zoom friends-item-img hover-alt" :src="$withBase('/img/logo/solon_logo_500_150.png')" msg="一个高效的应用开发框架:更快、更小、更简单。" />
</a>
<a href="https://gitee.com/xiaonuobase/snowy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" :src="$withBase('/img/logo/xiaonuo.png')" msg="国内首个国密前后端分离快速开发平台" />
</a>
</p>
<!-- AD -->
<div class="wwads-cn wwads-horizontal page-wwads" data-id="136"></div>
<style>
+17 -3
View File
@@ -12,7 +12,7 @@
</parent>
<groupId>org.dromara.neutrino-proxy</groupId>
<artifactId>neutrino-proxy-parent</artifactId>
<artifactId>neutrino-proxy</artifactId>
<packaging>pom</packaging>
<version>${revision}</version>
@@ -20,8 +20,6 @@
<module>neutrino-proxy-core</module>
<module>neutrino-proxy-client</module>
<module>neutrino-proxy-server</module>
<module>_solon_plugin/job-solon-plugin</module>
<module>_solon_plugin/orika-solon-plugin</module>
</modules>
<properties>
@@ -104,6 +102,22 @@
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.dromara.solon-plugins</groupId>
<artifactId>job-solon-plugin</artifactId>
<version>0.0.4</version>
<exclusions>
<exclusion>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.dromara.solon-plugins</groupId>
<artifactId>orika-solon-plugin</artifactId>
<version>0.0.4</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -17,7 +17,7 @@ export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
#初始化文件夹
cd ..
cd ../..
rm -rf $adminDeployDir
if [ ! -d "$deployDir" ];then
mkdir $deployDir
@@ -17,7 +17,7 @@ export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
#初始化文件夹
cd ..
cd ../..
rm -rf $adminDeployDir
if [ ! -d "$deployDir" ];then
mkdir $deployDir
@@ -9,7 +9,7 @@ deployDir="deploy"
clientDeployDir=$deployDir"/client"
#切到项目根目录
cd ..
cd ../..
#初始化文件夹
if [ ! -d "$deployDir" ];then
mkdir $deployDir
@@ -8,7 +8,7 @@ export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/li
mkdir -p /work/$NAME/heapError/
NAME=neutrino-proxy-client
WORK=$PWD/../deploy/client
WORK=$PWD/../../deploy/client
OUT=$WORK/$NAME.out
JAR_PATH=$WORK
@@ -37,7 +37,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out
fi
rm -f $OUT
cd $JAR_PATH
nohup java $JAVA_OPS -jar $NAME.jar $startupParams > $OUT 2>&1 &
nohup java -Dfile.encoding=utf-8 $JAVA_OPS -jar $NAME.jar $startupParams > $OUT 2>&1 &
echo "sleep 15s wating service start"
sleep 15
tail -200 $OUT
@@ -9,7 +9,7 @@ deployDir="deploy"
serverDeployDir=$deployDir"/server"
#切到项目根目录
cd ..
cd ../..
#初始化文件夹
if [ ! -d "$deployDir" ];then
mkdir $deployDir
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
# 镜像版本,每次更新版本时需要调整
ImageVer=1.0.0
ImageName=neutrino-proxy
DockerFilePath=$PWD/../..
#echo '打包jar...'
sh ./server_build.sh
#echo '打包管理后台...'
sh ./admin_build_docker.sh
# 删除老的本地镜像
docker rmi -f $(docker images | grep $ImageName | awk '{print $3}')
# 构建镜像
docker build -t $ImageName:$ImageVer -t $ImageName:latest $DockerFilePath
@@ -8,7 +8,7 @@ export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/li
mkdir -p /work/$NAME/heapError/
NAME=neutrino-proxy-server
WORK=$PWD/../deploy/server
WORK=$PWD/../../deploy/server
OUT=$WORK/$NAME.out
JAR_PATH=$WORK
@@ -29,7 +29,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out
fi
rm -f $OUT
cd $JAR_PATH
nohup java $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
nohup java -Dfile.encoding=utf-8 $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
echo "sleep 15s wating service start"
sleep 15
tail -200 $OUT
+29
View File
@@ -0,0 +1,29 @@
@echo off
@rem basic params
set ENV=dev
set NODE_VERSION=v13.12.0
set MODULE_NAME=neutrino-proxy-admin
@rem 判断当前node版本是否符合,如果不符合切换node版本
for /f "tokens=1" %%v in ('node -v') do set v=%%v
if not "%v%" == "%NODE_VERSION%" (
echo Node.js version is not %NODE_VERSION%.
@rem 判断是否安装nvm
set "nvm_home=%NVM_HOME%"
set "nvm_symlink=%NVM_SYMLINK%"
if not defined nvm_home (
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
) else if not defined nvm_symlink (
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
) else (
echo nvm is installed.
nvm use %NODE_VERSION%
)
)
cd %~dp0 && cd ../../%MODULE_NAME%
@rem 检查dist文件夹是否存在,存在就删除,/s选项表示将目录及其所有子目录一起删除,/q选项表示不需要确认操作
if exist dist (
rmdir /s /q dist
)
@rem 等待npm执行完毕
call npm install
npm run build:%ENV% && pause
+29
View File
@@ -0,0 +1,29 @@
@echo off
@rem basic params
set ENV=docker
set NODE_VERSION=v13.12.0
set MODULE_NAME=neutrino-proxy-admin
@rem 判断当前node版本是否符合,如果不符合切换node版本
for /f "tokens=1" %%v in ('node -v') do set v=%%v
if not "%v%" == "%NODE_VERSION%" (
echo Node.js version is not %NODE_VERSION%.
@rem 判断是否安装nvm
set "nvm_home=%NVM_HOME%"
set "nvm_symlink=%NVM_SYMLINK%"
if not defined nvm_home (
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
) else if not defined nvm_symlink (
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
) else (
echo nvm is installed.
nvm use %NODE_VERSION%
)
)
cd %~dp0 && cd ../../%MODULE_NAME%
@rem 检查dist文件夹是否存在,存在就删除,/s选项表示将目录及其所有子目录一起删除,/q选项表示不需要确认操作
if exist dist (
rmdir /s /q dist
)
@rem 等待npm执行完毕
call npm install
npm run build:%ENV% && pause
@@ -4,5 +4,5 @@ chcp 65001
@rem basic params
set MODULE_NAME="neutrino-proxy-client"
@rem step
cd %~dp0 && cd ../
cd %~dp0 && cd ../../
call mvn clean package -pl %MODULE_NAME% -am -Dmaven.test.skip=true
@@ -6,5 +6,5 @@ set MODULE_NAME="neutrino-proxy-client"
set JAR_NAME="neutrino-proxy-client.jar"
set JVM_OPTS="-Xmx256m" "-Xms256m"
@rem step
cd %~dp0 && cd ../%MODULE_NAME%/target
cd %~dp0 && cd ../../%MODULE_NAME%/target
java -jar %JVM_OPTS% %JAR_NAME%
@@ -4,5 +4,5 @@ chcp 65001
@rem basic params
set MODULE_NAME="neutrino-proxy-server"
@rem step
cd %~dp0 && cd ../
cd %~dp0 && cd ../../
call mvn clean package -pl %MODULE_NAME% -am -Dmaven.test.skip=true
@@ -6,5 +6,5 @@ set MODULE_NAME="neutrino-proxy-server"
set JAR_NAME="neutrino-proxy-server.jar"
set JVM_OPTS="-Xmx256m" "-Xms256m"
@rem step
cd %~dp0 && cd ../%MODULE_NAME%/target
cd %~dp0 && cd ../../%MODULE_NAME%/target
java -jar %JVM_OPTS% %JAR_NAME%
+3 -5
View File
@@ -1,10 +1,8 @@
# 1.x剩余规划
- [ ] 参考鹊桥,增加对域名的支持
- [ ] 官网上线
# Bug
- 指令通达被close的问题,org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelChannelHandler.channelInactive
- windows环境下直接运行发布版的jar包,日志输出乱码
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象
- 客户端连接映射某个端口以后,如果在服务器端禁用了,没有立刻反映出来,要灯客户端重连以后才会屏蔽被禁用的端口。
# 2.x规划
- 插件开发
@@ -26,4 +24,4 @@
# 3.x规划
- [ ] 支持针对用户限速、限流
- [ ] 支持P2P穿透
- [ ] 支持原生编译
- [ ] 支持原生编译