mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-11 03:36:37 +08:00
修复隧道添加问题
This commit is contained in:
@@ -13,6 +13,51 @@
|
|||||||
- 提供灵活的转发策略配置,适用于多种网络代理场景
|
- 提供灵活的转发策略配置,适用于多种网络代理场景
|
||||||
|
|
||||||
---
|
---
|
||||||
|
# 流量统计算法
|
||||||
|
|
||||||
|
## 流量流向定义
|
||||||
|
|
||||||
|
```
|
||||||
|
用户端 ←→ 转发服务器 ←→ 目标服务器/出口节点
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据流向说明
|
||||||
|
|
||||||
|
| 流向 | 描述 | 计费类型 |
|
||||||
|
|-----|-----|----------|
|
||||||
|
| 用户 → 转发服务器 | 用户发送到服务器的数据包 | 入站流量 |
|
||||||
|
| 转发服务器 → 目标服务器 | 服务器转发到目标的数据包 | 出站流量 |
|
||||||
|
| 目标服务器 → 转发服务器 | 目标服务器返回的数据包 | 入站流量 |
|
||||||
|
| 转发服务器 → 用户 | 服务器返回给用户的数据包 | 出站流量 |
|
||||||
|
|
||||||
|
## 计费模式
|
||||||
|
|
||||||
|
### 🔄 双向计费
|
||||||
|
统计所有方向的流量,包括:
|
||||||
|
- ✅ 用户 → 转发服务器
|
||||||
|
- ✅ 转发服务器 → 目标服务器
|
||||||
|
- ✅ 目标服务器 → 转发服务器
|
||||||
|
- ✅ 转发服务器 → 用户
|
||||||
|
|
||||||
|
**计费公式**:`总流量 = 入站流量 + 出站流量`
|
||||||
|
|
||||||
|
### ➡️ 单向计费
|
||||||
|
仅统计有效的转发流量,包括:
|
||||||
|
- ❌ 用户 → 转发服务器 (不计费)
|
||||||
|
- ✅ 转发服务器 → 目标服务器 (计费)
|
||||||
|
- ❌ 目标服务器 → 转发服务器 (不计费)
|
||||||
|
- ✅ 转发服务器 → 用户 (计费)
|
||||||
|
|
||||||
|
**计费公式**:`总流量 = 转发出站流量`
|
||||||
|
|
||||||
|
## 使用场景
|
||||||
|
|
||||||
|
- **双向计费**:适用于需要精确计量所有网络资源消耗的场景
|
||||||
|
- **单向计费**:适用于用户友好的计费模式,只对实际的数据转发服务收费
|
||||||
|
|
||||||
|
> 💡 **提示**:可以在创建隧道时选择不同的流量计算方式,灵活适配不同的业务需求
|
||||||
|
---
|
||||||
|
|
||||||
## 部署流程
|
## 部署流程
|
||||||
|
|
||||||
### 源码编译部署
|
### 源码编译部署
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -36,7 +36,7 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
|||||||
// ========== 常量定义 ==========
|
// ========== 常量定义 ==========
|
||||||
|
|
||||||
/** 节点默认状态:启用 */
|
/** 节点默认状态:启用 */
|
||||||
private static final int NODE_STATUS_ACTIVE = 1;
|
private static final int NODE_STATUS_ACTIVE = 0;
|
||||||
|
|
||||||
/** 成功响应消息 */
|
/** 成功响应消息 */
|
||||||
private static final String SUCCESS_CREATE_MSG = "节点创建成功";
|
private static final String SUCCESS_CREATE_MSG = "节点创建成功";
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
private static final String ERROR_IN_PORT_RANGE_INVALID = "入口端口开始不能大于结束端口";
|
private static final String ERROR_IN_PORT_RANGE_INVALID = "入口端口开始不能大于结束端口";
|
||||||
private static final String ERROR_OUT_PORT_RANGE_INVALID = "出口端口开始不能大于结束端口";
|
private static final String ERROR_OUT_PORT_RANGE_INVALID = "出口端口开始不能大于结束端口";
|
||||||
private static final String ERROR_NO_AVAILABLE_TUNNELS = "暂无可用隧道";
|
private static final String ERROR_NO_AVAILABLE_TUNNELS = "暂无可用隧道";
|
||||||
|
private static final String ERROR_IN_NODE_IP_INVALID = "入口节点IP地址无效,请先安装节点端的gost(支持IPv4和IPv6)";
|
||||||
|
private static final String ERROR_OUT_NODE_IP_INVALID = "出口节点IP地址无效,请先安装节点端的gost(支持IPv4和IPv6)";
|
||||||
|
|
||||||
/** 使用检查相关消息 */
|
/** 使用检查相关消息 */
|
||||||
private static final String ERROR_FORWARDS_IN_USE = "该隧道还有 %d 个转发在使用,请先删除相关转发";
|
private static final String ERROR_FORWARDS_IN_USE = "该隧道还有 %d 个转发在使用,请先删除相关转发";
|
||||||
@@ -221,6 +223,11 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
return NodeValidationResult.error(ERROR_IN_NODE_NOT_FOUND);
|
return NodeValidationResult.error(ERROR_IN_NODE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证入口节点IP地址
|
||||||
|
if (!isValidNodeIp(inNode.getIp())) {
|
||||||
|
return NodeValidationResult.error(ERROR_IN_NODE_IP_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
// 验证入口端口范围
|
// 验证入口端口范围
|
||||||
if (tunnelDto.getInPortSta() > tunnelDto.getInPortEnd()) {
|
if (tunnelDto.getInPortSta() > tunnelDto.getInPortEnd()) {
|
||||||
return NodeValidationResult.error(ERROR_IN_PORT_RANGE_INVALID);
|
return NodeValidationResult.error(ERROR_IN_PORT_RANGE_INVALID);
|
||||||
@@ -306,6 +313,11 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
return R.err(ERROR_OUT_NODE_NOT_FOUND);
|
return R.err(ERROR_OUT_NODE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证出口节点IP地址
|
||||||
|
if (!isValidNodeIp(outNode.getIp())) {
|
||||||
|
return R.err(ERROR_OUT_NODE_IP_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
// 验证出口端口参数
|
// 验证出口端口参数
|
||||||
if (tunnelDto.getOutIpSta() == null || tunnelDto.getOutIpEnd() == null) {
|
if (tunnelDto.getOutIpSta() == null || tunnelDto.getOutIpEnd() == null) {
|
||||||
return R.err(ERROR_OUT_PORT_REQUIRED);
|
return R.err(ERROR_OUT_PORT_REQUIRED);
|
||||||
@@ -333,6 +345,115 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
tunnel.setUpdatedTime(currentTime);
|
tunnel.setUpdatedTime(currentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证节点IP地址是否有效
|
||||||
|
* 支持IPv4和IPv6格式
|
||||||
|
*
|
||||||
|
* @param ip IP地址字符串
|
||||||
|
* @return IP地址是否有效
|
||||||
|
*/
|
||||||
|
private boolean isValidNodeIp(String ip) {
|
||||||
|
if (StrUtil.isBlank(ip)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证IPv4格式
|
||||||
|
if (isValidIpv4(ip)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证IPv6格式
|
||||||
|
return isValidIpv6(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证IPv4地址格式
|
||||||
|
*
|
||||||
|
* @param ip IP地址字符串
|
||||||
|
* @return 是否为有效的IPv4地址
|
||||||
|
*/
|
||||||
|
private boolean isValidIpv4(String ip) {
|
||||||
|
if (StrUtil.isBlank(ip)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = ip.split("\\.");
|
||||||
|
if (parts.length != 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (String part : parts) {
|
||||||
|
int num = Integer.parseInt(part);
|
||||||
|
if (num < 0 || num > 255) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证IPv6地址格式
|
||||||
|
*
|
||||||
|
* @param ip IP地址字符串
|
||||||
|
* @return 是否为有效的IPv6地址
|
||||||
|
*/
|
||||||
|
private boolean isValidIpv6(String ip) {
|
||||||
|
if (StrUtil.isBlank(ip)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简化的IPv6验证,支持标准格式和压缩格式
|
||||||
|
try {
|
||||||
|
// 移除可能的端口号
|
||||||
|
if (ip.startsWith("[") && ip.contains("]:")) {
|
||||||
|
ip = ip.substring(1, ip.indexOf("]:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含非法字符
|
||||||
|
if (!ip.matches("^[0-9a-fA-F:]+$")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理双冒号压缩格式
|
||||||
|
if (ip.contains("::")) {
|
||||||
|
if (ip.indexOf("::") != ip.lastIndexOf("::")) {
|
||||||
|
// 不能有多个双冒号
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 展开双冒号
|
||||||
|
String[] parts = ip.split("::");
|
||||||
|
int leftParts = parts[0].isEmpty() ? 0 : parts[0].split(":").length;
|
||||||
|
int rightParts = parts.length > 1 && !parts[1].isEmpty() ? parts[1].split(":").length : 0;
|
||||||
|
int totalParts = leftParts + rightParts;
|
||||||
|
if (totalParts >= 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 标准格式,必须有8个部分
|
||||||
|
String[] parts = ip.split(":");
|
||||||
|
if (parts.length != 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证每个部分都是有效的十六进制数
|
||||||
|
for (String part : parts) {
|
||||||
|
if (part.length() > 4 || part.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Integer.parseInt(part, 16); // 如果不是有效的十六进制会抛出异常
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查隧道是否存在
|
* 检查隧道是否存在
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user