diff --git a/README.md b/README.md index d4c1902..a56d559 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 转发面板 +# 哆啦A梦转发面板 本项目基于 [go-gost/gost](https://github.com/go-gost/gost) 和 [go-gost/x](https://.com/go-gost/x) 两个开源库,实现了转发面板。 --- diff --git a/go-gost/gost b/go-gost/gost index edfd8df..5059e6c 100755 Binary files a/go-gost/gost and b/go-gost/gost differ diff --git a/springboot-backend/src/main/java/com/admin/common/dto/PageDto.java b/springboot-backend/src/main/java/com/admin/common/dto/PageDto.java deleted file mode 100644 index 9ddcc5d..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/PageDto.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -@Data -public class PageDto { - - /** - * 当前页码,默认为1 - */ - private Long current = 1L; - - /** - * 每页显示条数,默认为10 - */ - private Long size = 10L; - - /** - * 搜索关键字(可选) - */ - private String keyword; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/TunnelDto.java b/springboot-backend/src/main/java/com/admin/common/dto/TunnelDto.java index 61972ee..e77caa3 100644 --- a/springboot-backend/src/main/java/com/admin/common/dto/TunnelDto.java +++ b/springboot-backend/src/main/java/com/admin/common/dto/TunnelDto.java @@ -32,7 +32,7 @@ public class TunnelDto { @DecimalMax(value = "100.0", message = "流量倍率不能大于100.0") private BigDecimal trafficRatio = new BigDecimal("1.0"); - // 协议类型(隧道转发时使用:tls、tcp、mtls),默认为tls + // 协议类型,默认为tls private String protocol; // TCP监听地址,默认为0.0.0.0 diff --git a/springboot-backend/src/main/java/com/admin/common/dto/TunnelUpdateDto.java b/springboot-backend/src/main/java/com/admin/common/dto/TunnelUpdateDto.java index e7032bd..2ceed52 100644 --- a/springboot-backend/src/main/java/com/admin/common/dto/TunnelUpdateDto.java +++ b/springboot-backend/src/main/java/com/admin/common/dto/TunnelUpdateDto.java @@ -26,9 +26,14 @@ public class TunnelUpdateDto { @DecimalMax(value = "100.0", message = "流量倍率不能大于100.0") private BigDecimal trafficRatio; + @NotBlank + private String protocol; + // TCP监听地址 + @NotBlank private String tcpListenAddr; // UDP监听地址 + @NotBlank private String udpListenAddr; } \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/utils/GostUtil.java b/springboot-backend/src/main/java/com/admin/common/utils/GostUtil.java index aee6bd7..3817d0f 100644 --- a/springboot-backend/src/main/java/com/admin/common/utils/GostUtil.java +++ b/springboot-backend/src/main/java/com/admin/common/utils/GostUtil.java @@ -181,6 +181,15 @@ public class GostUtil { public static GostDto AddChains(Long node_id, String name, String remoteAddr, String protocol) { JSONObject dialer = new JSONObject(); dialer.put("type", protocol); + if (Objects.equals(protocol, "quic")){ + JSONObject metadata = new JSONObject(); + metadata.put("keepAlive", true); + metadata.put("ttl", "10s"); + dialer.put("metadata", metadata); + } + + + JSONObject connector = new JSONObject(); connector.put("type", "relay"); @@ -212,6 +221,14 @@ public class GostUtil { JSONObject dialer = new JSONObject(); dialer.put("type", protocol); + if (Objects.equals(protocol, "quic")){ + JSONObject metadata = new JSONObject(); + metadata.put("keepAlive", true); + metadata.put("ttl", "10s"); + dialer.put("metadata", metadata); + } + + JSONObject connector = new JSONObject(); connector.put("type", "relay"); diff --git a/springboot-backend/src/main/java/com/admin/controller/ForwardController.java b/springboot-backend/src/main/java/com/admin/controller/ForwardController.java index 7be4851..e65fafa 100644 --- a/springboot-backend/src/main/java/com/admin/controller/ForwardController.java +++ b/springboot-backend/src/main/java/com/admin/controller/ForwardController.java @@ -4,7 +4,6 @@ import com.admin.common.aop.LogAnnotation; import com.admin.common.annotation.RequireRole; import com.admin.common.dto.ForwardDto; import com.admin.common.dto.ForwardUpdateDto; -import com.admin.common.dto.PageDto; import com.admin.common.lang.R; import com.admin.service.ForwardService; import org.springframework.beans.factory.annotation.Autowired; diff --git a/springboot-backend/src/main/java/com/admin/controller/NodeController.java b/springboot-backend/src/main/java/com/admin/controller/NodeController.java index 6adaaba..3a6f3de 100644 --- a/springboot-backend/src/main/java/com/admin/controller/NodeController.java +++ b/springboot-backend/src/main/java/com/admin/controller/NodeController.java @@ -5,7 +5,6 @@ import com.admin.common.annotation.RequireRole; import com.admin.common.aop.LogAnnotation; import com.admin.common.dto.NodeDto; import com.admin.common.dto.NodeUpdateDto; -import com.admin.common.dto.PageDto; import com.admin.common.lang.R; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; diff --git a/springboot-backend/src/main/java/com/admin/controller/TunnelController.java b/springboot-backend/src/main/java/com/admin/controller/TunnelController.java index 203269b..755355e 100644 --- a/springboot-backend/src/main/java/com/admin/controller/TunnelController.java +++ b/springboot-backend/src/main/java/com/admin/controller/TunnelController.java @@ -2,7 +2,6 @@ package com.admin.controller; import com.admin.common.aop.LogAnnotation; import com.admin.common.annotation.RequireRole; -import com.admin.common.dto.PageDto; import com.admin.common.dto.TunnelDto; import com.admin.common.dto.TunnelUpdateDto; @@ -47,7 +46,7 @@ public class TunnelController extends BaseController { @LogAnnotation @RequireRole @PostMapping("/list") - public R readAll(@RequestBody(required = false) PageDto pageDto) { + public R readAll() { return tunnelService.getAllTunnels(); } diff --git a/springboot-backend/src/main/java/com/admin/controller/UserController.java b/springboot-backend/src/main/java/com/admin/controller/UserController.java index 8ee8573..9fcd7b0 100644 --- a/springboot-backend/src/main/java/com/admin/controller/UserController.java +++ b/springboot-backend/src/main/java/com/admin/controller/UserController.java @@ -40,12 +40,8 @@ public class UserController extends BaseController { @LogAnnotation @RequireRole @PostMapping("/list") - public R readAll(@RequestBody(required = false) PageDto pageDto) { - // 如果没有传分页参数,使用默认值 - if (pageDto == null) { - pageDto = new PageDto(); - } - return userService.getAllUsers(pageDto); + public R readAll() { + return userService.getAllUsers(); } @LogAnnotation diff --git a/springboot-backend/src/main/java/com/admin/entity/Tunnel.java b/springboot-backend/src/main/java/com/admin/entity/Tunnel.java index b5b7f4f..1499019 100644 --- a/springboot-backend/src/main/java/com/admin/entity/Tunnel.java +++ b/springboot-backend/src/main/java/com/admin/entity/Tunnel.java @@ -55,7 +55,7 @@ public class Tunnel extends BaseEntity { private int flow; /** - * 协议类型(隧道转发时使用:tls、tcp、mtls) + * 协议类型 */ private String protocol; diff --git a/springboot-backend/src/main/java/com/admin/service/NodeService.java b/springboot-backend/src/main/java/com/admin/service/NodeService.java index 63571bf..05e4489 100644 --- a/springboot-backend/src/main/java/com/admin/service/NodeService.java +++ b/springboot-backend/src/main/java/com/admin/service/NodeService.java @@ -2,7 +2,6 @@ package com.admin.service; import com.admin.common.dto.NodeDto; import com.admin.common.dto.NodeUpdateDto; -import com.admin.common.dto.PageDto; import com.admin.common.lang.R; import com.admin.entity.Node; import com.baomidou.mybatisplus.extension.service.IService; diff --git a/springboot-backend/src/main/java/com/admin/service/TunnelService.java b/springboot-backend/src/main/java/com/admin/service/TunnelService.java index a62706a..f949f57 100644 --- a/springboot-backend/src/main/java/com/admin/service/TunnelService.java +++ b/springboot-backend/src/main/java/com/admin/service/TunnelService.java @@ -1,6 +1,5 @@ package com.admin.service; -import com.admin.common.dto.PageDto; import com.admin.common.dto.TunnelDto; import com.admin.common.dto.TunnelUpdateDto; diff --git a/springboot-backend/src/main/java/com/admin/service/UserService.java b/springboot-backend/src/main/java/com/admin/service/UserService.java index 70e16d2..0738afa 100644 --- a/springboot-backend/src/main/java/com/admin/service/UserService.java +++ b/springboot-backend/src/main/java/com/admin/service/UserService.java @@ -19,7 +19,7 @@ public interface UserService extends IService { R createUser(UserDto userDto); - R getAllUsers(PageDto pageDto); + R getAllUsers(); R updateUser(UserUpdateDto userUpdateDto); diff --git a/springboot-backend/src/main/java/com/admin/service/impl/ForwardServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/ForwardServiceImpl.java index 182483d..e07c61a 100644 --- a/springboot-backend/src/main/java/com/admin/service/impl/ForwardServiceImpl.java +++ b/springboot-backend/src/main/java/com/admin/service/impl/ForwardServiceImpl.java @@ -147,13 +147,42 @@ public class ForwardServiceImpl extends ServiceImpl impl if (tunnel.getStatus() != TUNNEL_STATUS_ACTIVE) { return R.err("隧道已禁用,无法更新转发"); } - + boolean tunnelChanged = isTunnelChanged(existForward, forwardUpdateDto); // 4. 检查权限和限制 UserPermissionResult permissionResult = null; - if (isTunnelChanged(existForward, forwardUpdateDto)) { - permissionResult = checkUserPermissions(currentUser, tunnel, forwardUpdateDto.getId()); - if (permissionResult.isHasError()) { - return R.err(permissionResult.getErrorMessage()); + if (tunnelChanged) { + // 管理员操作用户转发时,需要检查原用户是否有新隧道权限 + if (currentUser.getRoleId() == ADMIN_ROLE_ID) { + // 获取原转发用户的信息 + User originalUser = userService.getById(existForward.getUserId()); + if (originalUser == null) { + return R.err("用户不存在"); + } + + // 检查原用户是否有新隧道权限 + UserTunnel userTunnel = getUserTunnel(existForward.getUserId(), tunnel.getId().intValue()); + if (userTunnel == null) { + return R.err("用户没有该隧道权限"); + } + + // 检查隧道权限到期时间 + if (userTunnel.getExpTime() != null && userTunnel.getExpTime() <= System.currentTimeMillis()) { + return R.err("用户的该隧道权限已到期"); + } + + // 检查原用户的流量和转发数量限制 + R quotaCheckResult = checkForwardQuota(existForward.getUserId(), tunnel.getId().intValue(), userTunnel, originalUser, forwardUpdateDto.getId()); + if (quotaCheckResult.getCode() != 0) { + return R.err("用户" + quotaCheckResult.getMsg()); + } + + permissionResult = UserPermissionResult.success(userTunnel.getSpeedId(), userTunnel); + } else { + // 普通用户检查自己的权限 + permissionResult = checkUserPermissions(currentUser, tunnel, forwardUpdateDto.getId()); + if (permissionResult.isHasError()) { + return R.err(permissionResult.getErrorMessage()); + } } } @@ -181,16 +210,12 @@ public class ForwardServiceImpl extends ServiceImpl impl // 8. 调用Gost服务更新转发 R gostResult; - if (isTunnelChanged(existForward, forwardUpdateDto)) { + if (tunnelChanged) { // 隧道变化时:先删除原配置,再创建新配置 - gostResult = updateGostServicesWithTunnelChange(existForward, updatedForward, tunnel, - permissionResult != null ? permissionResult.getLimiter() : null, - nodeInfo, userTunnel); + gostResult = updateGostServicesWithTunnelChange(existForward, updatedForward, tunnel, permissionResult != null ? permissionResult.getLimiter() : null, nodeInfo, userTunnel); } else { // 隧道未变化时:直接更新配置 - gostResult = updateGostServices(updatedForward, tunnel, - permissionResult != null ? permissionResult.getLimiter() : null, - nodeInfo, userTunnel); + gostResult = updateGostServices(updatedForward, tunnel, permissionResult != null ? permissionResult.getLimiter() : null, nodeInfo, userTunnel); } if (gostResult.getCode() != 0) { diff --git a/springboot-backend/src/main/java/com/admin/service/impl/NodeServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/NodeServiceImpl.java index b566cff..8d8010d 100644 --- a/springboot-backend/src/main/java/com/admin/service/impl/NodeServiceImpl.java +++ b/springboot-backend/src/main/java/com/admin/service/impl/NodeServiceImpl.java @@ -4,7 +4,6 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import com.admin.common.dto.NodeDto; import com.admin.common.dto.NodeUpdateDto; -import com.admin.common.dto.PageDto; import com.admin.common.lang.R; import com.admin.entity.Node; import com.admin.entity.Tunnel; diff --git a/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java index 4b52f9b..6a9d016 100644 --- a/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java +++ b/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java @@ -1,10 +1,7 @@ package com.admin.service.impl; import cn.hutool.core.util.StrUtil; -import com.admin.common.dto.GostDto; -import com.admin.common.dto.TunnelDto; -import com.admin.common.dto.TunnelListDto; -import com.admin.common.dto.TunnelUpdateDto; +import com.admin.common.dto.*; import com.admin.common.lang.R; import com.admin.common.utils.GostUtil; @@ -30,10 +27,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; /** @@ -175,35 +169,50 @@ public class TunnelServiceImpl extends ServiceImpl impleme } // 2. 验证隧道名称唯一性(排除自身) - R nameValidationResult = validateTunnelNameUniquenessForUpdate( - tunnelUpdateDto.getName(), tunnelUpdateDto.getId()); + R nameValidationResult = validateTunnelNameUniquenessForUpdate(tunnelUpdateDto.getName(), tunnelUpdateDto.getId()); if (nameValidationResult.getCode() != 0) { return nameValidationResult; } - - + int up = 0; + if (!Objects.equals(existingTunnel.getTcpListenAddr(), tunnelUpdateDto.getTcpListenAddr()) || + !Objects.equals(existingTunnel.getUdpListenAddr(), tunnelUpdateDto.getUdpListenAddr()) || + !Objects.equals(existingTunnel.getProtocol(), tunnelUpdateDto.getProtocol())) { + up++; + } // 5. 更新允许修改的字段 existingTunnel.setName(tunnelUpdateDto.getName()); existingTunnel.setFlow(tunnelUpdateDto.getFlow()); - - // 更新流量倍率 - if (tunnelUpdateDto.getTrafficRatio() != null) { - existingTunnel.setTrafficRatio(tunnelUpdateDto.getTrafficRatio()); - } - - // 更新TCP和UDP监听地址 - if (StrUtil.isNotBlank(tunnelUpdateDto.getTcpListenAddr())) { - existingTunnel.setTcpListenAddr(tunnelUpdateDto.getTcpListenAddr()); - } - if (StrUtil.isNotBlank(tunnelUpdateDto.getUdpListenAddr())) { - existingTunnel.setUdpListenAddr(tunnelUpdateDto.getUdpListenAddr()); + existingTunnel.setTcpListenAddr(tunnelUpdateDto.getTcpListenAddr()); + existingTunnel.setUdpListenAddr(tunnelUpdateDto.getUdpListenAddr()); + existingTunnel.setTrafficRatio(tunnelUpdateDto.getTrafficRatio()); + existingTunnel.setProtocol(tunnelUpdateDto.getProtocol()); + this.updateById(existingTunnel); + int err = 0; + if (up == 1){ + List tunnel = forwardService.list(new QueryWrapper().eq("tunnel_id", tunnelUpdateDto.getId())); + if (!tunnel.isEmpty()) { + for (Forward forward : tunnel) { + ForwardUpdateDto forwardUpdateDto = new ForwardUpdateDto(); + forwardUpdateDto.setId(forward.getId()); + forwardUpdateDto.setUserId(forward.getUserId()); + forwardUpdateDto.setName(forward.getName()); + forwardUpdateDto.setTunnelId(forward.getTunnelId()); + forwardUpdateDto.setRemoteAddr(forward.getRemoteAddr()); + forwardUpdateDto.setStrategy(forward.getStrategy()); + forwardUpdateDto.setInPort(forward.getInPort()); + R r = forwardService.updateForward(forwardUpdateDto); + if (r.getCode() != 0){ + err++; + } + } + } } - - // 6. 保存更新 - boolean result = this.updateById(existingTunnel); - return result ? R.ok("隧道更新成功") : R.err("隧道更新失败"); + if (err != 0) { + return R.err("隧道信息更新成功,但部分转发同步更新失败"); + } + return R.ok("隧道更新成功"); } /** @@ -425,9 +434,8 @@ public class TunnelServiceImpl extends ServiceImpl impleme // 验证协议类型 String protocol = tunnelDto.getProtocol(); - if (StrUtil.isNotBlank(protocol) && - !protocol.equals("tls") && !protocol.equals("tcp") && !protocol.equals("mtls") && !protocol.equals("wss")) { - return R.err("协议类型只能为tls、tcp、wss或mtls"); + if (StrUtil.isBlank(protocol)) { + return R.err("协议类型必选"); } // 验证出口节点是否存在 diff --git a/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java index 1af9a20..672722c 100644 --- a/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java +++ b/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java @@ -183,21 +183,12 @@ public class UserServiceImpl extends ServiceImpl implements Us /** * 获取所有用户(分页) * 支持关键字搜索,排除管理员用户,清除密码信息 - * - * @param pageDto 分页查询数据传输对象 + * * @return 分页用户列表响应 */ @Override - public R getAllUsers(PageDto pageDto) { - // 1. 构建分页查询 - Page page = new Page<>(pageDto.getCurrent(), pageDto.getSize()); - QueryWrapper queryWrapper = buildUserQueryWrapper(pageDto); - - // 2. 执行查询并处理结果 - Page userPage = this.page(page, queryWrapper); - clearUserPasswords(userPage.getRecords()); - - return R.ok(userPage); + public R getAllUsers() { + return R.ok(this.list(new QueryWrapper().ne("role_id", ADMIN_ROLE_ID))); } /** @@ -446,39 +437,7 @@ public class UserServiceImpl extends ServiceImpl implements Us return user; } - /** - * 构建用户查询条件 - * - * @param pageDto 分页查询DTO - * @return 查询条件包装器 - */ - private QueryWrapper buildUserQueryWrapper(PageDto pageDto) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - - // 关键字搜索 - if (StrUtil.isNotBlank(pageDto.getKeyword())) { - queryWrapper.and(wrapper -> wrapper - .like("user", pageDto.getKeyword()) - ); - } - - // 排除管理员用户 - queryWrapper.ne("id", ADMIN_USER_ID); - - // 按更新时间降序排列 - queryWrapper.orderByDesc("updated_time"); - - return queryWrapper; - } - /** - * 清除用户列表中的密码信息 - * - * @param users 用户列表 - */ - private void clearUserPasswords(List users) { - users.forEach(user -> user.setPwd(null)); - } /** * 检查用户是否存在 diff --git a/vite-frontend/.env.development b/vite-frontend/.env.development index 2e92b25..f06e663 100644 --- a/vite-frontend/.env.development +++ b/vite-frontend/.env.development @@ -1 +1 @@ -VITE_API_BASE=http://127.0.0.1:6365 +VITE_API_BASE=http://192.168.100.8:6365 diff --git a/vite-frontend/package.json b/vite-frontend/package.json index f591617..22fa45c 100644 --- a/vite-frontend/package.json +++ b/vite-frontend/package.json @@ -39,7 +39,6 @@ "@heroui/switch": "^2.2.21", "@heroui/system": "2.4.19", "@heroui/table": "^2.2.24", - "@heroui/tabs": "^2.2.21", "@heroui/theme": "2.4.19", "@heroui/use-theme": "2.1.10", "@nextui-org/system": "^2.4.6", diff --git a/vite-frontend/src/layouts/admin.tsx b/vite-frontend/src/layouts/admin.tsx index 82e8b28..edabc4a 100644 --- a/vite-frontend/src/layouts/admin.tsx +++ b/vite-frontend/src/layouts/admin.tsx @@ -243,11 +243,11 @@ export default function AdminLayout({ ); return ( -
+
{/* 移动端遮罩层 */} {isMobile && mobileMenuVisible && (
)} @@ -263,7 +263,8 @@ export default function AdminLayout({ z-50 transition-transform duration-300 ease-in-out flex flex-col - h-full + ${isMobile ? 'h-screen' : 'h-full'} + ${isMobile ? 'top-0 left-0' : ''} `}> {/* Logo 区域 */}
@@ -277,7 +278,7 @@ export default function AdminLayout({
{/* 菜单导航 */} -