mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-29 16:06:36 +08:00
4edbb66224
- 后端: TunnelUpdateDto 添加 inNodeId, chainNodes, outNodeId 字段 - 后端: TunnelServiceImpl 重写 updateTunnel() 支持节点配置更新 - 后端: 添加自动回滚机制,更新失败时恢复原配置 - 前端: tunnel.tsx 解除编辑模式下的字段禁用限制 - 前端: 保留隧道类型不可修改(端口转发/隧道转发)
1040 lines
47 KiB
Java
1040 lines
47 KiB
Java
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 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())){
|
||
StringBuilder in_ip = new StringBuilder();
|
||
for (ChainTunnel chainTunnel : tunnelDto.getInNodeId()) {
|
||
Node node = nodes.get(chainTunnel.getNodeId());
|
||
in_ip.append(node.getServerIp()).append(",");
|
||
}
|
||
in_ip.deleteCharAt(in_ip.length() - 1);
|
||
tunnel.setInIp(in_ip.toString());
|
||
}
|
||
|
||
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 = GostUtil.AddChains(in_node.getNodeId(), tunnelDto.getOutNodeId(), nodes);
|
||
isError(gostDto);
|
||
|
||
} else {
|
||
GostDto gostDto = GostUtil.AddChains(in_node.getNodeId(), tunnelDto.getChainNodes().getFirst(), nodes);// 指向第一跳
|
||
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 = GostUtil.AddChains(chainTunnel.getNodeId(), tunnelDto.getOutNodeId(), nodes);
|
||
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 = GostUtil.AddChains(chainTunnel.getNodeId(), tunnelDto.getChainNodes().get(inx), nodes);
|
||
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();
|
||
|
||
// 查询所有隧道的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
|
||
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())) {
|
||
StringBuilder inIp = new StringBuilder();
|
||
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("隧道节点数据错误,部分节点不存在");
|
||
inIp.append(node.getServerIp()).append(",");
|
||
}
|
||
if (inIp.length() > 0) {
|
||
inIp.deleteCharAt(inIp.length() - 1);
|
||
}
|
||
tunnel.setInIp(inIp.toString());
|
||
}
|
||
|
||
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, toNode.getServerIp(), 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, toNode.getServerIp(), 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, toNode.getServerIp(), 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, toNode.getServerIp(), 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){
|
||
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
}
|