Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
843a58e99b | ||
|
|
ec4f8cbd7f | ||
|
|
25fab99723 | ||
|
|
f0769fb8fd |
+1
-3
@@ -54,6 +54,4 @@ hs_err_pid*
|
||||
|
||||
**/memo/**
|
||||
|
||||
neutrino-proxy-vuepress/deploy.sh
|
||||
.NEUTRINO_PROXY_CLIENT_ID
|
||||
logs
|
||||
neutrino-proxy-vuepress/deploy.sh
|
||||
@@ -47,52 +47,38 @@
|
||||
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
|
||||
```
|
||||
|
||||
## 5.2、修改服务端配置(app.yml)
|
||||
## 5.2、修改服务端配置(application.yml)
|
||||
```yml
|
||||
application:
|
||||
name: neutrino-proxy-server
|
||||
|
||||
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:
|
||||
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:
|
||||
```
|
||||
|
||||
## 5.3、启动服务端
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
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);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
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);
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
public interface IJobSource {
|
||||
|
||||
/**
|
||||
* 获取所有job列表
|
||||
* @return
|
||||
*/
|
||||
List<JobInfo> sourceList();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
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 "";
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
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 +1 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
solon.plugin=fun.asgc.solon.extend.job.XPluginImp
|
||||
solon.plugin.priority=2
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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>
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
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 +1 @@
|
||||
package fun.asgc.solon.extend.orika;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
solon.plugin=fun.asgc.solon.extend.orika.XPluginImp
|
||||
solon.plugin.priority=1
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
@@ -16,7 +16,6 @@ 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
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
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,11 +50,12 @@ 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: {
|
||||
@@ -73,6 +74,7 @@ export default {
|
||||
},
|
||||
smooth: true
|
||||
})
|
||||
legendList.push(item.name)
|
||||
})
|
||||
|
||||
const option = {
|
||||
@@ -85,13 +87,13 @@ export default {
|
||||
formatter: (value) => {
|
||||
let title = this.data.text + '<br/>'
|
||||
value.forEach(item => {
|
||||
title = title + item.marker + item.seriesName + ' : ' + getSizeDescByByteCount(item.data) + '<br/>'
|
||||
title = title + item.marker + item.seriesName + ' : ' + this.data.list[item.seriesIndex].label[item.dataIndex] + '<br/>'
|
||||
})
|
||||
return title
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: this.data.legendList,
|
||||
data: legendList,
|
||||
left: 'right'
|
||||
},
|
||||
grid: {
|
||||
|
||||
@@ -70,19 +70,37 @@ 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
|
||||
value: item.seriesData,
|
||||
label: label
|
||||
})
|
||||
})
|
||||
return {
|
||||
text: '流量监控',
|
||||
subtext: `最近${last7dFlow.dataList.length || 0}天流量监控`,
|
||||
title: last7dFlow.xDate,
|
||||
list: list,
|
||||
legendList: last7dFlow.legendData
|
||||
title: title,
|
||||
list: list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
<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' || temp.protocal === 'HTTP(S)') && domainName && domainName != ''">
|
||||
<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>
|
||||
</el-input>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM openjdk:8-jdk-alpine
|
||||
FROM openjdk:17-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 && \
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
package org.dromara.neutrinoproxy.client;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.SolonApp;
|
||||
import org.noear.solon.annotation.SolonMain;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
@SolonMain
|
||||
public class ProxyClient {
|
||||
|
||||
@@ -28,8 +20,6 @@ public class ProxyClient {
|
||||
setAlias("neutrino.proxy.client.jksPath", "jksPath");
|
||||
setAlias("neutrino.proxy.client.keyStorePassword", "keyStorePassword");
|
||||
setAlias("neutrino.proxy.client.licenseKey", "licenseKey");
|
||||
// 设置日志级别
|
||||
setLogLevel(app);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,21 +34,4 @@ public class ProxyClient {
|
||||
Solon.cfg().put(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setLogLevel(SolonApp app) {
|
||||
String loggerLevel = app.cfg().get("neutrino.proxy.logger.level");
|
||||
if (StringUtils.isBlank(loggerLevel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Level level = Level.toLevel(loggerLevel);
|
||||
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
for (Logger logger : loggerContext.getLoggerList()) {
|
||||
logger.setLevel(level);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("日志级别设置失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package 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;
|
||||
}
|
||||
-9
@@ -39,14 +39,5 @@ public class ProxyConfig {
|
||||
private Integer obtainLicenseInterval;
|
||||
private String licenseKey;
|
||||
private Integer threadCount;
|
||||
private String clientId;
|
||||
private Boolean transferLogEnable;
|
||||
private Reconnection reconnection;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Reconnection {
|
||||
private Integer intervalSeconds;
|
||||
private Boolean unlimited;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-7
@@ -31,13 +31,8 @@ public class ProxyConfiguration implements LifecycleBean {
|
||||
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
|
||||
}
|
||||
|
||||
@Bean("cmdTunnelBootstrap")
|
||||
public Bootstrap cmdTunnelBootstrap() {
|
||||
return new Bootstrap();
|
||||
}
|
||||
|
||||
@Bean("proxyTunnelBootstrap")
|
||||
public Bootstrap proxyTunnelBootstrap() {
|
||||
@Bean("bootstrap")
|
||||
public Bootstrap bootstrap() {
|
||||
return new Bootstrap();
|
||||
}
|
||||
|
||||
|
||||
+23
-16
@@ -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 ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
||||
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
|
||||
log.debug("Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType());
|
||||
log.info("recieved proxy message, type is {}", proxyMessage.getType());
|
||||
}
|
||||
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
|
||||
}
|
||||
@@ -41,10 +41,17 @@ public class ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessag
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
// 数据传输连接
|
||||
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (realServerChannel != null && realServerChannel.isActive()) {
|
||||
realServerChannel.close();
|
||||
// 控制连接
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
ProxyUtil.removeProxyChanel(ctx.channel());
|
||||
@@ -53,8 +60,10 @@ public class ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessag
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
log.error("Client ProxyChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
|
||||
ctx.close();
|
||||
super.exceptionCaught(ctx, cause);
|
||||
if (ctx.channel().isActive()) {
|
||||
ctx.channel().close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,15 +73,13 @@ public class ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessag
|
||||
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.debug("读写超时");
|
||||
ctx.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
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.debug("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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
-77
@@ -26,14 +26,13 @@ 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
|
||||
*/
|
||||
@@ -42,26 +41,23 @@ import java.util.concurrent.TimeUnit;
|
||||
public class ProxyClientService {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Inject("cmdTunnelBootstrap")
|
||||
private Bootstrap cmdTunnelBootstrap;
|
||||
@Inject("proxyTunnelBootstrap")
|
||||
private Bootstrap proxyTunnelBootstrap;
|
||||
@Inject("bootstrap")
|
||||
private Bootstrap bootstrap;
|
||||
@Inject("realServerBootstrap")
|
||||
private Bootstrap realServerBootstrap;
|
||||
private volatile Channel channel;
|
||||
// /**
|
||||
// * 重连间隔(秒)
|
||||
// */
|
||||
// private static final long RECONNECT_INTERVAL_SECONDS = 5;
|
||||
/**
|
||||
* 重连间隔(秒)
|
||||
*/
|
||||
private static final long RECONNECT_INTERVAL_SECONDS = 5;
|
||||
/**
|
||||
* 重连次数
|
||||
*/
|
||||
private volatile int reconnectCount = 0;
|
||||
// /**
|
||||
// * 启用重连服务
|
||||
// */
|
||||
// private volatile boolean reconnectServiceEnable = false;
|
||||
private NioEventLoopGroup workerGroup;
|
||||
/**
|
||||
* 启用重连服务
|
||||
*/
|
||||
private volatile boolean reconnectServiceEnable = false;
|
||||
/**
|
||||
* 重连服务执行器
|
||||
*/
|
||||
@@ -69,9 +65,9 @@ public class ProxyClientService {
|
||||
|
||||
@Init
|
||||
public void init() {
|
||||
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 10, proxyConfig.getClient().getReconnection().getIntervalSeconds(), TimeUnit.SECONDS);
|
||||
this.workerGroup = new NioEventLoopGroup(proxyConfig.getClient().getThreadCount());
|
||||
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 0, RECONNECT_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
|
||||
NioEventLoopGroup workerGroup = new NioEventLoopGroup(proxyConfig.getClient().getThreadCount());
|
||||
realServerBootstrap.group(workerGroup);
|
||||
realServerBootstrap.channel(NioSocketChannel.class);
|
||||
realServerBootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@@ -82,10 +78,9 @@ public class ProxyClientService {
|
||||
}
|
||||
});
|
||||
|
||||
proxyTunnelBootstrap.group(workerGroup);
|
||||
proxyTunnelBootstrap.channel(NioSocketChannel.class);
|
||||
proxyTunnelBootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort()));
|
||||
proxyTunnelBootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
bootstrap.group(workerGroup);
|
||||
bootstrap.channel(NioSocketChannel.class);
|
||||
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
@@ -98,46 +93,10 @@ public class ProxyClientService {
|
||||
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 ProxyChannelHandler());
|
||||
ch.pipeline().addLast(new ClientChannelHandler());
|
||||
}
|
||||
});
|
||||
|
||||
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());
|
||||
}
|
||||
if (null != proxyConfig.getClient().getTransferLogEnable() && proxyConfig.getClient().getTransferLogEnable()) {
|
||||
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);
|
||||
}
|
||||
this.start();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -152,7 +111,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;
|
||||
@@ -169,7 +128,7 @@ public class ProxyClientService {
|
||||
log.error("client start error", e);
|
||||
}
|
||||
} else {
|
||||
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey(), ProxyUtil.getClientId()));
|
||||
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +136,7 @@ public class ProxyClientService {
|
||||
* 连接代理服务器
|
||||
*/
|
||||
private void connectProxyServer() throws InterruptedException {
|
||||
cmdTunnelBootstrap.connect()
|
||||
bootstrap.connect(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort())
|
||||
.addListener(new ChannelFutureListener() {
|
||||
|
||||
@Override
|
||||
@@ -186,13 +145,13 @@ public class ProxyClientService {
|
||||
channel = future.channel();
|
||||
// 连接成功,向服务器发送客户端认证信息(licenseKey)
|
||||
ProxyUtil.setCmdChannel(future.channel());
|
||||
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey(), ProxyUtil.getClientId()));
|
||||
log.info("[客户端指令隧道] 连接代理服务成功. channelId:{}", future.channel().id().asLongText());
|
||||
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey()));
|
||||
log.info("连接代理服务成功. channelId:{}", future.channel().id().asLongText());
|
||||
|
||||
// reconnectServiceEnable = true;
|
||||
reconnectServiceEnable = true;
|
||||
reconnectCount = 0;
|
||||
} else {
|
||||
log.info("[客户端指令隧道] 连接代理服务失败!");
|
||||
log.info("连接代理服务失败!");
|
||||
}
|
||||
}
|
||||
}).sync();
|
||||
@@ -222,21 +181,17 @@ public class ProxyClientService {
|
||||
}
|
||||
|
||||
protected synchronized void reconnect() {
|
||||
// if (!reconnectServiceEnable) {
|
||||
// return;
|
||||
// }
|
||||
if (null != channel) {
|
||||
if (channel.isActive()) {
|
||||
return;
|
||||
}
|
||||
channel.close();
|
||||
if (!reconnectServiceEnable) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[客户端指令隧道] 客户端重连 seq:{}", ++reconnectCount);
|
||||
if (null != channel && channel.isActive()) {
|
||||
return;
|
||||
}
|
||||
log.info("客户端重连 seq:{}", ++reconnectCount);
|
||||
try {
|
||||
connectProxyServer();
|
||||
} catch (Exception e) {
|
||||
log.error("[客户端指令隧道] 重连异常", e);
|
||||
log.error("重连异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -22,7 +22,6 @@
|
||||
|
||||
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;
|
||||
@@ -37,7 +36,6 @@ import io.netty.channel.SimpleChannelInboundHandler;
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
|
||||
|
||||
@@ -87,6 +85,6 @@ public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBu
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
log.error("Client ProxyChannel Error", cause);
|
||||
super.exceptionCaught(ctx, cause);
|
||||
}
|
||||
}
|
||||
|
||||
-18
@@ -1,17 +1,13 @@
|
||||
package org.dromara.neutrinoproxy.client.handler;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
|
||||
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;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
/**
|
||||
* 认证信息处理器
|
||||
@@ -22,25 +18,11 @@ import org.noear.solon.annotation.Inject;
|
||||
@Match(type = Constants.ProxyDataTypeName.AUTH)
|
||||
@Component
|
||||
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext context, ProxyMessage proxyMessage) {
|
||||
String info = proxyMessage.getInfo();
|
||||
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();
|
||||
if (!proxyConfig.getClient().getReconnection().getUnlimited()) {
|
||||
Solon.stop();
|
||||
}
|
||||
} else if (ExceptionEnum.CONNECT_FAILED.getCode().equals(code) ||
|
||||
ExceptionEnum.LICENSE_CANNOT_REPEAT_CONNECT.getCode().equals(code)
|
||||
){
|
||||
context.channel().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -21,8 +21,8 @@ import org.noear.solon.annotation.Inject;
|
||||
@Match(type = Constants.ProxyDataTypeName.CONNECT)
|
||||
@Component
|
||||
public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
@Inject("proxyTunnelBootstrap")
|
||||
private Bootstrap proxyTunnelBootstrap;
|
||||
@Inject("bootstrap")
|
||||
private Bootstrap bootstrap;
|
||||
@Inject("realServerBootstrap")
|
||||
private Bootstrap realServerBootstrap;
|
||||
@Inject
|
||||
@@ -48,7 +48,7 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
realServerChannel.config().setOption(ChannelOption.AUTO_READ, false);
|
||||
|
||||
// 获取连接
|
||||
ProxyUtil.borrowProxyChanel(proxyTunnelBootstrap, new ProxyChannelBorrowListener() {
|
||||
ProxyUtil.borrowProxyChanel(bootstrap, new ProxyChannelBorrowListener() {
|
||||
|
||||
@Override
|
||||
public void success(Channel channel) {
|
||||
|
||||
+4
-29
@@ -21,8 +21,6 @@
|
||||
*/
|
||||
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;
|
||||
@@ -31,12 +29,10 @@ 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;
|
||||
|
||||
@@ -58,17 +54,16 @@ public class ProxyUtil {
|
||||
|
||||
private static volatile Channel cmdChannel;
|
||||
|
||||
private static String clientId;
|
||||
private static final String CLIENT_ID_FILE = ".NEUTRINO_PROXY_CLIENT_ID";
|
||||
|
||||
public static void borrowProxyChanel(Bootstrap proxyTunnelBootstrap, final ProxyChannelBorrowListener borrowListener) {
|
||||
public static void borrowProxyChanel(Bootstrap bootstrap, final ProxyChannelBorrowListener borrowListener) {
|
||||
Channel channel = proxyChannelPool.poll();
|
||||
if (null != channel) {
|
||||
borrowListener.success(channel);
|
||||
return;
|
||||
}
|
||||
|
||||
proxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> {
|
||||
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 -> {
|
||||
if (future.isSuccess()) {
|
||||
borrowListener.success(future.channel());
|
||||
} else {
|
||||
@@ -134,24 +129,4 @@ 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,33 +9,15 @@ neutrino:
|
||||
length-field-length: 4
|
||||
initial-bytes-to-strip: 0
|
||||
length-adjustment: 0
|
||||
read-idle-time: 8
|
||||
write-idle-time: 3
|
||||
all-idle-time-seconds: 8
|
||||
logger:
|
||||
# 日志级别
|
||||
level: ${LOG_LEVEL:info}
|
||||
read-idle-time: 40
|
||||
write-idle-time: 8
|
||||
all-idle-time-seconds: 0
|
||||
client:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
thread-count: 50
|
||||
# 隧道SSL证书配置
|
||||
key-store-password: ${STORE_PASS:123456}
|
||||
jks-path: ${JKS_PATH:classpath:/test.jks}
|
||||
# 服务端IP
|
||||
server-ip: ${SERVER_IP:localhost}
|
||||
# 服务端端口(对应服务端app.yml中的tunnel.port、tunnel.ssl-port)
|
||||
server-port: ${SERVER_PORT:9002}
|
||||
# 是否启用SSL(注意:该配置必须和server-port对应上)
|
||||
ssl-enable: ${SSL_ENABLE:true}
|
||||
# 客户端连接唯一凭证
|
||||
license-key: ${LICENSE_KEY:}
|
||||
# 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成)
|
||||
client-id: ${CLIENT_ID:}
|
||||
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${CLIENT_LOG:false}
|
||||
# 重连设置
|
||||
reconnection:
|
||||
# 重连间隔(秒)
|
||||
interval-seconds: 10
|
||||
# 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启)
|
||||
unlimited: false
|
||||
obtain-license-interval: 5
|
||||
license-key: ${LICENSE_KEY:b0a907332b474b25897c4dcb31fc7eb6}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<property name="LOG_FILE" value="./logs/neutrino-proxy-client.log"/>
|
||||
<property name="LOG_FILE" value="/work/projects/neutrino-proxy-client/app.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" />-->
|
||||
|
||||
|
||||
@@ -35,10 +35,7 @@ import lombok.Getter;
|
||||
public enum ExceptionEnum {
|
||||
SUCCESS(0, "成功"),
|
||||
AUTH_FAILED(1, "认证失败"),
|
||||
CONNECT_FAILED(2, "连接失败"),
|
||||
@Deprecated
|
||||
LICENSE_CANNOT_REPEAT_CONNECT(3, "license不能多个客户端同时使用"),
|
||||
;
|
||||
CONNECT_FAILED(2, "连接失败");
|
||||
|
||||
private Integer code;
|
||||
private String msg;
|
||||
|
||||
@@ -100,9 +100,9 @@ public class ProxyMessage {
|
||||
return create().setType(TYPE_HEARTBEAT);
|
||||
}
|
||||
|
||||
public static ProxyMessage buildAuthMessage(String info, String clientId) {
|
||||
public static ProxyMessage buildAuthMessage(String info) {
|
||||
return create().setType(TYPE_AUTH)
|
||||
.setInfo(info + "," + clientId);
|
||||
.setInfo(info);
|
||||
}
|
||||
|
||||
public static ProxyMessage buildAuthResultMessage(Integer code, String msg, String licenseKey) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM openjdk:8-jdk-alpine
|
||||
FROM openjdk:17-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 && \
|
||||
@@ -12,4 +12,4 @@ 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"]
|
||||
|
||||
#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/aoshiguchen-docker-images:1.7
|
||||
#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/aoshiguchen-docker-images:1.7
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
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
|
||||
@@ -30,13 +30,17 @@
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>activerecord-solon-plugin</artifactId>
|
||||
</dependency>
|
||||
<!--orika-->
|
||||
<dependency>
|
||||
<groupId>org.dromara.solon-plugins</groupId>
|
||||
<artifactId>job-solon-plugin</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara.solon-plugins</groupId>
|
||||
<groupId>fun.asgc</groupId>
|
||||
<artifactId>orika-solon-plugin</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
<!--job-->
|
||||
<dependency>
|
||||
<groupId>fun.asgc</groupId>
|
||||
<artifactId>job-solon-plugin</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara.neutrino-proxy</groupId>
|
||||
@@ -48,12 +52,8 @@
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
|
||||
+1
-30
@@ -1,25 +1,15 @@
|
||||
package org.dromara.neutrinoproxy.server;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.solonplugins.job.annotation.EnableJob;
|
||||
import fun.asgc.solon.extend.job.annotation.EnableJob;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.SolonApp;
|
||||
import org.noear.solon.annotation.SolonMain;
|
||||
import org.noear.solon.web.cors.CrossFilter;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableJob
|
||||
@SolonMain
|
||||
public class ProxyServer {
|
||||
@@ -28,25 +18,6 @@ public class ProxyServer {
|
||||
Solon.start(ProxyServer.class, args, app -> {
|
||||
// 跨域支持。加-1 优先级更高
|
||||
app.filter(-1, new CrossFilter().allowedOrigins("*"));
|
||||
// 设置日志级别
|
||||
setLogLevel(app);
|
||||
});
|
||||
}
|
||||
|
||||
private static void setLogLevel(SolonApp app) {
|
||||
String loggerLevel = app.cfg().get("neutrino.proxy.logger.level");
|
||||
if (StringUtils.isBlank(loggerLevel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Level level = Level.toLevel(loggerLevel);
|
||||
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
for (Logger logger : loggerContext.getLoggerList()) {
|
||||
logger.setLevel(level);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("日志级别设置失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -64,7 +64,8 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
|
||||
|
||||
@Override
|
||||
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
||||
|
||||
// TODO 该事件有50%的概率不触发
|
||||
System.out.println("11");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+14
-15
@@ -31,6 +31,7 @@ public class DbConfiguration {
|
||||
dataSource.setJournalMode(SQLiteConfig.JournalMode.WAL.getValue());
|
||||
return dataSource;
|
||||
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
|
||||
HikariDataSource dataSource = new HikariDataSource();
|
||||
String driver = "com.mysql.cj.jdbc.Driver";
|
||||
try {
|
||||
Class.forName(driver);
|
||||
@@ -38,26 +39,24 @@ public class DbConfiguration {
|
||||
// 对类名的判断,异常则说明不存在:
|
||||
driver = "com.mysql.jdbc.Driver";
|
||||
}
|
||||
return newHikariDataSource(dbConfig, driver);
|
||||
} else if (DbTypeEnum.MARIADB == dbTypeEnum) {
|
||||
return newHikariDataSource(dbConfig, "org.mariadb.jdbc.Driver");
|
||||
dataSource.setDriverClassName(driver);
|
||||
dataSource.setJdbcUrl(dbConfig.getUrl());
|
||||
dataSource.setMinimumIdle(5);
|
||||
dataSource.setMaximumPoolSize(20);
|
||||
dataSource.setMaxLifetime(60000);
|
||||
// dataSource.setInitialSize(5);
|
||||
// dataSource.setMinIdle(5);
|
||||
// dataSource.setMaxActive(20);
|
||||
// dataSource.setMaxWait(60000);
|
||||
// dataSource.setPoolPreparedStatements(true);
|
||||
dataSource.setUsername(dbConfig.getUsername());
|
||||
dataSource.setPassword(dbConfig.getPassword());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private HikariDataSource newHikariDataSource(DbConfig dbConfig, String driverClass) {
|
||||
HikariDataSource dataSource = new HikariDataSource();
|
||||
dataSource.setDriverClassName(driverClass);
|
||||
dataSource.setJdbcUrl(dbConfig.getUrl());
|
||||
dataSource.setMinimumIdle(5);
|
||||
dataSource.setMaximumPoolSize(20);
|
||||
dataSource.setMaxLifetime(60000);
|
||||
dataSource.setUsername(dbConfig.getUsername());
|
||||
dataSource.setPassword(dbConfig.getPassword());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public void db1_ext(@Db("db") GlobalConfig globalConfig) {
|
||||
MetaObjectHandler metaObjectHandler = new MetaObjectHandlerImpl();
|
||||
|
||||
+5
-21
@@ -18,15 +18,10 @@ 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 {
|
||||
@@ -42,26 +37,15 @@ 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;
|
||||
private Boolean transferLogEnable;
|
||||
}
|
||||
|
||||
@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 Boolean transferLogEnable;
|
||||
private Integer bossThreadCount;
|
||||
private Integer workThreadCount;
|
||||
private String domainName;
|
||||
private Integer httpProxyPort;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-39
@@ -1,10 +1,5 @@
|
||||
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 io.netty.handler.logging.LoggingHandler;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
@@ -12,9 +7,6 @@ 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.ProxyTunnelServer;
|
||||
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;
|
||||
@@ -51,35 +43,4 @@ public class ProxyConfiguration implements LifecycleBean {
|
||||
return new NioEventLoopGroup(proxyConfig.getServer().getWorkThreadCount());
|
||||
}
|
||||
|
||||
@Bean("tcpServerBootstrap")
|
||||
public ServerBootstrap tcpServerBootstrap(@Inject("serverBossGroup") NioEventLoopGroup serverBossGroup,
|
||||
@Inject("serverWorkerGroup") NioEventLoopGroup serverWorkerGroup,
|
||||
@Inject ProxyConfig proxyConfig
|
||||
) {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(serverBossGroup, serverWorkerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class));
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ public class VisitLogInterceptor implements RouterInterceptor {
|
||||
} finally {
|
||||
Date now = new Date();
|
||||
long elapsedTime = now.getTime() - startTime.getTime();
|
||||
log.debug("\n-----------------------------------------------------------------接口请求日志:\n{} url:{} 执行耗时:{}\n请求体参数:{}\n响应结果:{}\n客户端IP:{}\n",
|
||||
log.info("\n-----------------------------------------------------------------接口请求日志:\n{} url:{} 执行耗时:{}\n请求体参数:{}\n响应结果:{}\n客户端IP:{}\n",
|
||||
ctx.method(), ctx.path(), getElapsedTimeStr(elapsedTime),
|
||||
JSONObject.toJSONString(ctx.paramMap()),
|
||||
JSONObject.toJSONString(JSONObject.toJSONString(ctx.result)),
|
||||
|
||||
+1
-3
@@ -38,9 +38,7 @@ import java.util.stream.Stream;
|
||||
@AllArgsConstructor
|
||||
public enum DbTypeEnum {
|
||||
SQLITE("sqlite"),
|
||||
MYSQL("mysql"),
|
||||
MARIADB("mariadb"),
|
||||
;
|
||||
MYSQL("mysql");
|
||||
|
||||
private String type;
|
||||
|
||||
|
||||
+3
-16
@@ -2,7 +2,6 @@ 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;
|
||||
@@ -16,26 +15,14 @@ import java.util.stream.Stream;
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum NetworkProtocolEnum {
|
||||
TCP("TCP", "TCP"),
|
||||
UDP("UDP", "UDP"),
|
||||
HTTP("HTTP", "TCP"),
|
||||
TCP("TCP"),
|
||||
UDP("UDP"),
|
||||
HTTP("HTTP"),
|
||||
;
|
||||
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);
|
||||
}
|
||||
|
||||
public static Boolean isHttp(String desc) {
|
||||
NetworkProtocolEnum networkProtocolEnum = of(desc);
|
||||
return NetworkProtocolEnum.HTTP == networkProtocolEnum;
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -51,7 +51,6 @@ public class PortMappingController {
|
||||
// 目前仅HTTP支持绑定域名
|
||||
req.setSubdomain(null);
|
||||
}
|
||||
req.setProtocal(networkProtocolEnum.getDesc());
|
||||
|
||||
return portMappingService.create(req);
|
||||
}
|
||||
@@ -75,7 +74,6 @@ public class PortMappingController {
|
||||
// 目前仅HTTP支持绑定域名
|
||||
req.setSubdomain(null);
|
||||
}
|
||||
req.setProtocal(networkProtocolEnum.getDesc());
|
||||
|
||||
return portMappingService.update(req);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
|
||||
+3
-3
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -17,6 +17,6 @@ public class DemoJob implements IJobHandler {
|
||||
|
||||
@Override
|
||||
public void execute(String param) throws Exception {
|
||||
log.debug("DemoJob execute param: {}", param);
|
||||
System.out.println("DemoJob execute param:" + param);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
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()) || 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(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", proxyConfig.getServer().getHttpProxyPort()).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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
String host = getHost(bytes);
|
||||
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;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-16
@@ -27,19 +27,29 @@ import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
|
||||
/**
|
||||
* 代理隧道服务
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
|
||||
public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Inject("tunnelBossGroup")
|
||||
@Inject("serverBossGroup")
|
||||
private NioEventLoopGroup serverBossGroup;
|
||||
@Inject("tunnelWorkerGroup")
|
||||
@Inject("serverWorkerGroup")
|
||||
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();
|
||||
@@ -58,15 +68,15 @@ public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
|
||||
}
|
||||
});
|
||||
try {
|
||||
bootstrap.bind(proxyConfig.getTunnel().getPort()).sync();
|
||||
log.info("代理服务启动,端口:{}", proxyConfig.getTunnel().getPort());
|
||||
bootstrap.bind(port).sync();
|
||||
log.info("代理服务启动,端口:{}", port);
|
||||
} catch (Exception e) {
|
||||
log.error("代理服务异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startProxyServerForSSL() {
|
||||
if (null == proxyConfig.getTunnel().getSslPort()) {
|
||||
if (null == sslPort) {
|
||||
return;
|
||||
}
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
@@ -79,8 +89,8 @@ public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
|
||||
}
|
||||
});
|
||||
try {
|
||||
bootstrap.bind(proxyConfig.getTunnel().getSslPort()).sync();
|
||||
log.info("代理服务启动,SSL端口: {}", proxyConfig.getTunnel().getSslPort());
|
||||
bootstrap.bind(sslPort).sync();
|
||||
log.info("代理服务启动,SSL端口: {}", sslPort);
|
||||
} catch (Exception e) {
|
||||
log.error("代理服务异常", e);
|
||||
}
|
||||
@@ -88,13 +98,13 @@ public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
|
||||
|
||||
private ChannelHandler createSslHandler() {
|
||||
try {
|
||||
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getTunnel().getJksPath());
|
||||
InputStream jksInputStream = FileUtil.getInputStream(jksPath);
|
||||
SSLContext serverContext = SSLContext.getInstance("TLS");
|
||||
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||
|
||||
ks.load(jksInputStream, proxyConfig.getTunnel().getKeyStorePassword().toCharArray());
|
||||
ks.load(jksInputStream, keyStorePassword.toCharArray());
|
||||
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(ks, proxyConfig.getTunnel().getKeyManagerPassword().toCharArray());
|
||||
kmf.init(ks, keyManagerPassword.toCharArray());
|
||||
TrustManager[] trustManagers = null;
|
||||
|
||||
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
|
||||
@@ -112,14 +122,12 @@ public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
|
||||
}
|
||||
|
||||
private void proxyServerCommonInitHandler(SocketChannel ch) {
|
||||
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class));
|
||||
}
|
||||
// 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 ProxyTunnelChannelHandler());
|
||||
ch.pipeline().addLast(new ServerChannelHandler());
|
||||
}
|
||||
}
|
||||
+20
-28
@@ -47,10 +47,10 @@ import java.util.Date;
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
private static volatile Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher;
|
||||
|
||||
public ProxyTunnelChannelHandler() {
|
||||
public ServerChannelHandler() {
|
||||
dispatcher = Solon.context().getBean(Dispatcher.class);
|
||||
}
|
||||
|
||||
@@ -71,42 +71,33 @@ public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<Proxy
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != visitorChannel) {
|
||||
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (userChannel != null && userChannel.isActive()) {
|
||||
Integer licenseId = ctx.channel().attr(Constants.LICENSE_ID).get();
|
||||
String visitorId = ctx.channel().attr(Constants.VISITOR_ID).get();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseId);
|
||||
|
||||
if (null != cmdChannel) {
|
||||
if (cmdChannel != null) {
|
||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
|
||||
}
|
||||
|
||||
if (visitorChannel.isActive()) {
|
||||
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
|
||||
visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
visitorChannel.close();
|
||||
}
|
||||
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
|
||||
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
userChannel.close();
|
||||
} else {
|
||||
CmdChannelAttachInfo cmdChannelAttachInfo = ProxyUtil.getAttachInfo(ctx.channel());
|
||||
if (null != cmdChannelAttachInfo) {
|
||||
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());
|
||||
}
|
||||
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())
|
||||
);
|
||||
}
|
||||
// 即便是因为上述原因断开,断开的日志依然要记录,方便排查问题
|
||||
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())
|
||||
);
|
||||
ProxyUtil.removeCmdChannel(ctx.channel());
|
||||
}
|
||||
|
||||
super.channelInactive(ctx);
|
||||
@@ -127,10 +118,11 @@ public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<Proxy
|
||||
switch (event.state()) {
|
||||
case READER_IDLE:
|
||||
// 读超时,断开连接
|
||||
log.debug("读超时");
|
||||
log.info("读超时");
|
||||
ctx.channel().close();
|
||||
break;
|
||||
case WRITER_IDLE:
|
||||
log.info("写超时");
|
||||
break;
|
||||
case ALL_IDLE:
|
||||
break;
|
||||
+6
-4
@@ -4,6 +4,7 @@ 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;
|
||||
@@ -22,7 +23,7 @@ import java.net.InetSocketAddress;
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
@@ -90,7 +91,8 @@ public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBu
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
if (null == cmdChannel) {
|
||||
if (cmdChannel == null) {
|
||||
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
@@ -125,12 +127,12 @@ public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBu
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
if (null == cmdChannel) {
|
||||
if (cmdChannel == null) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != proxyChannel) {
|
||||
if (proxyChannel != null) {
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
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.logging.LoggingHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelServer;
|
||||
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 {
|
||||
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class));
|
||||
}
|
||||
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代理服务启动成功!port:{}", proxyConfig.getServer().getHttpProxyPort());
|
||||
} catch (Exception e) {
|
||||
log.error("http proxy start err!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
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);
|
||||
log.debug("HttpProxy host: {}", host);
|
||||
if (StringUtils.isBlank(host)) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
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.replaceAll(":.*", "");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
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.logging.LoggingHandler;
|
||||
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.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelServer;
|
||||
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 {
|
||||
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class));
|
||||
}
|
||||
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代理服务启动成功!port:{}", proxyConfig.getServer().getHttpsProxyPort());
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
+11
-37
@@ -79,19 +79,9 @@ 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())
|
||||
@@ -104,9 +94,7 @@ 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())
|
||||
@@ -118,9 +106,7 @@ 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())
|
||||
@@ -133,9 +119,7 @@ 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())
|
||||
@@ -148,21 +132,16 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
||||
}
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseDO.getId());
|
||||
if (null != cmdChannel) {
|
||||
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.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;
|
||||
}
|
||||
// 发送认证成功消息
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.SUCCESS.getCode(), "认证成功!", licenseKey));
|
||||
@@ -171,15 +150,10 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
||||
.setIp(ip)
|
||||
.setLicenseId(licenseDO.getId())
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(info)
|
||||
.setMsg(licenseKey)
|
||||
.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
|
||||
|
||||
+13
-35
@@ -19,24 +19,19 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -50,29 +45,6 @@ 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());
|
||||
@@ -93,15 +65,22 @@ 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).startById(String.valueOf(req.getId()));
|
||||
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)
|
||||
);
|
||||
} else {
|
||||
Solon.context().getBean(JobExecutor.class).stopById(String.valueOf(req.getId()));
|
||||
Solon.context().getBean(JobExecutor.class).remove(String.valueOf(req.getId()));
|
||||
}
|
||||
return new JobInfoUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
public JobInfoExecuteRes execute(JobInfoExecuteReq req) {
|
||||
Solon.context().getBean(JobExecutor.class).triggerById(String.valueOf(req.getId()), req.getParam());
|
||||
Solon.context().getBean(JobExecutor.class).trigger(String.valueOf(req.getId()), req.getParam());
|
||||
return new JobInfoExecuteRes();
|
||||
}
|
||||
|
||||
@@ -120,7 +99,6 @@ public class JobInfoService implements IJobSource {
|
||||
.setCron(item.getCron())
|
||||
.setParam(item.getParam())
|
||||
.setEnable(EnableStatusEnum.ENABLE.getStatus().equals(item.getEnable()))
|
||||
.setJobHandler(jobHandlerMap.get(item.getHandler()))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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;
|
||||
|
||||
@@ -61,7 +61,7 @@ public class JobLogService implements IJobCallback {
|
||||
String msg = "";
|
||||
if (null == throwable) {
|
||||
msg = "执行成功";
|
||||
log.debug("job[id={},name={}]执行完毕", jobInfo.getId(), jobInfo.getName());
|
||||
log.info("job[id={},name={}]执行完毕", jobInfo.getId(), jobInfo.getName());
|
||||
} else {
|
||||
log.error("job[id={},name={}]执行异常", jobInfo.getId(), jobInfo.getName(), throwable);
|
||||
msg = "执行异常:\r\n" + ExceptionUtils.getStackTrace(throwable);
|
||||
|
||||
+50
-37
@@ -8,8 +8,6 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.google.common.collect.Sets;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.base.db.DBInitialize;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
@@ -33,10 +31,14 @@ 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.util.ParamCheckUtil;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Init;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.Lifecycle;
|
||||
import org.noear.solon.core.bean.LifecycleBean;
|
||||
|
||||
import java.util.*;
|
||||
@@ -49,25 +51,25 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Component
|
||||
public class PortMappingService implements LifecycleBean {
|
||||
@Inject
|
||||
private MapperFacade mapperFacade;
|
||||
@Db
|
||||
private PortMappingMapper portMappingMapper;
|
||||
@Db
|
||||
private LicenseMapper licenseMapper;
|
||||
@Db
|
||||
private UserMapper userMapper;
|
||||
@Db
|
||||
private PortPoolMapper portPoolMapper;
|
||||
@Inject
|
||||
private VisitorChannelService visitorChannelService;
|
||||
@Inject
|
||||
private MapperFacade mapperFacade;
|
||||
@Db
|
||||
private PortMappingMapper portMappingMapper;
|
||||
@Db
|
||||
private LicenseMapper licenseMapper;
|
||||
@Db
|
||||
private UserMapper userMapper;
|
||||
@Db
|
||||
private PortPoolMapper portPoolMapper;
|
||||
@Inject
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
@Inject
|
||||
private PortPoolService portPoolService;
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Inject
|
||||
private DBInitialize dbInitialize;
|
||||
@Inject
|
||||
private PortPoolService portPoolService;
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Inject
|
||||
private DBInitialize dbInitialize;
|
||||
|
||||
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
|
||||
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
@@ -75,13 +77,6 @@ public class PortMappingService implements LifecycleBean {
|
||||
//描述字段为模糊查询,在应用层处理,否则sqlite不支持
|
||||
req.setDescription("%" + req.getDescription() + "%");
|
||||
}
|
||||
|
||||
// 协议名称转换
|
||||
if (StringUtils.isNotBlank(req.getProtocal())) {
|
||||
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
|
||||
req.setProtocal(networkProtocolEnum.getDesc());
|
||||
}
|
||||
|
||||
List<PortMappingDO> list = portMappingMapper.selectPortMappingByCondition(req);
|
||||
List<PortMappingListRes> respList = mapperFacade.mapAsList(list, PortMappingListRes.class);
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
@@ -113,12 +108,12 @@ public class PortMappingService implements LifecycleBean {
|
||||
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());
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -151,7 +146,9 @@ public class PortMappingService implements LifecycleBean {
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
|
||||
// 更新域名映射
|
||||
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(portMappingDO.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 PortMappingCreateRes();
|
||||
@@ -188,11 +185,14 @@ public class PortMappingService implements LifecycleBean {
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
|
||||
// 删除老的域名映射
|
||||
if (NetworkProtocolEnum.isHttp(oldPortMappingDO.getProtocal()) && StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) {
|
||||
if (NetworkProtocolEnum.HTTP.getDesc().equals(oldPortMappingDO.getProtocal()) &&
|
||||
StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) {
|
||||
ProxyUtil.removeSubdomainToServerPort(oldPortMappingDO.getSubdomain());
|
||||
}
|
||||
// 更新域名映射
|
||||
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(portMappingDO.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();
|
||||
@@ -203,7 +203,16 @@ public class PortMappingService implements LifecycleBean {
|
||||
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());
|
||||
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) {
|
||||
@@ -256,7 +265,8 @@ public class PortMappingService implements LifecycleBean {
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
|
||||
// 更新域名映射
|
||||
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
|
||||
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||
ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain());
|
||||
}
|
||||
}
|
||||
@@ -282,7 +292,10 @@ public class PortMappingService implements LifecycleBean {
|
||||
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
|
||||
return;
|
||||
}
|
||||
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()).isNotNull(PortMappingDO::getSubdomain));
|
||||
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>()
|
||||
.eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc())
|
||||
.isNotNull(PortMappingDO::getSubdomain)
|
||||
);
|
||||
if (CollectionUtil.isEmpty(portMappingDOList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-2
@@ -2,7 +2,6 @@ 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;
|
||||
|
||||
@@ -23,7 +22,7 @@ public class ProtocalService {
|
||||
public List<ProtocalListRes> list() {
|
||||
return Lists.newArrayList(
|
||||
new ProtocalListRes().setName("TCP").setEnable(Boolean.TRUE).setRemark("支持一切TCP之上的协议"),
|
||||
new ProtocalListRes().setName("HTTP(S)").setEnable(Boolean.TRUE).setRemark("支持绑定子域名,未绑定时等价于时使用TCP。 若配置了证书,则同时支持HTTPS。"),
|
||||
new ProtocalListRes().setName("HTTP").setEnable(Boolean.TRUE).setRemark("支持绑定子域名,未绑定时等价于时使用TCP"),
|
||||
new ProtocalListRes().setName("UDP").setEnable(Boolean.FALSE).setRemark("暂不支持")
|
||||
);
|
||||
}
|
||||
|
||||
+15
-9
@@ -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.TcpVisitorChannelHandler;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.VisitorChannelHandler;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
@@ -41,8 +41,10 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@Component
|
||||
public class VisitorChannelService {
|
||||
@Inject("tcpServerBootstrap")
|
||||
private ServerBootstrap tcpServerBootstrap;
|
||||
@Inject("serverBossGroup")
|
||||
private NioEventLoopGroup serverBossGroup;
|
||||
@Inject("serverWorkerGroup")
|
||||
private NioEventLoopGroup serverWorkerGroup;
|
||||
@Inject
|
||||
private ProxyMutualService proxyMutualService;
|
||||
@Db
|
||||
@@ -216,16 +218,20 @@ 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());
|
||||
tcpServerBootstrap.bind(portMapping.getServerPort()).get();
|
||||
bootstrap.bind(portMapping.getServerPort()).get();
|
||||
log.info("绑定用户端口: {}", portMapping.getServerPort());
|
||||
} catch (Exception ex) {
|
||||
// BindException表示该端口已经绑定过
|
||||
|
||||
-30
@@ -63,10 +63,6 @@ public class ProxyUtil {
|
||||
* 子域名 - 服务端端口映射
|
||||
*/
|
||||
private static Map<String, Integer> subdomainToServerPort = new HashMap<>();
|
||||
/**
|
||||
* licenseId - 客户端Id映射
|
||||
*/
|
||||
private static Map<Integer, String> licenseIdToClientIdMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 初始化代理信息
|
||||
@@ -391,30 +387,4 @@ public class ProxyUtil {
|
||||
// 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,5 +1,4 @@
|
||||
server:
|
||||
# 服务端web端口,用于支持HTTP接口,管理后台页面访问
|
||||
port: ${WEB_PORT:8888}
|
||||
|
||||
neutrino:
|
||||
@@ -10,51 +9,25 @@ neutrino:
|
||||
length-field-length: 4
|
||||
initial-bytes-to-strip: 0
|
||||
length-adjustment: 0
|
||||
read-idle-time: 30
|
||||
write-idle-time: 5
|
||||
read-idle-time: 40
|
||||
write-idle-time: 10
|
||||
all-idle-time-seconds: 0
|
||||
logger:
|
||||
# 日志级别
|
||||
level: ${LOG_LEVEL:info}
|
||||
# 隧道相关配置-用于维持服务端与客户端的通信
|
||||
tunnel:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
boss-thread-count: 2
|
||||
work-thread-count: 10
|
||||
# 隧道非SSL端口
|
||||
server:
|
||||
boss-thread-count: 10
|
||||
work-thread-count: 60
|
||||
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}
|
||||
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${TUNNEL_LOG:false}
|
||||
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:}
|
||||
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${SERVER_LOG:false}
|
||||
data:
|
||||
db:
|
||||
# 数据库类型,目前支持sqlite、mysql、mariadb
|
||||
type: ${DB_TYPE:sqlite}
|
||||
# 数据库连接URL
|
||||
url: ${DB_URL:jdbc:sqlite:data.db}
|
||||
# 数据库用户名
|
||||
username: ${DB_USER:}
|
||||
# 数据库密码
|
||||
password: ${DB_PASSWORD:}
|
||||
|
||||
#添加MIME印射(如果有需要?)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<property name="LOG_FILE" value="./logs/neutrino-proxy-server.log"/>
|
||||
<property name="LOG_FILE" value="/work/projects/neutrino-proxy-server/app.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" />-->
|
||||
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
#############################系统管理相关表#############################
|
||||
#用户表
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(50) NOT NULL COMMENT '用户名',
|
||||
`login_name` varchar(50) NOT NULL COMMENT '登录名',
|
||||
`login_password` varchar(255) NOT 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_user_login_name` (`login_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#用户token表
|
||||
CREATE TABLE IF NOT EXISTS `user_token` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`token` varchar(50) NOT NULL COMMENT 'token',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`expiration_time` datetime(3) NOT NULL COMMENT '过期时间',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
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;
|
||||
|
||||
#端口池
|
||||
CREATE TABLE IF NOT EXISTS `port_pool` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`group_id` int NOT NULL DEFAULT 1 COMMENT '分组ID',
|
||||
`port` int NOT 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_pool_port` (`port`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#端口分组
|
||||
CREATE TABLE IF NOT EXISTS `port_group` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(255) NOT NULL COMMENT '分组名称',
|
||||
`possessor_type` int NOT NULL DEFAULT '0' COMMENT '所有者类型 (0、全局共享 1、用户所有 2License所有) ',
|
||||
`possessor_id` int NOT NULL DEFAULT '-1' COMMENT '所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`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;
|
||||
|
||||
#############################代理配置相关表#############################
|
||||
#license表
|
||||
CREATE TABLE IF NOT EXISTS `license` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(50) NOT NULL COMMENT 'license名称',
|
||||
`key` varchar(100) NOT NULL COMMENT 'license key',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `I_license_key` (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#端口映射表
|
||||
CREATE TABLE IF NOT EXISTS `port_mapping` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseID',
|
||||
`protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议',
|
||||
`subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)',
|
||||
`server_port` int NOT NULL COMMENT '服务端端口',
|
||||
`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;
|
||||
#############################日志管理相关表#############################
|
||||
#用户登录记录表
|
||||
CREATE TABLE IF NOT EXISTS `user_login_record` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`ip` varchar(50) NOT NULL COMMENT 'IP',
|
||||
`token` varchar(100) NOT NULL COMMENT 'token',
|
||||
`type` int NOT NULL COMMENT '类型(1、登录 2、登出)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
#客户端连接记录表
|
||||
CREATE TABLE IF NOT EXISTS `client_connect_record` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`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 '消息',
|
||||
`code` int NOT NULL COMMENT '结果 (1、成功 2、失败)',
|
||||
`err` text DEFAULT NULL COMMENT '异常信息',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
#############################调度管理相关表#############################
|
||||
#触发器信息表
|
||||
CREATE TABLE IF NOT EXISTS `job_info` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`desc` varchar(255) NOT NULL COMMENT '描述',
|
||||
`handler` varchar(255) NOT NULL COMMENT '处理器',
|
||||
`cron` varchar(128) NOT NULL COMMENT 'cron',
|
||||
`param` varchar(512) DEFAULT NULL COMMENT '参数',
|
||||
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮箱',
|
||||
`alarm_ding` varchar(255) 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_job_info_handler` (`handler`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#触发器日志表
|
||||
CREATE TABLE IF NOT EXISTS `job_log` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`job_id` int NOT NULL COMMENT 'JobId',
|
||||
`handler` varchar(255) NOT NULL COMMENT '处理器',
|
||||
`param` varchar(512) DEFAULT NULL COMMENT '参数',
|
||||
`code` int NOT NULL COMMENT '结果(1、成功 2、失败)',
|
||||
`msg` text COMMENT '消息',
|
||||
`alarm_status` int NOT NULL COMMENT '报警状态(1、未报警 2、已报警)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
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;
|
||||
|
||||
#############################报表管理相关表#############################
|
||||
#流量统计报表-分钟(保留24小时)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_minute` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH:mm',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_flow_report_minute_create_time` (`create_time`) USING BTREE,
|
||||
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;
|
||||
|
||||
#流量统计报表-小时(保留60天)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`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;
|
||||
|
||||
#流量统计报表-天(保留1年)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_day` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`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;
|
||||
|
||||
#流量统计报表-月(全量保留)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_month` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`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;
|
||||
@@ -1,13 +0,0 @@
|
||||
#job_qrtz_trigger_info
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, '流量统计报表-分钟', 'FlowReportForMinuteJob', '0 */1 * * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(4, '流量统计报表-小时', 'FlowReportForHourJob', '0 0 */1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(5, '流量统计报表-天', 'FlowReportForDayJob', '0 0 1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, now(), now());
|
||||
@@ -1,3 +0,0 @@
|
||||
#license
|
||||
INSERT INTO license(`id`, `name`, `key`, `user_id`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, '我的mac', 'b0a907332b474b25897c4dcb31fc7eb6', 1, 2, 1, now(), now());
|
||||
@@ -1,3 +0,0 @@
|
||||
#port_group
|
||||
INSERT INTO `port_group`(`id`,`name`,`possessor_type`,`possessor_id`,`enable`,`create_time`,`update_time`) VALUES
|
||||
(1, '全局(默认)', 0, -1, 1, now(), now());
|
||||
@@ -1,7 +0,0 @@
|
||||
#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, now(), 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, now(), 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, now(), now());
|
||||
@@ -1,41 +0,0 @@
|
||||
#端口池
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, 1, 9101, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, 1, 9102, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, 1, 9103, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(4, 1, 9104, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(5, 1, 9105, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(6, 1, 9106, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(7, 1, 9107, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(8, 1, 9108, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(9, 1, 9109, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(10, 1, 9110, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(11, 1, 9111, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(12, 1, 9112, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(13, 1, 9113, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(14, 1, 9114, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(15, 1, 9115, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(16, 1, 9116, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(17, 1, 9117, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(18, 1, 9118, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(19, 1, 9119, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(20, 1, 9120, 1, now(), now());
|
||||
@@ -1,5 +0,0 @@
|
||||
#用户表 6613b92b77056faeb72068f184ed4c4f
|
||||
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
|
||||
(1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
|
||||
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
|
||||
(2, '游客', 'visitor', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
|
||||
@@ -96,7 +96,7 @@ CREATE TABLE IF NOT EXISTS `user_login_record` (
|
||||
#客户端连接记录表
|
||||
CREATE TABLE IF NOT EXISTS `client_connect_record` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`ip` varchar(50) NOT NULL COMMENT 'IP',
|
||||
`ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'IP',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`type` int NOT NULL COMMENT '类型(1、连接 2、断开连接)',
|
||||
`msg` varchar(512) DEFAULT NULL COMMENT '消息',
|
||||
@@ -163,7 +163,7 @@ 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) NOT NULL COMMENT '时间 yyyy-MM-dd HH',
|
||||
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci 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,
|
||||
@@ -180,7 +180,7 @@ 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) NOT NULL COMMENT '时间 yyyy-MM-dd',
|
||||
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci 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,
|
||||
@@ -197,7 +197,7 @@ 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) NOT NULL COMMENT '时间 yyyy-MM',
|
||||
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci 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,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB |
@@ -7,7 +7,7 @@ article: false
|
||||
|
||||
## 1、 部署服务端
|
||||
### 1.1、 Docker一键部署
|
||||
> 当前最新版本为1.8.4,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.4` 指定版本安装,推荐使用`latest`直接安装最新版。
|
||||
> 当前最新版本为1.8.0,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.0` 指定版本安装,推荐使用`latest`直接安装最新版。
|
||||
|
||||
#### 使用默认sqlite数据库
|
||||
```shell
|
||||
@@ -46,8 +46,8 @@ registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
|
||||
- 在服务器上新建部署目录:`/work/projects/neutrino-proxy-server`
|
||||
- 将` neutrino-proxy-server.jar`、`neutrino-proxy-admin.zip`上传至服务器部署目录。
|
||||
- 解压`neutrino-proxy-admin.zip`文件
|
||||
- 执行命令`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`启动服务端完成部署
|
||||
- 执行命令`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、管理后台配置
|
||||
@@ -115,4 +115,4 @@ module.exports = {
|
||||
}
|
||||
```
|
||||
:::
|
||||
-->
|
||||
-->
|
||||
@@ -27,5 +27,5 @@ License分组下的端口,归属于某一个特定的License。
|
||||
|
||||
# 基础端口说明
|
||||
- WEB端口:服务端API、后台管理访问端口,默认为:8888
|
||||
- 服务端等待客户端连接的端口,非SSL:默认9000,SSL端口:默认9002(若不需要SSL支持,可不配置SSL端口)
|
||||
- 服务端等待客户端连接的端口,非SSL:默认9000,SSL端口:默认9000(若不需要SSL支持,可不配置SSL端口)
|
||||
- HTTP代理端口:默认80,用于域名映射,若无需域名映射,可以忽略。
|
||||
@@ -35,28 +35,3 @@ permalink: /pages/269a2e/
|
||||
# 所有的代理映射都能绑定域名吗?
|
||||
不行的。之所以能实现域名绑定,原因是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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
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端口的情况类似
|
||||
@@ -1,65 +0,0 @@
|
||||
---
|
||||
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:
|
||||
logger:
|
||||
# 日志级别
|
||||
level: ${LOG_LEVEL:info}
|
||||
# 隧道相关配置-用于维持服务端与客户端的通信
|
||||
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}
|
||||
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${TUNNEL_LOG:false}
|
||||
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:}
|
||||
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${SERVER_LOG:false}
|
||||
data:
|
||||
db:
|
||||
# 数据库类型,目前支持sqlite、mysql、mariadb
|
||||
type: ${DB_TYPE:sqlite}
|
||||
# 数据库连接URL
|
||||
url: ${DB_URL:jdbc:sqlite:data.db}
|
||||
# 数据库用户名
|
||||
username: ${DB_USER:}
|
||||
# 数据库密码
|
||||
password: ${DB_PASSWORD:}
|
||||
```
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
title: 客户端配置
|
||||
date: 2023-06-09 21:15:35
|
||||
permalink: /pages/50ce10/
|
||||
---
|
||||
|
||||
# 以下是最新的客户端配置格式(app.yml)
|
||||
```yml
|
||||
neutrino:
|
||||
proxy:
|
||||
logger:
|
||||
# 日志级别
|
||||
level: ${LOG_LEVEL:info}
|
||||
client:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
thread-count: 50
|
||||
# 隧道SSL证书配置
|
||||
key-store-password: ${STORE_PASS:123456}
|
||||
jks-path: ${JKS_PATH:classpath:/test.jks}
|
||||
# 服务端IP
|
||||
server-ip: ${SERVER_IP:localhost}
|
||||
# 服务端端口(对应服务端app.yml中的tunnel.port、tunnel.ssl-port)
|
||||
server-port: ${SERVER_PORT:9002}
|
||||
# 是否启用SSL(注意:该配置必须和server-port对应上)
|
||||
ssl-enable: ${SSL_ENABLE:true}
|
||||
# 客户端连接唯一凭证
|
||||
license-key: ${LICENSE_KEY:}
|
||||
# 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成)
|
||||
client-id: ${CLIENT_ID:}
|
||||
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${CLIENT_LOG:false}
|
||||
# 重连设置
|
||||
reconnection:
|
||||
# 重连间隔(秒)
|
||||
interval-seconds: 10
|
||||
# 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启)
|
||||
unlimited: false
|
||||
```
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
title: 2023年
|
||||
date: 2023-06-09 21:33:17
|
||||
permalink: /pages/cded59/
|
||||
---
|
||||
|
||||
| 日期 | 渠道 | 金额 |昵称| 备注 |
|
||||
|:-----------|:---|:---|:-|:--------|
|
||||
| 2023-06-09 |Gitee捐助| 10 |失败女神| 感谢您的开源项目! |
|
||||
| 2023-06-09 |Gitee捐助| 50 |Admin| 感谢您的开源项目! |
|
||||
| 2023-06-09 |微信红包| 50 |李阳| 开源无限好 |
|
||||
| 2023-06-09 |Gitee捐助| 50 |罗宾| 感谢您的开源项目!|
|
||||
| 2023-06-07 |Gitee捐助| 50 |罗宾| 感谢您的开源项目!|
|
||||
| 2023-06-03 |Gitee捐助| 50 |Admin| 感谢您的开源项目!|
|
||||
| 2023-05-29 |微信红包| 20 |TYY| |
|
||||
| 2023-05-29 |Gitee捐助| 10 |笑看| 感谢您的开源项目!|
|
||||
| 2023-05-26 |微信红包| 50 |至少还有满天星光照耀你| |
|
||||
| 2023-02-23 |Gitee捐助| 10 |Yohanes|感谢您的开源项目! |
|
||||
| 2023-02-23 |Gitee捐助| 20 |jam_lee|感谢您的开源项目!希望能支持域名映射|
|
||||
| 2023-02-10 |Gitee捐助| 5 |实习两年半|感谢您的开源项目!|
|
||||
| 2023-02-02 |Gitee捐助| 10 |阳光很暖|感谢您的开源项目!|
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
home: true
|
||||
|
||||
heroImage: /img/logo-0.75x.png
|
||||
heroImage: /img/logo.png
|
||||
heroText: Neutrino-Proxy
|
||||
tagline: 🚀一个基于Netty的开源内网穿透神器
|
||||
actionText: 开始使用 →
|
||||
@@ -21,12 +21,8 @@ features: # 可选的
|
||||
details: 新增、编辑、删除、禁用实时生效。
|
||||
- title: Docker
|
||||
details: 服务端支持Docker一键部署。
|
||||
- title: 隧道SSL加密
|
||||
details: 隧道通信支持SSL,保护您的数据安全
|
||||
- title: HTTPS
|
||||
details: 支持HTTPS
|
||||
- title: 多客户端支持
|
||||
details: 暂未支持
|
||||
# - title: SSL证书
|
||||
# details: 支持SSL,保护您的信息安全。
|
||||
|
||||
# 文章列表显示方式: detailed 默认,显示详细版文章列表(包括作者、分类、标签、摘要、分页等)| simple => 显示简约版文章列表(仅标题和日期)| none 不显示文章列表
|
||||
postList: none
|
||||
@@ -292,11 +288,8 @@ postList: none
|
||||
|
||||
<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 href="https://gitee.com/noear/solon" target="_blank" class="friends-item" style="display: flex;">
|
||||
<img class="no-zoom friends-item-img hover-alt" :src="$withBase('/img/logo/solon.png')" msg="一个高效的应用开发框架:更快、更小、更简单。" style="margin-left: 15px;"/><div style="margin-left: 10px;font-size: 30px;">Solon</div>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
<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>
|
||||
@@ -27,11 +29,12 @@
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
|
||||
<revision>1.0-SNAPSHOT</revision>
|
||||
|
||||
<java.version>1.8</java.version>
|
||||
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<java.version>17</java.version>
|
||||
<maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
|
||||
<maven-flatten.version>1.1.0</maven-flatten.version>
|
||||
<org.projectlombok.version>1.18.4</org.projectlombok.version>
|
||||
<org.projectlombok.version>1.18.26</org.projectlombok.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -39,7 +42,7 @@
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.22.Final</version>
|
||||
<version>4.1.91.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@@ -92,37 +95,21 @@
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.39.2.1</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>mysql</groupId>-->
|
||||
<!-- <artifactId>mysql-connector-java</artifactId>-->
|
||||
<!-- <version>5.1.49</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>5.1.49</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<version>2.7.4</version>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>8.0.32</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<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>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out
|
||||
fi
|
||||
rm -f $OUT
|
||||
cd $JAR_PATH
|
||||
nohup java -Dfile.encoding=utf-8 $JAVA_OPS -jar $NAME.jar $startupParams > $OUT 2>&1 &
|
||||
nohup java $JAVA_OPS -jar $NAME.jar $startupParams > $OUT 2>&1 &
|
||||
echo "sleep 15s wating service start"
|
||||
sleep 15
|
||||
tail -200 $OUT
|
||||
|
||||
@@ -29,7 +29,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out
|
||||
fi
|
||||
rm -f $OUT
|
||||
cd $JAR_PATH
|
||||
nohup java -Dfile.encoding=utf-8 $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
|
||||
nohup java $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
|
||||
echo "sleep 15s wating service start"
|
||||
sleep 15
|
||||
tail -200 $OUT
|
||||
|
||||
+3
-17
@@ -1,21 +1,9 @@
|
||||
# 1.x规划
|
||||
- Bug
|
||||
- 优化
|
||||
- 心跳日志开关
|
||||
- 客户端启动无限重连开关
|
||||
- UDP支持
|
||||
- 官网文档完善
|
||||
- 常见问题汇总
|
||||
- HTTPS配置说明
|
||||
- 协议重构
|
||||
- 代码重构
|
||||
- [x] 适配mariadb
|
||||
# 1.x剩余规划
|
||||
- [ ] 增加针对https的支持
|
||||
|
||||
# Bug
|
||||
- 指令通达被close的问题,org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelChannelHandler.channelInactive
|
||||
- windows环境下直接运行发布版的jar包,日志输出乱码
|
||||
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象
|
||||
- 客户端连接映射某个端口以后,如果在服务器端禁用了,没有立刻反映出来,要灯客户端重连以后才会屏蔽被禁用的端口。
|
||||
|
||||
# 2.x规划
|
||||
- 插件开发
|
||||
@@ -30,8 +18,6 @@
|
||||
- [ ] 支持调整服务端端口、证书
|
||||
- [ ] 服务端版本、协议版本、客户端版本管理
|
||||
- [ ] 服务端日志
|
||||
- [ ] 支持持管理后台动态调整日志级别。
|
||||
- [ ] 动态调整转发、报文、心跳输出
|
||||
- client+计划启动
|
||||
- [ ] 安卓客户端
|
||||
- [ ] 基于electron-egg的多平台客户端
|
||||
@@ -39,4 +25,4 @@
|
||||
# 3.x规划
|
||||
- [ ] 支持针对用户限速、限流
|
||||
- [ ] 支持P2P穿透
|
||||
- [ ] 支持原生编译
|
||||
- [ ] 支持原生编译
|
||||
Reference in New Issue
Block a user