package com.admin.service.impl; import cn.hutool.core.util.StrUtil; import com.admin.common.dto.TunnelDto; import com.admin.common.dto.TunnelListDto; import com.admin.common.dto.TunnelUpdateDto; import com.admin.common.lang.R; import com.admin.common.utils.JwtUtil; import com.admin.entity.Forward; import com.admin.entity.Node; import com.admin.entity.Tunnel; import com.admin.entity.User; import com.admin.entity.UserTunnel; import com.admin.mapper.TunnelMapper; import com.admin.mapper.UserTunnelMapper; import com.admin.service.ForwardService; import com.admin.service.NodeService; import com.admin.service.TunnelService; import com.admin.service.UserTunnelService; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.Data; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; import java.util.stream.Collectors; /** *

* 隧道服务实现类 * 提供隧道的增删改查功能,包括隧道创建、删除和用户权限管理 * 支持端口转发和隧道转发两种模式 *

* * @author QAQ * @since 2025-06-03 */ @Service public class TunnelServiceImpl extends ServiceImpl implements TunnelService { // ========== 常量定义 ========== /** 隧道类型常量 */ private static final int TUNNEL_TYPE_PORT_FORWARD = 1; // 端口转发 private static final int TUNNEL_TYPE_TUNNEL_FORWARD = 2; // 隧道转发 /** 隧道状态常量 */ private static final int TUNNEL_STATUS_ACTIVE = 1; // 启用状态 /** 节点状态常量 */ private static final int NODE_STATUS_ONLINE = 1; // 节点在线状态 /** 用户角色常量 */ private static final int ADMIN_ROLE_ID = 0; // 管理员角色ID /** 成功响应消息 */ private static final String SUCCESS_CREATE_MSG = "隧道创建成功"; private static final String SUCCESS_DELETE_MSG = "隧道删除成功"; /** 错误响应消息 */ private static final String ERROR_CREATE_MSG = "隧道创建失败"; private static final String ERROR_DELETE_MSG = "隧道删除失败"; private static final String ERROR_TUNNEL_NOT_FOUND = "隧道不存在"; private static final String ERROR_TUNNEL_NAME_EXISTS = "隧道名称已存在"; private static final String ERROR_IN_NODE_NOT_FOUND = "入口节点不存在"; private static final String ERROR_OUT_NODE_NOT_FOUND = "出口节点不存在"; private static final String ERROR_OUT_NODE_REQUIRED = "出口节点不能为空"; private static final String ERROR_OUT_PORT_REQUIRED = "出口端口不能为空"; private static final String ERROR_SAME_NODE_NOT_ALLOWED = "隧道转发模式下,入口和出口不能是同一个节点"; private static final String ERROR_IN_PORT_RANGE_INVALID = "入口端口开始不能大于结束端口"; private static final String ERROR_OUT_PORT_RANGE_INVALID = "出口端口开始不能大于结束端口"; private static final String ERROR_NO_AVAILABLE_TUNNELS = "暂无可用隧道"; private static final String ERROR_IN_NODE_OFFLINE = "入口节点当前离线,请确保节点正常运行"; private static final String ERROR_OUT_NODE_OFFLINE = "出口节点当前离线,请确保节点正常运行"; /** 使用检查相关消息 */ private static final String ERROR_FORWARDS_IN_USE = "该隧道还有 %d 个转发在使用,请先删除相关转发"; private static final String ERROR_USER_PERMISSIONS_IN_USE = "该隧道还有 %d 个用户权限关联,请先取消用户权限分配"; // ========== 依赖注入 ========== @Resource UserTunnelMapper userTunnelMapper; @Resource NodeService nodeService; @Resource ForwardService forwardService; @Resource UserTunnelService userTunnelService; // ========== 公共接口实现 ========== /** * 创建隧道 * 支持端口转发和隧道转发两种模式 * * @param tunnelDto 隧道创建数据传输对象 * @return 创建结果响应 */ @Override public R createTunnel(TunnelDto tunnelDto) { // 1. 验证隧道名称唯一性 R nameValidationResult = validateTunnelNameUniqueness(tunnelDto.getName()); if (nameValidationResult.getCode() != 0) { return nameValidationResult; } // 2. 验证隧道转发类型的必要参数 if (tunnelDto.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) { R tunnelForwardValidationResult = validateTunnelForwardCreate(tunnelDto); if (tunnelForwardValidationResult.getCode() != 0) { return tunnelForwardValidationResult; } } // 3. 验证入口节点和端口 NodeValidationResult inNodeValidation = validateInNode(tunnelDto); if (inNodeValidation.isHasError()) { return R.err(inNodeValidation.getErrorMessage()); } // 4. 构建隧道实体 Tunnel tunnel = buildTunnelEntity(tunnelDto, inNodeValidation.getNode()); // 5. 根据隧道类型设置出口参数 R outNodeSetupResult = setupOutNodeParameters(tunnel, tunnelDto); if (outNodeSetupResult.getCode() != 0) { return outNodeSetupResult; } // 6. 设置默认属性并保存 setDefaultTunnelProperties(tunnel); boolean result = this.save(tunnel); return result ? R.ok(SUCCESS_CREATE_MSG) : R.err(ERROR_CREATE_MSG); } /** * 获取所有隧道列表 * * @return 包含所有隧道的响应对象 */ @Override public R getAllTunnels() { List tunnelList = this.list(); return R.ok(tunnelList); } /** * 更新隧道(只允许修改名称、流量计费、端口范围) * * @param tunnelUpdateDto 更新数据传输对象 * @return 更新结果响应 */ @Override public R updateTunnel(TunnelUpdateDto tunnelUpdateDto) { // 1. 验证隧道是否存在 Tunnel existingTunnel = this.getById(tunnelUpdateDto.getId()); if (existingTunnel == null) { return R.err(ERROR_TUNNEL_NOT_FOUND); } // 2. 验证隧道名称唯一性(排除自身) R nameValidationResult = validateTunnelNameUniquenessForUpdate( tunnelUpdateDto.getName(), tunnelUpdateDto.getId()); if (nameValidationResult.getCode() != 0) { return nameValidationResult; } // 3. 验证端口范围 R portValidationResult = validatePortRanges(tunnelUpdateDto); if (portValidationResult.getCode() != 0) { return portValidationResult; } // 4. 验证隧道转发类型的出口端口 if (existingTunnel.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) { R tunnelTypeValidationResult = validateTunnelForwardUpdate(tunnelUpdateDto); if (tunnelTypeValidationResult.getCode() != 0) { return tunnelTypeValidationResult; } } // 5. 更新允许修改的字段 existingTunnel.setName(tunnelUpdateDto.getName()); existingTunnel.setFlow(tunnelUpdateDto.getFlow()); existingTunnel.setInPortSta(tunnelUpdateDto.getInPortSta()); existingTunnel.setInPortEnd(tunnelUpdateDto.getInPortEnd()); // 更新TCP和UDP监听地址 if (StrUtil.isNotBlank(tunnelUpdateDto.getTcpListenAddr())) { existingTunnel.setTcpListenAddr(tunnelUpdateDto.getTcpListenAddr()); } if (StrUtil.isNotBlank(tunnelUpdateDto.getUdpListenAddr())) { existingTunnel.setUdpListenAddr(tunnelUpdateDto.getUdpListenAddr()); } if (existingTunnel.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) { existingTunnel.setOutIpSta(tunnelUpdateDto.getOutIpSta()); existingTunnel.setOutIpEnd(tunnelUpdateDto.getOutIpEnd()); }else{ existingTunnel.setOutIpSta(tunnelUpdateDto.getInPortSta()); existingTunnel.setOutIpEnd(tunnelUpdateDto.getInPortEnd()); } // 6. 保存更新 boolean result = this.updateById(existingTunnel); return result ? R.ok("隧道更新成功") : R.err("隧道更新失败"); } /** * 删除隧道 * 删除前会检查是否有转发或用户权限在使用该隧道 * * @param id 隧道ID * @return 删除结果响应 */ @Override public R deleteTunnel(Long id) { // 1. 验证隧道是否存在 if (!isTunnelExists(id)) { return R.err(ERROR_TUNNEL_NOT_FOUND); } // 2. 检查隧道使用情况 R usageCheckResult = checkTunnelUsage(id); if (usageCheckResult.getCode() != 0) { return usageCheckResult; } // 3. 执行删除操作 boolean result = this.removeById(id); return result ? R.ok(SUCCESS_DELETE_MSG) : R.err(ERROR_DELETE_MSG); } /** * 获取用户可用的隧道列表 * 管理员可以看到所有启用的隧道,普通用户只能看到有权限的启用隧道 * * @return 用户可用隧道列表响应 */ @Override public R userTunnel() { UserInfo currentUser = getCurrentUserInfo(); // 根据用户角色获取隧道列表 List tunnelEntities = getUserAccessibleTunnels(currentUser); // 转换为DTO并返回 List tunnelDtos = convertToTunnelListDtos(tunnelEntities); return R.ok(tunnelDtos); } // ========== 私有辅助方法 ========== /** * 获取当前用户信息 * * @return 用户信息对象 */ private UserInfo getCurrentUserInfo() { Integer roleId = JwtUtil.getRoleIdFromToken(); Integer userId = JwtUtil.getUserIdFromToken(); return new UserInfo(userId, roleId); } /** * 验证隧道名称唯一性 * * @param tunnelName 隧道名称 * @return 验证结果响应 */ private R validateTunnelNameUniqueness(String tunnelName) { Tunnel existTunnel = this.getOne(new QueryWrapper().eq("name", tunnelName)); if (existTunnel != null) { return R.err(ERROR_TUNNEL_NAME_EXISTS); } return R.ok(); } /** * 验证隧道名称唯一性(更新时使用,排除自身) * * @param tunnelName 隧道名称 * @param tunnelId 隧道ID(要排除的隧道) * @return 验证结果响应 */ private R validateTunnelNameUniquenessForUpdate(String tunnelName, Long tunnelId) { QueryWrapper query = new QueryWrapper<>(); query.eq("name", tunnelName); query.ne("id", tunnelId); // 排除自身 Tunnel existTunnel = this.getOne(query); if (existTunnel != null) { return R.err(ERROR_TUNNEL_NAME_EXISTS); } return R.ok(); } /** * 验证端口范围 * * @param tunnelUpdateDto 更新数据传输对象 * @return 验证结果响应 */ private R validatePortRanges(TunnelUpdateDto tunnelUpdateDto) { // 验证入口端口范围 if (tunnelUpdateDto.getInPortSta() > tunnelUpdateDto.getInPortEnd()) { return R.err(ERROR_IN_PORT_RANGE_INVALID); } // 验证出口端口范围(如果有的话) if (tunnelUpdateDto.getOutIpSta() != null && tunnelUpdateDto.getOutIpEnd() != null) { if (tunnelUpdateDto.getOutIpSta() > tunnelUpdateDto.getOutIpEnd()) { return R.err(ERROR_OUT_PORT_RANGE_INVALID); } } return R.ok(); } /** * 验证隧道转发更新时的出口端口 * * @param tunnelUpdateDto 更新数据传输对象 * @return 验证结果响应 */ private R validateTunnelForwardUpdate(TunnelUpdateDto tunnelUpdateDto) { // 验证出口端口参数不能为空 if (tunnelUpdateDto.getOutIpSta() == null || tunnelUpdateDto.getOutIpEnd() == null) { return R.err(ERROR_OUT_PORT_REQUIRED); } // 验证出口端口范围 if (tunnelUpdateDto.getOutIpSta() > tunnelUpdateDto.getOutIpEnd()) { return R.err(ERROR_OUT_PORT_RANGE_INVALID); } return R.ok(); } /** * 验证隧道转发创建时的必要参数 * * @param tunnelDto 隧道创建数据传输对象 * @return 验证结果响应 */ private R validateTunnelForwardCreate(TunnelDto tunnelDto) { // 验证出口节点不能为空 if (tunnelDto.getOutNodeId() == null) { return R.err(ERROR_OUT_NODE_REQUIRED); } // 验证出口端口参数不能为空 if (tunnelDto.getOutIpSta() == null || tunnelDto.getOutIpEnd() == null) { return R.err(ERROR_OUT_PORT_REQUIRED); } // 验证出口端口范围 if (tunnelDto.getOutIpSta() > tunnelDto.getOutIpEnd()) { return R.err(ERROR_OUT_PORT_RANGE_INVALID); } return R.ok(); } /** * 验证入口节点和端口 * * @param tunnelDto 隧道创建DTO * @return 节点验证结果 */ private NodeValidationResult validateInNode(TunnelDto tunnelDto) { // 验证入口节点是否存在 Node inNode = nodeService.getById(tunnelDto.getInNodeId()); if (inNode == null) { return NodeValidationResult.error(ERROR_IN_NODE_NOT_FOUND); } // 验证入口节点是否在线 if (inNode.getStatus() != NODE_STATUS_ONLINE) { return NodeValidationResult.error(ERROR_IN_NODE_OFFLINE); } // 验证入口端口范围 if (tunnelDto.getInPortSta() > tunnelDto.getInPortEnd()) { return NodeValidationResult.error(ERROR_IN_PORT_RANGE_INVALID); } return NodeValidationResult.success(inNode); } /** * 构建隧道实体对象 * * @param tunnelDto 隧道创建DTO * @param inNode 入口节点 * @return 构建完成的隧道对象 */ private Tunnel buildTunnelEntity(TunnelDto tunnelDto, Node inNode) { Tunnel tunnel = new Tunnel(); BeanUtils.copyProperties(tunnelDto, tunnel); // 设置入口节点信息 tunnel.setInNodeId(tunnelDto.getInNodeId()); tunnel.setInIp(inNode.getServerIp()); // 设置流量计算类型 tunnel.setFlow(tunnelDto.getFlow()); // 设置协议类型(仅隧道转发需要) if (tunnelDto.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) { // 隧道转发时,设置协议类型,默认为tls String protocol = StrUtil.isNotBlank(tunnelDto.getProtocol()) ? tunnelDto.getProtocol() : "tls"; tunnel.setProtocol(protocol); } else { // 端口转发时,协议类型为null tunnel.setProtocol(null); } // 设置TCP和UDP监听地址 tunnel.setTcpListenAddr(StrUtil.isNotBlank(tunnelDto.getTcpListenAddr()) ? tunnelDto.getTcpListenAddr() : "0.0.0.0"); tunnel.setUdpListenAddr(StrUtil.isNotBlank(tunnelDto.getUdpListenAddr()) ? tunnelDto.getUdpListenAddr() : "0.0.0.0"); return tunnel; } /** * 设置出口节点参数 * * @param tunnel 隧道对象 * @param tunnelDto 隧道创建DTO * @return 设置结果响应 */ private R setupOutNodeParameters(Tunnel tunnel, TunnelDto tunnelDto) { if (tunnelDto.getType() == TUNNEL_TYPE_PORT_FORWARD) { // 端口转发:出口参数使用入口参数 return setupPortForwardOutParameters(tunnel, tunnelDto); } else { // 隧道转发:需要验证出口参数 return setupTunnelForwardOutParameters(tunnel, tunnelDto); } } /** * 设置端口转发的出口参数 * * @param tunnel 隧道对象 * @param tunnelDto 隧道创建DTO * @return 设置结果响应 */ private R setupPortForwardOutParameters(Tunnel tunnel, TunnelDto tunnelDto) { tunnel.setOutNodeId(tunnelDto.getInNodeId()); tunnel.setOutIp(tunnel.getInIp()); tunnel.setOutIpSta(tunnelDto.getInPortSta()); tunnel.setOutIpEnd(tunnelDto.getInPortEnd()); return R.ok(); } /** * 设置隧道转发的出口参数 * * @param tunnel 隧道对象 * @param tunnelDto 隧道创建DTO * @return 设置结果响应 */ private R setupTunnelForwardOutParameters(Tunnel tunnel, TunnelDto tunnelDto) { // 验证出口节点不能为空 if (tunnelDto.getOutNodeId() == null) { return R.err(ERROR_OUT_NODE_REQUIRED); } // 验证入口和出口不能是同一个节点 if (tunnelDto.getInNodeId().equals(tunnelDto.getOutNodeId())) { return R.err(ERROR_SAME_NODE_NOT_ALLOWED); } // 验证协议类型 String protocol = tunnelDto.getProtocol(); if (StrUtil.isNotBlank(protocol) && !protocol.equals("tls") && !protocol.equals("tcp") && !protocol.equals("mtls")) { return R.err("协议类型只能为tls、tcp或mtls"); } // 验证出口节点是否存在 Node outNode = nodeService.getById(tunnelDto.getOutNodeId()); if (outNode == null) { return R.err(ERROR_OUT_NODE_NOT_FOUND); } // 验证出口节点是否在线 if (outNode.getStatus() != NODE_STATUS_ONLINE) { return R.err(ERROR_OUT_NODE_OFFLINE); } // 验证出口端口参数 if (tunnelDto.getOutIpSta() == null || tunnelDto.getOutIpEnd() == null) { return R.err(ERROR_OUT_PORT_REQUIRED); } if (tunnelDto.getOutIpSta() > tunnelDto.getOutIpEnd()) { return R.err(ERROR_OUT_PORT_RANGE_INVALID); } // 设置出口参数 tunnel.setOutNodeId(tunnelDto.getOutNodeId()); tunnel.setOutIp(outNode.getServerIp()); return R.ok(); } /** * 设置隧道默认属性 * * @param tunnel 隧道对象 */ private void setDefaultTunnelProperties(Tunnel tunnel) { tunnel.setStatus(TUNNEL_STATUS_ACTIVE); long currentTime = System.currentTimeMillis(); tunnel.setCreatedTime(currentTime); tunnel.setUpdatedTime(currentTime); } /** * 检查隧道是否存在 * * @param tunnelId 隧道ID * @return 隧道是否存在 */ private boolean isTunnelExists(Long tunnelId) { return this.getById(tunnelId) != null; } /** * 检查隧道使用情况 * * @param tunnelId 隧道ID * @return 检查结果响应 */ private R checkTunnelUsage(Long tunnelId) { // 检查转发使用情况 R forwardCheckResult = checkForwardUsage(tunnelId); if (forwardCheckResult.getCode() != 0) { return forwardCheckResult; } // 检查用户权限使用情况 return checkUserPermissionUsage(tunnelId); } /** * 检查转发使用情况 * * @param tunnelId 隧道ID * @return 检查结果响应 */ private R checkForwardUsage(Long tunnelId) { QueryWrapper forwardQuery = new QueryWrapper<>(); forwardQuery.eq("tunnel_id", tunnelId); long forwardCount = forwardService.count(forwardQuery); if (forwardCount > 0) { String errorMsg = String.format(ERROR_FORWARDS_IN_USE, forwardCount); return R.err(errorMsg); } return R.ok(); } /** * 检查用户权限使用情况 * * @param tunnelId 隧道ID * @return 检查结果响应 */ private R checkUserPermissionUsage(Long tunnelId) { QueryWrapper userTunnelQuery = new QueryWrapper<>(); userTunnelQuery.eq("tunnel_id", tunnelId); long userTunnelCount = userTunnelService.count(userTunnelQuery); if (userTunnelCount > 0) { String errorMsg = String.format(ERROR_USER_PERMISSIONS_IN_USE, userTunnelCount); return R.err(errorMsg); } return R.ok(); } /** * 获取用户可访问的隧道列表 * * @param userInfo 用户信息 * @return 隧道列表 */ private List getUserAccessibleTunnels(UserInfo userInfo) { if (userInfo.getRoleId() == ADMIN_ROLE_ID) { // 管理员:获取所有启用状态的隧道 return getActiveTunnels(); } else { // 普通用户:根据权限获取启用状态的隧道 return getUserAuthorizedTunnels(userInfo.getUserId()); } } /** * 获取所有启用状态的隧道 * * @return 启用状态的隧道列表 */ private List getActiveTunnels() { return this.list(new QueryWrapper().eq("status", TUNNEL_STATUS_ACTIVE)); } /** * 获取用户有权限的启用隧道 * * @param userId 用户ID * @return 用户有权限的隧道列表 */ private List getUserAuthorizedTunnels(Integer userId) { List userTunnels = userTunnelMapper.selectList( new QueryWrapper().eq("user_id", userId) ); if (userTunnels.isEmpty()) { return java.util.Collections.emptyList(); // 返回空列表 } List tunnelIds = userTunnels.stream() .map(UserTunnel::getTunnelId) .collect(Collectors.toList()); return this.list(new QueryWrapper() .in("id", tunnelIds) .eq("status", TUNNEL_STATUS_ACTIVE)); } /** * 将隧道实体列表转换为DTO列表 * * @param tunnelEntities 隧道实体列表 * @return 隧道DTO列表 */ private List convertToTunnelListDtos(List tunnelEntities) { return tunnelEntities.stream() .map(this::convertToTunnelListDto) .collect(Collectors.toList()); } /** * 将Tunnel实体转换为TunnelListDto * * @param tunnel 隧道实体 * @return 隧道列表DTO */ private TunnelListDto convertToTunnelListDto(Tunnel tunnel) { TunnelListDto dto = new TunnelListDto(); dto.setId(tunnel.getId().intValue()); dto.setName(tunnel.getName()); dto.setIp(tunnel.getInIp()); dto.setInPortSta(tunnel.getInPortSta()); dto.setInPortEnd(tunnel.getInPortEnd()); dto.setOutIp(tunnel.getOutIp()); dto.setOutIpSta(tunnel.getOutIpSta()); dto.setOutIpEnd(tunnel.getOutIpEnd()); dto.setType(tunnel.getType()); dto.setProtocol(tunnel.getProtocol()); return dto; } // ========== 内部数据类 ========== /** * 用户信息封装类 */ @Data private static class UserInfo { private final Integer userId; private final Integer roleId; } /** * 节点验证结果封装类 */ @Data private static class NodeValidationResult { private final boolean hasError; private final String errorMessage; private final Node node; private NodeValidationResult(boolean hasError, String errorMessage, Node node) { this.hasError = hasError; this.errorMessage = errorMessage; this.node = node; } public static NodeValidationResult success(Node node) { return new NodeValidationResult(false, null, node); } public static NodeValidationResult error(String errorMessage) { return new NodeValidationResult(true, errorMessage, null); } } }