增加域名映射支持

This commit is contained in:
aoshiguchen
2023-04-02 23:35:21 +08:00
parent b07b7567e2
commit 22d9bb9d39
35 changed files with 900 additions and 81 deletions
+9
View File
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function domainNameBindInfo(query) {
return request({
url: '/domain-name/bind-info',
method: 'get',
params: query
})
}
+17
View File
@@ -0,0 +1,17 @@
import request from '@/utils/request'
export function fetchList(query) {
return request({
url: '/protocal/page',
method: 'get',
params: query
})
}
export function protocalList(query) {
return request({
url: '/protocal/list',
method: 'get',
params: query
})
}
+5 -1
View File
@@ -49,6 +49,7 @@ export default {
system: '系统管理',
portPool: '端口池管理',
portGroup: '端口分组管理',
protocal: '协议管理',
proxy: '代理配置',
license: 'License管理',
portMapping: '端口映射',
@@ -162,7 +163,10 @@ export default {
resetKey: '重置Key',
upFlow: '上行流量',
downFlow: '下行流量',
totalFlow: '总流量'
totalFlow: '总流量',
protocalName: '协议',
supportStatus: '支持状态',
domainName: '域名'
},
button: {
lookOver: '查看'
+1
View File
@@ -77,6 +77,7 @@ export const asyncRouterMap = [
{ path: 'user', component: _import('system/user'), name: 'user', meta: { title: 'user' }},
{ path: 'portGroup', component: _import('system/portGroup'), name: 'portGroup', meta: { title: 'portGroup' }},
{ path: 'portPool', component: _import('system/portPool'), name: 'portPool', meta: { title: 'portPool' }},
{ path: 'protocal', component: _import('system/protocal'), name: 'protocal', meta: { title: 'protocal' }},
{ path: 'jobManager', component: _import('system/jobManager'), name: 'jobManager', meta: { title: 'jobManager' }}
]
},
@@ -7,6 +7,9 @@
<el-select v-model="listQuery.licenseId" placeholder="请选择license" clearable style="margin-right:10px">
<el-option v-for="item in licenseList" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.protocal" placeholder="请选择协议" clearable style="margin-right:10px">
<el-option v-for="item in protocalList" :key="item.name" :label="item.name" :value="item.name" :disabled="!item.enable"/>
</el-select>
<el-input v-model="listQuery.serverPort" type="text" style="width:150px;margin-right:10px" class="filter-item"
placeholder="请输入服务端端口" :maxlength="5" show-word-limit />
<el-select v-model="listQuery.isOnline" placeholder="请选择在线状态" clearable style="width:145px;margin-right:10px">
@@ -33,17 +36,27 @@
<span>{{ scope.row.userName }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.licenseName')" width="150">
<el-table-column align="center" :label="$t('table.licenseName')" width="130">
<template slot-scope="scope">
<span>{{ scope.row.licenseName }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.serverPort')" width="200">
<el-table-column align="center" :label="$t('table.protocalName')" width="100">
<template slot-scope="scope">
<span>{{ scope.row.protocal }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.domainName')" width="200">
<template slot-scope="scope">
<span>{{ scope.row.domain }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.serverPort')" width="80">
<template slot-scope="scope">
<span>{{ scope.row.serverPort }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.proxyClient')" width="200">
<el-table-column align="center" :label="$t('table.proxyClient')" width="120">
<template slot-scope="scope">
<span>{{ scope.row.clientIp }}:{{ scope.row.clientPort }}</span>
</template>
@@ -113,6 +126,12 @@
style="width: 280px"
/>-->
</el-form-item>
<el-form-item :label="$t('协议')" prop="protocal">
<el-select style="width: 280px;" class="filter-item" v-model="temp.protocal" placeholder="请选择">
<el-option v-for="item in protocalList" :key="item.name" :label="item.name" :value="item.name" :disabled="!item.enable">
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('服务端端口')" prop="serverPort">
<el-select style="width: 280px;" class="filter-item" v-model="temp.serverPort" placeholder="请选择">
<el-option v-for="item in serverPortList" :key="item.port" :label="item.port" :value="item.port">
@@ -125,6 +144,11 @@
<el-form-item :label="$t('客户端端口')" prop="clientPort">
<el-input v-model="temp.clientPort"></el-input>
</el-form-item>
<el-form-item :label="$t('域名')" prop="subdomain" v-if="temp.protocal === 'HTTP'">
<el-input v-model="temp.subdomain">
<template slot="append">.{{domainName}}</template>
</el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="
@@ -152,7 +176,9 @@
import { fetchList, createUserPortMapping, updateUserPortMapping, updateEnableStatus, deletePortMapping } from '@/api/portMapping'
import { portPoolList, availablePortList } from '@/api/portPool'
import { licenseList, licenseAuthList } from '@/api/license'
import { protocalList } from '@/api/protocal'
import { userList } from '@/api/user'
import { domainNameBindInfo } from '@/api/domain'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
@@ -205,8 +231,10 @@ export default {
statusOptions: ['published', 'draft', 'deleted'],
userList: [],
licenseList: [],
protocalList: [],
licenseAuthList: [],
serverPortList: [],
domainName: '',
showReviewer: false,
temp: {
id: undefined,
@@ -214,7 +242,8 @@ export default {
licenseName: undefined,
serverPort: undefined,
clientIp: undefined,
clientPort: undefined
clientPort: undefined,
protocal: undefined
},
selectObj: {
statusOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 2 }],
@@ -268,9 +297,11 @@ export default {
}
},
created() {
this.getDomainNameBindInfo()
this.getDataList()
this.getLicenseList()
this.getLicenseAuthList()
this.getProtocalList()
},
methods: {
getList() {
@@ -292,6 +323,11 @@ export default {
this.getList()
})
},
getDomainNameBindInfo() {
domainNameBindInfo().then(response => {
this.domainName = response.data.data
})
},
getPortPoolList() {
portPoolList().then(response => {
this.serverPortList = response.data.data
@@ -312,6 +348,11 @@ export default {
this.licenseList = response.data.data
})
},
getProtocalList() {
protocalList().then(response => {
this.protocalList = response.data.data
})
},
getLicenseAuthList() {
licenseAuthList().then(response => {
this.licenseAuthList = response.data.data
@@ -0,0 +1,169 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">
{{ $t('table.search') }}
</el-button>
</div>
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit
highlight-current-row
style="width: 100%">
<el-table-column align="center" :label="$t('table.protocalName')" width="150">
<template slot-scope="scope">
<span>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column class-name="status-col" :label="$t('table.supportStatus')" width="150">
<template slot-scope="scope">
<el-tag :type="scope.row.enable | statusFilter">{{scope.row.enable | statusName}}</el-tag>
</template>
</el-table-column>
<el-table-column align="left" :label="$t('table.remark')" width="500">
<template slot-scope="scope">
<span>{{ scope.row.remark }}</span>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-pageInfo.sync="listQuery.current"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size"
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
</div>
</template>
<script>
import { fetchList } from '@/api/protocal'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
{ key: 'US', display_name: 'USA' },
{ key: 'JP', display_name: 'Japan' },
{ key: 'EU', display_name: 'Eurozone' }
]
// arr to obj ,such as { CN : "China", US : "USA" }
const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
acc[cur.key] = cur.display_name
return acc
}, {})
export default {
name: 'complexTable',
directives: {
waves
},
components: {
ButtonPopover
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: true,
listQuery: {
current: 1,
size: 10,
importance: undefined,
title: undefined,
type: undefined
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
statusOptions: ['published', 'draft', 'deleted'],
showReviewer: false,
possessorTypeList: [
{
'id': 1,
'name': '用户'
},
{
'id': 2,
'name': '通道'
}
],
temp: {
name: '',
possessorType: 1,
possessorId: 1
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: '编辑',
create: '新建'
},
dialogPvVisible: false,
pvData: [],
rules: {
port: [{ required: true, message: '端口必填', trigger: 'blur' }]
},
downloadLoading: false
}
},
filters: {
statusName(status) {
const statusMap = {
true: '支持',
false: '不支持'
}
return statusMap[status]
},
statusFilter(status) {
const statusMap = {
true: 'success',
false: 'danger'
}
return statusMap[status]
},
typeFilter(type) {
return calendarTypeKeyValue[type]
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
}
}
}
</script>
@@ -44,6 +44,7 @@ public class ProxyConfig {
private String jksPath;
private Integer bossThreadCount;
private Integer workThreadCount;
private String domainName;
}
}
@@ -55,6 +55,9 @@ public enum ExceptionConstant {
// 端口映射管理(14000)
PORT_MAPPING_NOT_EXIST(14000, "端口映射记录不存在"),
PORT_CANNOT_REPEAT_MAPPING(14001, "服务端口[{}]不能重复映射"),
AN_UNSUPPORTED_PROTOCOL(14002, "不支持的协议[{}]"),
PORT_MAPPING_SUBDONAME_CONNOT_REPEAT(14003, "子域名不能重复使用!"),
// 调度管理(15000)
JOB_INFO_NOT_EXIST(15000, "调度管理记录不存在"),
SYSTEM_ERROR(500, "系统异常"),
@@ -0,0 +1,28 @@
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Getter
@AllArgsConstructor
public enum NetworkProtocolEnum {
TCP("TCP"),
UDP("UDP"),
HTTP("HTTP"),
;
private String desc;
private static final Map<String, NetworkProtocolEnum> map = Stream.of(NetworkProtocolEnum.values()).collect(Collectors.toMap(NetworkProtocolEnum::getDesc, Function.identity()));
public static NetworkProtocolEnum of(String desc) {
return map.get(desc);
}
}
@@ -0,0 +1,27 @@
package org.dromara.neutrinoproxy.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
/**
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Slf4j
@Mapping("/domain-name")
@Controller
public class DomainNameController {
@Inject
private ProxyConfig proxyConfig;
@Get
@Mapping("/bind-info")
public String bindInfo () {
return proxyConfig.getServer().getDomainName();
}
}
@@ -2,6 +2,8 @@ package org.dromara.neutrinoproxy.server.controller;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
@@ -37,10 +39,17 @@ public class PortMappingController {
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
if (StringUtils.isBlank(req.getClientIp())) {
// 没传客户端ip,默认为127.0.0.1
req.setClientIp("127.0.0.1");
}
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
// 目前仅HTTP支持绑定域名
req.setSubdomain("");
}
return portMappingService.create(req);
}
@@ -49,6 +58,20 @@ public class PortMappingController {
@Mapping("/update")
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
if (StringUtils.isBlank(req.getClientIp())) {
// 没传客户端ip,默认为127.0.0.1
req.setClientIp("127.0.0.1");
}
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
// 目前仅HTTP支持绑定域名
req.setSubdomain("");
}
return portMappingService.update(req);
}
@@ -0,0 +1,40 @@
package org.dromara.neutrinoproxy.server.controller;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
import org.dromara.neutrinoproxy.server.service.ProtocalService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
import java.util.List;
/**
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Mapping("/protocal")
@Controller
public class ProtocalController {
@Inject
private ProtocalService protocalService;
@Get
@Mapping("/page")
public PageInfo<ProtocalListRes> page(PageQuery pageQuery) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
List<ProtocalListRes> list = protocalService.list();
return PageInfo.of(protocalService.list(), (long)list.size(), pageQuery.getCurrent(), pageQuery.getSize());
}
@Get
@Mapping("/list")
public List<ProtocalListRes> list() {
return protocalService.list();
}
}
@@ -34,6 +34,14 @@ public class PortMappingCreateReq {
* licenseId
*/
private Integer licenseId;
/**
* 协议
*/
private String protocal;
/**
* 子域名
*/
private String subdomain;
/**
* 服务端端口
*/
@@ -36,6 +36,10 @@ public class PortMappingListReq {
* 用户ID
*/
private Integer userId;
/**
* 协议
*/
private String protocal;
/**
* licenseId
*/
@@ -38,6 +38,14 @@ public class PortMappingUpdateReq {
* licenseId
*/
private Integer licenseId;
/**
* 协议
*/
private String protocal;
/**
* 子域名
*/
private String subdomain;
/**
* 服务端端口
*/
@@ -39,6 +39,18 @@ public class PortMappingListRes {
* licenseId
*/
private Integer licenseId;
/**
* 协议
*/
private String protocal;
/**
* 子域名
*/
private String subdomain;
/**
* 域名
*/
private String domain;
/**
* license名称
*/
@@ -22,6 +22,7 @@
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
@@ -30,6 +31,7 @@ import java.util.Date;
* @author: aoshiguchen
* @date: 2022/8/7
*/
@Accessors(chain = true)
@Data
public class PortPoolListRes {
private Integer id;
@@ -0,0 +1,26 @@
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 协议列表响应
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Accessors(chain = true)
@Data
public class ProtocalListRes {
/**
* 协议名称
*/
private String name;
/**
* 启用状态
*/
private Boolean enable;
/**
* 备注
*/
private String remark;
}
@@ -42,6 +42,18 @@ public interface PortMappingMapper extends BaseMapper<PortMappingDO> {
);
}
/**
* 校验子域名是否重复
* @param subdomain
* @return
*/
default Boolean checkRepeatBySubdomain(String subdomain, Set<Integer> excludeIds) {
return this.selectCount(new LambdaQueryWrapper<PortMappingDO>()
.eq(PortMappingDO::getSubdomain, subdomain)
.notIn(!CollectionUtil.isEmpty(excludeIds), PortMappingDO::getId, excludeIds)
).intValue() > 0;
}
default List<PortMappingDO> findEnableListByLicenseId(Integer licenseId) {
return this.selectList(new LambdaQueryWrapper<PortMappingDO>()
.eq(PortMappingDO::getLicenseId, licenseId)
@@ -48,6 +48,14 @@ public class PortMappingDO {
* licenseId
*/
private Integer licenseId;
/**
* 协议
*/
private String protocal;
/**
* 子域名
*/
private String subdomain;
/**
* 服务端端口
*/
@@ -0,0 +1,193 @@
package org.dromara.neutrinoproxy.server.proxy.core;
import cn.hutool.core.util.StrUtil;
import com.sun.deploy.net.proxy.ProxyType;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.event.AppLoadEndEvent;
import org.noear.solon.core.event.EventListener;
import java.net.InetSocketAddress;
/**
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Slf4j
@Component
public class HttpProxy implements EventListener<AppLoadEndEvent> {
@Inject("serverBossGroup")
private NioEventLoopGroup serverBossGroup;
@Inject("serverWorkerGroup")
private NioEventLoopGroup serverWorkerGroup;
@Inject
private ProxyConfig proxyConfig;
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
log.info("no config domain name,nonsupport http proxy.");
return;
}
this.start();
}
private void start() {
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup)
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new VisitorChannelHandler());
}
});
bootstrap.bind("0.0.0.0", 80).sync();
log.info("Http代理服务启动成功!");
} catch (Exception e) {
log.error("http proxy start err!", e);
}
}
private class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
ctx.channel().close();
return;
}
String host = getHost(byteBuf);
if (StringUtils.isBlank(host)) {
ctx.channel().close();
return;
}
log.debug("HttpProxy host: {}", host);
if (!host.endsWith(proxyConfig.getServer().getDomainName())) {
ctx.channel().close();
return;
}
int index = host.lastIndexOf("." + proxyConfig.getServer().getDomainName());
String subdomain = host.substring(0, index);
// 根据域名拿到绑定的映射对应的cmdChannel
Integer serverPort = ProxyUtil.getServerPortBySubdomain(subdomain);
if (null == serverPort) {
ctx.channel().close();
return;
}
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(serverPort);
if (null == cmdChannel) {
ctx.channel().close();
return;
}
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(serverPort);
if (StringUtils.isBlank(lanInfo)) {
ctx.channel().close();
return;
}
String visitorId = ProxyUtil.newVisitorId();
Channel visitorChannel = ctx.channel();
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
ProxyAttachment proxyAttachment = new ProxyAttachment(ctx.channel(), bytes, (channel, buf) -> {
Channel proxyChannel = channel.attr(Constants.NEXT_CHANNEL).get();
if (null == proxyChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
return;
}
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
// 增加流量计数
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
});
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, serverPort);
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel visitorChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
// 用户连接断开,从控制连接中移除
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
// 删除代理附加对象
ProxyUtil.remoteProxyConnectAttachment(visitorId);
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null && proxyChannel.isActive()) {
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
proxyChannel.attr(Constants.LICENSE_ID).remove();
proxyChannel.attr(Constants.VISITOR_ID).remove();
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 通知客户端,用户连接已经断开
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
}
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
}
private String getHost(ByteBuf byteBuf) {
byte[] buf = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(buf);
byteBuf.resetReaderIndex();
String req = new String(buf);
String[] lines = req.split("\r\n");
String firstLine = lines[0];
if (!(firstLine.endsWith("HTTP/1.1") || firstLine.endsWith("HTTP/1.0"))) {
return null;
}
for (int i = 1; i < lines.length; i++) {
String line = lines[i];
if (!line.startsWith("Host: ")) {
continue;
}
// 域名
String domain = line.substring(6);
return domain;
}
return null;
}
}
}
@@ -1,8 +1,10 @@
package org.dromara.neutrinoproxy.server.proxy.core;
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;
@@ -14,44 +16,42 @@ import io.netty.channel.SimpleChannelInboundHandler;
import org.noear.solon.Solon;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicLong;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
private static AtomicLong visitorIdProducer = new AtomicLong(0);
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
log.error("VisitorChannel error", cause);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
// 通知代理客户端
Channel visitorChannel = ctx.channel();
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel == null) {
if (null == proxyChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
// 增加流量计数
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
return;
}
// 转发代理数据
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
// 增加流量计数
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
}
@Override
@@ -60,23 +60,26 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
if (null == cmdChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
String visitorId = newVisitorId();
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
if (StrUtil.isEmpty(lanInfo)) {
ctx.channel().close();
} else {
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort());
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
}
return;
}
// 根据代理服务端端口,获取被代理客户端局域网连接信息
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
if (StrUtil.isEmpty(lanInfo)) {
ctx.channel().close();
return;
}
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
String visitorId = ProxyUtil.newVisitorId();
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort());
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
super.channelActive(ctx);
}
@@ -84,8 +87,8 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel userChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
Channel visitorChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
@@ -95,10 +98,13 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
} else {
// 用户连接断开,从控制连接中移除
String userId = ProxyUtil.getVisitorIdByChannel(userChannel);
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, userId);
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
// 删除代理附加对象
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();
@@ -106,7 +112,7 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 通知客户端,用户连接已经断开
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(userId));
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
}
}
@@ -134,12 +140,4 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
super.channelWritabilityChanged(ctx);
}
/**
* 为访问者连接产生ID
*
* @return
*/
private static String newVisitorId() {
return String.valueOf(visitorIdProducer.incrementAndGet());
}
}
@@ -1,25 +1,3 @@
/**
* 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.server.proxy.domain;
import lombok.Data;
@@ -0,0 +1,29 @@
package org.dromara.neutrinoproxy.server.proxy.domain;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import java.util.function.BiConsumer;
/**
* 代理连接附件
* @author: aoshiguchen
* @date: 2023/4/2
*/
public class ProxyAttachment {
private Channel channel;
private byte[] bytes;
private BiConsumer<Channel, byte[]> executor;
public ProxyAttachment(Channel channel, byte[] bytes, BiConsumer<Channel, byte[]> executor) {
this.channel = channel;
this.bytes = bytes;
this.executor = executor;
}
public void execute() {
if (null != executor) {
this.executor.accept(channel, bytes);
}
}
}
@@ -7,6 +7,7 @@ import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.service.LicenseService;
import org.dromara.neutrinoproxy.server.service.UserService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
@@ -74,13 +75,20 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
}
Channel visitorChannel = ProxyUtil.getVisitorChannel(cmdChannel, visitorId);
if (visitorChannel != null) {
ctx.channel().attr(Constants.VISITOR_ID).set(visitorId);
ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId());
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
// 代理客户端与后端服务器连接成功,修改用户连接为可读状态
visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
if (null == visitorChannel) {
return;
}
ctx.channel().attr(Constants.VISITOR_ID).set(visitorId);
ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId());
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
// 代理客户端与后端服务器连接成功,修改用户连接为可读状态
visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 获取代理附加对象
ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(visitorId);
if (null != proxyAttachment) {
proxyAttachment.execute();
}
}
@@ -1,15 +1,19 @@
package org.dromara.neutrinoproxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.collect.Sets;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingCreateReq;
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingListReq;
@@ -28,6 +32,7 @@ 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.Inject;
import org.noear.solon.core.Lifecycle;
@@ -58,6 +63,8 @@ public class PortMappingService implements Lifecycle {
@Inject
private PortPoolService portPoolService;
@Inject
private ProxyConfig proxyConfig;
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
@@ -90,6 +97,9 @@ public class PortMappingService implements Lifecycle {
return;
}
item.setUserName(user.getName());
if (StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(item.getSubdomain())) {
item.setDomain(item.getSubdomain() + "." + proxyConfig.getServer().getDomainName());
}
});
//sorted [userId asc] [licenseId asc] [createTime asc]
respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId)
@@ -109,11 +119,13 @@ public class PortMappingService implements Lifecycle {
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), null), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), null), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
Date now = new Date();
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setLicenseId(req.getLicenseId());
portMappingDO.setProtocal(req.getProtocal());
portMappingDO.setSubdomain(req.getSubdomain());
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
@@ -124,6 +136,12 @@ public class PortMappingService implements Lifecycle {
portMappingMapper.insert(portMappingDO);
// 更新VisitorChannel
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
// 更新域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) &&
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
}
return new PortMappingCreateRes();
}
@@ -137,6 +155,7 @@ public class PortMappingService implements Lifecycle {
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
// 查询原端口映射
PortMappingDO oldPortMappingDO = portMappingMapper.findById(req.getId());
@@ -144,6 +163,8 @@ public class PortMappingService implements Lifecycle {
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setId(req.getId());
portMappingDO.setProtocal(req.getProtocal());
portMappingDO.setSubdomain(req.getSubdomain());
portMappingDO.setLicenseId(req.getLicenseId());
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
@@ -153,6 +174,12 @@ public class PortMappingService implements Lifecycle {
portMappingMapper.updateById(portMappingDO);
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
// 更新域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) &&
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
}
return new PortMappingUpdateRes();
}
@@ -222,6 +249,11 @@ public class PortMappingService implements Lifecycle {
// 更新VisitorChannel
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
// 更新域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain());
}
}
/**
@@ -239,6 +271,24 @@ public class PortMappingService implements Lifecycle {
@Override
public void start() throws Throwable {
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
// 未配置域名,则不需要处理域名映射逻辑
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
return;
}
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>()
.eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc())
.isNotNull(PortMappingDO::getSubdomain)
);
if (CollectionUtil.isEmpty(portMappingDOList)) {
return;
}
portMappingDOList.forEach(item -> {
if (StrUtil.isBlank(item.getSubdomain())) {
return;
}
ProxyUtil.setSubdomainToServerPort(item.getSubdomain(), item.getServerPort());
});
}
/**
@@ -0,0 +1,30 @@
package org.dromara.neutrinoproxy.server.service;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
import org.noear.solon.annotation.Component;
import java.util.List;
/**
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Slf4j
@Component
public class ProtocalService {
/**
* 获取协议列表
* @return
*/
public List<ProtocalListRes> list() {
return Lists.newArrayList(
new ProtocalListRes().setName("TCP").setEnable(Boolean.TRUE).setRemark("支持一切TCP之上的协议"),
new ProtocalListRes().setName("HTTP").setEnable(Boolean.TRUE).setRemark("支持绑定子域名,未绑定时等价于时使用TCP"),
new ProtocalListRes().setName("UDP").setEnable(Boolean.FALSE).setRemark("暂不支持")
);
}
}
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.Sets;
import org.dromara.neutrinoproxy.core.ChannelAttribute;
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import io.netty.channel.Channel;
@@ -12,6 +13,7 @@ import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -47,6 +49,18 @@ public class ProxyUtil {
* cmdChannelAttachInfo.getUserChannelMap() 读写锁
*/
private static final ReadWriteLock userChannelMapLock = new ReentrantReadWriteLock();
/**
* 访问者ID生成器
*/
private static AtomicLong visitorIdProducer = new AtomicLong(0);
/**
* 代理 - connect附加映射
*/
private static Map<String, ProxyAttachment> proxyConnectAttachmentMap = new HashMap<>();
/**
* 子域名 - 服务端端口映射
*/
private static Map<String, Integer> subdomainToServerPort = new HashMap<>();
/**
* 初始化代理信息
@@ -278,4 +292,65 @@ public class ProxyUtil {
}
return channel.attr(CHANNEL_ATTR_KEY).get().get("attachInfo");
}
/**
* 为访问者连接产生ID
*
* @return
*/
public static String newVisitorId() {
return String.valueOf(visitorIdProducer.incrementAndGet());
}
/**
* 添加代理附加对象
* @param visitorId
* @param proxyAttachment
*/
public static void addProxyConnectAttachment(String visitorId, ProxyAttachment proxyAttachment) {
proxyConnectAttachmentMap.put(visitorId, proxyAttachment);
}
/**
* 获取代理附加对象
* @param visitorId
* @return
*/
public static ProxyAttachment getProxyConnectAttachment(String visitorId) {
return proxyConnectAttachmentMap.get(visitorId);
}
/**
* 删除代理附加对象
* @param visitorId
*/
public static void remoteProxyConnectAttachment(String visitorId) {
proxyConnectAttachmentMap.remove(visitorId);
}
/**
* 设置子域名到服务端端口的映射
* @param subdomain
* @param serverPort
*/
public static void setSubdomainToServerPort(String subdomain, Integer serverPort) {
subdomainToServerPort.put(subdomain, serverPort);
}
/**
* 删除子域名到服务端端口的映射
* @param subdomain
*/
public static void removeSubdomainToServerPort(String subdomain) {
subdomainToServerPort.remove(subdomain);
}
/**
* 根据子域名获取外网端口
* @param subdomain
* @return
*/
public static Integer getServerPortBySubdomain(String subdomain) {
return subdomainToServerPort.get(subdomain);
}
}
@@ -20,6 +20,9 @@
<if test="req.enable != null">
AND pm.enable = #{req.enable}
</if>
<if test="req.protocal != null and req.protocal != ''">
AND pm.protocal = #{req.protocal}
</if>
</where>
order by pm.id asc
</select>
@@ -69,6 +69,8 @@ CREATE TABLE IF NOT EXISTS `license` (
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 '客户端端口',
@@ -1,5 +1,7 @@
#port_mapping
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1, now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, '127.0.0.1', 8080, 2, 1, now(), now());
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1, now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, '127.0.0.1', 3306, 2, 1, now(), now());
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1, now(), now());
@@ -0,0 +1,2 @@
ALTER TABLE port_mapping ADD `protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议';
ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)';
@@ -63,6 +63,8 @@ CREATE UNIQUE INDEX IF NOT EXISTS I_license_key ON `license` (`key` ASC);
CREATE TABLE IF NOT EXISTS `port_mapping` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`license_id` INTEGER(20) NOT NULL,
`protocal` VARCHAR(10) NOT NULL DEFAULT 'TCP',
`subdomain` VARCHAR(50) DEFAULT NULL,
`server_port` INTEGER NOT NULL,
`client_ip` VARCHAR(20) NOT NULL,
`client_port` INTEGER NOT NULL,
@@ -1,5 +1,7 @@
#port_mapping
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, '127.0.0.1', 8080, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, '127.0.0.1', 3306, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
@@ -0,0 +1,2 @@
ALTER TABLE port_mapping ADD `protocal` varchar(10) NOT NULL DEFAULT 'TCP';
ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL;