Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a5d50b84d | ||
|
|
22461066a4 | ||
|
|
cd4c9f2aae | ||
|
|
30b115610b | ||
|
|
c971bee96c | ||
|
|
7ea723b578 | ||
|
|
1625187bb1 | ||
|
|
934ceaadca | ||
|
|
bb7fe668b1 | ||
|
|
c385030fc4 | ||
|
|
767860643d | ||
|
|
c20a6db44a | ||
|
|
f423056361 | ||
|
|
86a3cf92b5 | ||
|
|
b28d476081 | ||
|
|
48c8df15e7 | ||
|
|
a31b0ebc95 | ||
|
|
fe86d786f4 | ||
|
|
c48404e3bb | ||
|
|
5daec7195f | ||
|
|
533675b367 | ||
|
|
e547895a9c | ||
|
|
42b7163f5b | ||
|
|
2ceacc6916 | ||
|
|
124273e8d7 | ||
|
|
5a36c88875 | ||
|
|
70e6d2ab45 |
@@ -34,144 +34,19 @@
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
# 4、项目结构
|
# 4、代理示意图
|
||||||
- neutrino-proxy
|
|
||||||
- neutrino-proxy-core 与代理相关的公共常量、编解码器
|
|
||||||
- neutrino-proxy-client 代理客户端项目
|
|
||||||
- neutrino-proxy-server 代理服务端项目
|
|
||||||
- neutrino-proxy-admin 代理监控项目(基于vue-element-admin开发)
|
|
||||||
|
|
||||||
# 5、运行
|
|
||||||
## 5.1、使用keytool工具生成ssl证书, 若不需要ssl加密可跳过
|
|
||||||
```shell
|
|
||||||
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5.2、修改服务端配置(app.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:
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5.3、启动服务端
|
|
||||||
> java -jar neutrino-proxy-server.jar
|
|
||||||
|
|
||||||
## 5.4、修改客户端配置
|
|
||||||
```yml
|
|
||||||
application:
|
|
||||||
name: neutrino-proxy-client
|
|
||||||
|
|
||||||
proxy:
|
|
||||||
protocol:
|
|
||||||
max-frame-length: 2097152
|
|
||||||
length-field-offset: 0
|
|
||||||
length-field-length: 4
|
|
||||||
initial-bytes-to-strip: 0
|
|
||||||
length-adjustment: 0
|
|
||||||
read-idle-time: 60
|
|
||||||
write-idle-time: 30
|
|
||||||
all-idle-time-seconds: 0
|
|
||||||
client:
|
|
||||||
# ssl证书密码
|
|
||||||
key-store-password: 123456
|
|
||||||
# ssl证书存放位置
|
|
||||||
jks-path: classpath:/test.jks
|
|
||||||
# 服务端ip,若部署到服务器,则配置服务器的ip
|
|
||||||
server-ip: localhost
|
|
||||||
# 服务端端口,若使用ssl,则需要配置为服务端的"ssl-port"
|
|
||||||
server-port: 9000
|
|
||||||
# 是否启用ssl,启用则必须配置ssl相关参数
|
|
||||||
ssl-enable: false
|
|
||||||
```
|
|
||||||
## 5.5、代理示意图
|
|
||||||

|

|
||||||
|
|
||||||
## 5.6、启动客户端
|
# 5、演示环境
|
||||||
### 5.6.1、启动参数直接指定配置
|
|
||||||
> java -jar neutrino-proxy-client.jar serverIp=localhost serverPort=9000 licenseKey=b0a907332b474b25897c4dcb31fc7eb6
|
|
||||||
|
|
||||||
### 5.6.2、启动参数指定外部配置文件
|
|
||||||
> java -jar neutrino-proxy-client.jar config=app.properties
|
|
||||||
|
|
||||||
配置文件格式如下:
|
|
||||||
```
|
|
||||||
neutrino.proxy.client.server-ip=localhost
|
|
||||||
neutrino.proxy.client.server-port=9002
|
|
||||||
neutrino.proxy.client.ssl-enable=true
|
|
||||||
neutrino.proxy.client.key-store-password=123456
|
|
||||||
neutrino.proxy.client.jks-path=classpath:/test.jks
|
|
||||||
neutrino.proxy.client.license-key=b0a907332b474b25897c4dcb31fc7eb6
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5.7、Docker快速启动
|
|
||||||
```shell script
|
|
||||||
# 一键部署前端和后端,客户端按上方部署即可
|
|
||||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 -d --name neutrino registry.cn-hangzhou.aliyuncs.com/asgc/aoshiguchen-docker-images:1.64
|
|
||||||
# 如没docker环境,可以一键部署docker环境(centos 7.8)系统
|
|
||||||
curl http://www.wangke666.cn/static/createDocker.sh | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
# 6、演示环境
|
|
||||||
> 可使用分配好的游客license试用。服务器带宽较低,仅供学习使用!
|
> 可使用分配好的游客license试用。服务器带宽较低,仅供学习使用!
|
||||||
- 管理后台地址:http://103.163.47.16:9527
|
- 管理后台地址:http://103.163.47.16:9527
|
||||||
- 游客账号:visitor/123456
|
- 游客账号:visitor/123456
|
||||||
|
|
||||||
# 7、未来迭代方向
|
# 6、联系我们
|
||||||
- 优化代码、增强稳定性
|
|
||||||
- 服务端增加管理页面,提供报表、授权、限流等功能
|
|
||||||
- 从项目中分离、孵化出另一个开源项目(neutrino-framework)
|
|
||||||
|
|
||||||
# 8、技术文档
|
|
||||||
- [Aop](./docs/Aop.MD)
|
|
||||||
- [Channel](./docs/Channel.MD)
|
|
||||||
|
|
||||||
# 9、联系我们
|
|
||||||
- 微信: yuyunshize
|
- 微信: yuyunshize
|
||||||
- Gitee: https://gitee.com/asgc/neutrino-proxy
|
- Gitee: https://gitee.com/asgc/neutrino-proxy
|
||||||
|
|
||||||
# 10、贡献者列表
|
# 7、贡献者列表
|
||||||
<p>
|
<p>
|
||||||
<a href="https://gitee.com/zcans" target="_blank">
|
<a href="https://gitee.com/zcans" target="_blank">
|
||||||
<img src="assets/developer/zcans.png" width="12%">
|
<img src="assets/developer/zcans.png" width="12%">
|
||||||
@@ -209,6 +84,9 @@ neutrino.proxy.client.license-key=b0a907332b474b25897c4dcb31fc7eb6
|
|||||||
<a href="https://gitee.com/zoo-plus" target="_blank">
|
<a href="https://gitee.com/zoo-plus" target="_blank">
|
||||||
<img src="assets/developer/zoo-plus.png" width="12%">
|
<img src="assets/developer/zoo-plus.png" width="12%">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://gitee.com/MetalXingxing" target="_blank">
|
||||||
|
<img src="assets/developer/metal.png" width="12%">
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# ❤️ 感谢
|
# ❤️ 感谢
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -1,5 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
NODE_ENV: '"development"',
|
NODE_ENV: '"development"',
|
||||||
ENV_CONFIG: '"docker"',
|
ENV_CONFIG: '"docker"',
|
||||||
BASE_API: '"/"'
|
BASE_API: '"/neutrino-proxy-server"'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,3 +60,13 @@ export function deletePortPool(id) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteBatchPortPool(ids) {
|
||||||
|
return request({
|
||||||
|
url: '/port-pool/delete-batch',
|
||||||
|
method: 'post',
|
||||||
|
data: {
|
||||||
|
ids: ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,60 +8,68 @@
|
|||||||
<el-button size="mini" @click="handleCancelClick">{{cancelText}}</el-button>
|
<el-button size="mini" @click="handleCancelClick">{{cancelText}}</el-button>
|
||||||
<el-button type="primary" size="mini" @click="handleCommitClick">{{okText}}</el-button>
|
<el-button type="primary" size="mini" @click="handleCommitClick">{{okText}}</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-button slot="reference" :type="type" :size="size">{{buttonText}}</el-button>
|
<el-button slot="reference" :type="type" :size="size" :icon="icon" :disabled="disabled">{{buttonText}}</el-button>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'deleteButton',
|
name: 'deleteButton',
|
||||||
props: {
|
props: {
|
||||||
width: {
|
width: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 160
|
default: 160
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
type: String,
|
||||||
|
default: '删除'
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'danger'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'mini'
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '确定删除吗?'
|
||||||
|
},
|
||||||
|
okText: {
|
||||||
|
type: String,
|
||||||
|
default: '确定'
|
||||||
|
},
|
||||||
|
cancelText: {
|
||||||
|
type: String,
|
||||||
|
default: '取消'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
buttonText: {
|
data() {
|
||||||
type: String,
|
return {
|
||||||
default: '删除'
|
visible: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
type: {
|
methods: {
|
||||||
type: String,
|
handleCancelClick() {
|
||||||
default: 'danger'
|
this.visible = false
|
||||||
},
|
this.$emit('handleCancelClick')
|
||||||
size: {
|
},
|
||||||
type: String,
|
handleCommitClick() {
|
||||||
default: 'mini'
|
this.visible = false
|
||||||
},
|
this.$emit('handleCommitClick')
|
||||||
title: {
|
}
|
||||||
type: String,
|
|
||||||
default: '确定删除吗?'
|
|
||||||
},
|
|
||||||
okText: {
|
|
||||||
type: String,
|
|
||||||
default: '确定'
|
|
||||||
},
|
|
||||||
cancelText: {
|
|
||||||
type: String,
|
|
||||||
default: '取消'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
visible: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleCancelClick() {
|
|
||||||
this.visible = false
|
|
||||||
this.$emit('handleCancelClick')
|
|
||||||
},
|
|
||||||
handleCommitClick() {
|
|
||||||
this.visible = false
|
|
||||||
this.$emit('handleCommitClick')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export default {
|
|||||||
publish: 'Publish',
|
publish: 'Publish',
|
||||||
draft: 'Draft',
|
draft: 'Draft',
|
||||||
delete: 'Delete',
|
delete: 'Delete',
|
||||||
|
deleteBatch: 'DeleteBatch',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
portOrRange: 'example: 3306 or 1000-2000'
|
portOrRange: 'example: 3306 or 1000-2000'
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ export default {
|
|||||||
publish: '发布',
|
publish: '发布',
|
||||||
draft: '草稿',
|
draft: '草稿',
|
||||||
delete: '删除',
|
delete: '删除',
|
||||||
|
deleteBatch: '批量删除',
|
||||||
cancel: '取 消',
|
cancel: '取 消',
|
||||||
confirm: '确 定',
|
confirm: '确 定',
|
||||||
userId: '用户ID',
|
userId: '用户ID',
|
||||||
|
|||||||
@@ -143,7 +143,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('服务端端口')" prop="serverPort">
|
<el-form-item :label="$t('服务端端口')" prop="serverPort">
|
||||||
<el-select style="width: 280px;" class="filter-item" v-model="temp.serverPort" placeholder="请选择">
|
<el-select style="width: 280px;" class="filter-item" v-model="temp.serverPort" placeholder="请选择" filterable>
|
||||||
<el-option v-for="item in serverPortList" :key="item.port" :label="item.port" :value="item.port">
|
<el-option v-for="item in serverPortList" :key="item.port" :label="item.port" :value="item.port">
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
||||||
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="el-icon-edit">{{$t('table.add')}}</el-button>
|
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="el-icon-edit">{{$t('table.add')}}</el-button>
|
||||||
|
<button-popover class="filter-item" style="margin-left: 10px;" size="medium" icon="el-icon-delete"
|
||||||
|
:buttonText="$t('table.deleteBatch')" :disabled="checkBoxData.length==0" @handleCommitClick="handleDeleteBatch" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row
|
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row
|
||||||
style="width: 100%">
|
style="width: 100%" @selection-change="onTableRowChange">
|
||||||
|
<el-table-column align="center" width="40" type="selection" />
|
||||||
<el-table-column align="center" :label="$t('table.id')" width="120">
|
<el-table-column align="center" :label="$t('table.id')" width="120">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span>{{scope.row.id}}</span>
|
<span>{{scope.row.id}}</span>
|
||||||
@@ -92,7 +95,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createPortPool, deletePortPool, fetchList, updateEnableStatus, updatePortPool } from '@/api/portPool'
|
import { createPortPool, deletePortPool, deleteBatchPortPool, fetchList, updateEnableStatus, updatePortPool } from '@/api/portPool'
|
||||||
import { portGroupList } from '@/api/portGroup'
|
import { portGroupList } from '@/api/portGroup'
|
||||||
import waves from '@/directive/waves' // 水波纹指令
|
import waves from '@/directive/waves' // 水波纹指令
|
||||||
import { parseTime } from '@/utils'
|
import { parseTime } from '@/utils'
|
||||||
@@ -157,7 +160,8 @@ const calendarTypeOptions = [
|
|||||||
rules: {
|
rules: {
|
||||||
port: [{ required: true, message: '端口必填', trigger: 'blur' }]
|
port: [{ required: true, message: '端口必填', trigger: 'blur' }]
|
||||||
},
|
},
|
||||||
downloadLoading: false
|
downloadLoading: false,
|
||||||
|
checkBoxData:[], //表单勾选的行
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
@@ -320,6 +324,24 @@ const calendarTypeOptions = [
|
|||||||
return v[j]
|
return v[j]
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
},
|
||||||
|
onTableRowChange(val){ //表单勾选后赋值
|
||||||
|
this.checkBoxData = val
|
||||||
|
},
|
||||||
|
handleDeleteBatch(){ //批量删除操作
|
||||||
|
let ids = []
|
||||||
|
this.checkBoxData.forEach(item => {ids.push(item.id)})
|
||||||
|
deleteBatchPortPool(ids).then(response => {
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
this.$notify({
|
||||||
|
title: '成功',
|
||||||
|
message: '删除成功',
|
||||||
|
type: 'success',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
this.getList()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<filtering>false</filtering>
|
<filtering>false</filtering>
|
||||||
|
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>app-*.yml</exclude>
|
<exclude>app-dev.yml</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
</resource>
|
</resource>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
package org.dromara.neutrinoproxy.client;
|
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 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.Solon;
|
||||||
|
import org.noear.solon.SolonApp;
|
||||||
import org.noear.solon.annotation.SolonMain;
|
import org.noear.solon.annotation.SolonMain;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author: aoshiguchen
|
* @author: aoshiguchen
|
||||||
* @date: 2022/6/16
|
* @date: 2022/6/16
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@SolonMain
|
@SolonMain
|
||||||
public class ProxyClient {
|
public class ProxyClient {
|
||||||
|
|
||||||
@@ -20,6 +28,8 @@ public class ProxyClient {
|
|||||||
setAlias("neutrino.proxy.client.jksPath", "jksPath");
|
setAlias("neutrino.proxy.client.jksPath", "jksPath");
|
||||||
setAlias("neutrino.proxy.client.keyStorePassword", "keyStorePassword");
|
setAlias("neutrino.proxy.client.keyStorePassword", "keyStorePassword");
|
||||||
setAlias("neutrino.proxy.client.licenseKey", "licenseKey");
|
setAlias("neutrino.proxy.client.licenseKey", "licenseKey");
|
||||||
|
// 设置日志级别
|
||||||
|
setLogLevel(app);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,4 +44,21 @@ public class ProxyClient {
|
|||||||
Solon.cfg().put(key, val);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -41,5 +41,13 @@ public class ProxyConfig {
|
|||||||
private Integer threadCount;
|
private Integer threadCount;
|
||||||
private String clientId;
|
private String clientId;
|
||||||
private Boolean transferLogEnable;
|
private Boolean transferLogEnable;
|
||||||
|
private Boolean heartbeatLogEnable;
|
||||||
|
private Reconnection reconnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Reconnection {
|
||||||
|
private Integer intervalSeconds;
|
||||||
|
private Boolean unlimited;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-5
@@ -1,5 +1,6 @@
|
|||||||
package org.dromara.neutrinoproxy.client.core;
|
package org.dromara.neutrinoproxy.client.core;
|
||||||
|
|
||||||
|
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
|
||||||
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
||||||
import org.dromara.neutrinoproxy.core.Constants;
|
import org.dromara.neutrinoproxy.core.Constants;
|
||||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||||
@@ -19,12 +20,19 @@ import org.noear.solon.Solon;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CmdChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
public class CmdChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||||
|
private static volatile Boolean transferLogEnable = Boolean.FALSE;
|
||||||
|
|
||||||
|
public CmdChannelHandler() {
|
||||||
|
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
|
||||||
|
if (null != proxyConfig.getClient() && null != proxyConfig.getClient().getHeartbeatLogEnable()) {
|
||||||
|
transferLogEnable = proxyConfig.getClient().getHeartbeatLogEnable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
||||||
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
|
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType() || transferLogEnable) {
|
||||||
log.info("Client CmdChannel recieved proxy message, type is {}", proxyMessage.getType());
|
log.debug("Client CmdChannel recieved proxy message, type is {}", proxyMessage.getType());
|
||||||
}
|
}
|
||||||
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
|
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
|
||||||
}
|
}
|
||||||
@@ -61,14 +69,14 @@ public class CmdChannelHandler extends SimpleChannelInboundHandler<ProxyMessage>
|
|||||||
switch (event.state()) {
|
switch (event.state()) {
|
||||||
case READER_IDLE:
|
case READER_IDLE:
|
||||||
// 读超时,断开连接
|
// 读超时,断开连接
|
||||||
// log.info("读超时");
|
log.error("Client CmdChannel 读超时,断开连接");
|
||||||
// ctx.channel().close();
|
ctx.channel().close();
|
||||||
break;
|
break;
|
||||||
case WRITER_IDLE:
|
case WRITER_IDLE:
|
||||||
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
|
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
|
||||||
break;
|
break;
|
||||||
case ALL_IDLE:
|
case ALL_IDLE:
|
||||||
log.info("Client CmdChannel 读写超时");
|
log.error("Client CmdChannel 读写超时,断开连接");
|
||||||
ctx.close();
|
ctx.close();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ public class ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessag
|
|||||||
@Override
|
@Override
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
||||||
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
|
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
|
||||||
log.info("Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType());
|
log.debug("Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType());
|
||||||
}
|
}
|
||||||
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
|
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ public class ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessag
|
|||||||
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
|
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
|
||||||
break;
|
break;
|
||||||
case ALL_IDLE:
|
case ALL_IDLE:
|
||||||
log.info("读写超时");
|
log.debug("读写超时");
|
||||||
ctx.close();
|
ctx.close();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-7
@@ -49,10 +49,10 @@ public class ProxyClientService {
|
|||||||
@Inject("realServerBootstrap")
|
@Inject("realServerBootstrap")
|
||||||
private Bootstrap realServerBootstrap;
|
private Bootstrap realServerBootstrap;
|
||||||
private volatile Channel channel;
|
private volatile Channel channel;
|
||||||
/**
|
// /**
|
||||||
* 重连间隔(秒)
|
// * 重连间隔(秒)
|
||||||
*/
|
// */
|
||||||
private static final long RECONNECT_INTERVAL_SECONDS = 5;
|
// private static final long RECONNECT_INTERVAL_SECONDS = 5;
|
||||||
/**
|
/**
|
||||||
* 重连次数
|
* 重连次数
|
||||||
*/
|
*/
|
||||||
@@ -69,7 +69,7 @@ public class ProxyClientService {
|
|||||||
|
|
||||||
@Init
|
@Init
|
||||||
public void init() {
|
public void init() {
|
||||||
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 10, RECONNECT_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 10, proxyConfig.getClient().getReconnection().getIntervalSeconds(), TimeUnit.SECONDS);
|
||||||
this.workerGroup = new NioEventLoopGroup(proxyConfig.getClient().getThreadCount());
|
this.workerGroup = new NioEventLoopGroup(proxyConfig.getClient().getThreadCount());
|
||||||
|
|
||||||
realServerBootstrap.group(workerGroup);
|
realServerBootstrap.group(workerGroup);
|
||||||
@@ -78,6 +78,9 @@ public class ProxyClientService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initChannel(SocketChannel ch) throws Exception {
|
public void initChannel(SocketChannel ch) throws Exception {
|
||||||
|
if (null != proxyConfig.getClient().getTransferLogEnable() && proxyConfig.getClient().getTransferLogEnable()) {
|
||||||
|
ch.pipeline().addFirst(new LoggingHandler(RealServerChannelHandler.class));
|
||||||
|
}
|
||||||
ch.pipeline().addLast(new RealServerChannelHandler());
|
ch.pipeline().addLast(new RealServerChannelHandler());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -92,7 +95,9 @@ public class ProxyClientService {
|
|||||||
if (proxyConfig.getClient().getSslEnable()) {
|
if (proxyConfig.getClient().getSslEnable()) {
|
||||||
ch.pipeline().addLast(createSslHandler());
|
ch.pipeline().addLast(createSslHandler());
|
||||||
}
|
}
|
||||||
// ch.pipeline().addFirst(new LoggingHandler(ProxyClientService.class));
|
if (null != proxyConfig.getClient().getTransferLogEnable() && proxyConfig.getClient().getTransferLogEnable()) {
|
||||||
|
ch.pipeline().addFirst(new LoggingHandler(ProxyChannelHandler.class));
|
||||||
|
}
|
||||||
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
|
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
|
||||||
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
|
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
|
||||||
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
|
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
|
||||||
@@ -121,7 +126,7 @@ public class ProxyClientService {
|
|||||||
ch.pipeline().addLast(createSslHandler());
|
ch.pipeline().addLast(createSslHandler());
|
||||||
}
|
}
|
||||||
if (null != proxyConfig.getClient().getTransferLogEnable() && proxyConfig.getClient().getTransferLogEnable()) {
|
if (null != proxyConfig.getClient().getTransferLogEnable() && proxyConfig.getClient().getTransferLogEnable()) {
|
||||||
ch.pipeline().addFirst(new LoggingHandler(ProxyClientService.class));
|
ch.pipeline().addFirst(new LoggingHandler(CmdChannelHandler.class));
|
||||||
}
|
}
|
||||||
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
|
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
|
||||||
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
|
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
|
||||||
|
|||||||
+7
-1
@@ -1,6 +1,7 @@
|
|||||||
package org.dromara.neutrinoproxy.client.handler;
|
package org.dromara.neutrinoproxy.client.handler;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
|
||||||
import org.dromara.neutrinoproxy.core.Constants;
|
import org.dromara.neutrinoproxy.core.Constants;
|
||||||
import org.dromara.neutrinoproxy.core.ExceptionEnum;
|
import org.dromara.neutrinoproxy.core.ExceptionEnum;
|
||||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||||
@@ -10,6 +11,7 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.noear.solon.Solon;
|
import org.noear.solon.Solon;
|
||||||
import org.noear.solon.annotation.Component;
|
import org.noear.solon.annotation.Component;
|
||||||
|
import org.noear.solon.annotation.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证信息处理器
|
* 认证信息处理器
|
||||||
@@ -20,6 +22,8 @@ import org.noear.solon.annotation.Component;
|
|||||||
@Match(type = Constants.ProxyDataTypeName.AUTH)
|
@Match(type = Constants.ProxyDataTypeName.AUTH)
|
||||||
@Component
|
@Component
|
||||||
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
||||||
|
@Inject
|
||||||
|
private ProxyConfig proxyConfig;
|
||||||
@Override
|
@Override
|
||||||
public void handle(ChannelHandlerContext context, ProxyMessage proxyMessage) {
|
public void handle(ChannelHandlerContext context, ProxyMessage proxyMessage) {
|
||||||
String info = proxyMessage.getInfo();
|
String info = proxyMessage.getInfo();
|
||||||
@@ -30,7 +34,9 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
|||||||
// 客户端认证失败,直接停止服务
|
// 客户端认证失败,直接停止服务
|
||||||
log.info("client auth failed , client stop.");
|
log.info("client auth failed , client stop.");
|
||||||
context.channel().close();
|
context.channel().close();
|
||||||
Solon.stop();
|
if (!proxyConfig.getClient().getReconnection().getUnlimited()) {
|
||||||
|
Solon.stop();
|
||||||
|
}
|
||||||
} else if (ExceptionEnum.CONNECT_FAILED.getCode().equals(code) ||
|
} else if (ExceptionEnum.CONNECT_FAILED.getCode().equals(code) ||
|
||||||
ExceptionEnum.LICENSE_CANNOT_REPEAT_CONNECT.getCode().equals(code)
|
ExceptionEnum.LICENSE_CANNOT_REPEAT_CONNECT.getCode().equals(code)
|
||||||
){
|
){
|
||||||
|
|||||||
@@ -12,14 +12,32 @@ neutrino:
|
|||||||
read-idle-time: 8
|
read-idle-time: 8
|
||||||
write-idle-time: 3
|
write-idle-time: 3
|
||||||
all-idle-time-seconds: 8
|
all-idle-time-seconds: 8
|
||||||
|
logger:
|
||||||
|
# 日志级别
|
||||||
|
level: ${LOG_LEVEL:info}
|
||||||
client:
|
client:
|
||||||
|
# 线程池相关配置,用于技术调优,可忽略
|
||||||
thread-count: 50
|
thread-count: 50
|
||||||
|
# 隧道SSL证书配置
|
||||||
key-store-password: ${STORE_PASS:123456}
|
key-store-password: ${STORE_PASS:123456}
|
||||||
jks-path: ${JKS_PATH:classpath:/test.jks}
|
jks-path: ${JKS_PATH:classpath:/test.jks}
|
||||||
|
# 服务端IP
|
||||||
server-ip: ${SERVER_IP:localhost}
|
server-ip: ${SERVER_IP:localhost}
|
||||||
|
# 服务端端口(对应服务端app.yml中的tunnel.port、tunnel.ssl-port)
|
||||||
server-port: ${SERVER_PORT:9002}
|
server-port: ${SERVER_PORT:9002}
|
||||||
|
# 是否启用SSL(注意:该配置必须和server-port对应上)
|
||||||
ssl-enable: ${SSL_ENABLE:true}
|
ssl-enable: ${SSL_ENABLE:true}
|
||||||
obtain-license-interval: 5
|
# 客户端连接唯一凭证
|
||||||
license-key: ${LICENSE_KEY:}
|
license-key: ${LICENSE_KEY:}
|
||||||
|
# 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成)
|
||||||
client-id: ${CLIENT_ID:}
|
client-id: ${CLIENT_ID:}
|
||||||
|
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||||
transfer-log-enable: ${CLIENT_LOG:false}
|
transfer-log-enable: ${CLIENT_LOG:false}
|
||||||
|
# 是否开启心跳日志
|
||||||
|
heartbeat-log-enable: ${HEARTBEAT_LOG:false}
|
||||||
|
# 重连设置
|
||||||
|
reconnection:
|
||||||
|
# 重连间隔(秒)
|
||||||
|
interval-seconds: 10
|
||||||
|
# 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启)
|
||||||
|
unlimited: false
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
<filtering>false</filtering>
|
<filtering>false</filtering>
|
||||||
|
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>app-*.yml</exclude>
|
<exclude>app-dev.yml</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
</resource>
|
</resource>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
package org.dromara.neutrinoproxy.server;
|
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 org.dromara.solonplugins.job.annotation.EnableJob;
|
||||||
import org.noear.solon.Solon;
|
import org.noear.solon.Solon;
|
||||||
|
import org.noear.solon.SolonApp;
|
||||||
import org.noear.solon.annotation.SolonMain;
|
import org.noear.solon.annotation.SolonMain;
|
||||||
import org.noear.solon.web.cors.CrossFilter;
|
import org.noear.solon.web.cors.CrossFilter;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author: aoshiguchen
|
* @author: aoshiguchen
|
||||||
* @date: 2022/6/16
|
* @date: 2022/6/16
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@EnableJob
|
@EnableJob
|
||||||
@SolonMain
|
@SolonMain
|
||||||
public class ProxyServer {
|
public class ProxyServer {
|
||||||
@@ -18,6 +28,25 @@ public class ProxyServer {
|
|||||||
Solon.start(ProxyServer.class, args, app -> {
|
Solon.start(ProxyServer.class, args, app -> {
|
||||||
// 跨域支持。加-1 优先级更高
|
// 跨域支持。加-1 优先级更高
|
||||||
app.filter(-1, new CrossFilter().allowedOrigins("*"));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -62,6 +62,7 @@ public class ProxyConfig {
|
|||||||
private String keyManagerPassword;
|
private String keyManagerPassword;
|
||||||
private String jksPath;
|
private String jksPath;
|
||||||
private Boolean transferLogEnable;
|
private Boolean transferLogEnable;
|
||||||
|
private Boolean heartbeatLogEnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@ public class ProxyConfiguration implements LifecycleBean {
|
|||||||
@Override
|
@Override
|
||||||
public void initChannel(SocketChannel ch) throws Exception {
|
public void initChannel(SocketChannel ch) throws Exception {
|
||||||
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
|
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
|
||||||
ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class));
|
ch.pipeline().addFirst(new LoggingHandler(TcpVisitorChannelHandler.class));
|
||||||
}
|
}
|
||||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||||
ch.pipeline().addLast(new TcpVisitorChannelHandler());
|
ch.pipeline().addLast(new TcpVisitorChannelHandler());
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ public class VisitLogInterceptor implements RouterInterceptor {
|
|||||||
} finally {
|
} finally {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
long elapsedTime = now.getTime() - startTime.getTime();
|
long elapsedTime = now.getTime() - startTime.getTime();
|
||||||
log.info("\n-----------------------------------------------------------------接口请求日志:\n{} url:{} 执行耗时:{}\n请求体参数:{}\n响应结果:{}\n客户端IP:{}\n",
|
log.debug("\n-----------------------------------------------------------------接口请求日志:\n{} url:{} 执行耗时:{}\n请求体参数:{}\n响应结果:{}\n客户端IP:{}\n",
|
||||||
ctx.method(), ctx.path(), getElapsedTimeStr(elapsedTime),
|
ctx.method(), ctx.path(), getElapsedTimeStr(elapsedTime),
|
||||||
JSONObject.toJSONString(ctx.paramMap()),
|
JSONObject.toJSONString(ctx.paramMap()),
|
||||||
JSONObject.toJSONString(JSONObject.toJSONString(ctx.result)),
|
JSONObject.toJSONString(JSONObject.toJSONString(ctx.result)),
|
||||||
|
|||||||
+10
@@ -123,4 +123,14 @@ public class PortPoolController {
|
|||||||
ParamCheckUtil.checkNotEmpty(req.getPortIdList(), "portIdList");
|
ParamCheckUtil.checkNotEmpty(req.getPortIdList(), "portIdList");
|
||||||
return portPoolService.updateGroup(req);
|
return portPoolService.updateGroup(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post
|
||||||
|
@Mapping("/delete-batch")
|
||||||
|
@Authorization(onlyAdmin = true)
|
||||||
|
public void deleteBatch(PortPoolDeleteBatchReq req) {
|
||||||
|
ParamCheckUtil.checkNotNull(req, "req");
|
||||||
|
ParamCheckUtil.checkNotNull(req.getIds(), "ids");
|
||||||
|
|
||||||
|
portPoolService.deleteBatch(req.getIds());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: Metal
|
||||||
|
* @date: 2023/7/3
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PortPoolDeleteBatchReq {
|
||||||
|
private List<Integer> ids;
|
||||||
|
}
|
||||||
+1
-1
@@ -17,6 +17,6 @@ public class DemoJob implements IJobHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(String param) throws Exception {
|
public void execute(String param) throws Exception {
|
||||||
System.out.println("DemoJob execute param:" + param);
|
log.debug("DemoJob execute param: {}", param);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-1
@@ -25,6 +25,7 @@ package org.dromara.neutrinoproxy.server.proxy.core;
|
|||||||
import org.dromara.neutrinoproxy.core.Constants;
|
import org.dromara.neutrinoproxy.core.Constants;
|
||||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||||
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
|
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
|
||||||
|
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||||
import org.dromara.neutrinoproxy.server.constant.ClientConnectTypeEnum;
|
import org.dromara.neutrinoproxy.server.constant.ClientConnectTypeEnum;
|
||||||
import org.dromara.neutrinoproxy.server.constant.SuccessCodeEnum;
|
import org.dromara.neutrinoproxy.server.constant.SuccessCodeEnum;
|
||||||
import org.dromara.neutrinoproxy.server.dal.entity.ClientConnectRecordDO;
|
import org.dromara.neutrinoproxy.server.dal.entity.ClientConnectRecordDO;
|
||||||
@@ -49,13 +50,21 @@ import java.util.Date;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||||
private static volatile Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher;
|
private static volatile Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher;
|
||||||
|
private static volatile Boolean transferLogEnable = Boolean.FALSE;
|
||||||
|
|
||||||
public ProxyTunnelChannelHandler() {
|
public ProxyTunnelChannelHandler() {
|
||||||
dispatcher = Solon.context().getBean(Dispatcher.class);
|
dispatcher = Solon.context().getBean(Dispatcher.class);
|
||||||
|
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
|
||||||
|
if (null != proxyConfig.getTunnel() && null != proxyConfig.getTunnel().getHeartbeatLogEnable()) {
|
||||||
|
transferLogEnable = proxyConfig.getTunnel().getHeartbeatLogEnable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
||||||
|
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType() || transferLogEnable) {
|
||||||
|
log.debug("Server CmdChannel recieved proxy message, type is {}", proxyMessage.getType());
|
||||||
|
}
|
||||||
dispatcher.dispatch(ctx, proxyMessage);
|
dispatcher.dispatch(ctx, proxyMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,10 +136,11 @@ public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<Proxy
|
|||||||
switch (event.state()) {
|
switch (event.state()) {
|
||||||
case READER_IDLE:
|
case READER_IDLE:
|
||||||
// 读超时,断开连接
|
// 读超时,断开连接
|
||||||
log.info("读超时");
|
log.debug("读超时");
|
||||||
ctx.channel().close();
|
ctx.channel().close();
|
||||||
break;
|
break;
|
||||||
case WRITER_IDLE:
|
case WRITER_IDLE:
|
||||||
|
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
|
||||||
break;
|
break;
|
||||||
case ALL_IDLE:
|
case ALL_IDLE:
|
||||||
break;
|
break;
|
||||||
|
|||||||
+2
-2
@@ -43,14 +43,14 @@ public class HttpProxy implements EventListener<AppLoadEndEvent> {
|
|||||||
@Override
|
@Override
|
||||||
public void initChannel(SocketChannel ch) throws Exception {
|
public void initChannel(SocketChannel ch) throws Exception {
|
||||||
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
|
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
|
||||||
ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class));
|
ch.pipeline().addFirst(new LoggingHandler(HttpProxy.class));
|
||||||
}
|
}
|
||||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||||
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName()));
|
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpProxyPort()).sync();
|
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpProxyPort()).sync();
|
||||||
log.info("Http代理服务启动成功!");
|
log.info("Http代理服务启动成功!port:{}", proxyConfig.getServer().getHttpProxyPort());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("http proxy start err!", e);
|
log.error("http proxy start err!", e);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -163,7 +163,8 @@ public class HttpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteB
|
|||||||
}
|
}
|
||||||
// 域名
|
// 域名
|
||||||
String domain = line.substring(6);
|
String domain = line.substring(6);
|
||||||
return domain;
|
// 去掉域名后面的端口号
|
||||||
|
return domain.replaceAll(":.*", "");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -51,7 +51,7 @@ public class HttpsProxy implements EventListener<AppLoadEndEvent> {
|
|||||||
@Override
|
@Override
|
||||||
public void initChannel(SocketChannel ch) throws Exception {
|
public void initChannel(SocketChannel ch) throws Exception {
|
||||||
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
|
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
|
||||||
ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class));
|
ch.pipeline().addFirst(new LoggingHandler(HttpsProxy.class));
|
||||||
}
|
}
|
||||||
ch.pipeline().addLast(createSslHandler());
|
ch.pipeline().addLast(createSslHandler());
|
||||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||||
@@ -59,7 +59,7 @@ public class HttpsProxy implements EventListener<AppLoadEndEvent> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpsProxyPort()).sync();
|
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpsProxyPort()).sync();
|
||||||
log.info("Https代理服务启动成功!");
|
log.info("Https代理服务启动成功!port:{}", proxyConfig.getServer().getHttpsProxyPort());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("https proxy start err!", e);
|
log.error("https proxy start err!", e);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ public class JobLogService implements IJobCallback {
|
|||||||
String msg = "";
|
String msg = "";
|
||||||
if (null == throwable) {
|
if (null == throwable) {
|
||||||
msg = "执行成功";
|
msg = "执行成功";
|
||||||
log.info("job[id={},name={}]执行完毕", jobInfo.getId(), jobInfo.getName());
|
log.debug("job[id={},name={}]执行完毕", jobInfo.getId(), jobInfo.getName());
|
||||||
} else {
|
} else {
|
||||||
log.error("job[id={},name={}]执行异常", jobInfo.getId(), jobInfo.getName(), throwable);
|
log.error("job[id={},name={}]执行异常", jobInfo.getId(), jobInfo.getName(), throwable);
|
||||||
msg = "执行异常:\r\n" + ExceptionUtils.getStackTrace(throwable);
|
msg = "执行异常:\r\n" + ExceptionUtils.getStackTrace(throwable);
|
||||||
|
|||||||
-2
@@ -26,7 +26,6 @@ import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
|||||||
import ma.glasnost.orika.MapperFacade;
|
import ma.glasnost.orika.MapperFacade;
|
||||||
import org.apache.ibatis.solon.annotation.Db;
|
import org.apache.ibatis.solon.annotation.Db;
|
||||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
|
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.noear.solon.annotation.Component;
|
import org.noear.solon.annotation.Component;
|
||||||
import org.noear.solon.annotation.Init;
|
import org.noear.solon.annotation.Init;
|
||||||
import org.noear.solon.annotation.Inject;
|
import org.noear.solon.annotation.Inject;
|
||||||
@@ -90,7 +89,6 @@ public class LicenseService implements LifecycleBean {
|
|||||||
return licenseList;
|
return licenseList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private List<LicenseListRes> assembleConvertLicenses(List<LicenseDO> list) {
|
private List<LicenseListRes> assembleConvertLicenses(List<LicenseDO> list) {
|
||||||
List<LicenseListRes> licenseList = mapperFacade.mapAsList(list, LicenseListRes.class);
|
List<LicenseListRes> licenseList = mapperFacade.mapAsList(list, LicenseListRes.class);
|
||||||
if (!CollectionUtil.isEmpty(licenseList)) {
|
if (!CollectionUtil.isEmpty(licenseList)) {
|
||||||
|
|||||||
+34
-50
@@ -8,6 +8,8 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
|||||||
import com.github.pagehelper.Page;
|
import com.github.pagehelper.Page;
|
||||||
import com.github.pagehelper.PageHelper;
|
import com.github.pagehelper.PageHelper;
|
||||||
import com.google.common.collect.Sets;
|
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.db.DBInitialize;
|
||||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||||
@@ -31,14 +33,10 @@ import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
|
|||||||
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
|
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
|
||||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
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.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||||
import org.noear.solon.annotation.Component;
|
import org.noear.solon.annotation.Component;
|
||||||
import org.noear.solon.annotation.Init;
|
import org.noear.solon.annotation.Init;
|
||||||
import org.noear.solon.annotation.Inject;
|
import org.noear.solon.annotation.Inject;
|
||||||
import org.noear.solon.core.Lifecycle;
|
|
||||||
import org.noear.solon.core.bean.LifecycleBean;
|
import org.noear.solon.core.bean.LifecycleBean;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -51,25 +49,25 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class PortMappingService implements LifecycleBean {
|
public class PortMappingService implements LifecycleBean {
|
||||||
@Inject
|
@Inject
|
||||||
private MapperFacade mapperFacade;
|
private MapperFacade mapperFacade;
|
||||||
@Db
|
@Db
|
||||||
private PortMappingMapper portMappingMapper;
|
private PortMappingMapper portMappingMapper;
|
||||||
@Db
|
@Db
|
||||||
private LicenseMapper licenseMapper;
|
private LicenseMapper licenseMapper;
|
||||||
@Db
|
@Db
|
||||||
private UserMapper userMapper;
|
private UserMapper userMapper;
|
||||||
@Db
|
@Db
|
||||||
private PortPoolMapper portPoolMapper;
|
private PortPoolMapper portPoolMapper;
|
||||||
@Inject
|
@Inject
|
||||||
private VisitorChannelService visitorChannelService;
|
private VisitorChannelService visitorChannelService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private PortPoolService portPoolService;
|
private PortPoolService portPoolService;
|
||||||
@Inject
|
@Inject
|
||||||
private ProxyConfig proxyConfig;
|
private ProxyConfig proxyConfig;
|
||||||
@Inject
|
@Inject
|
||||||
private DBInitialize dbInitialize;
|
private DBInitialize dbInitialize;
|
||||||
|
|
||||||
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
|
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
|
||||||
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||||
@@ -77,6 +75,13 @@ public class PortMappingService implements LifecycleBean {
|
|||||||
//描述字段为模糊查询,在应用层处理,否则sqlite不支持
|
//描述字段为模糊查询,在应用层处理,否则sqlite不支持
|
||||||
req.setDescription("%" + req.getDescription() + "%");
|
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<PortMappingDO> list = portMappingMapper.selectPortMappingByCondition(req);
|
||||||
List<PortMappingListRes> respList = mapperFacade.mapAsList(list, PortMappingListRes.class);
|
List<PortMappingListRes> respList = mapperFacade.mapAsList(list, PortMappingListRes.class);
|
||||||
if (CollectionUtils.isEmpty(list)) {
|
if (CollectionUtils.isEmpty(list)) {
|
||||||
@@ -113,10 +118,7 @@ public class PortMappingService implements LifecycleBean {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
//sorted [userId asc] [licenseId asc] [createTime asc]
|
//sorted [userId asc] [licenseId asc] [createTime asc]
|
||||||
respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId)
|
respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId).thenComparing(PortMappingListRes::getLicenseId).thenComparing(PortMappingListRes::getCreateTime)).collect(Collectors.toList());
|
||||||
.thenComparing(PortMappingListRes::getLicenseId)
|
|
||||||
.thenComparing(PortMappingListRes::getCreateTime))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,9 +151,7 @@ public class PortMappingService implements LifecycleBean {
|
|||||||
// 更新VisitorChannel
|
// 更新VisitorChannel
|
||||||
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
|
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
|
||||||
// 更新域名映射
|
// 更新域名映射
|
||||||
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) &&
|
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||||
StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) &&
|
|
||||||
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
|
||||||
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
|
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
|
||||||
}
|
}
|
||||||
return new PortMappingCreateRes();
|
return new PortMappingCreateRes();
|
||||||
@@ -188,14 +188,11 @@ public class PortMappingService implements LifecycleBean {
|
|||||||
// 更新VisitorChannel
|
// 更新VisitorChannel
|
||||||
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
|
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
|
||||||
// 删除老的域名映射
|
// 删除老的域名映射
|
||||||
if (NetworkProtocolEnum.isHttp(oldPortMappingDO.getProtocal()) &&
|
if (NetworkProtocolEnum.isHttp(oldPortMappingDO.getProtocal()) && StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) {
|
||||||
StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) {
|
|
||||||
ProxyUtil.removeSubdomainToServerPort(oldPortMappingDO.getSubdomain());
|
ProxyUtil.removeSubdomainToServerPort(oldPortMappingDO.getSubdomain());
|
||||||
}
|
}
|
||||||
// 更新域名映射
|
// 更新域名映射
|
||||||
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) &&
|
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||||
StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) &&
|
|
||||||
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
|
||||||
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
|
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
|
||||||
}
|
}
|
||||||
return new PortMappingUpdateRes();
|
return new PortMappingUpdateRes();
|
||||||
@@ -206,16 +203,7 @@ public class PortMappingService implements LifecycleBean {
|
|||||||
if (null == portMappingDO) {
|
if (null == portMappingDO) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
PortMappingDetailRes res = new PortMappingDetailRes()
|
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());
|
||||||
.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());
|
LicenseDO license = licenseMapper.findById(portMappingDO.getLicenseId());
|
||||||
if (null != license) {
|
if (null != license) {
|
||||||
@@ -268,8 +256,7 @@ public class PortMappingService implements LifecycleBean {
|
|||||||
// 更新VisitorChannel
|
// 更新VisitorChannel
|
||||||
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
|
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
|
||||||
// 更新域名映射
|
// 更新域名映射
|
||||||
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) &&
|
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||||
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
|
||||||
ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain());
|
ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,10 +282,7 @@ public class PortMappingService implements LifecycleBean {
|
|||||||
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
|
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>()
|
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()).isNotNull(PortMappingDO::getSubdomain));
|
||||||
.eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc())
|
|
||||||
.isNotNull(PortMappingDO::getSubdomain)
|
|
||||||
);
|
|
||||||
if (CollectionUtil.isEmpty(portMappingDOList)) {
|
if (CollectionUtil.isEmpty(portMappingDOList)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
@@ -179,4 +179,15 @@ public class PortPoolService {
|
|||||||
LicenseDO licenseDO = licenseMapper.queryById(req.getLicenseId());
|
LicenseDO licenseDO = licenseMapper.queryById(req.getLicenseId());
|
||||||
return portPoolMapper.getAvailablePortList(req.getLicenseId(), licenseDO.getUserId());
|
return portPoolMapper.getAvailablePortList(req.getLicenseId(), licenseDO.getUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteBatch(List<Integer> ids) {
|
||||||
|
List<PortPoolDO> portPoolDOList = portPoolMapper.selectBatchIds(ids);
|
||||||
|
ParamCheckUtil.checkNotNull(portPoolDOList, ExceptionConstant.PORT_NOT_EXIST);
|
||||||
|
|
||||||
|
portPoolMapper.deleteBatchIds(ids);
|
||||||
|
// 更新visitorChannel
|
||||||
|
portPoolDOList.stream().forEach(portPoolDO -> {
|
||||||
|
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
server:
|
server:
|
||||||
|
# 服务端web端口,用于支持HTTP接口,管理后台页面访问
|
||||||
port: ${WEB_PORT:8888}
|
port: ${WEB_PORT:8888}
|
||||||
|
context-path: /neutrino-proxy-server
|
||||||
|
|
||||||
neutrino:
|
neutrino:
|
||||||
proxy:
|
proxy:
|
||||||
@@ -12,30 +14,50 @@ neutrino:
|
|||||||
read-idle-time: 30
|
read-idle-time: 30
|
||||||
write-idle-time: 5
|
write-idle-time: 5
|
||||||
all-idle-time-seconds: 0
|
all-idle-time-seconds: 0
|
||||||
|
logger:
|
||||||
|
# 日志级别
|
||||||
|
level: ${LOG_LEVEL:info}
|
||||||
|
# 隧道相关配置-用于维持服务端与客户端的通信
|
||||||
tunnel:
|
tunnel:
|
||||||
|
# 线程池相关配置,用于技术调优,可忽略
|
||||||
boss-thread-count: 2
|
boss-thread-count: 2
|
||||||
work-thread-count: 10
|
work-thread-count: 10
|
||||||
|
# 隧道非SSL端口
|
||||||
port: ${OPEN_PORT:9000}
|
port: ${OPEN_PORT:9000}
|
||||||
|
# 隧道SSL端口
|
||||||
ssl-port: ${SSL_PORT:9002}
|
ssl-port: ${SSL_PORT:9002}
|
||||||
|
# 隧道SSL证书配置
|
||||||
key-store-password: ${STORE_PASS:123456}
|
key-store-password: ${STORE_PASS:123456}
|
||||||
key-manager-password: ${MGR_PASS:123456}
|
key-manager-password: ${MGR_PASS:123456}
|
||||||
jks-path: ${JKS_PATH:classpath:/test.jks}
|
jks-path: ${JKS_PATH:classpath:/test.jks}
|
||||||
|
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||||
transfer-log-enable: ${TUNNEL_LOG:false}
|
transfer-log-enable: ${TUNNEL_LOG:false}
|
||||||
|
# 是否开启心跳日志
|
||||||
|
heartbeat-log-enable: ${HEARTBEAT_LOG:false}
|
||||||
server:
|
server:
|
||||||
|
# 线程池相关配置,用于技术调优,可忽略
|
||||||
boss-thread-count: 5
|
boss-thread-count: 5
|
||||||
work-thread-count: 20
|
work-thread-count: 20
|
||||||
|
# http代理端口,默认80
|
||||||
http-proxy-port: ${HTTP_PROXY_PORT:80}
|
http-proxy-port: ${HTTP_PROXY_PORT:80}
|
||||||
|
# https代理端口,默认443 (需要配置域名、证书)
|
||||||
https-proxy-port: ${HTTPS_PROXY_PORT:443}
|
https-proxy-port: ${HTTPS_PROXY_PORT:443}
|
||||||
# 如果不配置,则不支持域名映射
|
# 如果不配置,则不支持域名映射
|
||||||
domain-name: ${DOMAIN_NAME:}
|
domain-name: ${DOMAIN_NAME:}
|
||||||
|
# https证书配置
|
||||||
key-store-password: ${HTTPS_STORE_PASS:}
|
key-store-password: ${HTTPS_STORE_PASS:}
|
||||||
jks-path: ${HTTPS_JKS_PATH:}
|
jks-path: ${HTTPS_JKS_PATH:}
|
||||||
|
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
|
||||||
transfer-log-enable: ${SERVER_LOG:false}
|
transfer-log-enable: ${SERVER_LOG:false}
|
||||||
data:
|
data:
|
||||||
db:
|
db:
|
||||||
|
# 数据库类型,目前支持sqlite、mysql、mariadb
|
||||||
type: ${DB_TYPE:sqlite}
|
type: ${DB_TYPE:sqlite}
|
||||||
|
# 数据库连接URL
|
||||||
url: ${DB_URL:jdbc:sqlite:data.db}
|
url: ${DB_URL:jdbc:sqlite:data.db}
|
||||||
|
# 数据库用户名
|
||||||
username: ${DB_USER:}
|
username: ${DB_USER:}
|
||||||
|
# 数据库密码
|
||||||
password: ${DB_PASSWORD:}
|
password: ${DB_PASSWORD:}
|
||||||
|
|
||||||
#添加MIME印射(如果有需要?)
|
#添加MIME印射(如果有需要?)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ article: false
|
|||||||
|
|
||||||
## 1、 部署服务端
|
## 1、 部署服务端
|
||||||
### 1.1、 Docker一键部署
|
### 1.1、 Docker一键部署
|
||||||
> 当前最新版本为1.8.2,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.2` 指定版本安装,推荐使用`latest`直接安装最新版。
|
> 当前最新版本为1.8.5,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.5` 指定版本安装,推荐使用`latest`直接安装最新版。
|
||||||
|
|
||||||
#### 使用默认sqlite数据库
|
#### 使用默认sqlite数据库
|
||||||
```shell
|
```shell
|
||||||
@@ -87,6 +87,11 @@ neutrino:
|
|||||||
|
|
||||||
访问成功,至此首次完整的内网穿透体验完成。开源不易,请速至[Gitee仓库](https://gitee.com/dromara/neutrino-proxy)一键三连🤝
|
访问成功,至此首次完整的内网穿透体验完成。开源不易,请速至[Gitee仓库](https://gitee.com/dromara/neutrino-proxy)一键三连🤝
|
||||||
|
|
||||||
|
## 5、客户端Docker部署
|
||||||
|
> 因为一些原因,官方不推荐客户端使用Docker方式部署,但有不少朋友希望采用这种方式,因此从1.8.5版本开始维护了客户端镜像,版本号同服务端,也可使用`latest`直接安装最新版。
|
||||||
|
- 客户端镜像地址:registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy-client:latest
|
||||||
|
- 注意:客户端采用docker部署时,服务端端口映射客户端ip请不要配置127.0.0.1应该配置为容器访问宿主机的ip或者hostname,具体请自行百度
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
## 4.开发&调试
|
## 4.开发&调试
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ server:
|
|||||||
|
|
||||||
neutrino:
|
neutrino:
|
||||||
proxy:
|
proxy:
|
||||||
|
logger:
|
||||||
|
# 日志级别
|
||||||
|
level: ${LOG_LEVEL:info}
|
||||||
# 隧道相关配置-用于维持服务端与客户端的通信
|
# 隧道相关配置-用于维持服务端与客户端的通信
|
||||||
tunnel:
|
tunnel:
|
||||||
# 线程池相关配置,用于技术调优,可忽略
|
# 线程池相关配置,用于技术调优,可忽略
|
||||||
@@ -32,7 +35,8 @@ neutrino:
|
|||||||
key-store-password: ${STORE_PASS:123456}
|
key-store-password: ${STORE_PASS:123456}
|
||||||
key-manager-password: ${MGR_PASS:123456}
|
key-manager-password: ${MGR_PASS:123456}
|
||||||
jks-path: ${JKS_PATH:classpath:/test.jks}
|
jks-path: ${JKS_PATH:classpath:/test.jks}
|
||||||
# 代理服务相关配置
|
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||||
|
transfer-log-enable: ${TUNNEL_LOG:false}
|
||||||
server:
|
server:
|
||||||
# 线程池相关配置,用于技术调优,可忽略
|
# 线程池相关配置,用于技术调优,可忽略
|
||||||
boss-thread-count: 5
|
boss-thread-count: 5
|
||||||
@@ -46,9 +50,11 @@ neutrino:
|
|||||||
# https证书配置
|
# https证书配置
|
||||||
key-store-password: ${HTTPS_STORE_PASS:}
|
key-store-password: ${HTTPS_STORE_PASS:}
|
||||||
jks-path: ${HTTPS_JKS_PATH:}
|
jks-path: ${HTTPS_JKS_PATH:}
|
||||||
|
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
|
||||||
|
transfer-log-enable: ${SERVER_LOG:false}
|
||||||
data:
|
data:
|
||||||
db:
|
db:
|
||||||
# 数据库类型,目前支持sqlite、mysql两种
|
# 数据库类型,目前支持sqlite、mysql、mariadb
|
||||||
type: ${DB_TYPE:sqlite}
|
type: ${DB_TYPE:sqlite}
|
||||||
# 数据库连接URL
|
# 数据库连接URL
|
||||||
url: ${DB_URL:jdbc:sqlite:data.db}
|
url: ${DB_URL:jdbc:sqlite:data.db}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
```
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: 常见问题汇总
|
||||||
|
date: 2023-07-01 13:35:00
|
||||||
|
permalink: /pages/a00001/
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1、客户端启动后连不上服务端,一直在重连
|
||||||
|
### 1.1、启动日志包含`not found license-key config`相关字样
|
||||||
|
出现这种情况,说明客户端没获取到配置的license,可按如下检查:
|
||||||
|
- 客户端是否有配置app.yml,app.yml中是否有配置`license-key`
|
||||||
|
- 客户端启动参数是否指定app.yml配置,若未指定客户端不会加载任何jar外部的配置文件
|
||||||
|
- 客户端启动参数指定的配置文件路径是否正确?如:`java -jar neutrino-proxy-client.jar config=app.yml`,代表`app.yml`文件与jar在同级别目录下,如果不在同级别目录,请使用绝对/相对路径指定
|
||||||
|
- 除此之外license还可以用以下方法直接在启动参数中指定:
|
||||||
|
- `java -jar -DLICENSE_KEY=xxxx -DSERVER_IP=x.x.x.x -DSERVER_PORT=9000 -DSSL_ENABLE=false neutrino-proxy-client.jar`
|
||||||
|
- `java -jar neutrino-proxy-client.jar serverIp=x.x.x.x serverPort=9000 sslEnable=false licenseKey=xxxx`
|
||||||
|
|
||||||
|
### 1.2、配置文件确定都已经配置,且启动参数正确指定了,但是没有出现`认证成功日志`
|
||||||
|
- 确认客户端配置的`server-ip`,必须是服务端所在机器的ip,且保证客户端所在机器能正常ping通
|
||||||
|
- 确认客户端配置的`server-port`与`ssl-enable`,默认情况应配置为9000、false或9002、true,除非服务端app.yml有改动隧道对外端口
|
||||||
|
- 确认服务端对外的9000、9002端口是否已经放开
|
||||||
|
- 服务端如果采用docker部署,需要确保容器的9000、9002端口映射到宿主机
|
||||||
|
|
||||||
|
## 2、关于中微子代理涉及的2类SSL证书
|
||||||
|
### 2.1、HTTPS证书
|
||||||
|
该证书用于服务端支持HTTPS,若需要在服务端`app.yml`中配置即可
|
||||||
|
|
||||||
|
### 2.2、隧道SSL证书
|
||||||
|
该证书用于对客户端-服务端之间的数据通信进行加密,因此客户端/服务端均需要配置该证书。可参考如下命令生成:
|
||||||
|
```
|
||||||
|
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
|
||||||
|
```
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: 2023年
|
||||||
|
date: 2023-06-09 21:33:17
|
||||||
|
permalink: /pages/cded59/
|
||||||
|
---
|
||||||
|
|
||||||
|
| 日期 | 渠道 | 金额 |昵称| 备注 |
|
||||||
|
|:-----------|:---|:---|:-|:-----------------|
|
||||||
|
| 2023-06-29 |微信红包| 16.8 |迟迟🌱| 加油 |
|
||||||
|
| 2023-06-27 |微信红包| 30 |Arno| |
|
||||||
|
| 2023-06-25 |Gitee捐助| 10 |zhujue888| 感谢您的开源项目! |
|
||||||
|
| 2023-06-16 |微信红包| 50 |小跟班| |
|
||||||
|
| 2023-06-10 |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 |阳光很暖| 感谢您的开源项目! |
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.noear</groupId>
|
<groupId>org.noear</groupId>
|
||||||
<artifactId>solon-parent</artifactId>
|
<artifactId>solon-parent</artifactId>
|
||||||
<version>2.2.8</version>
|
<version>2.3.6</version>
|
||||||
<relativePath />
|
<relativePath />
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -41,4 +41,6 @@ npm run build:$env
|
|||||||
#拷贝
|
#拷贝
|
||||||
cd ..
|
cd ..
|
||||||
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir/
|
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir/
|
||||||
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
|
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
|
||||||
|
cd $serverDeployDir
|
||||||
|
zip -r neutrino-proxy-admin.zip "neutrino-proxy-admin/"
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
- [ ] 支持调整服务端端口、证书
|
- [ ] 支持调整服务端端口、证书
|
||||||
- [ ] 服务端版本、协议版本、客户端版本管理
|
- [ ] 服务端版本、协议版本、客户端版本管理
|
||||||
- [ ] 服务端日志
|
- [ ] 服务端日志
|
||||||
|
- [ ] 支持持管理后台动态调整日志级别。
|
||||||
|
- [ ] 动态调整转发、报文、心跳输出
|
||||||
- client+计划启动
|
- client+计划启动
|
||||||
- [ ] 安卓客户端
|
- [ ] 安卓客户端
|
||||||
- [ ] 基于electron-egg的多平台客户端
|
- [ ] 基于electron-egg的多平台客户端
|
||||||
|
|||||||
Reference in New Issue
Block a user