package com.admin.service.impl; import com.admin.common.dto.*; 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.*; import com.admin.mapper.TunnelMapper; import com.admin.mapper.UserTunnelMapper; import com.admin.service.*; import com.alibaba.fastjson.JSONArray; 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.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.*; import java.util.stream.Collectors; /** * * @author QAQ * @since 2025-06-03 */ @Service public class TunnelServiceImpl extends ServiceImpl implements TunnelService { @Resource UserTunnelMapper userTunnelMapper; @Resource NodeService nodeService; @Resource ForwardService forwardService; @Resource UserTunnelService userTunnelService; @Resource ChainTunnelService chainTunnelService; @Resource ForwardPortService forwardPortService; @Override public R createTunnel(TunnelDto tunnelDto) { int count = this.count(new QueryWrapper().eq("name", tunnelDto.getName())); if (count > 0) return R.err("隧道名称重复"); if (tunnelDto.getType() == 2 && tunnelDto.getOutNodeId() == null) return R.err("出口不能为空"); List chainTunnels = new ArrayList<>(); Map nodes = new HashMap<>(); List node_ids = new ArrayList<>(); for (ChainTunnel in_node : tunnelDto.getInNodeId()) { node_ids.add(in_node.getNodeId()); chainTunnels.add(in_node); Node node = nodeService.getById(in_node.getNodeId()); if (node == null) return R.err("节点不存在"); nodes.put(node.getId(), node); } if (tunnelDto.getType() == 2) { // 处理转发链节点,为每一跳设置inx int inx = 1; for (List chainNode : tunnelDto.getChainNodes()) { for (ChainTunnel chain_node : chainNode) { node_ids.add(chain_node.getNodeId()); Node node = nodeService.getById(chain_node.getNodeId()); if (node == null) return R.err("节点不存在"); nodes.put(node.getId(), node); Integer nodePort = getNodePort(chain_node.getNodeId()); chain_node.setPort(nodePort); chain_node.setInx(inx); // 设置转发链序号 chainTunnels.add(chain_node); } inx++; // 每一跳递增 } for (ChainTunnel out_node : tunnelDto.getOutNodeId()) { node_ids.add(out_node.getNodeId()); Node node = nodeService.getById(out_node.getNodeId()); if (node == null) return R.err("节点不存在"); nodes.put(node.getId(), node); Integer nodePort = getNodePort(out_node.getNodeId()); out_node.setPort(nodePort); chainTunnels.add(out_node); } } Set set = new HashSet<>(node_ids); boolean hasDuplicate = set.size() != node_ids.size(); if (hasDuplicate) return R.err("节点重复"); List list = nodeService.list(new QueryWrapper().in("id", node_ids)); if (list.size() != node_ids.size()) return R.err("部分节点不存在"); for (Node node : list) { if (node.getStatus() != 1) return R.err("部分节点不在线"); } Tunnel tunnel = new Tunnel(); BeanUtils.copyProperties(tunnelDto, tunnel); tunnel.setStatus(1); long currentTime = System.currentTimeMillis(); tunnel.setCreatedTime(currentTime); tunnel.setUpdatedTime(currentTime); // When tunnels are ordered via `inx`, new tunnels should be appended. // Only apply this when an order already exists (max `inx` > 0) to avoid // changing behavior for deployments still relying on local ordering. Tunnel lastByInx = this.getOne(new QueryWrapper() .select("inx") .orderByDesc("inx") .orderByDesc("id") .last("LIMIT 1")); Integer maxInx = lastByInx == null ? null : lastByInx.getInx(); if (maxInx != null && maxInx > 0) { tunnel.setInx(maxInx + 1); } if (StringUtils.isEmpty(tunnel.getInIp())){ java.util.LinkedHashSet inIps = new java.util.LinkedHashSet<>(); for (ChainTunnel chainTunnel : tunnelDto.getInNodeId()) { Node node = nodes.get(chainTunnel.getNodeId()); if (node == null) continue; if (cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIpV4())) { inIps.add(node.getServerIpV4().trim()); } if (cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIpV6())) { inIps.add(node.getServerIpV6().trim()); } if (cn.hutool.core.util.StrUtil.isBlank(node.getServerIpV4()) && cn.hutool.core.util.StrUtil.isBlank(node.getServerIpV6()) && cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIp())) { inIps.add(node.getServerIp().trim()); } } if (!inIps.isEmpty()) { tunnel.setInIp(String.join(",", inIps)); } } this.save(tunnel); for (ChainTunnel chainTunnel : chainTunnels) { chainTunnel.setTunnelId(tunnel.getId()); } chainTunnelService.saveBatch(chainTunnels); List chain_success = new ArrayList<>(); List service_success = new ArrayList<>(); if (tunnel.getType() == 2) { for (ChainTunnel in_node : tunnelDto.getInNodeId()) { // 创建Chain, 指向chainNode的第一跳。如果chainNode为空就是指向出口 if (tunnelDto.getChainNodes().isEmpty()) { // 指向出口 GostDto gostDto; try { gostDto = GostUtil.AddChains(in_node.getNodeId(), tunnelDto.getOutNodeId(), nodes); } catch (RuntimeException e) { this.removeById(tunnel.getId()); chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); return R.err(e.getMessage()); } if (!Objects.equals(gostDto.getMsg(), "OK")) { this.removeById(tunnel.getId()); chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); return R.err(gostDto.getMsg()); } } else { GostDto gostDto; try { gostDto = GostUtil.AddChains(in_node.getNodeId(), tunnelDto.getChainNodes().getFirst(), nodes);// 指向第一跳 } catch (RuntimeException e) { this.removeById(tunnel.getId()); chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); for (JSONObject chainSuccess : chain_success) { GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); System.out.println(deleteChains); } return R.err(e.getMessage()); } if (Objects.equals(gostDto.getMsg(), "OK")){ JSONObject data = new JSONObject(); data.put("node_id", in_node.getNodeId()); data.put("name", "chains_" + tunnel.getId()); chain_success.add(data); }else { this.removeById(tunnel.getId()); chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); for (JSONObject chainSuccess : chain_success) { GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); System.out.println(deleteChains); } return R.err(gostDto.getMsg()); } } } for (int i = 0; i < tunnelDto.getChainNodes().size(); i++) { // 创建Chain和Service。每一条的Chain都是指向下一跳。最后一跳指向出口, Service是监听端口 List chainTunnels1 = tunnelDto.getChainNodes().get(i); for (ChainTunnel chainTunnel : chainTunnels1) { int inx = i+1; if (inx >= tunnelDto.getChainNodes().size()) { // 指向出口 GostDto gostDto; try { gostDto = GostUtil.AddChains(chainTunnel.getNodeId(), tunnelDto.getOutNodeId(), nodes); } catch (RuntimeException e) { this.removeById(tunnel.getId()); chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); for (JSONObject chainSuccess : chain_success) { GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); System.out.println(deleteChains); } return R.err(e.getMessage()); } if (Objects.equals(gostDto.getMsg(), "OK")){ JSONObject data = new JSONObject(); data.put("node_id", chainTunnel.getNodeId()); data.put("name", "chains_" + tunnel.getId()); chain_success.add(data); }else { this.removeById(tunnel.getId()); chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); for (JSONObject chainSuccess : chain_success) { GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); System.out.println(deleteChains); } return R.err(gostDto.getMsg()); } } else { GostDto gostDto; try { gostDto = GostUtil.AddChains(chainTunnel.getNodeId(), tunnelDto.getChainNodes().get(inx), nodes); } catch (RuntimeException e) { this.removeById(tunnel.getId()); chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); for (JSONObject chainSuccess : chain_success) { GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); System.out.println(deleteChains); } return R.err(e.getMessage()); } if (Objects.equals(gostDto.getMsg(), "OK")){ JSONObject data = new JSONObject(); data.put("node_id", chainTunnel.getNodeId()); data.put("name", "chains_" + tunnel.getId()); chain_success.add(data); }else { this.removeById(tunnel.getId()); chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); for (JSONObject chainSuccess : chain_success) { GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); System.out.println(deleteChains); } return R.err(gostDto.getMsg()); } } GostDto gostDto = GostUtil.AddChainService(chainTunnel.getNodeId(), chainTunnel, nodes); if (Objects.equals(gostDto.getMsg(), "OK")){ JSONObject data = new JSONObject(); data.put("node_id", chainTunnel.getNodeId()); data.put("name", tunnel.getId() + "_tls"); service_success.add(data); }else { this.removeById(tunnel.getId()); chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); for (JSONObject serviceSuccess : service_success) { JSONArray jsonArray = new JSONArray(); jsonArray.add(serviceSuccess.getString("name")); GostDto deleteService = GostUtil.DeleteService(serviceSuccess.getLong("node_id"), jsonArray); System.out.println(deleteService); } return R.err(gostDto.getMsg()); } } } for (ChainTunnel out_node : tunnelDto.getOutNodeId()) { GostDto gostDto = GostUtil.AddChainService(out_node.getNodeId(), out_node, nodes); if (Objects.equals(gostDto.getMsg(), "OK")){ JSONObject data = new JSONObject(); data.put("node_id", out_node.getNodeId()); data.put("name", tunnel.getId() + "_tls"); service_success.add(data); }else { this.removeById(tunnel.getId()); chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); for (JSONObject serviceSuccess : service_success) { JSONArray jsonArray = new JSONArray(); jsonArray.add(serviceSuccess.getString("name")); GostDto deleteService = GostUtil.DeleteService(serviceSuccess.getLong("node_id"), jsonArray); System.out.println(deleteService); } return R.err(gostDto.getMsg()); } } } return R.ok(); } @Override public R getAllTunnels() { List tunnelList = this.list(new QueryWrapper().orderByAsc("inx").orderByAsc("id")); // 查询所有隧道的ChainTunnel信息 List tunnelIds = tunnelList.stream() .map(Tunnel::getId) .collect(Collectors.toList()); if (tunnelIds.isEmpty()) { return R.ok(new ArrayList()); } // 批量查询所有ChainTunnel记录 List allChainTunnels = chainTunnelService.list( new QueryWrapper().in("tunnel_id", tunnelIds) ); // 按tunnelId分组 Map> chainTunnelMap = allChainTunnels.stream() .collect(Collectors.groupingBy(ChainTunnel::getTunnelId)); // 转换为TunnelDetailDto列表 List detailDtoList = tunnelList.stream() .map(tunnel -> { TunnelDetailDto detailDto = new TunnelDetailDto(); BeanUtils.copyProperties(tunnel, detailDto); List chainTunnels = chainTunnelMap.getOrDefault(tunnel.getId(), new ArrayList<>()); // 按chainType分类节点 // 入口节点 (chainType = 1) List inNodes = chainTunnels.stream() .filter(ct -> ct.getChainType() != null && ct.getChainType() == 1) .collect(Collectors.toList()); detailDto.setInNodeId(inNodes); detailDto.setInIp(tunnel.getInIp()); // 转发链节点 (chainType = 2) - 按inx分组 Map> chainNodesMap = chainTunnels.stream() .filter(ct -> ct.getChainType() != null && ct.getChainType() == 2) .collect(Collectors.groupingBy( ct -> ct.getInx() != null ? ct.getInx() : 0, Collectors.toList() )); // 将Map转换为按inx排序的二维列表 List> chainNodesList = chainNodesMap.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .map(Map.Entry::getValue) .collect(Collectors.toList()); detailDto.setChainNodes(chainNodesList); // 出口节点 (chainType = 3) List outNodes = chainTunnels.stream() .filter(ct -> ct.getChainType() != null && ct.getChainType() == 3) .collect(Collectors.toList()); detailDto.setOutNodeId(outNodes); return detailDto; }) .collect(Collectors.toList()); return R.ok(detailDtoList); } @Override @Transactional public R updateTunnelOrder(Map params) { if (!params.containsKey("tunnels")) { return R.err("缺少tunnels参数"); } @SuppressWarnings("unchecked") List> tunnelsList = (List>) params.get("tunnels"); if (tunnelsList == null || tunnelsList.isEmpty()) { return R.err("tunnels参数不能为空"); } List tunnelsToUpdate = new ArrayList<>(); for (Map tunnelData : tunnelsList) { Long id = Long.valueOf(tunnelData.get("id").toString()); Integer inx = Integer.valueOf(tunnelData.get("inx").toString()); Tunnel tunnel = new Tunnel(); tunnel.setId(id); tunnel.setInx(inx); tunnelsToUpdate.add(tunnel); } this.updateBatchById(tunnelsToUpdate); return R.ok(); } @Override public R updateTunnel(TunnelUpdateDto tunnelUpdateDto) { Tunnel existingTunnel = this.getById(tunnelUpdateDto.getId()); if (existingTunnel == null) return R.err("隧道不存在"); List oldChainTunnels = chainTunnelService.list( new QueryWrapper().eq("tunnel_id", tunnelUpdateDto.getId()) ); boolean hasNodeChanges = detectNodeChanges(oldChainTunnels, tunnelUpdateDto); if (hasNodeChanges && tunnelUpdateDto.getInNodeId() != null) { List backupChains = deepCopyChainTunnels(oldChainTunnels); List nodeIds = new ArrayList<>(); Map nodes = new HashMap<>(); for (ChainTunnel inNode : tunnelUpdateDto.getInNodeId()) { nodeIds.add(inNode.getNodeId()); Node node = nodeService.getById(inNode.getNodeId()); if (node == null) return R.err("入口节点不存在: " + inNode.getNodeId()); if (node.getStatus() != 1) return R.err("入口节点不在线: " + node.getName()); nodes.put(node.getId(), node); } List newChainTunnels = new ArrayList<>(); for (ChainTunnel inNode : tunnelUpdateDto.getInNodeId()) { inNode.setTunnelId(existingTunnel.getId()); inNode.setChainType(1); newChainTunnels.add(inNode); } if (existingTunnel.getType() == 2) { if (tunnelUpdateDto.getOutNodeId() == null || tunnelUpdateDto.getOutNodeId().isEmpty()) { return R.err("隧道转发类型必须配置出口节点"); } List> chainNodes = tunnelUpdateDto.getChainNodes() == null ? new ArrayList<>() : tunnelUpdateDto.getChainNodes(); int inx = 1; for (List hop : chainNodes) { for (ChainTunnel chainNode : hop) { nodeIds.add(chainNode.getNodeId()); Node node = nodeService.getById(chainNode.getNodeId()); if (node == null) return R.err("转发链节点不存在: " + chainNode.getNodeId()); if (node.getStatus() != 1) return R.err("转发链节点不在线: " + node.getName()); nodes.put(node.getId(), node); Integer port = getNodePort(chainNode.getNodeId()); chainNode.setPort(port); chainNode.setInx(inx); chainNode.setChainType(2); chainNode.setTunnelId(existingTunnel.getId()); newChainTunnels.add(chainNode); } inx++; } for (ChainTunnel outNode : tunnelUpdateDto.getOutNodeId()) { nodeIds.add(outNode.getNodeId()); Node node = nodeService.getById(outNode.getNodeId()); if (node == null) return R.err("出口节点不存在: " + outNode.getNodeId()); if (node.getStatus() != 1) return R.err("出口节点不在线: " + node.getName()); nodes.put(node.getId(), node); Integer port = getNodePort(outNode.getNodeId()); outNode.setPort(port); outNode.setChainType(3); outNode.setTunnelId(existingTunnel.getId()); newChainTunnels.add(outNode); } } Set nodeIdSet = new HashSet<>(nodeIds); if (nodeIdSet.size() != nodeIds.size()) { return R.err("节点配置重复"); } try { cleanupGostConfig(oldChainTunnels, existingTunnel.getId()); chainTunnelService.remove( new QueryWrapper().eq("tunnel_id", existingTunnel.getId()) ); R applyResult = applyNewGostConfig(tunnelUpdateDto, existingTunnel, nodes); if (applyResult.getCode() != 0) { chainTunnelService.saveBatch(backupChains); rebuildGostConfig(backupChains, existingTunnel); return R.err("更新失败,已回滚: " + applyResult.getMsg()); } chainTunnelService.saveBatch(newChainTunnels); } catch (Exception e) { chainTunnelService.saveBatch(backupChains); rebuildGostConfig(backupChains, existingTunnel); return R.err("更新失败,已回滚: " + e.getMessage()); } } Tunnel tunnel = new Tunnel(); tunnel.setId(tunnelUpdateDto.getId()); tunnel.setName(tunnelUpdateDto.getName()); tunnel.setFlow(tunnelUpdateDto.getFlow()); tunnel.setTrafficRatio(tunnelUpdateDto.getTrafficRatio()); tunnel.setInIp(tunnelUpdateDto.getInIp()); if (StringUtils.isEmpty(tunnel.getInIp())) { java.util.LinkedHashSet inIps = new java.util.LinkedHashSet<>(); List chainTunnels = chainTunnelService.list( new QueryWrapper().eq("tunnel_id", tunnel.getId()).eq("chain_type", 1) ); for (ChainTunnel chainTunnel : chainTunnels) { Node node = nodeService.getById(chainTunnel.getNodeId()); if (node == null) return R.err("隧道节点数据错误,部分节点不存在"); if (cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIpV4())) { inIps.add(node.getServerIpV4().trim()); } if (cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIpV6())) { inIps.add(node.getServerIpV6().trim()); } if (cn.hutool.core.util.StrUtil.isBlank(node.getServerIpV4()) && cn.hutool.core.util.StrUtil.isBlank(node.getServerIpV6()) && cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIp())) { inIps.add(node.getServerIp().trim()); } } if (!inIps.isEmpty()) { tunnel.setInIp(String.join(",", inIps)); } } tunnel.setUpdatedTime(System.currentTimeMillis()); this.updateById(tunnel); return R.ok(); } @Override public R deleteTunnel(Long id) { Tunnel tunnel = this.getById(id); if (tunnel == null) return R.err("隧道不存在"); List forwardList = forwardService.list(new QueryWrapper().eq("tunnel_id", id)); for (Forward forward : forwardList) { forwardService.deleteForward(forward.getId()); } forwardService.remove(new QueryWrapper().eq("tunnel_id", id)); userTunnelService.remove(new QueryWrapper().eq("tunnel_id", id)); this.removeById(id); List chainTunnels = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", id)); for (ChainTunnel chainTunnel : chainTunnels) { if (chainTunnel.getChainType() == 1){ // 入口 GostUtil.DeleteChains(chainTunnel.getNodeId(), "chains_" + chainTunnel.getTunnelId()); } else if (chainTunnel.getChainType() == 2){ // 链 GostUtil.DeleteChains(chainTunnel.getNodeId(), "chains_" + chainTunnel.getTunnelId()); JSONArray services = new JSONArray(); services.add(chainTunnel.getTunnelId() + "_tls"); GostUtil.DeleteService(chainTunnel.getNodeId(), services); } else { // 出口 JSONArray services = new JSONArray(); services.add(chainTunnel.getTunnelId() + "_tls"); GostUtil.DeleteService(chainTunnel.getNodeId(), services); } } chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", id)); return R.ok(); } @Override public R userTunnel() { List tunnelEntities; Integer roleId = JwtUtil.getRoleIdFromToken(); Integer userId = JwtUtil.getUserIdFromToken(); if (roleId == 0) { tunnelEntities = this.list(new QueryWrapper().eq("status", 1)); } else { tunnelEntities = java.util.Collections.emptyList(); // 返回空列表 List userTunnels = userTunnelMapper.selectList( new QueryWrapper().eq("user_id", userId) ); if (!userTunnels.isEmpty()) { List tunnelIds = userTunnels.stream() .map(UserTunnel::getTunnelId) .collect(Collectors.toList()); tunnelEntities = this.list(new QueryWrapper() .in("id", tunnelIds) .eq("status", 1)); } } return R.ok(tunnelEntities); } @Override public R diagnoseTunnel(Long tunnelId) { Tunnel tunnel = this.getById(tunnelId); if (tunnel == null) { return R.err("隧道不存在"); } List chainTunnels = chainTunnelService.list( new QueryWrapper().eq("tunnel_id", tunnelId) ); if (chainTunnels.isEmpty()) { return R.err("隧道配置不完整"); } List inNodes = chainTunnels.stream() .filter(ct -> ct.getChainType() == 1) .toList(); Map> chainNodesMap = chainTunnels.stream() .filter(ct -> ct.getChainType() == 2) .collect(Collectors.groupingBy( ct -> ct.getInx() != null ? ct.getInx() : 0, Collectors.toList() )); List> chainNodesList = chainNodesMap.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .map(Map.Entry::getValue) .toList(); List outNodes = chainTunnels.stream() .filter(ct -> ct.getChainType() == 3) .toList(); List results = new ArrayList<>(); if (tunnel.getType() == 1) { for (ChainTunnel inNode : inNodes) { Node node = nodeService.getById(inNode.getNodeId()); if (node != null) { DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( node, "www.google.com", 443, "入口(" + node.getName() + ")->外网" ); result.setFromChainType(1); // 入口 results.add(result); } } } else if (tunnel.getType() == 2) { for (ChainTunnel inNode : inNodes) { Node fromNode = nodeService.getById(inNode.getNodeId()); if (fromNode != null) { if (!chainNodesList.isEmpty()) { for (ChainTunnel firstChainNode : chainNodesList.getFirst()) { Node toNode = nodeService.getById(firstChainNode.getNodeId()); if (toNode != null) { DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( fromNode, GostUtil.selectDialHost(fromNode, toNode), firstChainNode.getPort(), "入口(" + fromNode.getName() + ")->第1跳(" + toNode.getName() + ")" ); result.setFromChainType(1); // 入口 result.setToChainType(2); // 链 result.setToInx(firstChainNode.getInx()); results.add(result); } } } else if (!outNodes.isEmpty()) { for (ChainTunnel outNode : outNodes) { Node toNode = nodeService.getById(outNode.getNodeId()); if (toNode != null) { DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( fromNode, GostUtil.selectDialHost(fromNode, toNode), outNode.getPort(), "入口(" + fromNode.getName() + ")->出口(" + toNode.getName() + ")" ); result.setFromChainType(1); result.setToChainType(3); results.add(result); } } } } } for (int i = 0; i < chainNodesList.size(); i++) { List currentHop = chainNodesList.get(i); for (ChainTunnel currentNode : currentHop) { Node fromNode = nodeService.getById(currentNode.getNodeId()); if (fromNode != null) { if (i + 1 < chainNodesList.size()) { for (ChainTunnel nextNode : chainNodesList.get(i + 1)) { Node toNode = nodeService.getById(nextNode.getNodeId()); if (toNode != null) { DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( fromNode, GostUtil.selectDialHost(fromNode, toNode), nextNode.getPort(), "第" + (i + 1) + "跳(" + fromNode.getName() + ")->第" + (i + 2) + "跳(" + toNode.getName() + ")" ); result.setFromChainType(2); result.setFromInx(currentNode.getInx()); result.setToChainType(2); result.setToInx(nextNode.getInx()); results.add(result); } } } else if (!outNodes.isEmpty()) { for (ChainTunnel outNode : outNodes) { Node toNode = nodeService.getById(outNode.getNodeId()); if (toNode != null) { DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( fromNode, GostUtil.selectDialHost(fromNode, toNode), outNode.getPort(), "第" + (i + 1) + "跳(" + fromNode.getName() + ")->出口(" + toNode.getName() + ")" ); result.setFromChainType(2); result.setFromInx(currentNode.getInx()); result.setToChainType(3); results.add(result); } } } } } } for (ChainTunnel outNode : outNodes) { Node node = nodeService.getById(outNode.getNodeId()); if (node != null) { DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( node, "www.google.com", 443, "出口(" + node.getName() + ")->外网" ); result.setFromChainType(3); results.add(result); } } } Map diagnosisReport = new HashMap<>(); diagnosisReport.put("tunnelId", tunnelId); diagnosisReport.put("tunnelName", tunnel.getName()); diagnosisReport.put("tunnelType", tunnel.getType() == 1 ? "端口转发" : "隧道转发"); diagnosisReport.put("results", results); diagnosisReport.put("timestamp", System.currentTimeMillis()); return R.ok(diagnosisReport); } public Integer getNodePort(Long nodeId) { Node node = nodeService.getById(nodeId); if (node == null){ throw new RuntimeException("节点不存在"); } // 1. 查询隧道转发链占用的端口 List chainTunnels = chainTunnelService.list( new QueryWrapper().eq("node_id", nodeId) ); Set usedPorts = chainTunnels.stream() .map(ChainTunnel::getPort) .filter(Objects::nonNull) .collect(Collectors.toSet()); List list = forwardPortService.list(new QueryWrapper().eq("node_id", nodeId)); Set forwardUsedPorts = new HashSet<>(); for (ForwardPort forwardPort : list) { forwardUsedPorts.add(forwardPort.getPort()); } usedPorts.addAll(forwardUsedPorts); // 3. 从可用端口范围中筛选未被占用的端口 List parsedPorts = parsePorts(node.getPort()); List availablePorts = parsedPorts.stream() .filter(p -> !usedPorts.contains(p)) .toList(); if (availablePorts.isEmpty()) { throw new RuntimeException("节点端口已满,无可用端口"); } return availablePorts.getFirst(); } public static List parsePorts(String input) { Set set = new HashSet<>(); String[] parts = input.split(","); for (String part : parts) { part = part.trim(); if (part.contains("-")) { String[] range = part.split("-"); int start = Integer.parseInt(range[0]); int end = Integer.parseInt(range[1]); for (int i = start; i <= end; i++) { set.add(i); } } else { set.add(Integer.parseInt(part)); } } return set.stream().sorted().collect(Collectors.toList()); } private void isError(GostDto gostDto){ if (gostDto == null) { throw new RuntimeException("节点无响应"); } if (!Objects.equals(gostDto.getMsg(), "OK")) { throw new RuntimeException(gostDto.getMsg()); } } private DiagnosisResult performTcpPingDiagnosis(Node node, String targetIp, int port, String description) { try { // 构建TCP ping请求数据 JSONObject tcpPingData = new JSONObject(); tcpPingData.put("ip", targetIp); tcpPingData.put("port", port); tcpPingData.put("count", 4); tcpPingData.put("timeout", 5000); // 5秒超时 // 发送TCP ping命令到节点 GostDto gostResult = WebSocketServer.send_msg(node.getId(), tcpPingData, "TcpPing"); DiagnosisResult result = new DiagnosisResult(); result.setNodeId(node.getId()); result.setNodeName(node.getName()); result.setTargetIp(targetIp); result.setTargetPort(port); result.setDescription(description); result.setTimestamp(System.currentTimeMillis()); if (gostResult != null && "OK".equals(gostResult.getMsg())) { // 尝试解析TCP ping响应数据 try { if (gostResult.getData() != null) { JSONObject tcpPingResponse = (JSONObject) gostResult.getData(); boolean success = tcpPingResponse.getBooleanValue("success"); result.setSuccess(success); if (success) { result.setMessage("TCP连接成功"); result.setAverageTime(tcpPingResponse.getDoubleValue("averageTime")); result.setPacketLoss(tcpPingResponse.getDoubleValue("packetLoss")); } else { result.setMessage(tcpPingResponse.getString("errorMessage")); result.setAverageTime(-1.0); result.setPacketLoss(100.0); } } else { // 没有详细数据,使用默认值 result.setSuccess(true); result.setMessage("TCP连接成功"); result.setAverageTime(0.0); result.setPacketLoss(0.0); } } catch (Exception e) { // 解析响应数据失败,但TCP ping命令本身成功了 result.setSuccess(true); result.setMessage("TCP连接成功,但无法解析详细数据"); 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.setTargetPort(port); result.setDescription(description); result.setSuccess(false); result.setMessage("诊断执行异常: " + e.getMessage()); result.setTimestamp(System.currentTimeMillis()); result.setAverageTime(-1.0); result.setPacketLoss(100.0); return result; } } private DiagnosisResult performTcpPingDiagnosisWithConnectionCheck(Node node, String targetIp, int port, String description) { DiagnosisResult result = new DiagnosisResult(); result.setNodeId(node.getId()); result.setNodeName(node.getName()); result.setTargetIp(targetIp); result.setTargetPort(port); result.setDescription(description); result.setTimestamp(System.currentTimeMillis()); try { return performTcpPingDiagnosis(node, targetIp, port, description); } catch (Exception e) { result.setSuccess(false); result.setMessage("连接检查异常: " + e.getMessage()); result.setAverageTime(-1.0); result.setPacketLoss(100.0); return result; } } private boolean detectNodeChanges(List oldChains, TunnelUpdateDto dto) { if (dto.getInNodeId() == null) { return false; } List oldInNodes = oldChains.stream() .filter(ct -> ct.getChainType() != null && ct.getChainType() == 1) .collect(Collectors.toList()); List oldChainNodes = oldChains.stream() .filter(ct -> ct.getChainType() != null && ct.getChainType() == 2) .collect(Collectors.toList()); List oldOutNodes = oldChains.stream() .filter(ct -> ct.getChainType() != null && ct.getChainType() == 3) .collect(Collectors.toList()); Set oldInNodeIds = oldInNodes.stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet()); Set newInNodeIds = dto.getInNodeId().stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet()); if (!oldInNodeIds.equals(newInNodeIds)) { return true; } List flatNewChainNodes = (dto.getChainNodes() == null) ? new ArrayList<>() : dto.getChainNodes().stream().flatMap(List::stream).collect(Collectors.toList()); if (oldChainNodes.size() != flatNewChainNodes.size()) { return true; } for (int i = 0; i < oldChainNodes.size(); i++) { ChainTunnel oldCt = oldChainNodes.get(i); boolean found = flatNewChainNodes.stream().anyMatch(newCt -> Objects.equals(oldCt.getNodeId(), newCt.getNodeId()) && Objects.equals(oldCt.getProtocol(), newCt.getProtocol()) && Objects.equals(oldCt.getStrategy(), newCt.getStrategy()) && Objects.equals(oldCt.getInx(), newCt.getInx()) ); if (!found) { return true; } } List newOutNodes = (dto.getOutNodeId() == null) ? new ArrayList<>() : dto.getOutNodeId(); if (oldOutNodes.size() != newOutNodes.size()) { return true; } Set oldOutNodeIds = oldOutNodes.stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet()); Set newOutNodeIds = newOutNodes.stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet()); if (!oldOutNodeIds.equals(newOutNodeIds)) { return true; } for (ChainTunnel oldOut : oldOutNodes) { boolean found = newOutNodes.stream().anyMatch(newOut -> Objects.equals(oldOut.getNodeId(), newOut.getNodeId()) && Objects.equals(oldOut.getProtocol(), newOut.getProtocol()) && Objects.equals(oldOut.getStrategy(), newOut.getStrategy()) ); if (!found) { return true; } } return false; } private void cleanupGostConfig(List chainTunnels, Long tunnelId) { for (ChainTunnel chainTunnel : chainTunnels) { if (chainTunnel.getChainType() == 1) { GostUtil.DeleteChains(chainTunnel.getNodeId(), "chains_" + tunnelId); } else if (chainTunnel.getChainType() == 2) { GostUtil.DeleteChains(chainTunnel.getNodeId(), "chains_" + tunnelId); JSONArray services = new JSONArray(); services.add(tunnelId + "_tls"); GostUtil.DeleteService(chainTunnel.getNodeId(), services); } else if (chainTunnel.getChainType() == 3) { JSONArray services = new JSONArray(); services.add(tunnelId + "_tls"); GostUtil.DeleteService(chainTunnel.getNodeId(), services); } } } private List deepCopyChainTunnels(List original) { List copy = new ArrayList<>(); for (ChainTunnel ct : original) { ChainTunnel newCt = new ChainTunnel(); newCt.setId(ct.getId()); newCt.setTunnelId(ct.getTunnelId()); newCt.setChainType(ct.getChainType()); newCt.setNodeId(ct.getNodeId()); newCt.setPort(ct.getPort()); newCt.setStrategy(ct.getStrategy()); newCt.setInx(ct.getInx()); newCt.setProtocol(ct.getProtocol()); copy.add(newCt); } return copy; } private R applyNewGostConfig(TunnelUpdateDto dto, Tunnel tunnel, Map nodes) { List chainSuccess = new ArrayList<>(); List serviceSuccess = new ArrayList<>(); if (tunnel.getType() == 2) { List> chainNodes = dto.getChainNodes() == null ? new ArrayList<>() : dto.getChainNodes(); for (ChainTunnel inNode : dto.getInNodeId()) { GostDto gostDto; if (chainNodes.isEmpty()) { gostDto = GostUtil.AddChains(inNode.getNodeId(), dto.getOutNodeId(), nodes); } else { gostDto = GostUtil.AddChains(inNode.getNodeId(), chainNodes.get(0), nodes); } if (!Objects.equals(gostDto.getMsg(), "OK")) { rollbackGostChanges(chainSuccess, serviceSuccess); return R.err("创建入口Chain失败: " + gostDto.getMsg()); } JSONObject data = new JSONObject(); data.put("node_id", inNode.getNodeId()); data.put("name", "chains_" + tunnel.getId()); chainSuccess.add(data); } for (int i = 0; i < chainNodes.size(); i++) { List currentHop = chainNodes.get(i); for (ChainTunnel chainTunnel : currentHop) { GostDto gostDto; if (i + 1 >= chainNodes.size()) { gostDto = GostUtil.AddChains(chainTunnel.getNodeId(), dto.getOutNodeId(), nodes); } else { gostDto = GostUtil.AddChains(chainTunnel.getNodeId(), chainNodes.get(i + 1), nodes); } if (!Objects.equals(gostDto.getMsg(), "OK")) { rollbackGostChanges(chainSuccess, serviceSuccess); return R.err("创建转发链Chain失败: " + gostDto.getMsg()); } JSONObject chainData = new JSONObject(); chainData.put("node_id", chainTunnel.getNodeId()); chainData.put("name", "chains_" + tunnel.getId()); chainSuccess.add(chainData); GostDto serviceResult = GostUtil.AddChainService(chainTunnel.getNodeId(), chainTunnel, nodes); if (!Objects.equals(serviceResult.getMsg(), "OK")) { rollbackGostChanges(chainSuccess, serviceSuccess); return R.err("创建转发链Service失败: " + serviceResult.getMsg()); } JSONObject serviceData = new JSONObject(); serviceData.put("node_id", chainTunnel.getNodeId()); serviceData.put("name", tunnel.getId() + "_tls"); serviceSuccess.add(serviceData); } } for (ChainTunnel outNode : dto.getOutNodeId()) { GostDto gostDto = GostUtil.AddChainService(outNode.getNodeId(), outNode, nodes); if (!Objects.equals(gostDto.getMsg(), "OK")) { rollbackGostChanges(chainSuccess, serviceSuccess); return R.err("创建出口Service失败: " + gostDto.getMsg()); } JSONObject serviceData = new JSONObject(); serviceData.put("node_id", outNode.getNodeId()); serviceData.put("name", tunnel.getId() + "_tls"); serviceSuccess.add(serviceData); } } return R.ok(); } private void rollbackGostChanges(List chainSuccess, List serviceSuccess) { for (JSONObject chain : chainSuccess) { GostUtil.DeleteChains(chain.getLong("node_id"), chain.getString("name")); } for (JSONObject service : serviceSuccess) { JSONArray services = new JSONArray(); services.add(service.getString("name")); GostUtil.DeleteService(service.getLong("node_id"), services); } } private void rebuildGostConfig(List chainTunnels, Tunnel tunnel) { Map nodes = new HashMap<>(); for (ChainTunnel ct : chainTunnels) { Node node = nodeService.getById(ct.getNodeId()); if (node != null) { nodes.put(node.getId(), node); } } List inNodes = chainTunnels.stream() .filter(ct -> ct.getChainType() == 1) .collect(Collectors.toList()); Map> chainNodesMap = chainTunnels.stream() .filter(ct -> ct.getChainType() == 2) .collect(Collectors.groupingBy(ct -> ct.getInx() != null ? ct.getInx() : 0)); List> chainNodesList = chainNodesMap.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .map(Map.Entry::getValue) .collect(Collectors.toList()); List outNodes = chainTunnels.stream() .filter(ct -> ct.getChainType() == 3) .collect(Collectors.toList()); if (tunnel.getType() == 2) { for (ChainTunnel inNode : inNodes) { if (chainNodesList.isEmpty()) { GostUtil.AddChains(inNode.getNodeId(), outNodes, nodes); } else { GostUtil.AddChains(inNode.getNodeId(), chainNodesList.get(0), nodes); } } for (int i = 0; i < chainNodesList.size(); i++) { for (ChainTunnel chainTunnel : chainNodesList.get(i)) { if (i + 1 >= chainNodesList.size()) { GostUtil.AddChains(chainTunnel.getNodeId(), outNodes, nodes); } else { GostUtil.AddChains(chainTunnel.getNodeId(), chainNodesList.get(i + 1), nodes); } GostUtil.AddChainService(chainTunnel.getNodeId(), chainTunnel, nodes); } } for (ChainTunnel outNode : outNodes) { GostUtil.AddChainService(outNode.getNodeId(), outNode, nodes); } } } }