Files
flvx/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java
T

1140 lines
52 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<TunnelMapper, Tunnel> 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<Tunnel>().eq("name", tunnelDto.getName()));
if (count > 0) return R.err("隧道名称重复");
if (tunnelDto.getType() == 2 && tunnelDto.getOutNodeId() == null) return R.err("出口不能为空");
List<ChainTunnel> chainTunnels = new ArrayList<>();
Map<Long, Node> nodes = new HashMap<>();
List<Long> 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<ChainTunnel> 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<Long> set = new HashSet<>(node_ids);
boolean hasDuplicate = set.size() != node_ids.size();
if (hasDuplicate) return R.err("节点重复");
List<Node> list = nodeService.list(new QueryWrapper<Node>().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);
if (StringUtils.isEmpty(tunnel.getInIp())){
java.util.LinkedHashSet<String> 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<JSONObject> chain_success = new ArrayList<>();
List<JSONObject> 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<ChainTunnel>().eq("tunnel_id", tunnel.getId()));
return R.err(e.getMessage());
}
if (!Objects.equals(gostDto.getMsg(), "OK")) {
this.removeById(tunnel.getId());
chainTunnelService.remove(new QueryWrapper<ChainTunnel>().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<ChainTunnel>().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<ChainTunnel>().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<ChainTunnel> 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<ChainTunnel>().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<ChainTunnel>().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<ChainTunnel>().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<ChainTunnel>().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<ChainTunnel>().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<ChainTunnel>().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<Tunnel> tunnelList = this.list(new QueryWrapper<Tunnel>().orderByAsc("inx").orderByAsc("id"));
// 查询所有隧道的ChainTunnel信息
List<Long> tunnelIds = tunnelList.stream()
.map(Tunnel::getId)
.collect(Collectors.toList());
if (tunnelIds.isEmpty()) {
return R.ok(new ArrayList<TunnelDetailDto>());
}
// 批量查询所有ChainTunnel记录
List<ChainTunnel> allChainTunnels = chainTunnelService.list(
new QueryWrapper<ChainTunnel>().in("tunnel_id", tunnelIds)
);
// 按tunnelId分组
Map<Long, List<ChainTunnel>> chainTunnelMap = allChainTunnels.stream()
.collect(Collectors.groupingBy(ChainTunnel::getTunnelId));
// 转换为TunnelDetailDto列表
List<TunnelDetailDto> detailDtoList = tunnelList.stream()
.map(tunnel -> {
TunnelDetailDto detailDto = new TunnelDetailDto();
BeanUtils.copyProperties(tunnel, detailDto);
List<ChainTunnel> chainTunnels = chainTunnelMap.getOrDefault(tunnel.getId(), new ArrayList<>());
// 按chainType分类节点
// 入口节点 (chainType = 1)
List<ChainTunnel> 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<Integer, List<ChainTunnel>> 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<List<ChainTunnel>> chainNodesList = chainNodesMap.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(Map.Entry::getValue)
.collect(Collectors.toList());
detailDto.setChainNodes(chainNodesList);
// 出口节点 (chainType = 3)
List<ChainTunnel> 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<String, Object> params) {
if (!params.containsKey("tunnels")) {
return R.err("缺少tunnels参数");
}
@SuppressWarnings("unchecked")
List<Map<String, Object>> tunnelsList = (List<Map<String, Object>>) params.get("tunnels");
if (tunnelsList == null || tunnelsList.isEmpty()) {
return R.err("tunnels参数不能为空");
}
List<Tunnel> tunnelsToUpdate = new ArrayList<>();
for (Map<String, Object> 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<ChainTunnel> oldChainTunnels = chainTunnelService.list(
new QueryWrapper<ChainTunnel>().eq("tunnel_id", tunnelUpdateDto.getId())
);
boolean hasNodeChanges = detectNodeChanges(oldChainTunnels, tunnelUpdateDto);
if (hasNodeChanges && tunnelUpdateDto.getInNodeId() != null) {
List<ChainTunnel> backupChains = deepCopyChainTunnels(oldChainTunnels);
List<Long> nodeIds = new ArrayList<>();
Map<Long, Node> 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<ChainTunnel> 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<List<ChainTunnel>> chainNodes = tunnelUpdateDto.getChainNodes() == null ?
new ArrayList<>() : tunnelUpdateDto.getChainNodes();
int inx = 1;
for (List<ChainTunnel> 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<Long> nodeIdSet = new HashSet<>(nodeIds);
if (nodeIdSet.size() != nodeIds.size()) {
return R.err("节点配置重复");
}
try {
cleanupGostConfig(oldChainTunnels, existingTunnel.getId());
chainTunnelService.remove(
new QueryWrapper<ChainTunnel>().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<String> inIps = new java.util.LinkedHashSet<>();
List<ChainTunnel> chainTunnels = chainTunnelService.list(
new QueryWrapper<ChainTunnel>().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<Forward> forwardList = forwardService.list(new QueryWrapper<Forward>().eq("tunnel_id", id));
for (Forward forward : forwardList) {
forwardService.deleteForward(forward.getId());
}
forwardService.remove(new QueryWrapper<Forward>().eq("tunnel_id", id));
userTunnelService.remove(new QueryWrapper<UserTunnel>().eq("tunnel_id", id));
this.removeById(id);
List<ChainTunnel> chainTunnels = chainTunnelService.list(new QueryWrapper<ChainTunnel>().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<ChainTunnel>().eq("tunnel_id", id));
return R.ok();
}
@Override
public R userTunnel() {
List<Tunnel> tunnelEntities;
Integer roleId = JwtUtil.getRoleIdFromToken();
Integer userId = JwtUtil.getUserIdFromToken();
if (roleId == 0) {
tunnelEntities = this.list(new QueryWrapper<Tunnel>().eq("status", 1));
} else {
tunnelEntities = java.util.Collections.emptyList(); // 返回空列表
List<UserTunnel> userTunnels = userTunnelMapper.selectList(
new QueryWrapper<UserTunnel>().eq("user_id", userId)
);
if (!userTunnels.isEmpty()) {
List<Integer> tunnelIds = userTunnels.stream()
.map(UserTunnel::getTunnelId)
.collect(Collectors.toList());
tunnelEntities = this.list(new QueryWrapper<Tunnel>()
.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<ChainTunnel> chainTunnels = chainTunnelService.list(
new QueryWrapper<ChainTunnel>().eq("tunnel_id", tunnelId)
);
if (chainTunnels.isEmpty()) {
return R.err("隧道配置不完整");
}
List<ChainTunnel> inNodes = chainTunnels.stream()
.filter(ct -> ct.getChainType() == 1)
.toList();
Map<Integer, List<ChainTunnel>> chainNodesMap = chainTunnels.stream()
.filter(ct -> ct.getChainType() == 2)
.collect(Collectors.groupingBy(
ct -> ct.getInx() != null ? ct.getInx() : 0,
Collectors.toList()
));
List<List<ChainTunnel>> chainNodesList = chainNodesMap.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(Map.Entry::getValue)
.toList();
List<ChainTunnel> outNodes = chainTunnels.stream()
.filter(ct -> ct.getChainType() == 3)
.toList();
List<DiagnosisResult> 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<ChainTunnel> 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<String, Object> 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<ChainTunnel> chainTunnels = chainTunnelService.list(
new QueryWrapper<ChainTunnel>().eq("node_id", nodeId)
);
Set<Integer> usedPorts = chainTunnels.stream()
.map(ChainTunnel::getPort)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
List<ForwardPort> list = forwardPortService.list(new QueryWrapper<ForwardPort>().eq("node_id", nodeId));
Set<Integer> forwardUsedPorts = new HashSet<>();
for (ForwardPort forwardPort : list) {
forwardUsedPorts.add(forwardPort.getPort());
}
usedPorts.addAll(forwardUsedPorts);
// 3. 从可用端口范围中筛选未被占用的端口
List<Integer> parsedPorts = parsePorts(node.getPort());
List<Integer> availablePorts = parsedPorts.stream()
.filter(p -> !usedPorts.contains(p))
.toList();
if (availablePorts.isEmpty()) {
throw new RuntimeException("节点端口已满,无可用端口");
}
return availablePorts.getFirst();
}
public static List<Integer> parsePorts(String input) {
Set<Integer> 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<ChainTunnel> oldChains, TunnelUpdateDto dto) {
if (dto.getInNodeId() == null) {
return false;
}
List<ChainTunnel> oldInNodes = oldChains.stream()
.filter(ct -> ct.getChainType() != null && ct.getChainType() == 1)
.collect(Collectors.toList());
List<ChainTunnel> oldChainNodes = oldChains.stream()
.filter(ct -> ct.getChainType() != null && ct.getChainType() == 2)
.collect(Collectors.toList());
List<ChainTunnel> oldOutNodes = oldChains.stream()
.filter(ct -> ct.getChainType() != null && ct.getChainType() == 3)
.collect(Collectors.toList());
Set<Long> oldInNodeIds = oldInNodes.stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet());
Set<Long> newInNodeIds = dto.getInNodeId().stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet());
if (!oldInNodeIds.equals(newInNodeIds)) {
return true;
}
List<ChainTunnel> 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<ChainTunnel> newOutNodes = (dto.getOutNodeId() == null) ? new ArrayList<>() : dto.getOutNodeId();
if (oldOutNodes.size() != newOutNodes.size()) {
return true;
}
Set<Long> oldOutNodeIds = oldOutNodes.stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet());
Set<Long> 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<ChainTunnel> 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<ChainTunnel> deepCopyChainTunnels(List<ChainTunnel> original) {
List<ChainTunnel> 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<Long, Node> nodes) {
List<JSONObject> chainSuccess = new ArrayList<>();
List<JSONObject> serviceSuccess = new ArrayList<>();
if (tunnel.getType() == 2) {
List<List<ChainTunnel>> 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<ChainTunnel> 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<JSONObject> chainSuccess, List<JSONObject> 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<ChainTunnel> chainTunnels, Tunnel tunnel) {
Map<Long, Node> nodes = new HashMap<>();
for (ChainTunnel ct : chainTunnels) {
Node node = nodeService.getById(ct.getNodeId());
if (node != null) {
nodes.put(node.getId(), node);
}
}
List<ChainTunnel> inNodes = chainTunnels.stream()
.filter(ct -> ct.getChainType() == 1)
.collect(Collectors.toList());
Map<Integer, List<ChainTunnel>> chainNodesMap = chainTunnels.stream()
.filter(ct -> ct.getChainType() == 2)
.collect(Collectors.groupingBy(ct -> ct.getInx() != null ? ct.getInx() : 0));
List<List<ChainTunnel>> chainNodesList = chainNodesMap.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(Map.Entry::getValue)
.collect(Collectors.toList());
List<ChainTunnel> 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);
}
}
}
}