mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-06 18:06:36 +08:00
增加流量不重置功能,ping改为tcpping
This commit is contained in:
@@ -71,7 +71,7 @@ jobs:
|
|||||||
if: steps.java-changes.outputs.java == 'true'
|
if: steps.java-changes.outputs.java == 'true'
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
java-version: 21
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
|
|
||||||
- name: Cache Maven dependencies
|
- name: Cache Maven dependencies
|
||||||
|
|||||||
Binary file not shown.
+1
-1
@@ -119,7 +119,7 @@ func main() {
|
|||||||
log := xlogger.NewLogger()
|
log := xlogger.NewLogger()
|
||||||
logger.SetDefault(log)
|
logger.SetDefault(log)
|
||||||
|
|
||||||
wsReporter := socket.StartWebSocketReporterWithConfig(config.Addr, config.Secret, "1.0.4")
|
wsReporter := socket.StartWebSocketReporterWithConfig(config.Addr, config.Secret, "1.0.6")
|
||||||
defer wsReporter.Stop()
|
defer wsReporter.Stop()
|
||||||
|
|
||||||
service.SetHTTPReportURL(config.Addr, config.Secret)
|
service.SetHTTPReportURL(config.Addr, config.Secret)
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync" // 新增:用于管理连接状态的互斥锁
|
"sync" // 新增:用于管理连接状态的互斥锁
|
||||||
"time"
|
"time"
|
||||||
@@ -65,19 +62,22 @@ type CommandResponse struct {
|
|||||||
RequestId string `json:"requestId,omitempty"`
|
RequestId string `json:"requestId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PingRequest ping请求结构体
|
// TcpPingRequest TCP ping请求结构体
|
||||||
type PingRequest struct {
|
type TcpPingRequest struct {
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
|
Port int `json:"port"`
|
||||||
Count int `json:"count"`
|
Count int `json:"count"`
|
||||||
|
Timeout int `json:"timeout"` // 超时时间(毫秒)
|
||||||
RequestId string `json:"requestId,omitempty"`
|
RequestId string `json:"requestId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PingResponse ping响应结构体
|
// TcpPingResponse TCP ping响应结构体
|
||||||
type PingResponse struct {
|
type TcpPingResponse struct {
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
|
Port int `json:"port"`
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
AverageTime float64 `json:"averageTime"` // 平均延迟(ms)
|
AverageTime float64 `json:"averageTime"` // 平均连接时间(ms)
|
||||||
PacketLoss float64 `json:"packetLoss"` // 丢包率(%)
|
PacketLoss float64 `json:"packetLoss"` // 连接失败率(%)
|
||||||
ErrorMessage string `json:"errorMessage,omitempty"`
|
ErrorMessage string `json:"errorMessage,omitempty"`
|
||||||
RequestId string `json:"requestId,omitempty"`
|
RequestId string `json:"requestId,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -517,12 +517,12 @@ func (w *WebSocketReporter) routeCommand(cmd CommandMessage) {
|
|||||||
err = w.handleDeleteLimiter(cmd.Data)
|
err = w.handleDeleteLimiter(cmd.Data)
|
||||||
response.Type = "DeleteLimitersResponse"
|
response.Type = "DeleteLimitersResponse"
|
||||||
|
|
||||||
// Ping 诊断命令
|
// TCP Ping 诊断命令
|
||||||
case "Ping":
|
case "TcpPing":
|
||||||
var pingResult PingResponse
|
var tcpPingResult TcpPingResponse
|
||||||
pingResult, err = w.handlePing(cmd.Data)
|
tcpPingResult, err = w.handleTcpPing(cmd.Data)
|
||||||
response.Type = "PingResponse"
|
response.Type = "TcpPingResponse"
|
||||||
response.Data = pingResult
|
response.Data = tcpPingResult
|
||||||
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("未知命令类型: %s", cmd.Type)
|
err = fmt.Errorf("未知命令类型: %s", cmd.Type)
|
||||||
@@ -944,38 +944,54 @@ func StartWebSocketReporterWithConfig(Addr string, Secret string, Version string
|
|||||||
return reporter
|
return reporter
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePing 处理ping诊断命令
|
// handleTcpPing 处理TCP ping诊断命令
|
||||||
func (w *WebSocketReporter) handlePing(data interface{}) (PingResponse, error) {
|
func (w *WebSocketReporter) handleTcpPing(data interface{}) (TcpPingResponse, error) {
|
||||||
jsonData, err := json.Marshal(data)
|
jsonData, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PingResponse{}, fmt.Errorf("序列化ping数据失败: %v", err)
|
return TcpPingResponse{}, fmt.Errorf("序列化TCP ping数据失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var req PingRequest
|
var req TcpPingRequest
|
||||||
if err := json.Unmarshal(jsonData, &req); err != nil {
|
if err := json.Unmarshal(jsonData, &req); err != nil {
|
||||||
return PingResponse{}, fmt.Errorf("解析ping请求失败: %v", err)
|
return TcpPingResponse{}, fmt.Errorf("解析TCP ping请求失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证IP地址格式
|
// 验证IP地址格式
|
||||||
if net.ParseIP(req.IP) == nil && !isValidHostname(req.IP) {
|
if net.ParseIP(req.IP) == nil && !isValidHostname(req.IP) {
|
||||||
return PingResponse{
|
return TcpPingResponse{
|
||||||
IP: req.IP,
|
IP: req.IP,
|
||||||
|
Port: req.Port,
|
||||||
Success: false,
|
Success: false,
|
||||||
ErrorMessage: "无效的IP地址或主机名",
|
ErrorMessage: "无效的IP地址或主机名",
|
||||||
RequestId: req.RequestId,
|
RequestId: req.RequestId,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置默认ping次数
|
// 验证端口范围
|
||||||
|
if req.Port <= 0 || req.Port > 65535 {
|
||||||
|
return TcpPingResponse{
|
||||||
|
IP: req.IP,
|
||||||
|
Port: req.Port,
|
||||||
|
Success: false,
|
||||||
|
ErrorMessage: "无效的端口号,范围应为1-65535",
|
||||||
|
RequestId: req.RequestId,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认值
|
||||||
if req.Count <= 0 {
|
if req.Count <= 0 {
|
||||||
req.Count = 4
|
req.Count = 4
|
||||||
}
|
}
|
||||||
|
if req.Timeout <= 0 {
|
||||||
|
req.Timeout = 5000 // 默认5秒超时
|
||||||
|
}
|
||||||
|
|
||||||
// 执行ping操作
|
// 执行TCP ping操作
|
||||||
avgTime, packetLoss, err := pingHost(req.IP, req.Count)
|
avgTime, packetLoss, err := tcpPingHost(req.IP, req.Port, req.Count, req.Timeout)
|
||||||
|
|
||||||
response := PingResponse{
|
response := TcpPingResponse{
|
||||||
IP: req.IP,
|
IP: req.IP,
|
||||||
|
Port: req.Port,
|
||||||
RequestId: req.RequestId,
|
RequestId: req.RequestId,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -991,138 +1007,52 @@ func (w *WebSocketReporter) handlePing(data interface{}) (PingResponse, error) {
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pingHost 执行ping操作,返回平均延迟和丢包率
|
// tcpPingHost 执行TCP连接测试,返回平均连接时间和失败率
|
||||||
func pingHost(ip string, count int) (float64, float64, error) {
|
func tcpPingHost(ip string, port int, count int, timeoutMs int) (float64, float64, error) {
|
||||||
var cmd *exec.Cmd
|
var totalTime float64
|
||||||
|
var successCount int
|
||||||
|
|
||||||
// 根据操作系统选择不同的ping命令
|
timeout := time.Duration(timeoutMs) * time.Millisecond
|
||||||
switch runtime.GOOS {
|
target := fmt.Sprintf("%s:%d", ip, port)
|
||||||
case "windows":
|
|
||||||
cmd = exec.Command("ping", "-n", strconv.Itoa(count), ip)
|
|
||||||
case "darwin", "linux":
|
|
||||||
cmd = exec.Command("ping", "-c", strconv.Itoa(count), ip)
|
|
||||||
default:
|
|
||||||
return 0, 0, fmt.Errorf("不支持的操作系统: %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := cmd.Output()
|
fmt.Printf("🔍 开始TCP ping测试: %s,次数: %d,超时: %dms\n", target, count, timeoutMs)
|
||||||
if err != nil {
|
|
||||||
return 0, 0, fmt.Errorf("ping命令执行失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析ping输出
|
for i := 0; i < count; i++ {
|
||||||
return parsePingOutput(string(output), runtime.GOOS)
|
start := time.Now()
|
||||||
}
|
|
||||||
|
|
||||||
// parsePingOutput 解析ping命令输出,提取平均延迟和丢包率
|
// 创建带超时的TCP连接
|
||||||
func parsePingOutput(output, osType string) (float64, float64, error) {
|
conn, err := net.DialTimeout("tcp", target, timeout)
|
||||||
lines := strings.Split(output, "\n")
|
|
||||||
|
|
||||||
switch osType {
|
elapsed := time.Since(start)
|
||||||
case "windows":
|
|
||||||
return parsePingOutputWindows(lines)
|
|
||||||
case "darwin", "linux":
|
|
||||||
return parsePingOutputUnix(lines)
|
|
||||||
default:
|
|
||||||
return 0, 0, fmt.Errorf("不支持的操作系统类型")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsePingOutputWindows 解析Windows系统的ping输出
|
if err != nil {
|
||||||
func parsePingOutputWindows(lines []string) (float64, float64, error) {
|
fmt.Printf(" 第%d次连接失败: %v (%.2fms)\n", i+1, err, elapsed.Seconds()*1000)
|
||||||
var avgTime float64
|
} else {
|
||||||
var packetLoss float64
|
fmt.Printf(" 第%d次连接成功: %.2fms\n", i+1, elapsed.Seconds()*1000)
|
||||||
|
conn.Close()
|
||||||
for _, line := range lines {
|
totalTime += elapsed.Seconds() * 1000 // 转换为毫秒
|
||||||
line = strings.TrimSpace(line)
|
successCount++
|
||||||
|
|
||||||
// 查找平均延迟 (例如: "最短 = 1ms,最长 = 2ms,平均 = 1ms")
|
|
||||||
if strings.Contains(line, "平均") && strings.Contains(line, "ms") {
|
|
||||||
parts := strings.Split(line, "平均 = ")
|
|
||||||
if len(parts) > 1 {
|
|
||||||
avgPart := strings.Split(parts[1], "ms")[0]
|
|
||||||
if avg, err := strconv.ParseFloat(avgPart, 64); err == nil {
|
|
||||||
avgTime = avg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找丢包率 (例如: "丢失 = 0 (0% 丢失)")
|
// 如果不是最后一次,等待一下再进行下次测试
|
||||||
if strings.Contains(line, "丢失") && strings.Contains(line, "%") {
|
if i < count-1 {
|
||||||
if strings.Contains(line, "(0%") {
|
time.Sleep(100 * time.Millisecond)
|
||||||
packetLoss = 0
|
|
||||||
} else {
|
|
||||||
// 提取百分比
|
|
||||||
start := strings.Index(line, "(")
|
|
||||||
end := strings.Index(line, "%")
|
|
||||||
if start != -1 && end != -1 && start < end {
|
|
||||||
lossStr := line[start+1 : end]
|
|
||||||
if loss, err := strconv.ParseFloat(lossStr, 64); err == nil {
|
|
||||||
packetLoss = loss
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if successCount == 0 {
|
||||||
|
return 0, 100.0, fmt.Errorf("所有TCP连接尝试都失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
avgTime := totalTime / float64(successCount)
|
||||||
|
packetLoss := float64(count-successCount) / float64(count) * 100
|
||||||
|
|
||||||
|
fmt.Printf("✅ TCP ping完成: 平均连接时间 %.2fms,失败率 %.1f%%\n", avgTime, packetLoss)
|
||||||
|
|
||||||
return avgTime, packetLoss, nil
|
return avgTime, packetLoss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsePingOutputUnix 解析Unix系统(Linux/macOS)的ping输出
|
|
||||||
func parsePingOutputUnix(lines []string) (float64, float64, error) {
|
|
||||||
var avgTime float64
|
|
||||||
var packetLoss float64
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
|
|
||||||
// 查找统计行 (例如: "4 packets transmitted, 4 received, 0% packet loss")
|
|
||||||
if strings.Contains(line, "packet loss") {
|
|
||||||
parts := strings.Split(line, "%")
|
|
||||||
if len(parts) > 0 {
|
|
||||||
// 查找百分比前的数字
|
|
||||||
lossStr := strings.Fields(parts[0])
|
|
||||||
if len(lossStr) > 0 {
|
|
||||||
if loss, err := strconv.ParseFloat(lossStr[len(lossStr)-1], 64); err == nil {
|
|
||||||
packetLoss = loss
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找往返时间统计 (例如: "round-trip min/avg/max/stddev = 0.123/0.456/0.789/0.012 ms")
|
|
||||||
if strings.Contains(line, "round-trip") && strings.Contains(line, "=") {
|
|
||||||
parts := strings.Split(line, "=")
|
|
||||||
if len(parts) > 1 {
|
|
||||||
times := strings.TrimSpace(parts[1])
|
|
||||||
times = strings.Split(times, " ")[0] // 去掉末尾的"ms"
|
|
||||||
timeValues := strings.Split(times, "/")
|
|
||||||
if len(timeValues) >= 2 {
|
|
||||||
if avg, err := strconv.ParseFloat(timeValues[1], 64); err == nil {
|
|
||||||
avgTime = avg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// macOS的格式可能不同,查找avg (例如: "min/avg/max/stddev = 0.123/0.456/0.789/0.012 ms")
|
|
||||||
if strings.Contains(line, "min/avg/max") && strings.Contains(line, "=") {
|
|
||||||
parts := strings.Split(line, "=")
|
|
||||||
if len(parts) > 1 {
|
|
||||||
times := strings.TrimSpace(parts[1])
|
|
||||||
times = strings.Split(times, " ")[0] // 去掉末尾的"ms"
|
|
||||||
timeValues := strings.Split(times, "/")
|
|
||||||
if len(timeValues) >= 2 {
|
|
||||||
if avg, err := strconv.ParseFloat(timeValues[1], 64); err == nil {
|
|
||||||
avgTime = avg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return avgTime, packetLoss, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValidHostname 验证主机名格式
|
// isValidHostname 验证主机名格式
|
||||||
func isValidHostname(hostname string) bool {
|
func isValidHostname(hostname string) bool {
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ install_gost() {
|
|||||||
# 询问是否有加速下载地址
|
# 询问是否有加速下载地址
|
||||||
echo ""
|
echo ""
|
||||||
echo "📥 检查下载地址..."
|
echo "📥 检查下载地址..."
|
||||||
|
echo "加速下载地址需提供完整的地址,浏览器打开就能直接下载的那种!!!!!"
|
||||||
read -p "是否有加速下载地址?(留空使用默认地址): " custom_url
|
read -p "是否有加速下载地址?(留空使用默认地址): " custom_url
|
||||||
if [[ -n "$custom_url" ]]; then
|
if [[ -n "$custom_url" ]]; then
|
||||||
DOWNLOAD_URL="$custom_url"
|
DOWNLOAD_URL="$custom_url"
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
# 构建阶段
|
# 构建阶段
|
||||||
FROM maven:3.8.6-jdk-8 AS build
|
FROM maven:3.9.6-openjdk-21 AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY pom.xml .
|
COPY pom.xml .
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
RUN mvn clean package -DskipTests
|
RUN mvn clean package -DskipTests
|
||||||
|
|
||||||
# 运行阶段
|
# 运行阶段
|
||||||
FROM openjdk:8-jre-alpine
|
FROM openjdk:21-jre-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV JAVA_OPTS="-Xmx512m -Xms256m -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"
|
ENV JAVA_OPTS="-Xmx512m -Xms256m -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"
|
||||||
ENV LANG=en_US.UTF-8
|
ENV LANG=en_US.UTF-8
|
||||||
ENV LANGUAGE=en_US:en
|
ENV LANGUAGE=en_US:en
|
||||||
ENV LC_ALL=en_US.UTF-8
|
ENV LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
|
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources \
|
||||||
&& apk update \
|
&& apt-get update \
|
||||||
&& apk --no-cache add ttf-dejavu fontconfig
|
&& apt-get install -y --no-install-recommends fontconfig fonts-dejavu \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY --from=build /app/target/*.jar app.jar
|
COPY --from=build /app/target/*.jar app.jar
|
||||||
EXPOSE 6365
|
EXPOSE 6365
|
||||||
|
|||||||
+21
-15
@@ -8,10 +8,12 @@
|
|||||||
<name>admin</name>
|
<name>admin</name>
|
||||||
<description>admin</description>
|
<description>admin</description>
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
<java.version>21</java.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<spring-boot.version>2.6.13</spring-boot.version>
|
<spring-boot.version>2.7.18</spring-boot.version>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
@@ -22,19 +24,14 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.fastjson2</groupId>
|
<groupId>com.alibaba.fastjson2</groupId>
|
||||||
<artifactId>fastjson2</artifactId>
|
<artifactId>fastjson2</artifactId>
|
||||||
<version>2.0.43</version> <!-- 版本号可根据需要调整 -->
|
<version>2.0.43</version>
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.sun.mail</groupId>
|
|
||||||
<artifactId>javax.mail</artifactId>
|
|
||||||
<version>1.6.2</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -72,8 +69,9 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>com.mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>8.2.0</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -97,12 +95,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.5.14</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.freemarker</groupId>
|
<groupId>org.freemarker</groupId>
|
||||||
<artifactId>freemarker</artifactId>
|
<artifactId>freemarker</artifactId>
|
||||||
<version>2.3.31</version> <!-- 请根据需要选择合适的版本 -->
|
<version>2.3.31</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
@@ -139,11 +138,18 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.1</version>
|
<version>3.11.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>1.8</source>
|
<source>21</source>
|
||||||
<target>1.8</target>
|
<target>21</target>
|
||||||
<encoding>UTF-8</encoding>
|
<encoding>UTF-8</encoding>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.springframework.web.servlet.HandlerInterceptor;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT拦截器,验证用户是否登录
|
* JWT拦截器,验证用户是否登录
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -68,9 +68,12 @@ public class ResetFlowAsync {
|
|||||||
*/
|
*/
|
||||||
private void resetUserFlow(int currentDay, int lastDayOfMonth) {
|
private void resetUserFlow(int currentDay, int lastDayOfMonth) {
|
||||||
try {
|
try {
|
||||||
// flowResetTime字段存储的是1-31的数字,表示每月第几号重置
|
// flowResetTime字段存储的是0-31的数字,0表示不重置,1-31表示每月第几号重置
|
||||||
// 构建查询条件:重置日期等于今天,或者重置日期大于当月最大天数且今天是月末
|
// 构建查询条件:重置日期等于今天,或者重置日期大于当月最大天数且今天是月末
|
||||||
|
// 排除flowResetTime为0的记录(不重置)
|
||||||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.ne("flow_reset_time", 0); // 排除不重置的用户
|
||||||
|
|
||||||
if (currentDay == lastDayOfMonth) {
|
if (currentDay == lastDayOfMonth) {
|
||||||
// 如果今天是月末,查询重置日期等于今天或者大于当月最大天数的记录
|
// 如果今天是月末,查询重置日期等于今天或者大于当月最大天数的记录
|
||||||
// 例如:当月30天,但用户设置31号重置,则在30号执行重置
|
// 例如:当月30天,但用户设置31号重置,则在30号执行重置
|
||||||
@@ -118,9 +121,12 @@ public class ResetFlowAsync {
|
|||||||
*/
|
*/
|
||||||
private void resetUserTunnelFlow(int currentDay, int lastDayOfMonth) {
|
private void resetUserTunnelFlow(int currentDay, int lastDayOfMonth) {
|
||||||
try {
|
try {
|
||||||
// flowResetTime字段存储的是1-31的数字,表示每月第几号重置
|
// flowResetTime字段存储的是0-31的数字,0表示不重置,1-31表示每月第几号重置
|
||||||
// 构建查询条件:重置日期等于今天,或者重置日期大于当月最大天数且今天是月末
|
// 构建查询条件:重置日期等于今天,或者重置日期大于当月最大天数且今天是月末
|
||||||
|
// 排除flowResetTime为0的记录(不重置)
|
||||||
QueryWrapper<UserTunnel> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<UserTunnel> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.ne("flow_reset_time", 0); // 排除不重置的用户隧道
|
||||||
|
|
||||||
if (currentDay == lastDayOfMonth) {
|
if (currentDay == lastDayOfMonth) {
|
||||||
// 如果今天是月末,查询重置日期等于今天或者大于当月最大天数的记录
|
// 如果今天是月末,查询重置日期等于今天或者大于当月最大天数的记录
|
||||||
// 例如:当月30天,但用户设置31号重置,则在30号执行重置
|
// 例如:当月30天,但用户设置31号重置,则在30号执行重置
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.util.Objects;
|
|||||||
@Configuration
|
@Configuration
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
|
public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
NodeService nodeService;
|
NodeService nodeService;
|
||||||
|
|
||||||
|
|||||||
@@ -411,9 +411,10 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
|
|||||||
String[] remoteAddresses = forward.getRemoteAddr().split(",");
|
String[] remoteAddresses = forward.getRemoteAddr().split(",");
|
||||||
String targetAddress = remoteAddresses[0].trim();
|
String targetAddress = remoteAddresses[0].trim();
|
||||||
|
|
||||||
// 提取IP部分(去掉端口)
|
// 提取IP和端口
|
||||||
String targetIp = extractIpFromAddress(targetAddress);
|
String targetIp = extractIpFromAddress(targetAddress);
|
||||||
if (targetIp == null) {
|
int targetPort = extractPortFromAddress(targetAddress);
|
||||||
|
if (targetIp == null || targetPort == -1) {
|
||||||
return R.err("无法解析目标地址: " + targetAddress);
|
return R.err("无法解析目标地址: " + targetAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,22 +422,22 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
|
|||||||
|
|
||||||
// 6. 根据隧道类型执行不同的诊断策略
|
// 6. 根据隧道类型执行不同的诊断策略
|
||||||
if (tunnel.getType() == TUNNEL_TYPE_PORT_FORWARD) {
|
if (tunnel.getType() == TUNNEL_TYPE_PORT_FORWARD) {
|
||||||
// 端口转发:入口节点直接ping目标地址
|
// 端口转发:入口节点直接TCP ping目标地址
|
||||||
DiagnosisResult result = performPingDiagnosis(inNode, targetIp, "转发->目标");
|
DiagnosisResult result = performTcpPingDiagnosis(inNode, targetIp, targetPort, "转发->目标");
|
||||||
results.add(result);
|
results.add(result);
|
||||||
} else {
|
} else {
|
||||||
// 隧道转发:入口ping出口,出口ping目标
|
// 隧道转发:入口TCP ping出口,出口TCP ping目标
|
||||||
Node outNode = nodeService.getNodeById(tunnel.getOutNodeId());
|
Node outNode = nodeService.getNodeById(tunnel.getOutNodeId());
|
||||||
if (outNode == null) {
|
if (outNode == null) {
|
||||||
return R.err("出口节点不存在");
|
return R.err("出口节点不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 入口ping出口
|
// 入口TCP ping出口(使用转发的出口端口)
|
||||||
DiagnosisResult inToOutResult = performPingDiagnosis(inNode, outNode.getServerIp(), "入口->出口");
|
DiagnosisResult inToOutResult = performTcpPingDiagnosis(inNode, outNode.getServerIp(), forward.getOutPort(), "入口->出口");
|
||||||
results.add(inToOutResult);
|
results.add(inToOutResult);
|
||||||
|
|
||||||
// 出口ping目标
|
// 出口TCP ping目标
|
||||||
DiagnosisResult outToTargetResult = performPingDiagnosis(outNode, targetIp, "出口->目标");
|
DiagnosisResult outToTargetResult = performTcpPingDiagnosis(outNode, targetIp, targetPort, "出口->目标");
|
||||||
results.add(outToTargetResult);
|
results.add(outToTargetResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,58 +482,101 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行ping诊断
|
* 从地址字符串中提取端口号
|
||||||
|
* 支持格式: ip:port, [ipv6]:port, domain:port
|
||||||
|
*/
|
||||||
|
private int extractPortFromAddress(String address) {
|
||||||
|
if (address == null || address.trim().isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
address = address.trim();
|
||||||
|
|
||||||
|
// IPv6格式: [ipv6]:port
|
||||||
|
if (address.startsWith("[")) {
|
||||||
|
int closeBracket = address.indexOf(']');
|
||||||
|
if (closeBracket > 1 && closeBracket + 1 < address.length() && address.charAt(closeBracket + 1) == ':') {
|
||||||
|
String portStr = address.substring(closeBracket + 2);
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(portStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv4或域名格式: ip:port 或 domain:port
|
||||||
|
int lastColon = address.lastIndexOf(':');
|
||||||
|
if (lastColon > 0 && lastColon + 1 < address.length()) {
|
||||||
|
String portStr = address.substring(lastColon + 1);
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(portStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有端口,返回-1表示无法解析
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行TCP ping诊断
|
||||||
*
|
*
|
||||||
* @param node 执行ping的节点
|
* @param node 执行TCP ping的节点
|
||||||
* @param targetIp 目标IP地址
|
* @param targetIp 目标IP地址
|
||||||
|
* @param port 目标端口
|
||||||
* @param description 诊断描述
|
* @param description 诊断描述
|
||||||
* @return 诊断结果
|
* @return 诊断结果
|
||||||
*/
|
*/
|
||||||
private DiagnosisResult performPingDiagnosis(Node node, String targetIp, String description) {
|
private DiagnosisResult performTcpPingDiagnosis(Node node, String targetIp, int port, String description) {
|
||||||
try {
|
try {
|
||||||
// 构建ping请求数据
|
// 构建TCP ping请求数据
|
||||||
JSONObject pingData = new JSONObject();
|
JSONObject tcpPingData = new JSONObject();
|
||||||
pingData.put("ip", targetIp);
|
tcpPingData.put("ip", targetIp);
|
||||||
pingData.put("count", 4);
|
tcpPingData.put("port", port);
|
||||||
|
tcpPingData.put("count", 4);
|
||||||
|
tcpPingData.put("timeout", 5000); // 5秒超时
|
||||||
|
|
||||||
// 发送ping命令到节点
|
// 发送TCP ping命令到节点
|
||||||
GostDto gostResult = WebSocketServer.send_msg(node.getId(), pingData, "Ping");
|
GostDto gostResult = WebSocketServer.send_msg(node.getId(), tcpPingData, "TcpPing");
|
||||||
|
|
||||||
DiagnosisResult result = new DiagnosisResult();
|
DiagnosisResult result = new DiagnosisResult();
|
||||||
result.setNodeId(node.getId());
|
result.setNodeId(node.getId());
|
||||||
result.setNodeName(node.getName());
|
result.setNodeName(node.getName());
|
||||||
result.setTargetIp(targetIp);
|
result.setTargetIp(targetIp);
|
||||||
|
result.setTargetPort(port);
|
||||||
result.setDescription(description);
|
result.setDescription(description);
|
||||||
result.setTimestamp(System.currentTimeMillis());
|
result.setTimestamp(System.currentTimeMillis());
|
||||||
|
|
||||||
if (gostResult != null && "OK".equals(gostResult.getMsg())) {
|
if (gostResult != null && "OK".equals(gostResult.getMsg())) {
|
||||||
// 尝试解析ping响应数据
|
// 尝试解析TCP ping响应数据
|
||||||
try {
|
try {
|
||||||
if (gostResult.getData() != null) {
|
if (gostResult.getData() != null) {
|
||||||
JSONObject pingResponse = (JSONObject) gostResult.getData();
|
JSONObject tcpPingResponse = (JSONObject) gostResult.getData();
|
||||||
boolean success = pingResponse.getBooleanValue("success");
|
boolean success = tcpPingResponse.getBooleanValue("success");
|
||||||
|
|
||||||
result.setSuccess(success);
|
result.setSuccess(success);
|
||||||
if (success) {
|
if (success) {
|
||||||
result.setMessage("ping成功");
|
result.setMessage("TCP连接成功");
|
||||||
result.setAverageTime(pingResponse.getDoubleValue("averageTime"));
|
result.setAverageTime(tcpPingResponse.getDoubleValue("averageTime"));
|
||||||
result.setPacketLoss(pingResponse.getDoubleValue("packetLoss"));
|
result.setPacketLoss(tcpPingResponse.getDoubleValue("packetLoss"));
|
||||||
} else {
|
} else {
|
||||||
result.setMessage(pingResponse.getString("errorMessage"));
|
result.setMessage(tcpPingResponse.getString("errorMessage"));
|
||||||
result.setAverageTime(-1.0);
|
result.setAverageTime(-1.0);
|
||||||
result.setPacketLoss(100.0);
|
result.setPacketLoss(100.0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 没有详细数据,使用默认值
|
// 没有详细数据,使用默认值
|
||||||
result.setSuccess(true);
|
result.setSuccess(true);
|
||||||
result.setMessage("ping成功");
|
result.setMessage("TCP连接成功");
|
||||||
result.setAverageTime(0.0);
|
result.setAverageTime(0.0);
|
||||||
result.setPacketLoss(0.0);
|
result.setPacketLoss(0.0);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 解析响应数据失败,但ping命令本身成功了
|
// 解析响应数据失败,但TCP ping命令本身成功了
|
||||||
result.setSuccess(true);
|
result.setSuccess(true);
|
||||||
result.setMessage("ping成功,但无法解析详细数据");
|
result.setMessage("TCP连接成功,但无法解析详细数据");
|
||||||
result.setAverageTime(0.0);
|
result.setAverageTime(0.0);
|
||||||
result.setPacketLoss(0.0);
|
result.setPacketLoss(0.0);
|
||||||
}
|
}
|
||||||
@@ -549,6 +593,7 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
|
|||||||
result.setNodeId(node.getId());
|
result.setNodeId(node.getId());
|
||||||
result.setNodeName(node.getName());
|
result.setNodeName(node.getName());
|
||||||
result.setTargetIp(targetIp);
|
result.setTargetIp(targetIp);
|
||||||
|
result.setTargetPort(port);
|
||||||
result.setDescription(description);
|
result.setDescription(description);
|
||||||
result.setSuccess(false);
|
result.setSuccess(false);
|
||||||
result.setMessage("诊断执行异常: " + e.getMessage());
|
result.setMessage("诊断执行异常: " + e.getMessage());
|
||||||
@@ -1326,6 +1371,7 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
|
|||||||
private Long nodeId;
|
private Long nodeId;
|
||||||
private String nodeName;
|
private String nodeName;
|
||||||
private String targetIp;
|
private String targetIp;
|
||||||
|
private Integer targetPort;
|
||||||
private String description;
|
private String description;
|
||||||
private boolean success;
|
private boolean success;
|
||||||
private String message;
|
private String message;
|
||||||
|
|||||||
@@ -643,16 +643,17 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
|
|
||||||
// 3. 根据隧道类型执行不同的诊断策略
|
// 3. 根据隧道类型执行不同的诊断策略
|
||||||
if (tunnel.getType() == TUNNEL_TYPE_PORT_FORWARD) {
|
if (tunnel.getType() == TUNNEL_TYPE_PORT_FORWARD) {
|
||||||
// 端口转发:只给入口节点发送诊断指令,ping谷歌DNS
|
// 端口转发:只给入口节点发送诊断指令,TCP ping谷歌443端口
|
||||||
DiagnosisResult inResult = performPingDiagnosisWithConnectionCheck(inNode, "www.google.com", "入口->外网");
|
DiagnosisResult inResult = performTcpPingDiagnosisWithConnectionCheck(inNode, "www.google.com", 443, "入口->外网");
|
||||||
results.add(inResult);
|
results.add(inResult);
|
||||||
} else {
|
} else {
|
||||||
// 隧道转发:入口ping出口,出口ping谷歌DNS
|
// 隧道转发:入口TCP ping出口,出口TCP ping谷歌443端口
|
||||||
DiagnosisResult inToOutResult = performPingDiagnosisWithConnectionCheck(inNode, outNode.getServerIp(), "入口->出口");
|
int outNodePort = getOutNodeTcpPort(tunnel.getId());
|
||||||
|
DiagnosisResult inToOutResult = performTcpPingDiagnosisWithConnectionCheck(inNode, outNode.getServerIp(), outNodePort, "入口->出口");
|
||||||
results.add(inToOutResult);
|
results.add(inToOutResult);
|
||||||
|
|
||||||
// 先检查出口节点的真实连接状态,然后再进行诊断
|
// 先检查出口节点的真实连接状态,然后再进行诊断
|
||||||
DiagnosisResult outToExternalResult = performPingDiagnosisWithConnectionCheck(outNode, "www.google.com", "出口->外网");
|
DiagnosisResult outToExternalResult = performTcpPingDiagnosisWithConnectionCheck(outNode, "www.google.com", 443, "出口->外网");
|
||||||
results.add(outToExternalResult);
|
results.add(outToExternalResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,58 +669,78 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行ping诊断
|
* 获取出口节点的TCP端口
|
||||||
|
* 通过隧道ID查找转发服务的出口端口,如果没有则使用默认SSH端口22
|
||||||
*
|
*
|
||||||
* @param node 执行ping的节点
|
* @param tunnelId 隧道ID
|
||||||
|
* @return TCP端口号
|
||||||
|
*/
|
||||||
|
private int getOutNodeTcpPort(Long tunnelId) {
|
||||||
|
List<Forward> forwards = forwardService.list(new QueryWrapper<Forward>().eq("tunnel_id", tunnelId));
|
||||||
|
if (!forwards.isEmpty()) {
|
||||||
|
return forwards.get(0).getOutPort();
|
||||||
|
}
|
||||||
|
// 如果没有转发服务,使用默认SSH端口22
|
||||||
|
return 22;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行TCP ping诊断
|
||||||
|
*
|
||||||
|
* @param node 执行TCP ping的节点
|
||||||
* @param targetIp 目标IP地址
|
* @param targetIp 目标IP地址
|
||||||
|
* @param port 目标端口
|
||||||
* @param description 诊断描述
|
* @param description 诊断描述
|
||||||
* @return 诊断结果
|
* @return 诊断结果
|
||||||
*/
|
*/
|
||||||
private DiagnosisResult performPingDiagnosis(Node node, String targetIp, String description) {
|
private DiagnosisResult performTcpPingDiagnosis(Node node, String targetIp, int port, String description) {
|
||||||
try {
|
try {
|
||||||
// 构建ping请求数据
|
// 构建TCP ping请求数据
|
||||||
JSONObject pingData = new JSONObject();
|
JSONObject tcpPingData = new JSONObject();
|
||||||
pingData.put("ip", targetIp);
|
tcpPingData.put("ip", targetIp);
|
||||||
pingData.put("count", 4);
|
tcpPingData.put("port", port);
|
||||||
|
tcpPingData.put("count", 4);
|
||||||
|
tcpPingData.put("timeout", 5000); // 5秒超时
|
||||||
|
|
||||||
// 发送ping命令到节点
|
// 发送TCP ping命令到节点
|
||||||
GostDto gostResult = WebSocketServer.send_msg(node.getId(), pingData, "Ping");
|
GostDto gostResult = WebSocketServer.send_msg(node.getId(), tcpPingData, "TcpPing");
|
||||||
|
|
||||||
DiagnosisResult result = new DiagnosisResult();
|
DiagnosisResult result = new DiagnosisResult();
|
||||||
result.setNodeId(node.getId());
|
result.setNodeId(node.getId());
|
||||||
result.setNodeName(node.getName());
|
result.setNodeName(node.getName());
|
||||||
result.setTargetIp(targetIp);
|
result.setTargetIp(targetIp);
|
||||||
|
result.setTargetPort(port);
|
||||||
result.setDescription(description);
|
result.setDescription(description);
|
||||||
result.setTimestamp(System.currentTimeMillis());
|
result.setTimestamp(System.currentTimeMillis());
|
||||||
|
|
||||||
if (gostResult != null && "OK".equals(gostResult.getMsg())) {
|
if (gostResult != null && "OK".equals(gostResult.getMsg())) {
|
||||||
// 尝试解析ping响应数据
|
// 尝试解析TCP ping响应数据
|
||||||
try {
|
try {
|
||||||
if (gostResult.getData() != null) {
|
if (gostResult.getData() != null) {
|
||||||
JSONObject pingResponse = (JSONObject) gostResult.getData();
|
JSONObject tcpPingResponse = (JSONObject) gostResult.getData();
|
||||||
boolean success = pingResponse.getBooleanValue("success");
|
boolean success = tcpPingResponse.getBooleanValue("success");
|
||||||
|
|
||||||
result.setSuccess(success);
|
result.setSuccess(success);
|
||||||
if (success) {
|
if (success) {
|
||||||
result.setMessage("ping成功");
|
result.setMessage("TCP连接成功");
|
||||||
result.setAverageTime(pingResponse.getDoubleValue("averageTime"));
|
result.setAverageTime(tcpPingResponse.getDoubleValue("averageTime"));
|
||||||
result.setPacketLoss(pingResponse.getDoubleValue("packetLoss"));
|
result.setPacketLoss(tcpPingResponse.getDoubleValue("packetLoss"));
|
||||||
} else {
|
} else {
|
||||||
result.setMessage(pingResponse.getString("errorMessage"));
|
result.setMessage(tcpPingResponse.getString("errorMessage"));
|
||||||
result.setAverageTime(-1.0);
|
result.setAverageTime(-1.0);
|
||||||
result.setPacketLoss(100.0);
|
result.setPacketLoss(100.0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 没有详细数据,使用默认值
|
// 没有详细数据,使用默认值
|
||||||
result.setSuccess(true);
|
result.setSuccess(true);
|
||||||
result.setMessage("ping成功");
|
result.setMessage("TCP连接成功");
|
||||||
result.setAverageTime(0.0);
|
result.setAverageTime(0.0);
|
||||||
result.setPacketLoss(0.0);
|
result.setPacketLoss(0.0);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 解析响应数据失败,但ping命令本身成功了
|
// 解析响应数据失败,但TCP ping命令本身成功了
|
||||||
result.setSuccess(true);
|
result.setSuccess(true);
|
||||||
result.setMessage("ping成功,但无法解析详细数据");
|
result.setMessage("TCP连接成功,但无法解析详细数据");
|
||||||
result.setAverageTime(0.0);
|
result.setAverageTime(0.0);
|
||||||
result.setPacketLoss(0.0);
|
result.setPacketLoss(0.0);
|
||||||
}
|
}
|
||||||
@@ -736,6 +757,7 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
result.setNodeId(node.getId());
|
result.setNodeId(node.getId());
|
||||||
result.setNodeName(node.getName());
|
result.setNodeName(node.getName());
|
||||||
result.setTargetIp(targetIp);
|
result.setTargetIp(targetIp);
|
||||||
|
result.setTargetPort(port);
|
||||||
result.setDescription(description);
|
result.setDescription(description);
|
||||||
result.setSuccess(false);
|
result.setSuccess(false);
|
||||||
result.setMessage("诊断执行异常: " + e.getMessage());
|
result.setMessage("诊断执行异常: " + e.getMessage());
|
||||||
@@ -747,23 +769,25 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行ping诊断(带连接状态检查)
|
* 执行TCP ping诊断(带连接状态检查)
|
||||||
*
|
*
|
||||||
* @param node 执行ping的节点
|
* @param node 执行TCP ping的节点
|
||||||
* @param targetIp 目标IP地址
|
* @param targetIp 目标IP地址
|
||||||
|
* @param port 目标端口
|
||||||
* @param description 诊断描述
|
* @param description 诊断描述
|
||||||
* @return 诊断结果
|
* @return 诊断结果
|
||||||
*/
|
*/
|
||||||
private DiagnosisResult performPingDiagnosisWithConnectionCheck(Node node, String targetIp, String description) {
|
private DiagnosisResult performTcpPingDiagnosisWithConnectionCheck(Node node, String targetIp, int port, String description) {
|
||||||
DiagnosisResult result = new DiagnosisResult();
|
DiagnosisResult result = new DiagnosisResult();
|
||||||
result.setNodeId(node.getId());
|
result.setNodeId(node.getId());
|
||||||
result.setNodeName(node.getName());
|
result.setNodeName(node.getName());
|
||||||
result.setTargetIp(targetIp);
|
result.setTargetIp(targetIp);
|
||||||
|
result.setTargetPort(port);
|
||||||
result.setDescription(description);
|
result.setDescription(description);
|
||||||
result.setTimestamp(System.currentTimeMillis());
|
result.setTimestamp(System.currentTimeMillis());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return performPingDiagnosis(node, targetIp, description);
|
return performTcpPingDiagnosis(node, targetIp, port, description);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
result.setSuccess(false);
|
result.setSuccess(false);
|
||||||
result.setMessage("连接检查异常: " + e.getMessage());
|
result.setMessage("连接检查异常: " + e.getMessage());
|
||||||
@@ -817,6 +841,7 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
|||||||
private Long nodeId;
|
private Long nodeId;
|
||||||
private String nodeName;
|
private String nodeName;
|
||||||
private String targetIp;
|
private String targetIp;
|
||||||
|
private Integer targetPort;
|
||||||
private String description;
|
private String description;
|
||||||
private boolean success;
|
private boolean success;
|
||||||
private String message;
|
private String message;
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ export default function AdminLayout({
|
|||||||
<Logo size={28} />
|
<Logo size={28} />
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-base font-bold text-foreground">哆啦A梦</h1>
|
<h1 className="text-base font-bold text-foreground">哆啦A梦</h1>
|
||||||
<p className="text-xs text-default-500">v1.0.7</p>
|
<p className="text-xs text-default-500">v1.0.8</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -341,7 +341,8 @@ export default function DashboardPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatResetTime = (resetDay?: number): string => {
|
const formatResetTime = (resetDay?: number): string => {
|
||||||
if (!resetDay) return '';
|
if (resetDay === undefined || resetDay === null) return '';
|
||||||
|
if (resetDay === 0) return '不重置';
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const currentDay = now.getDate();
|
const currentDay = now.getDate();
|
||||||
@@ -592,7 +593,7 @@ export default function DashboardPage() {
|
|||||||
<p className="text-xs text-default-500 truncate">
|
<p className="text-xs text-default-500 truncate">
|
||||||
{userInfo.flow === 99999 ? '无限制' : `${calculateUsagePercentage('flow').toFixed(1)}%`}
|
{userInfo.flow === 99999 ? '无限制' : `${calculateUsagePercentage('flow').toFixed(1)}%`}
|
||||||
</p>
|
</p>
|
||||||
{userInfo.flowResetTime && (
|
{(userInfo.flowResetTime !== undefined && userInfo.flowResetTime !== null) && (
|
||||||
<div className="text-xs text-default-500 flex items-center gap-1">
|
<div className="text-xs text-default-500 flex items-center gap-1">
|
||||||
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clipRule="evenodd" />
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clipRule="evenodd" />
|
||||||
@@ -682,7 +683,7 @@ export default function DashboardPage() {
|
|||||||
<span className={`px-2 py-1 rounded-md text-xs font-medium border ${tunnelExpStatus.bg} ${tunnelExpStatus.color}`}>
|
<span className={`px-2 py-1 rounded-md text-xs font-medium border ${tunnelExpStatus.bg} ${tunnelExpStatus.color}`}>
|
||||||
{tunnelExpStatus.text}
|
{tunnelExpStatus.text}
|
||||||
</span>
|
</span>
|
||||||
{tunnel.flowResetTime && (
|
{(tunnel.flowResetTime !== undefined && tunnel.flowResetTime !== null) && (
|
||||||
<span className="text-xs text-default-500">
|
<span className="text-xs text-default-500">
|
||||||
{formatResetTime(tunnel.flowResetTime)}
|
{formatResetTime(tunnel.flowResetTime)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ interface DiagnosisResult {
|
|||||||
nodeName: string;
|
nodeName: string;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
targetIp: string;
|
targetIp: string;
|
||||||
|
targetPort?: number;
|
||||||
message?: string;
|
message?: string;
|
||||||
averageTime?: number;
|
averageTime?: number;
|
||||||
packetLoss?: number;
|
packetLoss?: number;
|
||||||
@@ -1075,14 +1076,18 @@ export default function ForwardPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-small text-default-500 flex items-center gap-1">
|
<div className="text-small text-default-500 flex items-center gap-1">
|
||||||
<span className="flex-shrink-0">目标地址:</span>
|
<span className="flex-shrink-0">目标地址:</span>
|
||||||
<code className="font-mono truncate min-w-0" title={result.targetIp}>{result.targetIp}</code>
|
<code className="font-mono truncate min-w-0" title={`${result.targetIp}${result.targetPort ? ':' + result.targetPort : ''}`}>
|
||||||
|
{result.targetIp}{result.targetPort ? ':' + result.targetPort : ''}
|
||||||
|
</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="text-small text-default-500 flex items-center gap-1">
|
<div className="text-small text-default-500 flex items-center gap-1">
|
||||||
<span className="flex-shrink-0">目标地址:</span>
|
<span className="flex-shrink-0">目标地址:</span>
|
||||||
<code className="font-mono truncate min-w-0" title={result.targetIp}>{result.targetIp}</code>
|
<code className="font-mono truncate min-w-0" title={`${result.targetIp}${result.targetPort ? ':' + result.targetPort : ''}`}>
|
||||||
|
{result.targetIp}{result.targetPort ? ':' + result.targetPort : ''}
|
||||||
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<Alert
|
<Alert
|
||||||
color="danger"
|
color="danger"
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ interface DiagnosisResult {
|
|||||||
nodeName: string;
|
nodeName: string;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
targetIp: string;
|
targetIp: string;
|
||||||
|
targetPort: number;
|
||||||
message?: string;
|
message?: string;
|
||||||
averageTime?: number;
|
averageTime?: number;
|
||||||
packetLoss?: number;
|
packetLoss?: number;
|
||||||
@@ -974,13 +975,13 @@ export default function TunnelPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-small text-default-500">
|
<div className="text-small text-default-500">
|
||||||
目标地址: <code className="font-mono">{result.targetIp}</code>
|
目标地址: <code className="font-mono">{result.targetIp}:{result.targetPort}</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="text-small text-default-500">
|
<div className="text-small text-default-500">
|
||||||
目标地址: <code className="font-mono">{result.targetIp}</code>
|
目标地址: <code className="font-mono">{result.targetIp}:{result.targetPort}</code>
|
||||||
</div>
|
</div>
|
||||||
<Alert
|
<Alert
|
||||||
color="danger"
|
color="danger"
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export default function UserPage() {
|
|||||||
flow: 100,
|
flow: 100,
|
||||||
num: 10,
|
num: 10,
|
||||||
expTime: null,
|
expTime: null,
|
||||||
flowResetTime: 1
|
flowResetTime: 0
|
||||||
});
|
});
|
||||||
const [userFormLoading, setUserFormLoading] = useState(false);
|
const [userFormLoading, setUserFormLoading] = useState(false);
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ export default function UserPage() {
|
|||||||
flow: 100,
|
flow: 100,
|
||||||
num: 10,
|
num: 10,
|
||||||
expTime: null,
|
expTime: null,
|
||||||
flowResetTime: 1,
|
flowResetTime: 0,
|
||||||
speedId: null
|
speedId: null
|
||||||
});
|
});
|
||||||
const [assignLoading, setAssignLoading] = useState(false);
|
const [assignLoading, setAssignLoading] = useState(false);
|
||||||
@@ -253,7 +253,7 @@ export default function UserPage() {
|
|||||||
flow: 100,
|
flow: 100,
|
||||||
num: 10,
|
num: 10,
|
||||||
expTime: null,
|
expTime: null,
|
||||||
flowResetTime: 1
|
flowResetTime: 0
|
||||||
});
|
});
|
||||||
onUserModalOpen();
|
onUserModalOpen();
|
||||||
};
|
};
|
||||||
@@ -269,7 +269,7 @@ export default function UserPage() {
|
|||||||
flow: user.flow,
|
flow: user.flow,
|
||||||
num: user.num,
|
num: user.num,
|
||||||
expTime: user.expTime ? new Date(user.expTime) : null,
|
expTime: user.expTime ? new Date(user.expTime) : null,
|
||||||
flowResetTime: user.flowResetTime || 1
|
flowResetTime: user.flowResetTime ?? 0
|
||||||
});
|
});
|
||||||
onUserModalOpen();
|
onUserModalOpen();
|
||||||
};
|
};
|
||||||
@@ -338,7 +338,7 @@ export default function UserPage() {
|
|||||||
flow: 100,
|
flow: 100,
|
||||||
num: 10,
|
num: 10,
|
||||||
expTime: null,
|
expTime: null,
|
||||||
flowResetTime: 1,
|
flowResetTime: 0,
|
||||||
speedId: null
|
speedId: null
|
||||||
});
|
});
|
||||||
onTunnelModalOpen();
|
onTunnelModalOpen();
|
||||||
@@ -370,7 +370,7 @@ export default function UserPage() {
|
|||||||
flow: 100,
|
flow: 100,
|
||||||
num: 10,
|
num: 10,
|
||||||
expTime: null,
|
expTime: null,
|
||||||
flowResetTime: 1,
|
flowResetTime: 0,
|
||||||
speedId: null
|
speedId: null
|
||||||
});
|
});
|
||||||
loadUserTunnels(currentUser.id);
|
loadUserTunnels(currentUser.id);
|
||||||
@@ -592,7 +592,7 @@ export default function UserPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-default-600">重置日期</span>
|
<span className="text-default-600">重置日期</span>
|
||||||
<span className="text-xs">每月{user.flowResetTime || 1}号</span>
|
<span className="text-xs">{user.flowResetTime === 0 ? '不重置' : `每月${user.flowResetTime}号`}</span>
|
||||||
</div>
|
</div>
|
||||||
{user.expTime && (
|
{user.expTime && (
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
@@ -716,6 +716,9 @@ export default function UserPage() {
|
|||||||
setUserForm(prev => ({ ...prev, flowResetTime: Number(value) }));
|
setUserForm(prev => ({ ...prev, flowResetTime: Number(value) }));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<SelectItem key="0" textValue="不重置">
|
||||||
|
不重置
|
||||||
|
</SelectItem>
|
||||||
{Array.from({ length: 31 }, (_, i) => i + 1).map(day => (
|
{Array.from({ length: 31 }, (_, i) => i + 1).map(day => (
|
||||||
<SelectItem key={day.toString()} textValue={`每月${day}号(0点重置)`}>
|
<SelectItem key={day.toString()} textValue={`每月${day}号(0点重置)`}>
|
||||||
每月{day}号(0点重置)
|
每月{day}号(0点重置)
|
||||||
@@ -836,6 +839,9 @@ export default function UserPage() {
|
|||||||
setTunnelForm(prev => ({ ...prev, flowResetTime: Number(value) }));
|
setTunnelForm(prev => ({ ...prev, flowResetTime: Number(value) }));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<SelectItem key="0" textValue="不重置">
|
||||||
|
不重置
|
||||||
|
</SelectItem>
|
||||||
{Array.from({ length: 31 }, (_, i) => i + 1).map(day => (
|
{Array.from({ length: 31 }, (_, i) => i + 1).map(day => (
|
||||||
<SelectItem key={day.toString()} textValue={`每月${day}号(0点重置)`}>
|
<SelectItem key={day.toString()} textValue={`每月${day}号(0点重置)`}>
|
||||||
每月{day}号(0点重置)
|
每月{day}号(0点重置)
|
||||||
@@ -919,7 +925,7 @@ export default function UserPage() {
|
|||||||
{userTunnel.speedLimitName || '不限速'}
|
{userTunnel.speedLimitName || '不限速'}
|
||||||
</Chip>
|
</Chip>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>每月{userTunnel.flowResetTime}号</TableCell>
|
<TableCell>{userTunnel.flowResetTime === 0 ? '不重置' : `每月${userTunnel.flowResetTime}号`}</TableCell>
|
||||||
<TableCell>{formatDate(userTunnel.expTime)}</TableCell>
|
<TableCell>{formatDate(userTunnel.expTime)}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -1012,6 +1018,9 @@ export default function UserPage() {
|
|||||||
setEditTunnelForm(prev => prev ? { ...prev, flowResetTime: Number(value) } : null);
|
setEditTunnelForm(prev => prev ? { ...prev, flowResetTime: Number(value) } : null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<SelectItem key="0" textValue="不重置">
|
||||||
|
不重置
|
||||||
|
</SelectItem>
|
||||||
{Array.from({ length: 31 }, (_, i) => i + 1).map(day => (
|
{Array.from({ length: 31 }, (_, i) => i + 1).map(day => (
|
||||||
<SelectItem key={day.toString()} textValue={`每月${day}号(0点重置)`}>
|
<SelectItem key={day.toString()} textValue={`每月${day}号(0点重置)`}>
|
||||||
每月{day}号(0点重置)
|
每月{day}号(0点重置)
|
||||||
|
|||||||
Reference in New Issue
Block a user