Files
flvx/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java
T
2025-06-26 15:33:01 +08:00

951 lines
34 KiB
Java

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;
import com.admin.entity.User;
import com.admin.entity.UserTunnel;
import com.admin.mapper.TunnelMapper;
import com.admin.mapper.UserTunnelMapper;
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;
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;
/**
* <p>
* 隧道服务实现类
* 提供隧道的增删改查功能,包括隧道创建、删除和用户权限管理
* 支持端口转发和隧道转发两种模式
* </p>
*
* @author QAQ
* @since 2025-06-03
*/
@Service
public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> implements TunnelService {
// ========== 常量定义 ==========
/** 隧道类型常量 */
private static final int TUNNEL_TYPE_PORT_FORWARD = 1; // 端口转发
private static final int TUNNEL_TYPE_TUNNEL_FORWARD = 2; // 隧道转发
/** 隧道状态常量 */
private static final int TUNNEL_STATUS_ACTIVE = 1; // 启用状态
/** 节点状态常量 */
private static final int NODE_STATUS_ONLINE = 1; // 节点在线状态
/** 用户角色常量 */
private static final int ADMIN_ROLE_ID = 0; // 管理员角色ID
/** 成功响应消息 */
private static final String SUCCESS_CREATE_MSG = "隧道创建成功";
private static final String SUCCESS_DELETE_MSG = "隧道删除成功";
/** 错误响应消息 */
private static final String ERROR_CREATE_MSG = "隧道创建失败";
private static final String ERROR_DELETE_MSG = "隧道删除失败";
private static final String ERROR_TUNNEL_NOT_FOUND = "隧道不存在";
private static final String ERROR_TUNNEL_NAME_EXISTS = "隧道名称已存在";
private static final String ERROR_IN_NODE_NOT_FOUND = "入口节点不存在";
private static final String ERROR_OUT_NODE_NOT_FOUND = "出口节点不存在";
private static final String ERROR_OUT_NODE_REQUIRED = "出口节点不能为空";
private static final String ERROR_OUT_PORT_REQUIRED = "出口端口不能为空";
private static final String ERROR_SAME_NODE_NOT_ALLOWED = "隧道转发模式下,入口和出口不能是同一个节点";
private static final String ERROR_IN_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_IN_NODE_OFFLINE = "入口节点当前离线,请确保节点正常运行";
private static final String ERROR_OUT_NODE_OFFLINE = "出口节点当前离线,请确保节点正常运行";
/** 使用检查相关消息 */
private static final String ERROR_FORWARDS_IN_USE = "该隧道还有 %d 个转发在使用,请先删除相关转发";
private static final String ERROR_USER_PERMISSIONS_IN_USE = "该隧道还有 %d 个用户权限关联,请先取消用户权限分配";
// ========== 依赖注入 ==========
@Resource
UserTunnelMapper userTunnelMapper;
@Resource
NodeService nodeService;
@Resource
ForwardService forwardService;
@Resource
UserTunnelService userTunnelService;
// ========== 公共接口实现 ==========
/**
* 创建隧道
* 支持端口转发和隧道转发两种模式
*
* @param tunnelDto 隧道创建数据传输对象
* @return 创建结果响应
*/
@Override
public R createTunnel(TunnelDto tunnelDto) {
// 1. 验证隧道名称唯一性
R nameValidationResult = validateTunnelNameUniqueness(tunnelDto.getName());
if (nameValidationResult.getCode() != 0) {
return nameValidationResult;
}
// 2. 验证隧道转发类型的必要参数
if (tunnelDto.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) {
R tunnelForwardValidationResult = validateTunnelForwardCreate(tunnelDto);
if (tunnelForwardValidationResult.getCode() != 0) {
return tunnelForwardValidationResult;
}
}
// 3. 验证入口节点和端口
NodeValidationResult inNodeValidation = validateInNode(tunnelDto);
if (inNodeValidation.isHasError()) {
return R.err(inNodeValidation.getErrorMessage());
}
// 4. 构建隧道实体
Tunnel tunnel = buildTunnelEntity(tunnelDto, inNodeValidation.getNode());
// 5. 根据隧道类型设置出口参数
R outNodeSetupResult = setupOutNodeParameters(tunnel, tunnelDto, inNodeValidation.getNode().getServerIp());
if (outNodeSetupResult.getCode() != 0) {
return outNodeSetupResult;
}
// 6. 设置默认属性并保存
setDefaultTunnelProperties(tunnel);
boolean result = this.save(tunnel);
return result ? R.ok(SUCCESS_CREATE_MSG) : R.err(ERROR_CREATE_MSG);
}
/**
* 获取所有隧道列表
*
* @return 包含所有隧道的响应对象
*/
@Override
public R getAllTunnels() {
List<Tunnel> tunnelList = this.list();
return R.ok(tunnelList);
}
/**
* 更新隧道(只允许修改名称、流量计费、端口范围)
*
* @param tunnelUpdateDto 更新数据传输对象
* @return 更新结果响应
*/
@Override
public R updateTunnel(TunnelUpdateDto tunnelUpdateDto) {
// 1. 验证隧道是否存在
Tunnel existingTunnel = this.getById(tunnelUpdateDto.getId());
if (existingTunnel == null) {
return R.err(ERROR_TUNNEL_NOT_FOUND);
}
// 2. 验证隧道名称唯一性(排除自身)
R nameValidationResult = validateTunnelNameUniquenessForUpdate(
tunnelUpdateDto.getName(), tunnelUpdateDto.getId());
if (nameValidationResult.getCode() != 0) {
return nameValidationResult;
}
// 3. 验证端口范围
R portValidationResult = validatePortRanges(tunnelUpdateDto);
if (portValidationResult.getCode() != 0) {
return portValidationResult;
}
// 4. 验证隧道转发类型的出口端口
if (existingTunnel.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) {
R tunnelTypeValidationResult = validateTunnelForwardUpdate(tunnelUpdateDto);
if (tunnelTypeValidationResult.getCode() != 0) {
return tunnelTypeValidationResult;
}
}
// 5. 更新允许修改的字段
existingTunnel.setName(tunnelUpdateDto.getName());
existingTunnel.setFlow(tunnelUpdateDto.getFlow());
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());
}
if (StrUtil.isNotBlank(tunnelUpdateDto.getUdpListenAddr())) {
existingTunnel.setUdpListenAddr(tunnelUpdateDto.getUdpListenAddr());
}
if (existingTunnel.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) {
existingTunnel.setOutIpSta(tunnelUpdateDto.getOutIpSta());
existingTunnel.setOutIpEnd(tunnelUpdateDto.getOutIpEnd());
}else{
existingTunnel.setOutIpSta(tunnelUpdateDto.getInPortSta());
existingTunnel.setOutIpEnd(tunnelUpdateDto.getInPortEnd());
}
// 6. 保存更新
boolean result = this.updateById(existingTunnel);
return result ? R.ok("隧道更新成功") : R.err("隧道更新失败");
}
/**
* 删除隧道
* 删除前会检查是否有转发或用户权限在使用该隧道
*
* @param id 隧道ID
* @return 删除结果响应
*/
@Override
public R deleteTunnel(Long id) {
// 1. 验证隧道是否存在
if (!isTunnelExists(id)) {
return R.err(ERROR_TUNNEL_NOT_FOUND);
}
// 2. 检查隧道使用情况
R usageCheckResult = checkTunnelUsage(id);
if (usageCheckResult.getCode() != 0) {
return usageCheckResult;
}
// 3. 执行删除操作
boolean result = this.removeById(id);
return result ? R.ok(SUCCESS_DELETE_MSG) : R.err(ERROR_DELETE_MSG);
}
/**
* 获取用户可用的隧道列表
* 管理员可以看到所有启用的隧道,普通用户只能看到有权限的启用隧道
*
* @return 用户可用隧道列表响应
*/
@Override
public R userTunnel() {
UserInfo currentUser = getCurrentUserInfo();
// 根据用户角色获取隧道列表
List<Tunnel> tunnelEntities = getUserAccessibleTunnels(currentUser);
// 转换为DTO并返回
List<TunnelListDto> tunnelDtos = convertToTunnelListDtos(tunnelEntities);
return R.ok(tunnelDtos);
}
// ========== 私有辅助方法 ==========
/**
* 获取当前用户信息
*
* @return 用户信息对象
*/
private UserInfo getCurrentUserInfo() {
Integer roleId = JwtUtil.getRoleIdFromToken();
Integer userId = JwtUtil.getUserIdFromToken();
return new UserInfo(userId, roleId);
}
/**
* 验证隧道名称唯一性
*
* @param tunnelName 隧道名称
* @return 验证结果响应
*/
private R validateTunnelNameUniqueness(String tunnelName) {
Tunnel existTunnel = this.getOne(new QueryWrapper<Tunnel>().eq("name", tunnelName));
if (existTunnel != null) {
return R.err(ERROR_TUNNEL_NAME_EXISTS);
}
return R.ok();
}
/**
* 验证隧道名称唯一性(更新时使用,排除自身)
*
* @param tunnelName 隧道名称
* @param tunnelId 隧道ID(要排除的隧道)
* @return 验证结果响应
*/
private R validateTunnelNameUniquenessForUpdate(String tunnelName, Long tunnelId) {
QueryWrapper<Tunnel> query = new QueryWrapper<>();
query.eq("name", tunnelName);
query.ne("id", tunnelId); // 排除自身
Tunnel existTunnel = this.getOne(query);
if (existTunnel != null) {
return R.err(ERROR_TUNNEL_NAME_EXISTS);
}
return R.ok();
}
/**
* 验证端口范围
*
* @param tunnelUpdateDto 更新数据传输对象
* @return 验证结果响应
*/
private R validatePortRanges(TunnelUpdateDto tunnelUpdateDto) {
// 验证入口端口范围
if (tunnelUpdateDto.getInPortSta() > tunnelUpdateDto.getInPortEnd()) {
return R.err(ERROR_IN_PORT_RANGE_INVALID);
}
// 验证出口端口范围(如果有的话)
if (tunnelUpdateDto.getOutIpSta() != null && tunnelUpdateDto.getOutIpEnd() != null) {
if (tunnelUpdateDto.getOutIpSta() > tunnelUpdateDto.getOutIpEnd()) {
return R.err(ERROR_OUT_PORT_RANGE_INVALID);
}
}
return R.ok();
}
/**
* 验证隧道转发更新时的出口端口
*
* @param tunnelUpdateDto 更新数据传输对象
* @return 验证结果响应
*/
private R validateTunnelForwardUpdate(TunnelUpdateDto tunnelUpdateDto) {
// 验证出口端口参数不能为空
if (tunnelUpdateDto.getOutIpSta() == null || tunnelUpdateDto.getOutIpEnd() == null) {
return R.err(ERROR_OUT_PORT_REQUIRED);
}
// 验证出口端口范围
if (tunnelUpdateDto.getOutIpSta() > tunnelUpdateDto.getOutIpEnd()) {
return R.err(ERROR_OUT_PORT_RANGE_INVALID);
}
return R.ok();
}
/**
* 验证隧道转发创建时的必要参数
*
* @param tunnelDto 隧道创建数据传输对象
* @return 验证结果响应
*/
private R validateTunnelForwardCreate(TunnelDto tunnelDto) {
// 验证出口节点不能为空
if (tunnelDto.getOutNodeId() == null) {
return R.err(ERROR_OUT_NODE_REQUIRED);
}
// 验证出口端口参数不能为空
if (tunnelDto.getOutIpSta() == null || tunnelDto.getOutIpEnd() == null) {
return R.err(ERROR_OUT_PORT_REQUIRED);
}
// 验证出口端口范围
if (tunnelDto.getOutIpSta() > tunnelDto.getOutIpEnd()) {
return R.err(ERROR_OUT_PORT_RANGE_INVALID);
}
return R.ok();
}
/**
* 验证入口节点和端口
*
* @param tunnelDto 隧道创建DTO
* @return 节点验证结果
*/
private NodeValidationResult validateInNode(TunnelDto tunnelDto) {
// 验证入口节点是否存在
Node inNode = nodeService.getById(tunnelDto.getInNodeId());
if (inNode == null) {
return NodeValidationResult.error(ERROR_IN_NODE_NOT_FOUND);
}
// 验证入口节点是否在线
if (inNode.getStatus() != NODE_STATUS_ONLINE) {
return NodeValidationResult.error(ERROR_IN_NODE_OFFLINE);
}
// 验证入口端口范围
if (tunnelDto.getInPortSta() > tunnelDto.getInPortEnd()) {
return NodeValidationResult.error(ERROR_IN_PORT_RANGE_INVALID);
}
return NodeValidationResult.success(inNode);
}
/**
* 构建隧道实体对象
*
* @param tunnelDto 隧道创建DTO
* @param inNode 入口节点
* @return 构建完成的隧道对象
*/
private Tunnel buildTunnelEntity(TunnelDto tunnelDto, Node inNode) {
Tunnel tunnel = new Tunnel();
BeanUtils.copyProperties(tunnelDto, tunnel);
// 设置入口节点信息
tunnel.setInNodeId(tunnelDto.getInNodeId());
tunnel.setInIp(inNode.getIp());
// 设置流量计算类型
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
String protocol = StrUtil.isNotBlank(tunnelDto.getProtocol()) ? tunnelDto.getProtocol() : "tls";
tunnel.setProtocol(protocol);
} else {
// 端口转发时,协议类型为null
tunnel.setProtocol(null);
}
// 设置TCP和UDP监听地址
tunnel.setTcpListenAddr(StrUtil.isNotBlank(tunnelDto.getTcpListenAddr()) ?
tunnelDto.getTcpListenAddr() : "0.0.0.0");
tunnel.setUdpListenAddr(StrUtil.isNotBlank(tunnelDto.getUdpListenAddr()) ?
tunnelDto.getUdpListenAddr() : "0.0.0.0");
return tunnel;
}
/**
* 设置出口节点参数
*
* @param tunnel 隧道对象
* @param tunnelDto 隧道创建DTO
* @return 设置结果响应
*/
private R setupOutNodeParameters(Tunnel tunnel, TunnelDto tunnelDto, String server_ip) {
if (tunnelDto.getType() == TUNNEL_TYPE_PORT_FORWARD) {
// 端口转发:出口参数使用入口参数
return setupPortForwardOutParameters(tunnel, tunnelDto, server_ip);
} else {
// 隧道转发:需要验证出口参数
return setupTunnelForwardOutParameters(tunnel, tunnelDto);
}
}
/**
* 设置端口转发的出口参数
*
* @param tunnel 隧道对象
* @param tunnelDto 隧道创建DTO
* @return 设置结果响应
*/
private R setupPortForwardOutParameters(Tunnel tunnel, TunnelDto tunnelDto, String server_ip) {
tunnel.setOutNodeId(tunnelDto.getInNodeId());
tunnel.setOutIp(server_ip);
tunnel.setOutIpSta(tunnelDto.getInPortSta());
tunnel.setOutIpEnd(tunnelDto.getInPortEnd());
return R.ok();
}
/**
* 设置隧道转发的出口参数
*
* @param tunnel 隧道对象
* @param tunnelDto 隧道创建DTO
* @return 设置结果响应
*/
private R setupTunnelForwardOutParameters(Tunnel tunnel, TunnelDto tunnelDto) {
// 验证出口节点不能为空
if (tunnelDto.getOutNodeId() == null) {
return R.err(ERROR_OUT_NODE_REQUIRED);
}
// 验证入口和出口不能是同一个节点
if (tunnelDto.getInNodeId().equals(tunnelDto.getOutNodeId())) {
return R.err(ERROR_SAME_NODE_NOT_ALLOWED);
}
// 验证协议类型
String protocol = tunnelDto.getProtocol();
if (StrUtil.isNotBlank(protocol) &&
!protocol.equals("tls") && !protocol.equals("tcp") && !protocol.equals("mtls")) {
return R.err("协议类型只能为tls、tcp或mtls");
}
// 验证出口节点是否存在
Node outNode = nodeService.getById(tunnelDto.getOutNodeId());
if (outNode == null) {
return R.err(ERROR_OUT_NODE_NOT_FOUND);
}
// 验证出口节点是否在线
if (outNode.getStatus() != NODE_STATUS_ONLINE) {
return R.err(ERROR_OUT_NODE_OFFLINE);
}
// 验证出口端口参数
if (tunnelDto.getOutIpSta() == null || tunnelDto.getOutIpEnd() == null) {
return R.err(ERROR_OUT_PORT_REQUIRED);
}
if (tunnelDto.getOutIpSta() > tunnelDto.getOutIpEnd()) {
return R.err(ERROR_OUT_PORT_RANGE_INVALID);
}
// 设置出口参数
tunnel.setOutNodeId(tunnelDto.getOutNodeId());
tunnel.setOutIp(outNode.getServerIp());
return R.ok();
}
/**
* 设置隧道默认属性
*
* @param tunnel 隧道对象
*/
private void setDefaultTunnelProperties(Tunnel tunnel) {
tunnel.setStatus(TUNNEL_STATUS_ACTIVE);
long currentTime = System.currentTimeMillis();
tunnel.setCreatedTime(currentTime);
tunnel.setUpdatedTime(currentTime);
}
/**
* 检查隧道是否存在
*
* @param tunnelId 隧道ID
* @return 隧道是否存在
*/
private boolean isTunnelExists(Long tunnelId) {
return this.getById(tunnelId) != null;
}
/**
* 检查隧道使用情况
*
* @param tunnelId 隧道ID
* @return 检查结果响应
*/
private R checkTunnelUsage(Long tunnelId) {
// 检查转发使用情况
R forwardCheckResult = checkForwardUsage(tunnelId);
if (forwardCheckResult.getCode() != 0) {
return forwardCheckResult;
}
// 检查用户权限使用情况
return checkUserPermissionUsage(tunnelId);
}
/**
* 检查转发使用情况
*
* @param tunnelId 隧道ID
* @return 检查结果响应
*/
private R checkForwardUsage(Long tunnelId) {
QueryWrapper<Forward> forwardQuery = new QueryWrapper<>();
forwardQuery.eq("tunnel_id", tunnelId);
long forwardCount = forwardService.count(forwardQuery);
if (forwardCount > 0) {
String errorMsg = String.format(ERROR_FORWARDS_IN_USE, forwardCount);
return R.err(errorMsg);
}
return R.ok();
}
/**
* 检查用户权限使用情况
*
* @param tunnelId 隧道ID
* @return 检查结果响应
*/
private R checkUserPermissionUsage(Long tunnelId) {
QueryWrapper<UserTunnel> userTunnelQuery = new QueryWrapper<>();
userTunnelQuery.eq("tunnel_id", tunnelId);
long userTunnelCount = userTunnelService.count(userTunnelQuery);
if (userTunnelCount > 0) {
String errorMsg = String.format(ERROR_USER_PERMISSIONS_IN_USE, userTunnelCount);
return R.err(errorMsg);
}
return R.ok();
}
/**
* 获取用户可访问的隧道列表
*
* @param userInfo 用户信息
* @return 隧道列表
*/
private List<Tunnel> getUserAccessibleTunnels(UserInfo userInfo) {
if (userInfo.getRoleId() == ADMIN_ROLE_ID) {
// 管理员:获取所有启用状态的隧道
return getActiveTunnels();
} else {
// 普通用户:根据权限获取启用状态的隧道
return getUserAuthorizedTunnels(userInfo.getUserId());
}
}
/**
* 获取所有启用状态的隧道
*
* @return 启用状态的隧道列表
*/
private List<Tunnel> getActiveTunnels() {
return this.list(new QueryWrapper<Tunnel>().eq("status", TUNNEL_STATUS_ACTIVE));
}
/**
* 获取用户有权限的启用隧道
*
* @param userId 用户ID
* @return 用户有权限的隧道列表
*/
private List<Tunnel> getUserAuthorizedTunnels(Integer userId) {
List<UserTunnel> userTunnels = userTunnelMapper.selectList(
new QueryWrapper<UserTunnel>().eq("user_id", userId)
);
if (userTunnels.isEmpty()) {
return java.util.Collections.emptyList(); // 返回空列表
}
List<Integer> tunnelIds = userTunnels.stream()
.map(UserTunnel::getTunnelId)
.collect(Collectors.toList());
return this.list(new QueryWrapper<Tunnel>()
.in("id", tunnelIds)
.eq("status", TUNNEL_STATUS_ACTIVE));
}
/**
* 将隧道实体列表转换为DTO列表
*
* @param tunnelEntities 隧道实体列表
* @return 隧道DTO列表
*/
private List<TunnelListDto> convertToTunnelListDtos(List<Tunnel> tunnelEntities) {
return tunnelEntities.stream()
.map(this::convertToTunnelListDto)
.collect(Collectors.toList());
}
/**
* 将Tunnel实体转换为TunnelListDto
*
* @param tunnel 隧道实体
* @return 隧道列表DTO
*/
private TunnelListDto convertToTunnelListDto(Tunnel tunnel) {
TunnelListDto dto = new TunnelListDto();
dto.setId(tunnel.getId().intValue());
dto.setName(tunnel.getName());
dto.setIp(tunnel.getInIp());
dto.setInPortSta(tunnel.getInPortSta());
dto.setInPortEnd(tunnel.getInPortEnd());
dto.setOutIp(tunnel.getOutIp());
dto.setOutIpSta(tunnel.getOutIpSta());
dto.setOutIpEnd(tunnel.getOutIpEnd());
dto.setType(tunnel.getType());
dto.setProtocol(tunnel.getProtocol());
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 = performPingDiagnosisWithConnectionCheck(inNode, "8.8.8.8", "入口->外网");
results.add(inResult);
} else {
// 隧道转发:入口ping出口,出口ping谷歌DNS
DiagnosisResult inToOutResult = performPingDiagnosisWithConnectionCheck(inNode, outNode.getServerIp(), "入口->出口");
results.add(inToOutResult);
// 先检查出口节点的真实连接状态,然后再进行诊断
DiagnosisResult outToExternalResult = performPingDiagnosisWithConnectionCheck(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;
}
}
/**
* 执行ping诊断(带连接状态检查)
*
* @param node 执行ping的节点
* @param targetIp 目标IP地址
* @param description 诊断描述
* @return 诊断结果
*/
private DiagnosisResult performPingDiagnosisWithConnectionCheck(Node node, String targetIp, String description) {
DiagnosisResult result = new DiagnosisResult();
result.setNodeId(node.getId());
result.setNodeName(node.getName());
result.setTargetIp(targetIp);
result.setDescription(description);
result.setTimestamp(System.currentTimeMillis());
try {
// 首先检查节点的WebSocket连接状态
if (!isNodeWebSocketConnected(node.getId())) {
// 如果WebSocket连接有问题,更新节点状态并返回错误
node.setStatus(0); // 标记为离线
nodeService.updateById(node);
result.setSuccess(false);
result.setMessage("节点WebSocket连接已断开,请检查节点是否正常运行");
result.setAverageTime(-1.0);
result.setPacketLoss(100.0);
return result;
}
// 如果连接正常,执行标准的ping诊断
return performPingDiagnosis(node, targetIp, description);
} catch (Exception e) {
result.setSuccess(false);
result.setMessage("连接检查异常: " + e.getMessage());
result.setAverageTime(-1.0);
result.setPacketLoss(100.0);
return result;
}
}
/**
* 检查节点的WebSocket连接是否有效
*
* @param nodeId 节点ID
* @return 连接是否有效
*/
private boolean isNodeWebSocketConnected(Long nodeId) {
try {
// 发送一个简单的测试ping,超时时间设为3秒
JSONObject testData = new JSONObject();
testData.put("ip", "127.0.0.1");
testData.put("count", 1);
GostDto testResult = WebSocketServer.send_msg(nodeId, testData, "Ping");
// 如果能收到任何响应(不管是成功还是失败),说明WebSocket连接正常
return testResult != null &&
!testResult.getMsg().equals("节点不在线") &&
!testResult.getMsg().equals("节点连接已断开") &&
!testResult.getMsg().equals("等待响应超时");
} catch (Exception e) {
return false;
}
}
// ========== 内部数据类 ==========
/**
* 用户信息封装类
*/
@Data
private static class UserInfo {
private final Integer userId;
private final Integer roleId;
}
/**
* 节点验证结果封装类
*/
@Data
private static class NodeValidationResult {
private final boolean hasError;
private final String errorMessage;
private final Node node;
private NodeValidationResult(boolean hasError, String errorMessage, Node node) {
this.hasError = hasError;
this.errorMessage = errorMessage;
this.node = node;
}
public static NodeValidationResult success(Node node) {
return new NodeValidationResult(false, null, node);
}
public static NodeValidationResult error(String errorMessage) {
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;
}
}