mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-30 00:06:38 +08:00
优化ui 添加诊断功能
This commit is contained in:
@@ -7,4 +7,6 @@ public class GostDto {
|
||||
private Integer code;
|
||||
|
||||
private String msg;
|
||||
|
||||
private Object data; // 添加数据字段,用于存储响应的详细数据
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.DecimalMax;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class TunnelDto {
|
||||
@@ -44,6 +47,11 @@ public class TunnelDto {
|
||||
@NotNull(message = "流量计算类型不能为空")
|
||||
private Integer flow;
|
||||
|
||||
// 流量倍率,默认为1.0
|
||||
@DecimalMin(value = "0.0", message = "流量倍率不能小于0.0")
|
||||
@DecimalMax(value = "100.0", message = "流量倍率不能大于100.0")
|
||||
private BigDecimal trafficRatio = new BigDecimal("1.0");
|
||||
|
||||
// 协议类型(隧道转发时使用:tls、tcp、mtls),默认为tls
|
||||
private String protocol;
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.DecimalMax;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class TunnelUpdateDto {
|
||||
@@ -18,6 +21,11 @@ public class TunnelUpdateDto {
|
||||
@NotNull(message = "流量计算类型不能为空")
|
||||
private Integer flow;
|
||||
|
||||
// 流量倍率
|
||||
@DecimalMin(value = "0.0", message = "流量倍率不能小于0.0")
|
||||
@DecimalMax(value = "100.0", message = "流量倍率不能大于100.0")
|
||||
private BigDecimal trafficRatio;
|
||||
|
||||
@NotNull(message = "入口端口开始不能为空")
|
||||
@Min(value = 1, message = "入口端口开始必须大于0")
|
||||
@Max(value = 65535, message = "入口端口开始不能超过65535")
|
||||
|
||||
@@ -70,12 +70,27 @@ public class WebSocketServer extends TextWebSocketHandler {
|
||||
JSONObject responseJson = JSONObject.parseObject(message.getPayload());
|
||||
String requestId = responseJson.getString("requestId");
|
||||
String responseMessage = responseJson.getString("message");
|
||||
String responseType = responseJson.getString("type");
|
||||
JSONObject responseData = responseJson.getJSONObject("data");
|
||||
|
||||
if (requestId != null) {
|
||||
CompletableFuture<GostDto> future = pendingRequests.remove(requestId);
|
||||
if (future != null) {
|
||||
GostDto result = new GostDto();
|
||||
result.setMsg(responseMessage != null ? responseMessage : "无响应消息");
|
||||
|
||||
// 根据响应类型处理不同的数据
|
||||
if ("PingResponse".equals(responseType) && responseData != null) {
|
||||
// 特殊处理ping响应,将完整的响应数据返回
|
||||
result.setMsg(responseMessage != null ? responseMessage : "OK");
|
||||
result.setData(responseData); // 保存ping详细结果
|
||||
} else {
|
||||
// 其他类型的响应
|
||||
result.setMsg(responseMessage != null ? responseMessage : "无响应消息");
|
||||
if (responseData != null) {
|
||||
result.setData(responseData);
|
||||
}
|
||||
}
|
||||
|
||||
future.complete(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@ import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
|
||||
@@ -18,6 +23,16 @@ public class WebSocketConfig implements WebSocketConfigurer {
|
||||
@Resource
|
||||
private WebSocketInterceptor webSocketInterceptor;
|
||||
|
||||
@Bean
|
||||
public ServletContextInitializer websocketBufferConfig() {
|
||||
return new ServletContextInitializer() {
|
||||
@Override
|
||||
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize", String.valueOf(100 * 1024 * 1024));
|
||||
servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize", String.valueOf(100 * 1024 * 1024));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -75,24 +76,15 @@ public class FlowController extends BaseController {
|
||||
return SUCCESS_RESPONSE;
|
||||
}
|
||||
|
||||
List<FlowDto> validFlowData = flowDataList;
|
||||
// // 2. 过滤有效流量数据
|
||||
// List<FlowDto> validFlowData = filterValidFlowData(flowDataList);
|
||||
// if (validFlowData.isEmpty()) {
|
||||
// return SUCCESS_RESPONSE;
|
||||
// }
|
||||
|
||||
|
||||
// 3. 解析服务名称获取ID信息
|
||||
String[] serviceIds = parseServiceName(validFlowData.get(0).getN());
|
||||
// 2. 解析服务名称获取ID信息
|
||||
String[] serviceIds = parseServiceName(flowDataList.get(0).getN());
|
||||
String forwardId = serviceIds[0];
|
||||
String userId = serviceIds[1];
|
||||
String userTunnelId = serviceIds[2];
|
||||
|
||||
// 4. 计算总流量
|
||||
FlowStatistics flowStats = calculateTotalFlow(validFlowData);
|
||||
|
||||
// 5. 一次性查询相关实体,避免后续重复查询
|
||||
// 3. 一次性查询相关实体,避免后续重复查询
|
||||
Forward forward = forwardService.getById(forwardId);
|
||||
User user = userService.getById(userId);
|
||||
UserTunnel userTunnel = null;
|
||||
@@ -100,6 +92,20 @@ public class FlowController extends BaseController {
|
||||
userTunnel = userTunnelService.getById(userTunnelId);
|
||||
}
|
||||
|
||||
// 4. 处理流量倍率
|
||||
List<FlowDto> validFlowData = flowDataList;
|
||||
if (forward != null) {
|
||||
validFlowData = filterFlowData(flowDataList, forward.getTunnelId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 5. 计算总流量
|
||||
FlowStatistics flowStats = calculateTotalFlow(validFlowData);
|
||||
|
||||
|
||||
|
||||
// 6. 获取流量计费类型
|
||||
int flowType = getFlowType(forward);
|
||||
|
||||
@@ -138,6 +144,27 @@ public class FlowController extends BaseController {
|
||||
return SUCCESS_RESPONSE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private List<FlowDto> filterFlowData(List<FlowDto> flowDataList, Integer tunnel_id) {
|
||||
Tunnel tunnel = tunnelService.getById(tunnel_id);
|
||||
if (tunnel != null && tunnel.getTrafficRatio() != null){
|
||||
BigDecimal trafficRatio = tunnel.getTrafficRatio();
|
||||
for (FlowDto flowDto : flowDataList) {
|
||||
// 将Long转为BigDecimal进行计算,然后转回Long
|
||||
BigDecimal originalD = BigDecimal.valueOf(flowDto.getD());
|
||||
BigDecimal originalU = BigDecimal.valueOf(flowDto.getU());
|
||||
|
||||
BigDecimal newD = originalD.multiply(trafficRatio);
|
||||
BigDecimal newU = originalU.multiply(trafficRatio);
|
||||
|
||||
flowDto.setD(newD.longValue());
|
||||
flowDto.setU(newU.longValue());
|
||||
}
|
||||
}
|
||||
return flowDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证节点是否有效
|
||||
*/
|
||||
|
||||
@@ -138,4 +138,17 @@ public class TunnelController extends BaseController {
|
||||
return tunnelService.userTunnel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 隧道诊断功能
|
||||
* @param params 包含tunnelId的参数
|
||||
* @return 诊断结果
|
||||
*/
|
||||
@LogAnnotation
|
||||
@RequireRole
|
||||
@PostMapping("/diagnose")
|
||||
public R diagnoseTunnel(@RequestBody Map<String, Object> params) {
|
||||
Long tunnelId = Long.valueOf(params.get("tunnelId").toString());
|
||||
return tunnelService.diagnoseTunnel(tunnelId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.admin.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@@ -78,6 +79,12 @@ public class Tunnel extends BaseEntity {
|
||||
*/
|
||||
private String protocol;
|
||||
|
||||
/**
|
||||
* 流量倍率
|
||||
*/
|
||||
private BigDecimal trafficRatio;
|
||||
|
||||
|
||||
private String tcpListenAddr;
|
||||
|
||||
private String udpListenAddr;
|
||||
|
||||
@@ -45,5 +45,16 @@ public interface TunnelService extends IService<Tunnel> {
|
||||
*/
|
||||
R deleteTunnel(Long id);
|
||||
|
||||
/**
|
||||
* 获取用户可用的隧道列表
|
||||
* @return 结果
|
||||
*/
|
||||
R userTunnel();
|
||||
|
||||
/**
|
||||
* 隧道诊断功能
|
||||
* @param tunnelId 隧道ID
|
||||
* @return 诊断结果
|
||||
*/
|
||||
R diagnoseTunnel(Long tunnelId);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.admin.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.admin.common.dto.GostDto;
|
||||
import com.admin.common.dto.TunnelDto;
|
||||
import com.admin.common.dto.TunnelListDto;
|
||||
import com.admin.common.dto.TunnelUpdateDto;
|
||||
|
||||
import com.admin.common.lang.R;
|
||||
import com.admin.common.utils.GostUtil;
|
||||
import com.admin.common.utils.JwtUtil;
|
||||
import com.admin.common.utils.WebSocketServer;
|
||||
import com.admin.entity.Forward;
|
||||
import com.admin.entity.Node;
|
||||
import com.admin.entity.Tunnel;
|
||||
@@ -18,6 +21,7 @@ import com.admin.service.ForwardService;
|
||||
import com.admin.service.NodeService;
|
||||
import com.admin.service.TunnelService;
|
||||
import com.admin.service.UserTunnelService;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.Data;
|
||||
@@ -25,7 +29,11 @@ import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -193,6 +201,11 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
||||
existingTunnel.setInPortSta(tunnelUpdateDto.getInPortSta());
|
||||
existingTunnel.setInPortEnd(tunnelUpdateDto.getInPortEnd());
|
||||
|
||||
// 更新流量倍率
|
||||
if (tunnelUpdateDto.getTrafficRatio() != null) {
|
||||
existingTunnel.setTrafficRatio(tunnelUpdateDto.getTrafficRatio());
|
||||
}
|
||||
|
||||
// 更新TCP和UDP监听地址
|
||||
if (StrUtil.isNotBlank(tunnelUpdateDto.getTcpListenAddr())) {
|
||||
existingTunnel.setTcpListenAddr(tunnelUpdateDto.getTcpListenAddr());
|
||||
@@ -413,6 +426,13 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
||||
// 设置流量计算类型
|
||||
tunnel.setFlow(tunnelDto.getFlow());
|
||||
|
||||
// 设置流量倍率,如果为空则设置默认值1.0
|
||||
if (tunnelDto.getTrafficRatio() != null) {
|
||||
tunnel.setTrafficRatio(tunnelDto.getTrafficRatio());
|
||||
} else {
|
||||
tunnel.setTrafficRatio(new BigDecimal("1.0"));
|
||||
}
|
||||
|
||||
// 设置协议类型(仅隧道转发需要)
|
||||
if (tunnelDto.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) {
|
||||
// 隧道转发时,设置协议类型,默认为tls
|
||||
@@ -527,8 +547,6 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
||||
tunnel.setUpdatedTime(currentTime);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查隧道是否存在
|
||||
*
|
||||
@@ -676,6 +694,140 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 隧道诊断功能
|
||||
*
|
||||
* @param tunnelId 隧道ID
|
||||
* @return 诊断结果响应
|
||||
*/
|
||||
@Override
|
||||
public R diagnoseTunnel(Long tunnelId) {
|
||||
// 1. 验证隧道是否存在
|
||||
Tunnel tunnel = this.getById(tunnelId);
|
||||
if (tunnel == null) {
|
||||
return R.err(ERROR_TUNNEL_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 2. 获取入口和出口节点信息
|
||||
Node inNode = nodeService.getById(tunnel.getInNodeId());
|
||||
if (inNode == null) {
|
||||
return R.err(ERROR_IN_NODE_NOT_FOUND);
|
||||
}
|
||||
|
||||
Node outNode = null;
|
||||
if (tunnel.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) {
|
||||
outNode = nodeService.getById(tunnel.getOutNodeId());
|
||||
if (outNode == null) {
|
||||
return R.err(ERROR_OUT_NODE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
List<DiagnosisResult> results = new ArrayList<>();
|
||||
|
||||
// 3. 根据隧道类型执行不同的诊断策略
|
||||
if (tunnel.getType() == TUNNEL_TYPE_PORT_FORWARD) {
|
||||
// 端口转发:只给入口节点发送诊断指令,ping谷歌DNS
|
||||
DiagnosisResult inResult = performPingDiagnosis(inNode, "8.8.8.8", "入口->外网");
|
||||
results.add(inResult);
|
||||
} else {
|
||||
// 隧道转发:入口ping出口,出口ping谷歌DNS
|
||||
DiagnosisResult inToOutResult = performPingDiagnosis(inNode, outNode.getServerIp(), "入口->出口");
|
||||
results.add(inToOutResult);
|
||||
|
||||
DiagnosisResult outToExternalResult = performPingDiagnosis(outNode, "8.8.8.8", "出口->外网");
|
||||
results.add(outToExternalResult);
|
||||
}
|
||||
|
||||
// 4. 构建诊断报告
|
||||
Map<String, Object> diagnosisReport = new HashMap<>();
|
||||
diagnosisReport.put("tunnelId", tunnelId);
|
||||
diagnosisReport.put("tunnelName", tunnel.getName());
|
||||
diagnosisReport.put("tunnelType", tunnel.getType() == TUNNEL_TYPE_PORT_FORWARD ? "端口转发" : "隧道转发");
|
||||
diagnosisReport.put("results", results);
|
||||
diagnosisReport.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
return R.ok(diagnosisReport);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行ping诊断
|
||||
*
|
||||
* @param node 执行ping的节点
|
||||
* @param targetIp 目标IP地址
|
||||
* @param description 诊断描述
|
||||
* @return 诊断结果
|
||||
*/
|
||||
private DiagnosisResult performPingDiagnosis(Node node, String targetIp, String description) {
|
||||
try {
|
||||
// 构建ping请求数据
|
||||
JSONObject pingData = new JSONObject();
|
||||
pingData.put("ip", targetIp);
|
||||
pingData.put("count", 4);
|
||||
|
||||
// 发送ping命令到节点
|
||||
GostDto gostResult = WebSocketServer.send_msg(node.getId(), pingData, "Ping");
|
||||
|
||||
DiagnosisResult result = new DiagnosisResult();
|
||||
result.setNodeId(node.getId());
|
||||
result.setNodeName(node.getName());
|
||||
result.setTargetIp(targetIp);
|
||||
result.setDescription(description);
|
||||
result.setTimestamp(System.currentTimeMillis());
|
||||
|
||||
if (gostResult != null && "OK".equals(gostResult.getMsg())) {
|
||||
// 尝试解析ping响应数据
|
||||
try {
|
||||
if (gostResult.getData() != null) {
|
||||
JSONObject pingResponse = (JSONObject) gostResult.getData();
|
||||
boolean success = pingResponse.getBooleanValue("success");
|
||||
|
||||
result.setSuccess(success);
|
||||
if (success) {
|
||||
result.setMessage("ping成功");
|
||||
result.setAverageTime(pingResponse.getDoubleValue("averageTime"));
|
||||
result.setPacketLoss(pingResponse.getDoubleValue("packetLoss"));
|
||||
} else {
|
||||
result.setMessage(pingResponse.getString("errorMessage"));
|
||||
result.setAverageTime(-1.0);
|
||||
result.setPacketLoss(100.0);
|
||||
}
|
||||
} else {
|
||||
// 没有详细数据,使用默认值
|
||||
result.setSuccess(true);
|
||||
result.setMessage("ping成功");
|
||||
result.setAverageTime(0.0);
|
||||
result.setPacketLoss(0.0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 解析响应数据失败,但ping命令本身成功了
|
||||
result.setSuccess(true);
|
||||
result.setMessage("ping成功,但无法解析详细数据");
|
||||
result.setAverageTime(0.0);
|
||||
result.setPacketLoss(0.0);
|
||||
}
|
||||
} else {
|
||||
result.setSuccess(false);
|
||||
result.setMessage(gostResult != null ? gostResult.getMsg() : "节点无响应");
|
||||
result.setAverageTime(-1.0);
|
||||
result.setPacketLoss(100.0);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
DiagnosisResult result = new DiagnosisResult();
|
||||
result.setNodeId(node.getId());
|
||||
result.setNodeName(node.getName());
|
||||
result.setTargetIp(targetIp);
|
||||
result.setDescription(description);
|
||||
result.setSuccess(false);
|
||||
result.setMessage("诊断执行异常: " + e.getMessage());
|
||||
result.setTimestamp(System.currentTimeMillis());
|
||||
result.setAverageTime(-1.0);
|
||||
result.setPacketLoss(100.0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 内部数据类 ==========
|
||||
|
||||
/**
|
||||
@@ -710,4 +862,20 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
||||
return new NodeValidationResult(true, errorMessage, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊断结果数据类
|
||||
*/
|
||||
@Data
|
||||
public static class DiagnosisResult {
|
||||
private Long nodeId;
|
||||
private String nodeName;
|
||||
private String targetIp;
|
||||
private String description;
|
||||
private boolean success;
|
||||
private String message;
|
||||
private double averageTime;
|
||||
private double packetLoss;
|
||||
private long timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user