diff --git a/go-backend/internal/http/handler/mutations.go b/go-backend/internal/http/handler/mutations.go index f04177d..ab4d701 100644 --- a/go-backend/internal/http/handler/mutations.go +++ b/go-backend/internal/http/handler/mutations.go @@ -841,6 +841,13 @@ func (h *Handler) userTunnelUpdate(w http.ResponseWriter, r *http.Request) { response.WriteJSON(w, response.Err(-2, err.Error())) return } + + // Fetch details to sync forwards + var userID, tunnelID int64 + if err := h.repo.DB().QueryRow("SELECT user_id, tunnel_id FROM user_tunnel WHERE id = ?", id).Scan(&userID, &tunnelID); err == nil { + h.syncUserTunnelForwards(userID, tunnelID) + } + response.WriteJSON(w, response.OKEmpty()) } @@ -2279,8 +2286,8 @@ func replaceTunnelChainsTx(tx *sql.Tx, tunnelID int64, req map[string]interface{ return pickErr } } - _, err := tx.Exec(`INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol) VALUES(?, 3, ?, ?, NULL, 0, ?)`, - tunnelID, nodeID, port, defaultString(asString(n["protocol"]), "tls")) + _, err := tx.Exec(`INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol) VALUES(?, 3, ?, ?, ?, 0, ?)`, + tunnelID, nodeID, port, defaultString(asString(n["strategy"]), "round"), defaultString(asString(n["protocol"]), "tls")) if err != nil { return err } @@ -2683,9 +2690,26 @@ func (h *Handler) upsertUserTunnel(req map[string]interface{}) error { _, err = db.Exec(`UPDATE user_tunnel SET speed_id = ?, flow = ?, num = ?, exp_time = ?, flow_reset_time = ?, status = ? WHERE id = ?`, newSpeedID, newFlow, newNum, newExpTime, newFlowReset, newStatus, existingID) + + if err == nil { + h.syncUserTunnelForwards(userID, tunnelID) + } return err } +func (h *Handler) syncUserTunnelForwards(userID, tunnelID int64) { + forwards, err := h.listForwardsByTunnel(tunnelID) + if err != nil { + return + } + for i := range forwards { + f := &forwards[i] + if f.UserID == userID { + _ = h.syncForwardServices(f, "UpdateService", true) + } + } +} + func asAnySlice(v interface{}) []interface{} { if v == nil { return nil diff --git a/go-gost/x/socket/chain.go b/go-gost/x/socket/chain.go index 5d6149e..74b6325 100644 --- a/go-gost/x/socket/chain.go +++ b/go-gost/x/socket/chain.go @@ -43,8 +43,8 @@ func updateChain(req updateChainRequest) error { name := strings.TrimSpace(req.Chain) - if !registry.ChainRegistry().IsRegistered(name) { - return errors.New("chain " + name + " not found") + if registry.ChainRegistry().IsRegistered(name) { + registry.ChainRegistry().Unregister(name) } req.Data.Name = name @@ -54,19 +54,22 @@ func updateChain(req updateChainRequest) error { return errors.New("create chain " + name + " failed: " + err.Error()) } - registry.ChainRegistry().Unregister(name) - if err := registry.ChainRegistry().Register(name, v); err != nil { return errors.New("chain " + name + " already exists") } config.OnUpdate(func(c *config.Config) error { + found := false for i := range c.Chains { if c.Chains[i].Name == name { c.Chains[i] = &req.Data + found = true break } } + if !found { + c.Chains = append(c.Chains, &req.Data) + } return nil }) @@ -77,10 +80,9 @@ func deleteChain(req deleteChainRequest) error { name := strings.TrimSpace(req.Chain) - if !registry.ChainRegistry().IsRegistered(name) { - return errors.New("chain " + name + " not found") + if registry.ChainRegistry().IsRegistered(name) { + registry.ChainRegistry().Unregister(name) } - registry.ChainRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { chains := c.Chains diff --git a/go-gost/x/socket/limiter.go b/go-gost/x/socket/limiter.go index a41db66..61ed9e5 100644 --- a/go-gost/x/socket/limiter.go +++ b/go-gost/x/socket/limiter.go @@ -37,27 +37,30 @@ func updateLimiter(req updateLimiterRequest) error { name := strings.TrimSpace(req.Limiter) - if !registry.TrafficLimiterRegistry().IsRegistered(name) { - return errors.New("limiter " + name + " not found") + if registry.TrafficLimiterRegistry().IsRegistered(name) { + registry.TrafficLimiterRegistry().Unregister(name) } req.Data.Name = name v := parser.ParseTrafficLimiter(&req.Data) - registry.TrafficLimiterRegistry().Unregister(name) - if err := registry.TrafficLimiterRegistry().Register(name, v); err != nil { return errors.New("limiter " + name + " already exists") } config.OnUpdate(func(c *config.Config) error { + found := false for i := range c.Limiters { if c.Limiters[i].Name == name { c.Limiters[i] = &req.Data + found = true break } } + if !found { + c.Limiters = append(c.Limiters, &req.Data) + } return nil }) @@ -68,10 +71,9 @@ func deleteLimiter(req deleteLimiterRequest) error { name := strings.TrimSpace(req.Limiter) - if !registry.TrafficLimiterRegistry().IsRegistered(name) { - return errors.New("limiter " + name + " not found") + if registry.TrafficLimiterRegistry().IsRegistered(name) { + registry.TrafficLimiterRegistry().Unregister(name) } - registry.TrafficLimiterRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { limiteres := c.Limiters diff --git a/go-gost/x/socket/service.go b/go-gost/x/socket/service.go index 1811cd4..33b23ae 100644 --- a/go-gost/x/socket/service.go +++ b/go-gost/x/socket/service.go @@ -87,36 +87,32 @@ func updateServices(req updateServicesRequest) error { return errors.New("services list cannot be empty") } - // 第一阶段:验证所有服务存在 - for _, serviceConfig := range req.Data { - name := strings.TrimSpace(serviceConfig.Name) + // 第一阶段:验证所有服务名称有效性 + for i := range req.Data { + name := strings.TrimSpace(req.Data[i].Name) if name == "" { return errors.New("service name is required") } - serviceConfig.Name = name - - old := registry.ServiceRegistry().Get(name) - if old == nil { - return errors.New("service " + name + " not found") - } + req.Data[i].Name = name } - // 第二阶段:按照原来的updateService逻辑,逐个更新服务 - for _, serviceConfig := range req.Data { - name := strings.TrimSpace(serviceConfig.Name) - serviceConfig.Name = name + // 第二阶段:逐个更新服务(Upsert模式:存在则更新,不存在则创建) + for i := range req.Data { + serviceConfig := &req.Data[i] + name := serviceConfig.Name // 1. 获取旧服务 old := registry.ServiceRegistry().Get(name) - // 2. 关闭旧服务 - old.Close() - - // 3. 从注册表移除旧服务 - registry.ServiceRegistry().Unregister(name) + // 2. 关闭旧服务 (如果存在) + if old != nil { + old.Close() + // 3. 从注册表移除旧服务 + registry.ServiceRegistry().Unregister(name) + } // 4. 解析新服务配置 - svc, err := parser.ParseService(&serviceConfig) + svc, err := parser.ParseService(serviceConfig) if err != nil { return errors.New("create service " + name + " failed: " + err.Error()) } @@ -133,13 +129,20 @@ func updateServices(req updateServicesRequest) error { // 第三阶段:更新配置 config.OnUpdate(func(c *config.Config) error { - for _, serviceConfig := range req.Data { - for i := range c.Services { - if c.Services[i].Name == serviceConfig.Name { - c.Services[i] = &serviceConfig + for i := range req.Data { + // 创建副本以确保指针安全 + cfgCopy := req.Data[i] + found := false + for j := range c.Services { + if c.Services[j].Name == cfgCopy.Name { + c.Services[j] = &cfgCopy + found = true break } } + if !found { + c.Services = append(c.Services, &cfgCopy) + } } return nil }) @@ -158,22 +161,22 @@ func deleteServices(req deleteServicesRequest) error { name string service service.Service } + var namesToRemove []string for _, serviceName := range req.Services { name := strings.TrimSpace(serviceName) if name == "" { return errors.New("service name is required") } + namesToRemove = append(namesToRemove, name) svc := registry.ServiceRegistry().Get(name) - if svc == nil { - return errors.New("service " + name + " not found") + if svc != nil { + servicesToDelete = append(servicesToDelete, struct { + name string + service service.Service + }{name, svc}) } - - servicesToDelete = append(servicesToDelete, struct { - name string - service service.Service - }{name, svc}) } // 第二阶段:删除所有服务 @@ -181,6 +184,12 @@ func deleteServices(req deleteServicesRequest) error { registry.ServiceRegistry().Unregister(std.name) std.service.Close() } + // 确保所有请求删除的服务都从注册表中移除(即使之前未找到实例) + for _, name := range namesToRemove { + if registry.ServiceRegistry().IsRegistered(name) { + registry.ServiceRegistry().Unregister(name) + } + } // 第三阶段:更新配置 config.OnUpdate(func(c *config.Config) error { @@ -188,8 +197,8 @@ func deleteServices(req deleteServicesRequest) error { c.Services = nil for _, s := range services { shouldDelete := false - for _, std := range servicesToDelete { - if s.Name == std.name { + for _, name := range namesToRemove { + if s.Name == name { shouldDelete = true break } diff --git a/springboot-backend/.factorypath b/springboot-backend/.factorypath deleted file mode 100644 index 40ad18f..0000000 --- a/springboot-backend/.factorypath +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/springboot-backend/AGENTS.md b/springboot-backend/AGENTS.md deleted file mode 100644 index 0c3873a..0000000 --- a/springboot-backend/AGENTS.md +++ /dev/null @@ -1,39 +0,0 @@ -# SPRINGBOOT BACKEND KNOWLEDGE BASE - -**Generated:** Mon Feb 02 2026 - -## OVERVIEW -Admin API for Flux Panel. Manages users, tunnels, nodes, forwards, quotas, and speed limits. -**Stack:** Java 21, Spring Boot 2.7.18, SQLite, MyBatis Plus (+ join), FastJSON2. - -## STRUCTURE -``` -springboot-backend/ -├── src/main/java/com/admin/ -│ ├── controller/ # /api/v1/* endpoints -│ ├── entity/ # DB models -│ ├── mapper/ # MyBatis Plus mappers -│ ├── service/ # Business logic -│ ├── config/ # WebMvc/JWT/CORS/WebSocket config -│ └── common/ # DTOs, auth, exception handling, utilities -└── src/main/resources/ - ├── application.yml # Config (DB_PATH/JWT_SECRET/LOG_DIR) - ├── mapper/ # XML mappers - ├── schema.sql # Schema - └── data.sql # Seed data -``` - -## CONVENTIONS -- **DB**: SQLite URL is `jdbc:sqlite:${DB_PATH:/app/data/gost.db}` (`springboot-backend/src/main/resources/application.yml`). -- **Auth**: JWT in `Authorization` header; enforced by `com.admin.common.interceptor.JwtInterceptor` for `/api/**` (with explicit excludes in `com.admin.config.WebMvcConfig`). -- **Roles**: `@RequireRole` means admin-only (`role_id == 0`) via `com.admin.common.aop.RoleAspect`. -- **Responses**: Controllers return `com.admin.common.lang.R` (`code == 0` success). -- **CORS**: Allow-all origins; `Authorization` is exposed (`com.admin.config.WebMvcConfig`). - -## COMMANDS -```bash -cd springboot-backend -mvn clean package -mvn test -java -jar target/admin-0.0.1-SNAPSHOT.jar -``` diff --git a/springboot-backend/Dockerfile b/springboot-backend/Dockerfile deleted file mode 100644 index 208217e..0000000 --- a/springboot-backend/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# 构建阶段 -FROM maven:3.9.6-eclipse-temurin-21 AS build -WORKDIR /app -COPY pom.xml . -COPY src ./src -RUN mvn clean package -DskipTests - -# 运行阶段 -FROM eclipse-temurin:21-jre -WORKDIR /app -ENV JAVA_OPTS="-Xmx512m -Xms256m -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai" -ENV LANG=en_US.UTF-8 -ENV LANGUAGE=en_US:en -ENV LC_ALL=en_US.UTF-8 - -RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list \ - && sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list \ - && apt-get update \ - && apt-get install -y --no-install-recommends fontconfig fonts-dejavu sqlite3 \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=build /app/target/*.jar app.jar - -EXPOSE 6365 - -ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /app/app.jar"] diff --git a/springboot-backend/pom.xml b/springboot-backend/pom.xml deleted file mode 100644 index 834fd04..0000000 --- a/springboot-backend/pom.xml +++ /dev/null @@ -1,180 +0,0 @@ - - - 4.0.0 - com.admin - admin - 0.0.1-SNAPSHOT - admin - admin - - 21 - UTF-8 - UTF-8 - 2.7.18 - 21 - 21 - - - - - org.springframework.boot - spring-boot-starter-web - - - com.alibaba.fastjson2 - fastjson2 - 2.0.43 - - - - - org.projectlombok - lombok - 1.18.30 - true - - - - com.github.yulichang - mybatis-plus-join - 1.3.0 - - - - - com.alibaba - fastjson - 1.2.70 - - - - - - - org.springframework.boot - spring-boot-starter-aop - - - - com.baomidou - mybatis-plus-boot-starter - 3.4.1 - - - - com.baomidou - mybatis-plus-generator - 3.4.1 - - - - org.xerial - sqlite-jdbc - 3.45.0.0 - - - - cn.hutool - hutool-all - 5.3.3 - - - - org.apache.commons - commons-lang3 - 3.11 - - - - org.springframework.boot - spring-boot-starter-validation - - - - org.apache.httpcomponents - httpclient - 4.5.14 - - - - org.freemarker - freemarker - 2.3.31 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-websocket - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - cloud.tianai.captcha - tianai-captcha-springboot-starter - 1.5.2 - - - - - - org.springframework.boot - spring-boot-dependencies - ${spring-boot.version} - pom - import - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - 21 - 21 - UTF-8 - - - org.projectlombok - lombok - 1.18.30 - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - com.admin.AdminApplication - - - - repackage - - repackage - - - - - - - - diff --git a/springboot-backend/src/main/java/com/admin/AGENTS.md b/springboot-backend/src/main/java/com/admin/AGENTS.md deleted file mode 100644 index 1505a82..0000000 --- a/springboot-backend/src/main/java/com/admin/AGENTS.md +++ /dev/null @@ -1,32 +0,0 @@ -# SPRINGBOOT BACKEND (com.admin) KNOWLEDGE BASE - -## OVERVIEW -Primary Java code for the admin API. Controllers expose `/api/v1/*` endpoints and return `R` response envelopes. - -## STRUCTURE -``` -springboot-backend/src/main/java/com/admin/ -├── controller/ # REST controllers (e.g., /api/v1/user) -├── service/ # Business logic interfaces + impl/ -├── mapper/ # MyBatis Plus mappers -├── entity/ # DB entities -├── config/ # WebMvc/JWT/CORS/WebSocket config -└── common/ # DTOs, auth, exception handling, utilities -``` - -## WHERE TO LOOK -| Task | Location | Notes | -|------|----------|-------| -| User/login endpoints | `springboot-backend/src/main/java/com/admin/controller/UserController.java` | `/api/v1/user/*` | -| Auth enforcement | `springboot-backend/src/main/java/com/admin/config/WebMvcConfig.java` | Intercepts `/api/**`, excludes login/config/captcha | -| JWT validation | `springboot-backend/src/main/java/com/admin/common/interceptor/JwtInterceptor.java` | Requires `Authorization` header | -| Admin-only ops | `springboot-backend/src/main/java/com/admin/common/annotation/RequireRole.java` | Enforced by `RoleAspect` | -| Response envelope | `springboot-backend/src/main/java/com/admin/common/lang/R.java` | `code == 0` success | -| Global error handling | `springboot-backend/src/main/java/com/admin/common/exception/GlobalExceptionHandler.java` | Maps exceptions -> `R.err(...)` | - -## CONVENTIONS -- Controllers are mostly `@PostMapping` (even for list/get/delete) and use `/api/v1/*` prefixes. -- JWT is custom (no 3p lib) and includes `role_id` in payload (`springboot-backend/src/main/java/com/admin/common/utils/JwtUtil.java`). - -## ANTI-PATTERNS -- Do not change auth header format lightly: frontend expects `Authorization: ` (no `Bearer`). diff --git a/springboot-backend/src/main/java/com/admin/AdminApplication.java b/springboot-backend/src/main/java/com/admin/AdminApplication.java deleted file mode 100644 index b0989df..0000000 --- a/springboot-backend/src/main/java/com/admin/AdminApplication.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.admin; - -import com.baomidou.mybatisplus.annotation.DbType; -import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; -import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.annotation.EnableScheduling; - -@SpringBootApplication -@EnableAsync -@EnableScheduling -@MapperScan("com.admin.mapper") -public class AdminApplication { - - public static void main(String[] args) { - SpringApplication.run(AdminApplication.class, args); - } - - -} diff --git a/springboot-backend/src/main/java/com/admin/common/annotation/RequireRole.java b/springboot-backend/src/main/java/com/admin/common/annotation/RequireRole.java deleted file mode 100644 index 7f463e6..0000000 --- a/springboot-backend/src/main/java/com/admin/common/annotation/RequireRole.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.admin.common.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 权限控制注解 - * 用于标记需要管理员权限的方法(role_id = 0) - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface RequireRole { -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/aop/LogAnnotation.java b/springboot-backend/src/main/java/com/admin/common/aop/LogAnnotation.java deleted file mode 100644 index fe35460..0000000 --- a/springboot-backend/src/main/java/com/admin/common/aop/LogAnnotation.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.admin.common.aop; - - -import java.lang.annotation.*; - -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface LogAnnotation {} diff --git a/springboot-backend/src/main/java/com/admin/common/aop/LogAspect.java b/springboot-backend/src/main/java/com/admin/common/aop/LogAspect.java deleted file mode 100644 index 3313c74..0000000 --- a/springboot-backend/src/main/java/com/admin/common/aop/LogAspect.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.admin.common.aop; - - -import cn.hutool.core.util.ArrayUtil; -import com.admin.common.utils.JwtUtil; -import com.alibaba.fastjson.JSON; -import com.admin.common.utils.HttpContextUtils; -import com.admin.common.utils.IpUtils; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.*; -import org.aspectj.lang.reflect.CodeSignature; -import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -@Component -@Aspect -@Slf4j -public class LogAspect { - - @Pointcut("@annotation(com.admin.common.aop.LogAnnotation)") - public void pt() { - - } - - /** - * 返回后通知(@AfterReturning):在某连接点(joinpoint) - * 正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回 - * 方法执行完毕之后 - * 注意在这里不能使用ProceedingJoinPoint - * 不然会报错ProceedingJoinPoint is only supported for around advice - * crmAspect()指向需要控制的方法 - * returning 注解返回值 - * - * @param joinPoint - * @param returnValue 返回值 - * @throws Exception - */ - @AfterReturning(value = "pt()", returning = "returnValue") - public void log(JoinPoint joinPoint, Object returnValue) throws Throwable { - // 获取请求信息 - HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); - - // 获取请求方法类型(POST/GET等) - String requestMethod = request.getMethod(); - - // 获取用户ID - String authorization = request.getHeader("Authorization") + ""; - Object user_id = "未登录"; // 请求用户的id - if (!authorization.equals("null")) { - user_id = JwtUtil.getUserIdFromToken(authorization); - } - - // 获取请求IP - String ipAddr = IpUtils.getIpAddr(request); - - // 获取方法签名信息 - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - Method method = signature.getMethod(); - - // 获取控制器方法名 - String className = joinPoint.getTarget().getClass().getName(); - String methodName = signature.getName(); - String controllerMethod = className + "." + methodName; - - - // 获取请求参数 - String requestParams = getRequestParams(joinPoint); - - // 获取返回参数 - String responseParams = returnValue != null ? JSON.toJSONString(returnValue) : "无返回值"; - - // 合并为一条完整的日志信息 - String logMessage = String.format( - "【请求日志】用户ID:[%s], IP地址:[%s], 请求方式:[%s], 控制器方法:[%s], 请求参数:[%s], 返回参数:[%s]", user_id, ipAddr, requestMethod, controllerMethod, requestParams, responseParams - ); - - // 打印单条完整日志 - log.info(logMessage); - } - - - /** - * 抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知 - * 注意在这里不能使用ProceedingJoinPoint - * 不然会报错ProceedingJoinPoint is only supported for around advice - * throwing注解为错误信息 - * - * @param joinPoint - * @param ex - */ - @AfterThrowing(value = "pt()", throwing = "ex") - public void recordLog(JoinPoint joinPoint, Exception ex) { - try { - // 获取请求信息 - HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); - - // 获取请求方法类型(POST/GET等) - String requestMethod = request.getMethod(); - - // 获取用户ID - String authorization = request.getHeader("Authorization") + ""; - Object user_id = "未登录"; // 请求用户的id - if (!authorization.equals("null")) { - user_id = JwtUtil.getUserIdFromToken(authorization); - } - - // 获取请求IP - String ipAddr = IpUtils.getIpAddr(request); - - // 获取方法签名信息 - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - Method method = signature.getMethod(); - - // 获取控制器方法名 - String className = joinPoint.getTarget().getClass().getName(); - String methodName = signature.getName(); - String controllerMethod = className + "." + methodName; - - - - // 获取请求参数 - String requestParams = getRequestParams(joinPoint); - - // 获取异常信息 - String exceptionMsg = ex != null ? ex.getMessage() : "未知异常"; - - // 合并为一条完整的异常日志信息 - String errorMessage = String.format( - "【异常日志】用户ID:[%s], IP地址:[%s], 请求方式:[%s], 控制器方法:[%s], 请求参数:[%s], 异常信息:[%s]", user_id, ipAddr, requestMethod, controllerMethod, requestParams, exceptionMsg - ); - - // 打印单条完整异常日志 - log.info(errorMessage, ex); - } catch (Exception e) { - log.info("记录异常日志时出错: {}", e.getMessage()); - } - } - - /** - * 获取请求参数 - */ - private String getRequestParams(JoinPoint joinPoint) { - try { - Object[] args = joinPoint.getArgs(); - if (args.length == 0) { - return "无参数"; - } else if (args[0] != null && args[0].toString().contains("SecurityContextHolderAwareRequestWrapper")) { - return JSON.toJSONString(Arrays.toString(ArrayUtil.remove(args, 0))); - } else { - // 检查是否只有一个参数且已经是JSON字符串格式 - if (args.length == 1 && args[0] != null) { - // 如果参数本身就是字符串且是JSON格式,直接返回 - if (args[0] instanceof String && ((String) args[0]).startsWith("{") && ((String) args[0]).endsWith("}")) { - return (String) args[0]; - } - - // 如果参数是普通对象,直接序列化 - try { - return JSON.toJSONString(args[0]); - } catch (Exception e) { - // 如果序列化失败,再尝试使用参数名映射 - Map map = new HashMap<>(); - String[] names = ((CodeSignature) joinPoint.getSignature()).getParameterNames(); - if (names != null) { - map.put(names[0], args[0]); - return JSON.toJSONString(map); - } - return JSON.toJSONString(args[0]); - } - } else { - // 多个参数时,使用参数名映射 - Map map = new HashMap<>(); - String[] names = ((CodeSignature) joinPoint.getSignature()).getParameterNames(); - if (names != null) { - for (int i = 0; i < names.length; i++) { - map.put(names[i], args[i]); - } - } - return JSON.toJSONString(map); - } - } - } catch (Exception e) { - return "获取参数失败: " + e.getMessage(); - } - } -} diff --git a/springboot-backend/src/main/java/com/admin/common/aop/RoleAspect.java b/springboot-backend/src/main/java/com/admin/common/aop/RoleAspect.java deleted file mode 100644 index 69cc375..0000000 --- a/springboot-backend/src/main/java/com/admin/common/aop/RoleAspect.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.admin.common.aop; - -import com.admin.common.annotation.RequireRole; -import com.admin.common.lang.R; -import com.admin.common.utils.JwtUtil; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.springframework.stereotype.Component; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; - -/** - * 权限控制切面 - * 处理 @RequireRole 注解,检查管理员权限(role_id = 0) - * 注意:JWT拦截器已经验证了token的有效性,这里只需要检查权限 - */ -@Aspect -@Component -public class RoleAspect { - - @Around("@annotation(requireRole)") - public Object checkRole(ProceedingJoinPoint joinPoint, RequireRole requireRole) throws Throwable { - // 获取当前请求 - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (attributes == null) { - return R.err(500, "无法获取请求信息"); - } - - HttpServletRequest request = attributes.getRequest(); - String token = request.getHeader("Authorization"); - - // JWT拦截器已经验证过token存在且有效,这里直接获取role_id - Integer roleId = JwtUtil.getRoleIdFromToken(token); - if (roleId == null) { - return R.err(401, "无法获取用户权限信息"); - } - - // 检查是否为管理员(role_id = 0) - if (roleId != 0) { - return R.err(403, "权限不足,仅管理员可操作"); - } - - // 权限检查通过,执行原方法 - return joinPoint.proceed(); - } -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/BatchChangeTunnelDto.java b/springboot-backend/src/main/java/com/admin/common/dto/BatchChangeTunnelDto.java deleted file mode 100644 index 19923f3..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/BatchChangeTunnelDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.util.List; - -@Data -public class BatchChangeTunnelDto { - - @NotEmpty(message = "转发ID列表不能为空") - private List forwardIds; - - @NotNull(message = "目标隧道ID不能为空") - private Long targetTunnelId; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/BatchDeleteDto.java b/springboot-backend/src/main/java/com/admin/common/dto/BatchDeleteDto.java deleted file mode 100644 index 46e31d4..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/BatchDeleteDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; -import javax.validation.constraints.NotEmpty; -import java.util.List; - -@Data -public class BatchDeleteDto { - - @NotEmpty(message = "ID列表不能为空") - private List ids; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/BatchOperationResultDto.java b/springboot-backend/src/main/java/com/admin/common/dto/BatchOperationResultDto.java deleted file mode 100644 index 37b87e1..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/BatchOperationResultDto.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; -import java.util.List; -import java.util.ArrayList; - -@Data -public class BatchOperationResultDto { - - private int successCount; - private int failCount; - private List failedItems = new ArrayList<>(); - - @Data - public static class FailedItem { - private Long id; - private String reason; - - public FailedItem() {} - - public FailedItem(Long id, String reason) { - this.id = id; - this.reason = reason; - } - } - - public void addFailedItem(Long id, String reason) { - this.failedItems.add(new FailedItem(id, reason)); - this.failCount++; - } - - public void incrementSuccess() { - this.successCount++; - } - - public boolean isAllSuccess() { - return failCount == 0; - } -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/BatchRedeployDto.java b/springboot-backend/src/main/java/com/admin/common/dto/BatchRedeployDto.java deleted file mode 100644 index 27c6fae..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/BatchRedeployDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; -import javax.validation.constraints.NotEmpty; -import java.util.List; - -@Data -public class BatchRedeployDto { - - @NotEmpty(message = "ID列表不能为空") - private List ids; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/CaptchaVerifyDto.java b/springboot-backend/src/main/java/com/admin/common/dto/CaptchaVerifyDto.java deleted file mode 100644 index 0b6c8db..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/CaptchaVerifyDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.admin.common.dto; - -import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack; -import lombok.Data; - -import javax.validation.constraints.NotBlank; - -/** - * 验证码验证请求DTO - */ -@Data -public class CaptchaVerifyDto { - - private String id; - - private ImageCaptchaTrack data; - -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/ChainNodesItems.java b/springboot-backend/src/main/java/com/admin/common/dto/ChainNodesItems.java deleted file mode 100644 index 1c84f1c..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/ChainNodesItems.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.admin.common.dto; - - -import lombok.Data; - -import java.util.List; - -@Data -public class ChainNodesItems { - - private String mode; - - private List nodeIds; - -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/ChangePasswordDto.java b/springboot-backend/src/main/java/com/admin/common/dto/ChangePasswordDto.java deleted file mode 100644 index c6d6bd3..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/ChangePasswordDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotBlank; - -@Data -public class ChangePasswordDto { - - @NotBlank(message = "新用户名不能为空") - private String newUsername; - - @NotBlank(message = "当前密码不能为空") - private String currentPassword; - - @NotBlank(message = "新密码不能为空") - private String newPassword; - - @NotBlank(message = "确认密码不能为空") - private String confirmPassword; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/ConfigItem.java b/springboot-backend/src/main/java/com/admin/common/dto/ConfigItem.java deleted file mode 100644 index b8dae8e..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/ConfigItem.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.admin.common.dto; - - -import lombok.Data; - -@Data -public class ConfigItem { - private String name; -} - - diff --git a/springboot-backend/src/main/java/com/admin/common/dto/DiagnosisResult.java b/springboot-backend/src/main/java/com/admin/common/dto/DiagnosisResult.java deleted file mode 100644 index 1ef2184..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/DiagnosisResult.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -@Data -public class DiagnosisResult { - private Long nodeId; - private String nodeName; - private String targetIp; - private Integer targetPort; - private String description; - private boolean success; - private String message; - private double averageTime; - private double packetLoss; - private long timestamp; - private Integer fromChainType; - private Integer fromInx; - private Integer toChainType; - private Integer toInx; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/FlowDto.java b/springboot-backend/src/main/java/com/admin/common/dto/FlowDto.java deleted file mode 100644 index c067f49..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/FlowDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.common.dto; - - -import lombok.Data; - -@Data -public class FlowDto { - // 转发id_类型 - private String n; - - // 上传流量 - private Long u; - - // 下载流量 - private Long d; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/ForwardDto.java b/springboot-backend/src/main/java/com/admin/common/dto/ForwardDto.java deleted file mode 100644 index 59ddfd2..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/ForwardDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.admin.common.dto; - -import com.baomidou.mybatisplus.annotation.FieldStrategy; -import com.baomidou.mybatisplus.annotation.TableField; -import lombok.Data; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Min; -import javax.validation.constraints.Max; - -@Data -public class ForwardDto { - - @NotBlank(message = "转发名称不能为空") - private String name; - - @NotNull(message = "隧道ID不能为空") - private Integer tunnelId; - - @NotBlank(message = "远程地址不能为空") - private String remoteAddr; - - private String strategy; - - private Integer inPort; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/ForwardPortDto.java b/springboot-backend/src/main/java/com/admin/common/dto/ForwardPortDto.java deleted file mode 100644 index c49ea28..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/ForwardPortDto.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.admin.common.dto; - - -import lombok.Data; - -@Data -public class ForwardPortDto { - private Integer node_id; - private Integer port; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/ForwardUpdateDto.java b/springboot-backend/src/main/java/com/admin/common/dto/ForwardUpdateDto.java deleted file mode 100644 index b5feb49..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/ForwardUpdateDto.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.admin.common.dto; - -import com.baomidou.mybatisplus.annotation.FieldStrategy; -import com.baomidou.mybatisplus.annotation.TableField; -import lombok.Data; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Min; -import javax.validation.constraints.Max; - -@Data -public class ForwardUpdateDto { - - @NotNull(message = "ID不能为空") - private Long id; - - @NotNull(message = "用户ID不能为空") - private Integer userId; - - @NotBlank(message = "转发名称不能为空") - private String name; - - private Integer tunnelId; - - @NotBlank(message = "远程地址不能为空") - private String remoteAddr; - - private String strategy; - - private Integer inPort; - -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/ForwardWithTunnelDto.java b/springboot-backend/src/main/java/com/admin/common/dto/ForwardWithTunnelDto.java deleted file mode 100644 index c6e8438..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/ForwardWithTunnelDto.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -/** - *

- * 转发信息及关联隧道信息DTO - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@Data -public class ForwardWithTunnelDto { - - /** - * 转发记录ID - */ - private Long id; - - - /** - * 转发名称 - */ - private String name; - - - /** - * 入口IP - */ - private String inIp; - - /** - * 入口端口 - */ - private Integer inPort; - - /** - * 远程地址 - */ - private String remoteAddr; - - /** - * 转发状态 - */ - private Integer status; - - /** - * 创建时间 - */ - private Long createdTime; - - /** - * 更新时间 - */ - private Long updatedTime; - - // 以下为隧道相关字段 - - /** - * 隧道名称 - */ - private String tunnelName; - - - private String userName; - - /** - * 用户ID - */ - private Integer userId; - /** - * 隧道ID - */ - private Integer tunnelId; - - /** - * 入站流量(字节) - */ - private Long inFlow; - - /** - * 出站流量(字节) - */ - private Long outFlow; - - private String strategy; - - private Integer inx; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/GostConfigDto.java b/springboot-backend/src/main/java/com/admin/common/dto/GostConfigDto.java deleted file mode 100644 index edcde19..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/GostConfigDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.admin.common.dto; - - -import lombok.Data; - -import java.util.List; - -@Data -public class GostConfigDto { - - private List limiters; - - private List chains; - - private List services; - - -} - diff --git a/springboot-backend/src/main/java/com/admin/common/dto/GostDto.java b/springboot-backend/src/main/java/com/admin/common/dto/GostDto.java deleted file mode 100644 index a57f2b7..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/GostDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -@Data -public class GostDto { - private Integer code; - - private String msg; - - private Object data; // 添加数据字段,用于存储响应的详细数据 -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/GroupCreateDto.java b/springboot-backend/src/main/java/com/admin/common/dto/GroupCreateDto.java deleted file mode 100644 index 1516a90..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/GroupCreateDto.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotBlank; - -@Data -public class GroupCreateDto { - - @NotBlank(message = "分组名称不能为空") - private String name; - - private Integer status; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/GroupPermissionAssignDto.java b/springboot-backend/src/main/java/com/admin/common/dto/GroupPermissionAssignDto.java deleted file mode 100644 index bb6784e..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/GroupPermissionAssignDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotNull; - -@Data -public class GroupPermissionAssignDto { - - @NotNull(message = "用户分组ID不能为空") - private Long userGroupId; - - @NotNull(message = "隧道分组ID不能为空") - private Long tunnelGroupId; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/GroupPermissionDetailDto.java b/springboot-backend/src/main/java/com/admin/common/dto/GroupPermissionDetailDto.java deleted file mode 100644 index a3ab62b..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/GroupPermissionDetailDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -@Data -public class GroupPermissionDetailDto { - private Long id; - private Long userGroupId; - private String userGroupName; - private Long tunnelGroupId; - private String tunnelGroupName; - private Long createdTime; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/GroupUpdateDto.java b/springboot-backend/src/main/java/com/admin/common/dto/GroupUpdateDto.java deleted file mode 100644 index cec38d2..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/GroupUpdateDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; - -@Data -public class GroupUpdateDto { - - @NotNull(message = "分组ID不能为空") - private Long id; - - @NotBlank(message = "分组名称不能为空") - private String name; - - private Integer status; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/LoginDto.java b/springboot-backend/src/main/java/com/admin/common/dto/LoginDto.java deleted file mode 100644 index ac587fa..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/LoginDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.admin.common.dto; - - -import lombok.Data; - -import javax.validation.constraints.NotBlank; - -@Data -public class LoginDto { - - @NotBlank(message = "用户名不能为空") - private String username; - - @NotBlank(message = "密码不能为空") - private String password; - - private String captchaId; - - -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/NodeDto.java b/springboot-backend/src/main/java/com/admin/common/dto/NodeDto.java deleted file mode 100644 index 3985eff..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/NodeDto.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Max; -import javax.validation.constraints.Min; - -@Data -public class NodeDto { - - @NotBlank(message = "节点名称不能为空") - private String name; - - @NotBlank(message = "服务器ip不能为空") - private String serverIp; - - private String serverIpV4; - - private String serverIpV6; - - @NotBlank(message = "可用端口不能为空") - private String port; - - private String interfaceName; - - private String tcpListenAddr = "0.0.0.0"; - - private String udpListenAddr = "0.0.0.0"; - -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/NodeUpdateDto.java b/springboot-backend/src/main/java/com/admin/common/dto/NodeUpdateDto.java deleted file mode 100644 index 27c3f18..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/NodeUpdateDto.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; - -@Data -public class NodeUpdateDto { - - @NotNull(message = "节点ID不能为空") - private Long id; - - @NotBlank(message = "节点名称不能为空") - private String name; - - @NotBlank(message = "服务器ip不能为空") - private String serverIp; - - private String serverIpV4; - - private String serverIpV6; - - @NotBlank(message = "可用port不能为空") - private String port; - - private String interfaceName; - private Integer http; - private Integer tls; - private Integer socks; - - private String tcpListenAddr = "0.0.0.0"; - - private String udpListenAddr = "0.0.0.0"; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/ResetFlowDto.java b/springboot-backend/src/main/java/com/admin/common/dto/ResetFlowDto.java deleted file mode 100644 index 8854cfc..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/ResetFlowDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.common.dto; - - -import lombok.Data; - -import javax.validation.constraints.NotNull; - -@Data -public class ResetFlowDto { - - @NotNull(message = "重置账号id不能为空") - private Integer id; - - @NotNull(message = "重置类型不能为空") - private Integer type; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/SpeedLimitDto.java b/springboot-backend/src/main/java/com/admin/common/dto/SpeedLimitDto.java deleted file mode 100644 index 5ba0520..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/SpeedLimitDto.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Min; - -@Data -public class SpeedLimitDto { - - @NotBlank(message = "限速规则名称不能为空") - private String name; - - @NotNull(message = "速度限制不能为空") - @Min(value = 1, message = "速度限制必须大于0") - private Integer speed; - - @NotNull(message = "隧道ID不能为空") - private Long tunnelId; - - @NotBlank(message = "隧道名称不能为空") - private String tunnelName; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/SpeedLimitUpdateDto.java b/springboot-backend/src/main/java/com/admin/common/dto/SpeedLimitUpdateDto.java deleted file mode 100644 index 3cbb939..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/SpeedLimitUpdateDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Min; - -@Data -public class SpeedLimitUpdateDto { - - @NotNull(message = "ID不能为空") - private Long id; - - @NotBlank(message = "限速规则名称不能为空") - private String name; - - @NotNull(message = "速度限制不能为空") - @Min(value = 1, message = "速度限制必须大于0") - private Integer speed; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/SystemInfoDto.java b/springboot-backend/src/main/java/com/admin/common/dto/SystemInfoDto.java deleted file mode 100644 index fa805a0..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/SystemInfoDto.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.admin.common.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * 系统信息DTO - * 对应Go客户端上报的系统信息结构 - */ -@Data -public class SystemInfoDto { - - /** - * 主机IP地址 - */ - @JsonProperty("host_ip") - private String hostIp; - - /** - * 开机时间(秒) - */ - @JsonProperty("uptime") - private Long uptime; - - /** - * 接收字节数 - */ - @JsonProperty("bytes_received") - private Long bytesReceived; - - /** - * 发送字节数 - */ - @JsonProperty("bytes_transmitted") - private Long bytesTransmitted; - - /** - * CPU使用率(百分比) - */ - @JsonProperty("cpu_usage") - private Double cpuUsage; - - /** - * 内存使用率(百分比) - */ - @JsonProperty("memory_usage") - private Double memoryUsage; - - /** - * 上报时间戳 - */ - private Long timestamp; - - public SystemInfoDto() { - this.timestamp = System.currentTimeMillis(); - } -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/TunnelDetailDto.java b/springboot-backend/src/main/java/com/admin/common/dto/TunnelDetailDto.java deleted file mode 100644 index 186387e..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/TunnelDetailDto.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.admin.common.dto; - -import com.admin.entity.ChainTunnel; -import lombok.Data; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -/** - * 隧道详情DTO - 包含节点配置信息 - */ -@Data -public class TunnelDetailDto { - - private Long id; - - /** - * Display/order index for drag-sorting in admin UI. - * Lower value appears first. - */ - private Integer inx; - - private String name; - - private Integer type; // 1: 端口转发, 2: 隧道转发 - - private Integer flow; // 1: 单向, 2: 双向 - - private BigDecimal trafficRatio; - - private Integer status; - - private Long createdTime; - - private Long updatedTime; - - private String inIp; - - // 入口节点列表 - private List inNodeId = new ArrayList<>(); - - // 转发链节点列表(二维数组结构) - private List> chainNodes = new ArrayList<>(); - - // 出口节点列表 - private List outNodeId = new ArrayList<>(); -} 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 deleted file mode 100644 index 45fb75c..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/TunnelDto.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.admin.common.dto; - -import com.admin.entity.ChainTunnel; -import lombok.Data; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.DecimalMin; -import javax.validation.constraints.DecimalMax; -import java.math.BigDecimal; -import java.util.List; - -@Data -public class TunnelDto { - - @NotBlank(message = "隧道名称不能为空") - private String name; - - @NotNull(message = "入口节点不能为空") - private List inNodeId; - - private List> chainNodes; - - private List outNodeId; - - private String inIp; - - @NotNull(message = "隧道类型不能为空") - private Integer type; - - @NotNull(message = "流量计算类型不能为空") - private Integer flow; - - @DecimalMin(value = "0.0", inclusive = false, message = "流量倍率必须大于0.0") - @DecimalMax(value = "100.0", message = "流量倍率不能大于100.0") - private BigDecimal trafficRatio; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/TunnelGroupAssignTunnelsDto.java b/springboot-backend/src/main/java/com/admin/common/dto/TunnelGroupAssignTunnelsDto.java deleted file mode 100644 index 2a99010..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/TunnelGroupAssignTunnelsDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotNull; -import java.util.List; - -@Data -public class TunnelGroupAssignTunnelsDto { - - @NotNull(message = "隧道分组ID不能为空") - private Long groupId; - - @NotNull(message = "隧道列表不能为空") - private List tunnelIds; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/TunnelGroupDetailDto.java b/springboot-backend/src/main/java/com/admin/common/dto/TunnelGroupDetailDto.java deleted file mode 100644 index e681692..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/TunnelGroupDetailDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -@Data -public class TunnelGroupDetailDto { - private Long id; - private String name; - private Integer status; - private Long createdTime; - private Long updatedTime; - private List tunnelIds = new ArrayList<>(); - private List tunnelNames = new ArrayList<>(); -} 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 deleted file mode 100644 index ed5c499..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/TunnelUpdateDto.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.admin.common.dto; - -import com.admin.entity.ChainTunnel; -import com.baomidou.mybatisplus.annotation.FieldStrategy; -import com.baomidou.mybatisplus.annotation.TableField; -import lombok.Data; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Min; -import javax.validation.constraints.Max; -import javax.validation.constraints.DecimalMin; -import javax.validation.constraints.DecimalMax; -import java.math.BigDecimal; -import java.util.List; - -@Data -public class TunnelUpdateDto { - - @NotNull(message = "隧道ID不能为空") - private Long id; - - @NotBlank(message = "隧道名称不能为空") - private String name; - - @NotNull(message = "流量计算类型不能为空") - private Integer flow; - - private String inIp; - - @DecimalMin(value = "0.0", inclusive = false, message = "流量倍率必须大于0.0") - @DecimalMax(value = "100.0", message = "流量倍率不能大于100.0") - private BigDecimal trafficRatio; - - // 入口节点配置(可选,为空时不更新节点配置) - private List inNodeId; - - // 转发链节点配置(二维数组,每一跳可有多个节点) - private List> chainNodes; - - // 出口节点配置 - private List outNodeId; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserDto.java deleted file mode 100644 index 61b7ad5..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Min; - -@Data -public class UserDto { - - @NotBlank(message = "用户名不能为空") - private String user; - - @NotBlank(message = "密码不能为空") - private String pwd; - - @NotNull(message = "流量不能为空") - @Min(value = 0, message = "流量不能小于0") - private Long flow; - - @NotNull(message = "转发数量不能为空") - @Min(value = 0, message = "转发数量不能小于0") - private Integer num; - - @NotNull(message = "过期时间不能为空") - private Long expTime; - - @NotNull(message = "流量重置时间不能为空") - private Long flowResetTime; - - private Integer status; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserGroupAssignUsersDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserGroupAssignUsersDto.java deleted file mode 100644 index a4f81a9..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserGroupAssignUsersDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotNull; -import java.util.List; - -@Data -public class UserGroupAssignUsersDto { - - @NotNull(message = "用户分组ID不能为空") - private Long groupId; - - @NotNull(message = "用户列表不能为空") - private List userIds; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserGroupDetailDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserGroupDetailDto.java deleted file mode 100644 index 87a0eeb..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserGroupDetailDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -@Data -public class UserGroupDetailDto { - private Long id; - private String name; - private Integer status; - private Long createdTime; - private Long updatedTime; - private List userIds = new ArrayList<>(); - private List userNames = new ArrayList<>(); -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserInfo.java b/springboot-backend/src/main/java/com/admin/common/dto/UserInfo.java deleted file mode 100644 index f3c7241..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserInfo.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -@Data -public class UserInfo { - private final Integer userId; - private final Integer roleId; - private final String userName; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserPackageDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserPackageDto.java deleted file mode 100644 index bf31bcb..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserPackageDto.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.admin.common.dto; - -import com.admin.entity.StatisticsFlow; -import lombok.Data; - -import java.util.List; - -/** - * 用户套餐信息DTO - */ -@Data -public class UserPackageDto { - - /** - * 用户基本信息 - */ - private UserInfoDto userInfo; - - /** - * 用户隧道权限列表 - */ - private List tunnelPermissions; - - /** - * 用户转发列表 - */ - private List forwards; - - /** - * 用户转发列表 - */ - private List statisticsFlows; - - /** - * 用户基本信息 - */ - @Data - public static class UserInfoDto { - private Long id; - private String name; - private String user; - private Integer status; - private Long flow; // 总流量配额(GB) - private Long inFlow; // 已用入站流量(字节) - private Long outFlow; // 已用出站流量(字节) - private Integer num; // 转发数量配额 - private Long expTime; // 过期时间 - private Long flowResetTime; // 流量重置时间 - private Long createdTime; - private Long updatedTime; - } - - /** - * 用户隧道权限详情 - */ - @Data - public static class UserTunnelDetailDto { - private Integer id; - private Integer userId; - private Integer tunnelId; - private String tunnelName; - private Integer tunnelFlow; // 隧道流量计算类型(1-单向,2-双向) - private Long flow; // 隧道流量配额(GB) - private Long inFlow; // 隧道已用入站流量(字节) - private Long outFlow; // 隧道已用出站流量(字节) - private Integer num; // 隧道转发数量配额 - private Long flowResetTime; // 流量重置时间 - private Long expTime; // 隧道权限过期时间 - private Integer speedId; - private String speedLimitName; - private Integer speed; - } - - /** - * 用户转发详情 - */ - @Data - public static class UserForwardDetailDto { - private Long id; - private String name; - private Integer tunnelId; - private String tunnelName; - private String inIp; - private Integer inPort; - private String remoteAddr; - private Long inFlow; // 转发入站流量(字节) - private Long outFlow; // 转发出站流量(字节) - private Integer status; - private Long createdTime; - } -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserPermissionResult.java b/springboot-backend/src/main/java/com/admin/common/dto/UserPermissionResult.java deleted file mode 100644 index 4e51daa..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserPermissionResult.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.admin.common.dto; - -import com.admin.entity.UserTunnel; -import com.admin.service.impl.ForwardServiceImpl; -import lombok.Data; - -@Data -public class UserPermissionResult { - public boolean hasError; - private String errorMessage; - private Integer limiter; - private UserTunnel userTunnel; - - public static UserPermissionResult success(Integer limiter, UserTunnel userTunnel) { - UserPermissionResult result = new UserPermissionResult(); - result.setLimiter(limiter); - result.setUserTunnel(userTunnel); - return result; - } - - public static UserPermissionResult error(String errorMessage) { - UserPermissionResult result = new UserPermissionResult(); - result.setErrorMessage(errorMessage); - return result; - } -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelBatchAssignDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelBatchAssignDto.java deleted file mode 100644 index faeedc2..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelBatchAssignDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotEmpty; -import java.util.List; - -@Data -public class UserTunnelBatchAssignDto { - - @NotNull(message = "用户ID不能为空") - private Integer userId; - - @Valid - @NotEmpty(message = "隧道列表不能为空") - private List tunnels; - - @Data - public static class TunnelAssignItem { - @NotNull(message = "隧道ID不能为空") - private Integer tunnelId; - - private Integer speedId; - } -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelDto.java deleted file mode 100644 index 2afd2e8..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelDto.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Min; - -@Data -public class UserTunnelDto { - - @NotNull(message = "用户ID不能为空") - private Integer userId; - - @NotNull(message = "隧道ID不能为空") - private Integer tunnelId; - - @Min(value = 0, message = "流量限制不能小于0") - private Long flow; - - @Min(value = 0, message = "转发数量不能小于0") - private Integer num; - - private Long flowResetTime; - - private Long expTime; - - private Integer speedId; -} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelQueryDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelQueryDto.java deleted file mode 100644 index 039b417..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelQueryDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotNull; - -@Data -public class UserTunnelQueryDto { - - @NotNull - private Integer userId; - -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelUpdateDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelUpdateDto.java deleted file mode 100644 index e36ebf8..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelUpdateDto.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Min; - -@Data -public class UserTunnelUpdateDto { - - @NotNull(message = "用户隧道权限ID不能为空") - private Integer id; - - @NotNull(message = "流量限制不能为空") - @Min(value = 0, message = "流量限制不能小于0") - private Long flow; - - @NotNull(message = "转发数量不能为空") - @Min(value = 0, message = "转发数量不能小于0") - private Integer num; - - /** - * 流量重置时间(时间戳) - */ - @NotNull(message = "流量重置时间不能为空") - private Long flowResetTime; - - /** - * 到期时间(时间戳) - */ - @NotNull(message = "到期时间不能为空") - private Long expTime; - - @NotNull(message = "状态必选") - private Integer status; - - /** - * 限速规则ID(可选,null表示不限速) - */ - private Integer speedId; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelWithDetailDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelWithDetailDto.java deleted file mode 100644 index 7ef8039..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelWithDetailDto.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -/** - *

- * 用户隧道权限及隧道详细信息DTO - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@Data -public class UserTunnelWithDetailDto { - - /** - * 用户隧道权限记录ID - */ - private Integer id; - - /** - * 用户ID - */ - private Integer userId; - - /** - * 隧道ID - */ - private Integer tunnelId; - - /** - * 流量限制 - */ - private Integer flow; - - /** - * 转发数量限制 - */ - private Integer num; - - /** - * 流量重置时间(时间戳) - */ - private Long flowResetTime; - - /** - * 到期时间(时间戳) - */ - private Long expTime; - - /** - * 限速规则ID - */ - private Integer speedId; - - /** - * 限速规则名称 - */ - private String speedLimitName; - - /** - * 限速值 - */ - private Integer speed; - - /** - * 隧道名称 - */ - private String tunnelName; - - /** - * 隧道流量计算类型(1-单向,2-双向) - */ - private Integer tunnelFlow; - - /** - * 入站流量(字节) - */ - private Long inFlow; - - /** - * 出站流量(字节) - */ - private Long outFlow; - - private Integer status; - -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserUpdateDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserUpdateDto.java deleted file mode 100644 index 7a47f05..0000000 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserUpdateDto.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.admin.common.dto; - -import lombok.Data; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Min; - -@Data -public class UserUpdateDto { - - @NotNull(message = "用户ID不能为空") - private Long id; - - @NotBlank(message = "用户名不能为空") - private String user; - - private String pwd; // 更新时密码可选 - - @NotNull(message = "流量不能为空") - @Min(value = 0, message = "流量不能小于0") - private Long flow; - - @NotNull(message = "转发数量不能为空") - @Min(value = 0, message = "转发数量不能小于0") - private Integer num; - - @NotNull(message = "过期时间不能为空") - private Long expTime; - - @NotNull(message = "流量重置时间不能为空") - private Long flowResetTime; - - private Integer status; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/exception/GlobalExceptionHandler.java b/springboot-backend/src/main/java/com/admin/common/exception/GlobalExceptionHandler.java deleted file mode 100644 index 4c241ae..0000000 --- a/springboot-backend/src/main/java/com/admin/common/exception/GlobalExceptionHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.admin.common.exception; - -import com.admin.common.lang.R; -import lombok.extern.slf4j.Slf4j; -import org.apache.catalina.connector.ClientAbortException; -import org.springframework.validation.BindingResult; -import org.springframework.validation.ObjectError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -@Slf4j -@RestControllerAdvice -public class GlobalExceptionHandler { - - // - - // 实体校验异常捕获 - //@ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(value = MethodArgumentNotValidException.class) - public R MethodArgumentNotValidException(MethodArgumentNotValidException e) { - BindingResult result = e.getBindingResult(); - ObjectError objectError = result.getAllErrors().stream().findFirst().get(); - log.info("实体校验异常:----------------{}", objectError.getDefaultMessage()); - return R.err(500, objectError.getDefaultMessage()); - } - - // 未授权异常捕获 - @ExceptionHandler(value = UnauthorizedException.class) - public R handleUnauthorizedException(UnauthorizedException e) { - log.info("未授权异常:----------------{}", e.getMessage()); - return R.err(401, e.getMessage()); - } - - @ExceptionHandler(value = Exception.class) - public R Exception(Exception e){ - log.info("异常:----------------{}", e.getMessage()); - return R.err(-2, e.getMessage()); - } - -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/exception/HttpErrorHandler.java b/springboot-backend/src/main/java/com/admin/common/exception/HttpErrorHandler.java deleted file mode 100644 index 3311836..0000000 --- a/springboot-backend/src/main/java/com/admin/common/exception/HttpErrorHandler.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.admin.common.exception; - -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.ResponseErrorHandler; - -import java.io.IOException; - -public class HttpErrorHandler implements ResponseErrorHandler { - @Override - public boolean hasError(ClientHttpResponse response) throws IOException { - return false; - } - - @Override - public void handleError(ClientHttpResponse clientHttpResponse) throws IOException { - - } -} diff --git a/springboot-backend/src/main/java/com/admin/common/exception/UnauthorizedException.java b/springboot-backend/src/main/java/com/admin/common/exception/UnauthorizedException.java deleted file mode 100644 index 3b87dc8..0000000 --- a/springboot-backend/src/main/java/com/admin/common/exception/UnauthorizedException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.admin.common.exception; - -/** - * 未授权异常类 - */ -public class UnauthorizedException extends RuntimeException { - - public UnauthorizedException(String message) { - super(message); - } - - public UnauthorizedException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/interceptor/JwtInterceptor.java b/springboot-backend/src/main/java/com/admin/common/interceptor/JwtInterceptor.java deleted file mode 100644 index ad52cca..0000000 --- a/springboot-backend/src/main/java/com/admin/common/interceptor/JwtInterceptor.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.admin.common.interceptor; - - -import com.admin.common.exception.UnauthorizedException; -import com.admin.common.utils.JwtUtil; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.HandlerInterceptor; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - - -/** - * JWT拦截器,验证用户是否登录 - */ -public class JwtInterceptor implements HandlerInterceptor { - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - String token = request.getHeader("Authorization"); - - if (!StringUtils.hasText(token)) { - throw new UnauthorizedException("未登录或token已过期"); - } - - - if (!JwtUtil.validateToken(token)) { - throw new UnauthorizedException("无效的token或token已过期"); - } - - - return true; - } -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/lang/R.java b/springboot-backend/src/main/java/com/admin/common/lang/R.java deleted file mode 100644 index 051972e..0000000 --- a/springboot-backend/src/main/java/com/admin/common/lang/R.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.admin.common.lang; - - -import lombok.Data; - -@Data -public class R { - - private int code = 0; - private String msg = "操作成功"; - private long ts = System.currentTimeMillis(); - private Object data; - - - - public static R ok(Object data){ - R m = new R(); - m.setData(data); - return m; - } - - public static R ok(){ - return new R(); - } - - public static R err(int code, String msg){ - R m = new R(); - m.setCode(code); - m.setMsg(msg); - return m; - } - - public static R err(String msg){ - R m = new R(); - m.setCode(-1); - m.setMsg(msg); - return m; - } - - public static R err(){ - R m = new R(); - m.setCode(-1); - m.setMsg("请求失败"); - return m; - } - - - -} diff --git a/springboot-backend/src/main/java/com/admin/common/migration/SqliteSchemaMigration.java b/springboot-backend/src/main/java/com/admin/common/migration/SqliteSchemaMigration.java deleted file mode 100644 index dca5e99..0000000 --- a/springboot-backend/src/main/java/com/admin/common/migration/SqliteSchemaMigration.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.admin.common.migration; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Component; - -import java.util.HashSet; -import java.util.Set; - -/** - * Lightweight SQLite schema migration. - * - * Spring Boot SQL init uses CREATE TABLE IF NOT EXISTS, so existing installations - * won't automatically receive new columns. This runner adds missing columns in-place. - */ -@Slf4j -@Component -@Order(Ordered.HIGHEST_PRECEDENCE) -public class SqliteSchemaMigration implements ApplicationRunner { - - private final JdbcTemplate jdbcTemplate; - - public SqliteSchemaMigration(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - @Override - public void run(ApplicationArguments args) { - ensureColumn("node", "inx", "INTEGER NOT NULL DEFAULT 0"); - ensureColumn("tunnel", "inx", "INTEGER NOT NULL DEFAULT 0"); - ensureTable("CREATE TABLE IF NOT EXISTS tunnel_group (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(100) NOT NULL, created_time INTEGER NOT NULL, updated_time INTEGER NOT NULL, status INTEGER NOT NULL)"); - ensureTable("CREATE TABLE IF NOT EXISTS user_group (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(100) NOT NULL, created_time INTEGER NOT NULL, updated_time INTEGER NOT NULL, status INTEGER NOT NULL)"); - ensureTable("CREATE TABLE IF NOT EXISTS tunnel_group_tunnel (id INTEGER PRIMARY KEY AUTOINCREMENT, tunnel_group_id INTEGER NOT NULL, tunnel_id INTEGER NOT NULL, created_time INTEGER NOT NULL)"); - ensureTable("CREATE TABLE IF NOT EXISTS user_group_user (id INTEGER PRIMARY KEY AUTOINCREMENT, user_group_id INTEGER NOT NULL, user_id INTEGER NOT NULL, created_time INTEGER NOT NULL)"); - ensureTable("CREATE TABLE IF NOT EXISTS group_permission (id INTEGER PRIMARY KEY AUTOINCREMENT, user_group_id INTEGER NOT NULL, tunnel_group_id INTEGER NOT NULL, created_time INTEGER NOT NULL)"); - ensureTable("CREATE TABLE IF NOT EXISTS group_permission_grant (id INTEGER PRIMARY KEY AUTOINCREMENT, user_group_id INTEGER NOT NULL, tunnel_group_id INTEGER NOT NULL, user_tunnel_id INTEGER NOT NULL, created_by_group INTEGER NOT NULL DEFAULT 0, created_time INTEGER NOT NULL)"); - ensureColumn("group_permission_grant", "created_by_group", "INTEGER NOT NULL DEFAULT 0"); - ensureTable("CREATE UNIQUE INDEX IF NOT EXISTS idx_tunnel_group_name ON tunnel_group(name)"); - ensureTable("CREATE UNIQUE INDEX IF NOT EXISTS idx_user_group_name ON user_group(name)"); - ensureTable("CREATE UNIQUE INDEX IF NOT EXISTS idx_tunnel_group_tunnel_unique ON tunnel_group_tunnel(tunnel_group_id, tunnel_id)"); - ensureTable("CREATE UNIQUE INDEX IF NOT EXISTS idx_user_group_user_unique ON user_group_user(user_group_id, user_id)"); - ensureTable("CREATE UNIQUE INDEX IF NOT EXISTS idx_group_permission_unique ON group_permission(user_group_id, tunnel_group_id)"); - ensureTable("CREATE UNIQUE INDEX IF NOT EXISTS idx_group_permission_grant_unique ON group_permission_grant(user_group_id, tunnel_group_id, user_tunnel_id)"); - } - - private void ensureColumn(String table, String column, String columnDefinition) { - Set columns = new HashSet<>( - jdbcTemplate.query( - "PRAGMA table_info(" + table + ")", - (rs, rowNum) -> rs.getString("name") - ) - ); - - if (columns.contains(column)) { - return; - } - - log.info("Adding missing column {}.{}", table, column); - jdbcTemplate.execute( - "ALTER TABLE " + table + " ADD COLUMN " + column + " " + columnDefinition - ); - } - - private void ensureTable(String ddl) { - jdbcTemplate.execute(ddl); - } -} diff --git a/springboot-backend/src/main/java/com/admin/common/task/CheckGostConfigAsync.java b/springboot-backend/src/main/java/com/admin/common/task/CheckGostConfigAsync.java deleted file mode 100644 index d07381b..0000000 --- a/springboot-backend/src/main/java/com/admin/common/task/CheckGostConfigAsync.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.admin.common.task; - -import com.admin.common.dto.*; -import com.admin.common.lang.R; -import com.admin.common.utils.GostUtil; -import com.admin.entity.*; -import com.admin.service.*; -import com.alibaba.fastjson.JSONArray; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Lazy; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -@Slf4j -@Service -public class CheckGostConfigAsync { - - @Resource - private NodeService nodeService; - - @Resource - @Lazy - private ForwardService forwardService; - - @Resource - @Lazy - private SpeedLimitService speedLimitService; - - @Resource - TunnelService tunnelService; - - - - /** - * 清理孤立的Gost配置项 - */ - @Async - public void cleanNodeConfigs(String node_id, GostConfigDto gostConfig) { - Node node = nodeService.getById(node_id); - if (node != null) { - cleanOrphanedServices(gostConfig.getServices(), node); - cleanOrphanedChains(gostConfig.getChains(), node); - cleanOrphanedLimiters(gostConfig.getLimiters(), node); - } - } - - /** - * 清理孤立的服务 - */ - private void cleanOrphanedServices(List configItems, Node node) { - if (configItems == null) return; - for (ConfigItem service : configItems) { - safeExecute(() -> { - - if (!Objects.equals(service.getName(), "web_api")){ - List serviceIds = parseServiceName(service.getName()); - - JSONArray services = new JSONArray(); - if (Objects.equals(serviceIds.getLast(), "tls")){ - String forward_id = serviceIds.getFirst(); - services.add(forward_id + "_tls"); - - Tunnel tunnel = tunnelService.getById(forward_id); - if (tunnel == null) { - GostUtil.DeleteService(node.getId(), services); - log.info("删除孤立的服务: {} (节点: {})", service.getName(), node.getId()); - } - - } - - if (Objects.equals(serviceIds.getLast(), "tcp")){ - String forward_id = serviceIds.getFirst(); - services.add(forward_id + "_" + serviceIds.get(1) + "_" + serviceIds.get(2) + "_tcp"); - services.add(forward_id + "_" + serviceIds.get(1) + "_" + serviceIds.get(2) + "_udp"); - - Forward forward = forwardService.getById(forward_id); - if (forward == null) { - GostUtil.DeleteService(node.getId(), services); - log.info("删除孤立的服务: {} (节点: {})", service.getName(), node.getId()); - } - } - - } - }, "清理服务 " + service.getName()); - } - - } - - /** - * 清理孤立的链 - */ - private void cleanOrphanedChains(List configItems, Node node) { - if (configItems == null) return; - for (ConfigItem chain : configItems) { - safeExecute(() -> { - List serviceIds = parseServiceName(chain.getName()); - Tunnel tunnel = tunnelService.getById(serviceIds.getLast()); - if (tunnel == null) { - GostUtil.DeleteChains(node.getId(), chain.getName()); - log.info("删除孤立的链: {} (节点: {})", chain.getName(), node.getId()); - } - }, "清理链 " + chain.getName()); - } - } - - /** - * 清理孤立的限流器 - */ - private void cleanOrphanedLimiters(List configItems, Node node) { - if (configItems == null) return; - - - for (ConfigItem limiter : configItems) { - safeExecute(() -> { - SpeedLimit speedLimit = speedLimitService.getById(limiter.getName()); - if (speedLimit == null) { - GostUtil.DeleteLimiters(node.getId(), Long.parseLong(limiter.getName())); - log.info("删除孤立的限流器: {} (节点: {})", limiter.getName(), node.getId()); - } - }, "清理限流器 " + limiter.getName()); - } - } - - - /** - * 安全执行操作,捕获异常 - */ - private void safeExecute(Runnable operation, String operationDesc) { - try { - operation.run(); - } catch (Exception e) { - log.info("执行操作失败: {}", operationDesc, e); - } - } - - - /** - * 解析服务名称 - */ - private List parseServiceName(String serviceName) { - String[] split = serviceName.split("_"); - return new ArrayList<>(Arrays.asList(split)); - } -} diff --git a/springboot-backend/src/main/java/com/admin/common/task/ResetFlowAsync.java b/springboot-backend/src/main/java/com/admin/common/task/ResetFlowAsync.java deleted file mode 100644 index 208e79c..0000000 --- a/springboot-backend/src/main/java/com/admin/common/task/ResetFlowAsync.java +++ /dev/null @@ -1,246 +0,0 @@ -package com.admin.common.task; - -import com.admin.common.dto.GostDto; -import com.admin.common.utils.GostUtil; -import com.admin.entity.*; -import com.admin.service.*; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; - -import javax.annotation.Resource; -import java.time.LocalDate; -import java.util.Date; -import java.util.List; - -@Slf4j -@Configuration -@EnableScheduling -public class ResetFlowAsync { - - @Resource - UserService userService; - - @Resource - UserTunnelService userTunnelService; - - @Resource - ForwardService forwardService; - - @Resource - TunnelService tunnelService; - - @Resource - ChainTunnelService chainTunnelService; - - /** - * 每天0点执行流量重置任务 - * 查询出用户和隧道的重置流量日期是今天的数据,将上下流量重置为0 - * 考虑当月是29、30天,但是选择是31的这种边界情况 - * - * 并发安全说明: - * - 使用setSql()进行原子SQL更新,只更新流量字段(in_flow, out_flow) - * - 不会影响DelayQueueManager的到期任务对status等其他字段的更新 - * - 避免了并发修改导致的数据覆盖问题 - */ - @Scheduled(cron = "5 0 0 * * ?") - public void reset_flow(){ - log.info("开始执行流量重置任务"); - - try { - // 获取当前日期信息 - LocalDate today = LocalDate.now(); - int currentDay = today.getDayOfMonth(); // 当前是几号 - int lastDayOfMonth = today.lengthOfMonth(); // 当月最后一天 - - log.info("当前日期: {}, 当月第{}天, 当月最后一天: {}", today, currentDay, lastDayOfMonth); - - // 重置用户流量 - resetUserFlow(currentDay, lastDayOfMonth); - - // 重置用户隧道流量 - resetUserTunnelFlow(currentDay, lastDayOfMonth); - - log.info("流量重置任务执行完成"); - - - // 处理过期账号 - user(); - - // 处理过期隧道 - userTunnel(); - - log.info("到期任务执行完成"); - - } catch (Exception e) { - log.info("定时任务执行失败", e); - } - } - - /** - * 重置用户流量 - * @param currentDay 当前日期(几号) - * @param lastDayOfMonth 当月最后一天 - */ - private void resetUserFlow(int currentDay, int lastDayOfMonth) { - try { - // flowResetTime字段存储的是0-31的数字,0表示不重置,1-31表示每月第几号重置 - // 构建查询条件:重置日期等于今天,或者重置日期大于当月最大天数且今天是月末 - // 排除flowResetTime为0的记录(不重置) - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.ne("flow_reset_time", 0); // 排除不重置的用户 - - if (currentDay == lastDayOfMonth) { - // 如果今天是月末,查询重置日期等于今天或者大于当月最大天数的记录 - // 例如:当月30天,但用户设置31号重置,则在30号执行重置 - queryWrapper.and(wrapper -> wrapper.eq("flow_reset_time", currentDay) - .or().gt("flow_reset_time", lastDayOfMonth)); - } else { - // 否则只查询重置日期等于今天的记录 - queryWrapper.eq("flow_reset_time", currentDay); - } - - // 查询需要重置的用户 - List usersToReset = userService.list(queryWrapper); - - if (usersToReset.isEmpty()) { - log.info("没有需要重置流量的用户"); - return; - } - - log.info("找到{}个需要重置流量的用户", usersToReset.size()); - - // 批量重置用户流量 - 使用SQL原子操作避免与到期任务的并发冲突 - for (User user : usersToReset) { - UpdateWrapper updateWrapper = new UpdateWrapper<>(); - updateWrapper.eq("id", user.getId()) - .setSql("in_flow = 0, out_flow = 0"); // 使用SQL原子操作,只更新流量字段 - - boolean success = userService.update(null, updateWrapper); - if (success) { - log.info("用户[ID: {}, 用户名: {}]流量重置成功,重置日期: 每月{}号", - user.getId(), user.getUser(), user.getFlowResetTime()); - } else { - log.info("用户[ID: {}, 用户名: {}]流量重置失败", user.getId(), user.getUser()); - } - } - - } catch (Exception e) { - log.info("重置用户流量失败", e); - } - } - - /** - * 重置用户隧道流量 - * @param currentDay 当前日期(几号) - * @param lastDayOfMonth 当月最后一天 - */ - private void resetUserTunnelFlow(int currentDay, int lastDayOfMonth) { - try { - // flowResetTime字段存储的是0-31的数字,0表示不重置,1-31表示每月第几号重置 - // 构建查询条件:重置日期等于今天,或者重置日期大于当月最大天数且今天是月末 - // 排除flowResetTime为0的记录(不重置) - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.ne("flow_reset_time", 0); // 排除不重置的用户隧道 - - if (currentDay == lastDayOfMonth) { - // 如果今天是月末,查询重置日期等于今天或者大于当月最大天数的记录 - // 例如:当月30天,但用户设置31号重置,则在30号执行重置 - queryWrapper.and(wrapper -> wrapper.eq("flow_reset_time", currentDay) - .or().gt("flow_reset_time", lastDayOfMonth)); - } else { - // 否则只查询重置日期等于今天的记录 - queryWrapper.eq("flow_reset_time", currentDay); - } - - // 查询需要重置的用户隧道 - List userTunnelsToReset = userTunnelService.list(queryWrapper); - - if (userTunnelsToReset.isEmpty()) { - log.info("没有需要重置流量的用户隧道"); - return; - } - - log.info("找到{}个需要重置流量的用户隧道", userTunnelsToReset.size()); - - // 批量重置用户隧道流量 - 使用SQL原子操作避免与到期任务的并发冲突 - for (UserTunnel userTunnel : userTunnelsToReset) { - UpdateWrapper updateWrapper = new UpdateWrapper<>(); - updateWrapper.eq("id", userTunnel.getId()) - .setSql("in_flow = 0, out_flow = 0"); // 使用SQL原子操作,只更新流量字段 - - boolean success = userTunnelService.update(null, updateWrapper); - if (success) { - log.info("用户隧道[ID: {}, 用户ID: {}, 隧道ID: {}]流量重置成功,重置日期: 每月{}号", - userTunnel.getId(), userTunnel.getUserId(), userTunnel.getTunnelId(), userTunnel.getFlowResetTime()); - } else { - log.info("用户隧道[ID: {}, 用户ID: {}, 隧道ID: {}]流量重置失败", - userTunnel.getId(), userTunnel.getUserId(), userTunnel.getTunnelId()); - } - } - - } catch (Exception e) { - log.info("重置用户隧道流量失败", e); - } - } - - - public void user(){ - // 查询过期用户 - List user_list = userService.list(new QueryWrapper().ne("role_id", 0).eq("status", 1).isNotNull("exp_time").lt("exp_time", new Date().getTime())); - for (User user : user_list) { - // 查询对应转发 - List forwardList = forwardService.list(new QueryWrapper().eq("user_id", user.getId()).eq("status", 1)); - for (Forward forward : forwardList) { - - List chainTunnels = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", forward.getTunnelId()).eq("chain_type", 1)); - for (ChainTunnel chainTunnel : chainTunnels) { - UserTunnel userTunnel = userTunnelService.getOne(new QueryWrapper().eq("user_id", forward.getUserId()).eq("tunnel_id", forward.getTunnelId())); - if (userTunnel != null) { - String name = buildServiceName(forward.getId(), forward.getUserId(), userTunnel.getId()); - GostUtil.PauseAndResumeService(chainTunnel.getNodeId(), name, "PauseService"); - } - } - - forward.setStatus(0); - forwardService.updateById(forward); - } - user.setStatus(0); - userService.updateById(user); - } - } - - - public void userTunnel(){ - // 查询过期隧道 - List user_tunnel_list = userTunnelService.list(new QueryWrapper().eq("status", 1).isNotNull("exp_time").lt("exp_time", new Date().getTime())); - // 查询对应转发 - for (UserTunnel userTunnel : user_tunnel_list) { - List forwardList = forwardService.list(new QueryWrapper().eq("tunnel_id", userTunnel.getTunnelId()).eq("user_id", userTunnel.getUserId()).eq("status", 1)); - for (Forward forward : forwardList) { - - List chainTunnels = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", forward.getTunnelId()).eq("chain_type", 1)); - for (ChainTunnel chainTunnel : chainTunnels) { - String name = buildServiceName(forward.getId(), forward.getUserId(), userTunnel.getId()); - GostUtil.PauseAndResumeService(chainTunnel.getNodeId(), name, "PauseService"); - } - forward.setStatus(0); - forwardService.updateById(forward); - } - - - userTunnel.setStatus(0); - userTunnelService.updateById(userTunnel); - } - } - - - - - private String buildServiceName(Long forwardId, Integer userId, Integer userTunnelId) { - return forwardId + "_" + userId + "_" + userTunnelId; - } -} diff --git a/springboot-backend/src/main/java/com/admin/common/task/StatisticsFlowAsync.java b/springboot-backend/src/main/java/com/admin/common/task/StatisticsFlowAsync.java deleted file mode 100644 index 102ee9f..0000000 --- a/springboot-backend/src/main/java/com/admin/common/task/StatisticsFlowAsync.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.admin.common.task; - - -import com.admin.entity.StatisticsFlow; -import com.admin.entity.User; -import com.admin.service.StatisticsFlowService; -import com.admin.service.UserService; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; - -import javax.annotation.PostConstruct; -import javax.annotation.Resource; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -@Slf4j -@Configuration -@EnableScheduling -public class StatisticsFlowAsync { - - @Resource - UserService userService; - - @Resource - StatisticsFlowService statisticsFlowService; - - @Scheduled(cron = "0 0 * * * ?") - public void statistics_flow() { - LocalDateTime currentHour = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0); - String hourString = currentHour.format(DateTimeFormatter.ofPattern("HH:mm")); - long time = new Date().getTime(); - - // 删除48小时前的数据 - long nowMs = new Date().getTime(); - long cutoffMs = nowMs - 48L * 60 * 60 * 1000; - statisticsFlowService.remove( - new LambdaQueryWrapper() - .lt(StatisticsFlow::getCreatedTime, cutoffMs) - ); - - - - - - List list = userService.list(); - List statisticsFlowList = new ArrayList<>(); - - for (User user : list) { - long currentFlow = user.getInFlow() + user.getOutFlow(); - - // 从数据库获取上一次记录 - StatisticsFlow lastFlowRecord = statisticsFlowService.getOne( - new LambdaQueryWrapper() - .eq(StatisticsFlow::getUserId, user.getId()) - .orderByDesc(StatisticsFlow::getId) - .last("LIMIT 1") - ); - - long currentTotalFlow = currentFlow; - long incrementFlow = currentTotalFlow; - - if (lastFlowRecord != null) { - long lastTotalFlow = lastFlowRecord.getTotalFlow(); - incrementFlow = currentTotalFlow - lastTotalFlow; - - if (incrementFlow < 0) { - incrementFlow = currentTotalFlow; - } - } - - StatisticsFlow statisticsFlow = new StatisticsFlow(); - statisticsFlow.setUserId(user.getId()); - statisticsFlow.setFlow(incrementFlow); - statisticsFlow.setTotalFlow(currentTotalFlow); - statisticsFlow.setTime(hourString); - statisticsFlow.setCreatedTime(time); - - statisticsFlowList.add(statisticsFlow); - } - - statisticsFlowService.saveBatch(statisticsFlowList); - } - -} diff --git a/springboot-backend/src/main/java/com/admin/common/utils/AESCrypto.java b/springboot-backend/src/main/java/com/admin/common/utils/AESCrypto.java deleted file mode 100644 index a6ba337..0000000 --- a/springboot-backend/src/main/java/com/admin/common/utils/AESCrypto.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.admin.common.utils; - -import lombok.extern.slf4j.Slf4j; - -import javax.crypto.Cipher; -import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.Base64; - -/** - * AES加密工具类 - * 使用AES-256-GCM模式,与Go端保持兼容 - */ -@Slf4j -public class AESCrypto { - - private static final String ALGORITHM = "AES"; - private static final String TRANSFORMATION = "AES/GCM/NoPadding"; - private static final int GCM_IV_LENGTH = 12; // GCM推荐的IV长度 - private static final int GCM_TAG_LENGTH = 16; // GCM认证标签长度 - - private final SecretKeySpec secretKey; - private final SecureRandom secureRandom; - - /** - * 构造函数 - * @param secret 密钥字符串,将使用SHA-256转换为32字节密钥 - */ - public AESCrypto(String secret) { - if (secret == null || secret.isEmpty()) { - throw new IllegalArgumentException("密钥不能为空"); - } - - try { - // 使用SHA-256将密码转换为32字节密钥,与Go端保持一致 - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - byte[] keyBytes = sha256.digest(secret.getBytes(StandardCharsets.UTF_8)); - this.secretKey = new SecretKeySpec(keyBytes, ALGORITHM); - this.secureRandom = new SecureRandom(); - - } catch (Exception e) { - log.info("AES加密器初始化失败", e); - throw new RuntimeException("AES加密器初始化失败: " + e.getMessage(), e); - } - } - - /** - * 加密数据 - * @param data 要加密的原始数据 - * @return Base64编码的加密数据,格式为: nonce + ciphertext - */ - public String encrypt(byte[] data) { - if (data == null || data.length == 0) { - throw new IllegalArgumentException("待加密数据不能为空"); - } - - try { - // 生成随机IV(nonce) - byte[] iv = new byte[GCM_IV_LENGTH]; - secureRandom.nextBytes(iv); - - // 创建GCM参数规范 - GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv); - - // 初始化Cipher - Cipher cipher = Cipher.getInstance(TRANSFORMATION); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmSpec); - - // 加密数据 - byte[] ciphertext = cipher.doFinal(data); - - // 组合IV + ciphertext,与Go端格式保持一致 - ByteBuffer buffer = ByteBuffer.allocate(iv.length + ciphertext.length); - buffer.put(iv); - buffer.put(ciphertext); - - // 返回Base64编码结果 - return Base64.getEncoder().encodeToString(buffer.array()); - - } catch (Exception e) { - log.info("数据加密失败", e); - throw new RuntimeException("数据加密失败: " + e.getMessage(), e); - } - } - - /** - * 加密字符串 - * @param data 要加密的字符串 - * @return Base64编码的加密数据 - */ - public String encrypt(String data) { - if (data == null) { - throw new IllegalArgumentException("待加密字符串不能为空"); - } - return encrypt(data.getBytes(StandardCharsets.UTF_8)); - } - - /** - * 解密数据 - * @param encryptedData Base64编码的加密数据 - * @return 解密后的原始数据 - */ - public byte[] decrypt(String encryptedData) { - if (encryptedData == null || encryptedData.isEmpty()) { - throw new IllegalArgumentException("加密数据不能为空"); - } - - try { - // Base64解码 - byte[] encrypted = Base64.getDecoder().decode(encryptedData); - - if (encrypted.length < GCM_IV_LENGTH) { - throw new IllegalArgumentException("加密数据长度不足"); - } - - // 分离IV和密文 - ByteBuffer buffer = ByteBuffer.wrap(encrypted); - byte[] iv = new byte[GCM_IV_LENGTH]; - buffer.get(iv); - - byte[] ciphertext = new byte[buffer.remaining()]; - buffer.get(ciphertext); - - // 创建GCM参数规范 - GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv); - - // 初始化Cipher - Cipher cipher = Cipher.getInstance(TRANSFORMATION); - cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec); - - // 解密数据 - return cipher.doFinal(ciphertext); - - } catch (Exception e) { - log.info("数据解密失败", e); - throw new RuntimeException("数据解密失败: " + e.getMessage(), e); - } - } - - /** - * 解密字符串 - * @param encryptedData Base64编码的加密数据 - * @return 解密后的字符串 - */ - public String decryptString(String encryptedData) { - byte[] decrypted = decrypt(encryptedData); - return new String(decrypted, StandardCharsets.UTF_8); - } - - /** - * 创建AES加密器实例 - * @param secret 密钥字符串 - * @return AES加密器实例,如果创建失败返回null - */ - public static AESCrypto create(String secret) { - try { - return new AESCrypto(secret); - } catch (Exception e) { - log.info("创建AES加密器失败: {}", e.getMessage()); - return null; - } - } -} \ 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 deleted file mode 100644 index e681359..0000000 --- a/springboot-backend/src/main/java/com/admin/common/utils/GostUtil.java +++ /dev/null @@ -1,411 +0,0 @@ -package com.admin.common.utils; - -import cn.hutool.core.util.StrUtil; -import com.admin.common.dto.GostDto; -import com.admin.entity.*; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import org.apache.commons.lang3.StringUtils; - -import java.util.regex.Pattern; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -public class GostUtil { - - - public static GostDto AddLimiters(Long node_id, Long name, String speed) { - JSONObject data = createLimiterData(name, speed); - GostDto gostDto = WebSocketServer.send_msg(node_id, data, "AddLimiters"); - if (gostDto.getMsg().contains("exists")){ - gostDto.setMsg("OK"); - } - return gostDto; - } - - public static GostDto UpdateLimiters(Long node_id, Long name, String speed) { - JSONObject data = createLimiterData(name, speed); - JSONObject req = new JSONObject(); - req.put("limiter", name + ""); - req.put("data", data); - return WebSocketServer.send_msg(node_id, req, "UpdateLimiters"); - } - - public static GostDto DeleteLimiters(Long node_id, Long name) { - JSONObject req = new JSONObject(); - req.put("limiter", name + ""); - GostDto gostDto = WebSocketServer.send_msg(node_id, req, "DeleteLimiters"); - if (gostDto.getMsg().contains("not found")){ - gostDto.setMsg("OK"); - } - return gostDto; - } - - public static GostDto AddChains(Long node_id, List chainTunnels, Map node_s) { - JSONArray nodes = new JSONArray(); - Node fromNode = node_s.get(node_id); - for (ChainTunnel chainTunnel : chainTunnels) { - JSONObject dialer = new JSONObject(); - dialer.put("type", chainTunnel.getProtocol()); - - JSONObject connector = new JSONObject(); - connector.put("type", "relay"); - - Node node_info = node_s.get(chainTunnel.getNodeId()); - JSONObject node = new JSONObject(); - node.put("name", "node_" + chainTunnel.getInx()); - - String dialHost = (fromNode != null && node_info != null) - ? selectDialHost(fromNode, node_info) - : (node_info != null ? node_info.getServerIp() : null); - node.put("addr", processServerAddress(dialHost + ":" + chainTunnel.getPort())); - node.put("connector", connector); - node.put("dialer", dialer); - - - - nodes.add(node); - } - JSONObject hop = new JSONObject(); - hop.put("name", "hop_" + chainTunnels.getFirst().getTunnelId()); - - // interface设置在转发链 - if (StringUtils.isNotBlank(node_s.get(node_id).getInterfaceName())) { - hop.put("interface", node_s.get(node_id).getInterfaceName()); - } - - - JSONObject selector = new JSONObject(); - selector.put("strategy", chainTunnels.getFirst().getStrategy()); - selector.put("maxFails", 1); - selector.put("failTimeout", 600000000000L); // 600 秒(纳秒单位) - - - hop.put("selector", selector); - hop.put("nodes", nodes); - - JSONArray hops = new JSONArray(); - hops.add(hop); - - JSONObject data = new JSONObject(); - data.put("name", "chains_" + chainTunnels.getFirst().getTunnelId()); - data.put("hops", hops); - - GostDto gostDto = WebSocketServer.send_msg(node_id, data, "AddChains"); - if (gostDto.getMsg().contains("exists")){ - gostDto.setMsg("OK"); - } - return gostDto; - } - - public static GostDto DeleteChains(Long node_id, String name) { - JSONObject data = new JSONObject(); - data.put("chain", name); - GostDto gostDto = WebSocketServer.send_msg(node_id, data, "DeleteChains"); - if (gostDto.getMsg().contains("not found")){ - gostDto.setMsg("OK"); - } - return gostDto; - } - - public static GostDto AddChainService(Long node_id, ChainTunnel chainTunnel, Map node_s) { - JSONArray services = new JSONArray(); - Node node_info = node_s.get(chainTunnel.getNodeId()); - JSONObject service_item = new JSONObject(); - service_item.put("name", chainTunnel.getTunnelId() + "_tls"); - service_item.put("addr", node_info.getTcpListenAddr() + ":" + chainTunnel.getPort()); - - // 只为出口节点(chainType=3)设置 interface - if (chainTunnel.getChainType() == 3 && StringUtils.isNotBlank(node_s.get(node_id).getInterfaceName())) { - JSONObject metadata = new JSONObject(); - metadata.put("interface", node_s.get(node_id).getInterfaceName()); - service_item.put("metadata", metadata); - } - - JSONObject handler = new JSONObject(); - handler.put("type", "relay"); - if (chainTunnel.getChainType() == 2){ - handler.put("chain","chains_" + chainTunnel.getTunnelId()); - } - service_item.put("handler", handler); - - JSONObject listener = new JSONObject(); - listener.put("type", chainTunnel.getProtocol()); - service_item.put("listener", listener); - - services.add(service_item); - - GostDto gostDto = WebSocketServer.send_msg(node_id, services, "AddService"); - if (gostDto.getMsg().contains("exists")){ - gostDto.setMsg("OK"); - } - return gostDto; - } - - public static GostDto AddAndUpdateService(String name, Integer limiter, Node node, Forward forward, ForwardPort forwardPort, Tunnel tunnel, String meth) { - JSONArray services = new JSONArray(); - String[] protocols = {"tcp", "udp"}; - for (String protocol : protocols) { - JSONObject service = new JSONObject(); - service.put("name", name + "_" + protocol); - if (Objects.equals(protocol, "tcp")){ - service.put("addr", node.getTcpListenAddr() + ":" + forwardPort.getPort()); - }else { - service.put("addr", node.getUdpListenAddr() + ":" + forwardPort.getPort()); - } - - // 只在端口转发时设置 interface(隧道转发时 interface 在转发链的节点上设置) - if (tunnel.getType() == 1 && StringUtils.isNotBlank(node.getInterfaceName())) { - JSONObject metadata = new JSONObject(); - metadata.put("interface", node.getInterfaceName()); - service.put("metadata", metadata); - } - - // 添加限流器配置 - if (limiter != null) { - service.put("limiter", limiter.toString()); - } - - // 配置处理器 - JSONObject handler = new JSONObject(); - handler.put("type", protocol); - if (tunnel.getType() == 2){ - handler.put("chain", "chains_" + forward.getTunnelId()); - } - service.put("handler", handler); - - // 配置监听器 - JSONObject listener = createListener(protocol); - service.put("listener", listener); - - JSONObject forwarder = createForwarder(forward.getRemoteAddr(), forward.getStrategy()); - service.put("forwarder", forwarder); - - services.add(service); - } - GostDto gostDto = WebSocketServer.send_msg(node.getId(), services, meth); - if (gostDto.getMsg().contains("exists")){ - gostDto.setMsg("OK"); - } - return gostDto; - } - - public static GostDto DeleteService(Long node_id, JSONArray services) { - JSONObject data = new JSONObject(); - data.put("services", services); - GostDto gostDto = WebSocketServer.send_msg(node_id, data, "DeleteService"); - if (gostDto.getMsg().contains("not found")){ - gostDto.setMsg("OK"); - } - return gostDto; - } - - public static GostDto PauseAndResumeService(Long node_id, String name, String meth) { - JSONObject data = new JSONObject(); - JSONArray services = new JSONArray(); - services.add(name + "_tcp"); - services.add(name + "_udp"); - data.put("services", services); - return WebSocketServer.send_msg(node_id, data, meth); - } - - - private static JSONObject createLimiterData(Long name, String speed) { - JSONObject data = new JSONObject(); - data.put("name", name.toString()); - JSONArray limits = new JSONArray(); - limits.add("$ " + speed + "MB " + speed + "MB"); - data.put("limits", limits); - return data; - } - - private static JSONObject createListener(String protocol) { - JSONObject listener = new JSONObject(); - listener.put("type", protocol); - if (Objects.equals(protocol, "udp")) { - JSONObject metadata = new JSONObject(); - metadata.put("keepAlive", true); - listener.put("metadata", metadata); - } - return listener; - } - - private static JSONObject createForwarder(String remoteAddr, String strategy) { - JSONObject forwarder = new JSONObject(); - JSONArray nodes = new JSONArray(); - - String[] split = remoteAddr.split(","); - int num = 1; - for (String addr : split) { - JSONObject node = new JSONObject(); - node.put("name", "node_" + num); - node.put("addr", addr); - nodes.add(node); - num++; - } - - if (strategy == null || strategy.isEmpty()) { - strategy = "fifo"; - } - - forwarder.put("nodes", nodes); - - JSONObject selector = new JSONObject(); - selector.put("strategy", strategy); - selector.put("maxFails", 1); - selector.put("failTimeout", "600s"); - forwarder.put("selector", selector); - return forwarder; - } - - public static String processServerAddress(String serverAddr) { - if (StrUtil.isBlank(serverAddr)) { - return serverAddr; - } - - // 如果已经被方括号包裹,直接返回 - if (serverAddr.startsWith("[")) { - return serverAddr; - } - - // 查找最后一个冒号,分离主机和端口 - int lastColonIndex = serverAddr.lastIndexOf(':'); - if (lastColonIndex == -1) { - // 没有端口号,直接检查是否需要包裹 - return isIPv6Address(serverAddr) ? "[" + serverAddr + "]" : serverAddr; - } - - String host = serverAddr.substring(0, lastColonIndex); - String port = serverAddr.substring(lastColonIndex); - - // 检查主机部分是否为IPv6地址 - if (isIPv6Address(host)) { - return "[" + host + "]" + port; - } - - return serverAddr; - } - - private static boolean isIPv6Address(String address) { - // IPv6地址包含多个冒号,至少2个 - if (!address.contains(":")) { - return false; - } - - // 计算冒号数量,IPv6地址至少有2个冒号 - long colonCount = address.chars().filter(ch -> ch == ':').count(); - return colonCount >= 2; - } - - /** - * v4 优先:当两端都有 v4 时选择 v4,否则尝试 v6。 - * 用于节点之间建立链路(A -> B 需要选择 B 的地址族,且 A 需要支持该地址族)。 - */ - public static String selectDialHost(Node fromNode, Node toNode) { - if (fromNode == null || toNode == null) { - throw new IllegalArgumentException("node is null"); - } - - boolean fromV4 = supportsV4(fromNode); - boolean fromV6 = supportsV6(fromNode); - boolean toV4 = supportsV4(toNode); - boolean toV6 = supportsV6(toNode); - - if (fromV4 && toV4) { - return pickToAddressV4(toNode); - } - if (fromV6 && toV6) { - return pickToAddressV6(toNode); - } - - throw new RuntimeException( - "节点链路不兼容:" + safeName(fromNode) + "(v4=" + fromV4 + ",v6=" + fromV6 + ") -> " - + safeName(toNode) + "(v4=" + toV4 + ",v6=" + toV6 + ")" - ); - } - - private static String safeName(Node node) { - if (node.getName() == null || node.getName().isBlank()) { - return "node_" + node.getId(); - } - return node.getName(); - } - - private static boolean supportsV4(Node node) { - // New dual-stack fields take precedence over legacy serverIp. - // If user explicitly provided only v6, treat as v6-only. - if (StrUtil.isNotBlank(node.getServerIpV4())) { - return true; - } - - if (StrUtil.isNotBlank(node.getServerIpV6())) { - return false; - } - - String legacy = node.getServerIp(); - if (StrUtil.isBlank(legacy)) { - return false; - } - - legacy = legacy.trim(); - if (looksLikeIpv4(legacy)) { - return true; - } - if (isIPv6Address(legacy)) { - return false; - } - - // 域名/其它:无法判断,按双栈处理以保持兼容 - return true; - } - - private static boolean supportsV6(Node node) { - // New dual-stack fields take precedence over legacy serverIp. - // If user explicitly provided only v4, treat as v4-only. - if (StrUtil.isNotBlank(node.getServerIpV6())) { - return true; - } - - if (StrUtil.isNotBlank(node.getServerIpV4())) { - return false; - } - - String legacy = node.getServerIp(); - if (StrUtil.isBlank(legacy)) { - return false; - } - - legacy = legacy.trim(); - if (isIPv6Address(legacy)) { - return true; - } - if (looksLikeIpv4(legacy)) { - return false; - } - - // 域名/其它:无法判断,按双栈处理以保持兼容 - return true; - } - - private static String pickToAddressV4(Node toNode) { - if (StrUtil.isNotBlank(toNode.getServerIpV4())) { - return toNode.getServerIpV4().trim(); - } - String legacy = toNode.getServerIp(); - return legacy != null ? legacy.trim() : null; - } - - private static String pickToAddressV6(Node toNode) { - if (StrUtil.isNotBlank(toNode.getServerIpV6())) { - return toNode.getServerIpV6().trim(); - } - String legacy = toNode.getServerIp(); - return legacy != null ? legacy.trim() : null; - } - - private static boolean looksLikeIpv4(String value) { - Pattern ipv4 = Pattern.compile("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"); - return ipv4.matcher(value).matches(); - } -} diff --git a/springboot-backend/src/main/java/com/admin/common/utils/HttpContextUtils.java b/springboot-backend/src/main/java/com/admin/common/utils/HttpContextUtils.java deleted file mode 100644 index 5407ea0..0000000 --- a/springboot-backend/src/main/java/com/admin/common/utils/HttpContextUtils.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.admin.common.utils; - -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; - -public class HttpContextUtils { - - public static HttpServletRequest getHttpServletRequest(){ - return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); - } - -} diff --git a/springboot-backend/src/main/java/com/admin/common/utils/HttpUtils.java b/springboot-backend/src/main/java/com/admin/common/utils/HttpUtils.java deleted file mode 100644 index b0036e3..0000000 --- a/springboot-backend/src/main/java/com/admin/common/utils/HttpUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.admin.common.utils; - -import com.admin.common.dto.GostConfigDto; -import com.admin.common.dto.GostDto; -import com.admin.config.RestTemplateConfig; -import com.alibaba.fastjson.JSONObject; -import lombok.SneakyThrows; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.http.*; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.stereotype.Component; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; - -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.*; - -/** - * HTTP请求工具类 - * 支持GET和POST请求,支持表单和JSON格式的请求体 - */ -@Component -public class HttpUtils{ - - -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/utils/IpUtils.java b/springboot-backend/src/main/java/com/admin/common/utils/IpUtils.java deleted file mode 100644 index 5f60d09..0000000 --- a/springboot-backend/src/main/java/com/admin/common/utils/IpUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.admin.common.utils; - -import javax.servlet.http.HttpServletRequest; -import java.net.InetAddress; -import java.net.UnknownHostException; - -public class IpUtils { - - public static String getIpAddr(HttpServletRequest request) { - String ipAddress = null; - try { - ipAddress = request.getHeader("x-forwarded-for"); - if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { - ipAddress = request.getHeader("Proxy-Client-IP"); - } - if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { - ipAddress = request.getHeader("WL-Proxy-Client-IP"); - } - if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { - ipAddress = request.getRemoteAddr(); - if (ipAddress.equals("127.0.0.1")) { - // 根据网卡取本机配置的IP - InetAddress inet = null; - try { - inet = InetAddress.getLocalHost(); - } catch (UnknownHostException e) { - e.printStackTrace(); - } - ipAddress = inet.getHostAddress(); - } - } - // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 - if (ipAddress != null && ipAddress.length() > 15) { - // "***.***.***.***".length() - // = 15 - if (ipAddress.indexOf(",") > 0) { - ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); - } - } - } catch (Exception e) { - ipAddress=""; - } - return ipAddress; - } - - -} diff --git a/springboot-backend/src/main/java/com/admin/common/utils/JwtUtil.java b/springboot-backend/src/main/java/com/admin/common/utils/JwtUtil.java deleted file mode 100644 index f82e83e..0000000 --- a/springboot-backend/src/main/java/com/admin/common/utils/JwtUtil.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.admin.common.utils; - -import com.admin.entity.User; -import com.alibaba.fastjson2.JSON; -import lombok.SneakyThrows; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -/** - * JWT工具类,不使用第三方库实现 - */ -@Component -public class JwtUtil { - - @Value("${jwt-secret}") - private String secretKey; - - private static String SECRET_KEY; - - // token有效期,7天 - private static final long EXPIRE_TIME = 90L * 24 * 60 * 60 * 1000; - // 算法 - private static final String ALGORITHM = "HmacSHA256"; - - @PostConstruct - public void init() { - SECRET_KEY = this.secretKey; - } - - /** - * 生成JWT Token - * - * @param user 用户信息 - * @return 生成的JWT Token - */ - public static String generateToken(User user) { - try { - long nowMillis = System.currentTimeMillis(); - Date now = new Date(nowMillis); - Date expireDate = new Date(nowMillis + EXPIRE_TIME); - - // Header - Map header = new HashMap<>(); - header.put("alg", ALGORITHM); - header.put("typ", "JWT"); - String headerJson = JSON.toJSONString(header); - String encodedHeader = Base64.getUrlEncoder().withoutPadding() - .encodeToString(headerJson.getBytes(StandardCharsets.UTF_8)); - - // Payload - Map payload = new HashMap<>(); - payload.put("sub", user.getId().toString()); - payload.put("iat", now.getTime() / 1000); // 发布时间 - payload.put("exp", expireDate.getTime() / 1000); // 过期时间 - payload.put("user", user.getUser()); - payload.put("name", user.getUser()); - payload.put("role_id", user.getRoleId()); - - String payloadJson = JSON.toJSONString(payload); - String encodedPayload = Base64.getUrlEncoder().withoutPadding() - .encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8)); - - // Signature - String signature = calculateSignature(encodedHeader, encodedPayload); - - // Token - return encodedHeader + "." + encodedPayload + "." + signature; - } catch (Exception e) { - throw new RuntimeException("JWT token generation failed", e); - } - } - - /** - * 验证JWT Token - * - * @param token JWT Token - * @return 验证是否通过 - */ - public static boolean validateToken(String token) { - try { - if (token == null || token.isEmpty()) { - return false; - } - - String[] parts = token.split("\\."); - if (parts.length != 3) { - return false; - } - - String encodedHeader = parts[0]; - String encodedPayload = parts[1]; - String signature = parts[2]; - - // 验证签名 - String expectedSignature = calculateSignature(encodedHeader, encodedPayload); - if (!expectedSignature.equals(signature)) { - return false; - } - - // 验证过期时间 - String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8); - Map payload = JSON.parseObject(decodedPayload, Map.class); - long exp = Long.parseLong(payload.get("exp").toString()); - long now = System.currentTimeMillis() / 1000; - - return exp > now; - } catch (Exception e) { - return false; - } - } - - /** - * 从JWT Token中获取用户ID - * - * @param token JWT Token - * @return 用户ID - */ - public static Long getUserIdFromToken(String token) { - String[] parts = token.split("\\."); - String encodedPayload = parts[1]; - String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8); - Map payload = JSON.parseObject(decodedPayload, Map.class); - return Long.parseLong(payload.get("sub").toString()); - } - - - public static Integer getUserIdFromToken() { - String token = HttpContextUtils.getHttpServletRequest().getHeader("Authorization"); - String[] parts = token.split("\\."); - String encodedPayload = parts[1]; - String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8); - Map payload = JSON.parseObject(decodedPayload, Map.class); - return Integer.parseInt(payload.get("sub").toString()); - } - - public static String getNameFromToken() { - String token = HttpContextUtils.getHttpServletRequest().getHeader("Authorization"); - String[] parts = token.split("\\."); - String encodedPayload = parts[1]; - String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8); - Map payload = JSON.parseObject(decodedPayload, Map.class); - return payload.get("name").toString(); - } - - /** - * 从JWT Token中获取用户角色ID - * - * @param token JWT Token - * @return 角色ID - */ - public static Integer getRoleIdFromToken(String token) { - String[] parts = token.split("\\."); - String encodedPayload = parts[1]; - String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8); - Map payload = JSON.parseObject(decodedPayload, Map.class); - return Integer.parseInt(payload.get("role_id").toString()); - } - - @SneakyThrows - public static Integer getRoleIdFromToken() { - String token = HttpContextUtils.getHttpServletRequest().getHeader("Authorization"); - if (token == null || token.isEmpty()) throw new Exception(); - String[] parts = token.split("\\."); - String encodedPayload = parts[1]; - String decodedPayload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8); - Map payload = JSON.parseObject(decodedPayload, Map.class); - return Integer.parseInt(payload.get("role_id").toString()); - } - /** - * 计算签名 - * - * @param encodedHeader 编码后的头部 - * @param encodedPayload 编码后的负载 - * @return 签名 - * @throws Exception 签名计算异常 - */ - private static String calculateSignature(String encodedHeader, String encodedPayload) throws Exception { - String content = encodedHeader + "." + encodedPayload; - Mac hmac = Mac.getInstance(ALGORITHM); - SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM); - hmac.init(secretKeySpec); - byte[] signatureBytes = hmac.doFinal(content.getBytes(StandardCharsets.UTF_8)); - return Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes); - } -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/utils/Md5Util.java b/springboot-backend/src/main/java/com/admin/common/utils/Md5Util.java deleted file mode 100644 index 88b71b0..0000000 --- a/springboot-backend/src/main/java/com/admin/common/utils/Md5Util.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.admin.common.utils; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Base64; - -/** - * MD5工具类 - */ -public class Md5Util { - - private static final String MD5_ALGORITHM = "MD5"; - private static final String DEFAULT_SALT = "admin_salt_2024"; - - /** - * 基础MD5加密 - * - * @param input 待加密字符串 - * @return MD5加密后的字符串(32位小写) - */ - public static String md5(String input) { - if (input == null || input.isEmpty()) { - return null; - } - - try { - MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM); - byte[] digest = md.digest(input.getBytes()); - return bytesToHex(digest); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("MD5算法不可用", e); - } - } - - /** - * MD5加密(使用默认盐值) - * - * @param input 待加密字符串 - * @return MD5加密后的字符串 - */ - public static String md5WithSalt(String input) { - return md5WithSalt(input, DEFAULT_SALT); - } - - /** - * MD5加密(使用自定义盐值) - * - * @param input 待加密字符串 - * @param salt 盐值 - * @return MD5加密后的字符串 - */ - public static String md5WithSalt(String input, String salt) { - if (input == null || input.isEmpty()) { - return null; - } - - if (salt == null) { - salt = DEFAULT_SALT; - } - - return md5(input + salt); - } - - /** - * 生成随机盐值 - * - * @param length 盐值长度 - * @return 随机盐值 - */ - public static String generateSalt(int length) { - SecureRandom random = new SecureRandom(); - byte[] salt = new byte[length]; - random.nextBytes(salt); - return Base64.getEncoder().encodeToString(salt); - } - - /** - * 生成默认长度(16字节)的随机盐值 - * - * @return 随机盐值 - */ - public static String generateSalt() { - return generateSalt(16); - } - - /** - * 验证密码 - * - * @param password 原始密码 - * @param hashedPassword 已加密的密码 - * @return 是否匹配 - */ - public static boolean verify(String password, String hashedPassword) { - if (password == null || hashedPassword == null) { - return false; - } - - String encrypted = md5WithSalt(password); - return encrypted.equals(hashedPassword); - } - - /** - * 验证密码(使用自定义盐值) - * - * @param password 原始密码 - * @param salt 盐值 - * @param hashedPassword 已加密的密码 - * @return 是否匹配 - */ - public static boolean verify(String password, String salt, String hashedPassword) { - if (password == null || hashedPassword == null) { - return false; - } - - String encrypted = md5WithSalt(password, salt); - return encrypted.equals(hashedPassword); - } - - /** - * 多次MD5加密 - * - * @param input 待加密字符串 - * @param times 加密次数 - * @return 加密后的字符串 - */ - public static String md5Multiple(String input, int times) { - if (input == null || input.isEmpty() || times <= 0) { - return input; - } - - String result = input; - for (int i = 0; i < times; i++) { - result = md5(result); - } - return result; - } - - /** - * 字节数组转十六进制字符串 - * - * @param bytes 字节数组 - * @return 十六进制字符串 - */ - private static String bytesToHex(byte[] bytes) { - StringBuilder result = new StringBuilder(); - for (byte b : bytes) { - result.append(String.format("%02x", b)); - } - return result.toString(); - } - - /** - * 获取文件的MD5值 - * - * @param bytes 文件字节数组 - * @return MD5值 - */ - public static String getFileMd5(byte[] bytes) { - if (bytes == null || bytes.length == 0) { - return null; - } - - try { - MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM); - byte[] digest = md.digest(bytes); - return bytesToHex(digest); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("MD5算法不可用", e); - } - } -} diff --git a/springboot-backend/src/main/java/com/admin/common/utils/WebSocketServer.java b/springboot-backend/src/main/java/com/admin/common/utils/WebSocketServer.java deleted file mode 100644 index 79ec6af..0000000 --- a/springboot-backend/src/main/java/com/admin/common/utils/WebSocketServer.java +++ /dev/null @@ -1,496 +0,0 @@ -package com.admin.common.utils; - - -import com.admin.common.dto.GostConfigDto; -import com.admin.common.dto.GostDto; -import com.admin.common.task.CheckGostConfigAsync; -import com.admin.entity.Node; -import com.admin.service.NodeService; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.handler.TextWebSocketHandler; - -import javax.annotation.Resource; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.UUID; - - -@Slf4j -public class WebSocketServer extends TextWebSocketHandler { - - @Resource - NodeService nodeService; - - // 存储所有活跃的 WebSocket 连接( - private static final CopyOnWriteArraySet activeSessions = new CopyOnWriteArraySet<>(); - - // 存储节点ID和对应的WebSocket session映射 - private static final ConcurrentHashMap nodeSessions = new ConcurrentHashMap<>(); - - // 为每个session提供锁对象,防止并发发送消息 - private static final ConcurrentHashMap sessionLocks = new ConcurrentHashMap<>(); - - // 存储等待响应的请求,key为requestId,value为CompletableFuture - private static final ConcurrentHashMap> pendingRequests = new ConcurrentHashMap<>(); - - // 缓存加密器实例,避免重复创建 - private static final ConcurrentHashMap cryptoCache = new ConcurrentHashMap<>(); - - /** - * 加密消息包装器 - */ - public static class EncryptedMessage { - private boolean encrypted; - private String data; - private Long timestamp; - - // getters and setters - public boolean isEncrypted() { return encrypted; } - public void setEncrypted(boolean encrypted) { this.encrypted = encrypted; } - public String getData() { return data; } - public void setData(String data) { this.data = data; } - public Long getTimestamp() { return timestamp; } - public void setTimestamp(Long timestamp) { this.timestamp = timestamp; } - } - - //接受客户端消息 - @Override - public void handleTextMessage(WebSocketSession session, TextMessage message) { - try { - if (StringUtils.isNoneBlank(message.getPayload())) { - - String id = session.getAttributes().get("id").toString(); - String type = session.getAttributes().get("type").toString(); - String nodeSecret = (String) session.getAttributes().get("nodeSecret"); - - // 尝试解密消息 - String decryptedPayload = decryptMessageIfNeeded(message.getPayload(), nodeSecret); - - if (decryptedPayload.contains("memory_usage")){ - // 先发送确认消息 - sendToUser(session, "{\"type\":\"call\"}", nodeSecret); - }else if (decryptedPayload.contains("requestId")) { - log.info("收到消息: {}", decryptedPayload); - // 处理命令响应消息 - try { - JSONObject responseJson = JSONObject.parseObject(decryptedPayload); - String requestId = responseJson.getString("requestId"); - String responseMessage = responseJson.getString("message"); - String responseType = responseJson.getString("type"); - JSONObject responseData = responseJson.getJSONObject("data"); - - if (requestId != null) { - CompletableFuture future = pendingRequests.remove(requestId); - - if (future != null) { - GostDto result = new GostDto(); - - // 根据响应类型处理不同的数据 - if ("PingResponse".equals(responseType) && responseData != null) { - // 特殊处理ping响应,将完整的响应数据返回 - result.setMsg(responseMessage != null ? responseMessage : "OK"); - result.setData(responseData); // 保存ping详细结果 - } else { - // 其他类型的响应 - result.setMsg(responseMessage != null ? responseMessage : "无响应消息"); - if (responseData != null) { - result.setData(responseData); - } - } - - future.complete(result); - } - } - } catch (Exception e) { - log.info("处理响应消息失败: {}", e.getMessage(), e); - } - } else { - log.info("收到消息: {}", decryptedPayload); - } - - // 如果是节点类型,转发消息给其他会话 - if (Objects.equals(type, "1")) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("id", id); - jsonObject.put("type", "info"); - jsonObject.put("data", decryptedPayload); - String broadcastMessage = jsonObject.toJSONString(); - - // 异步处理广播消息,避免阻塞当前线程 - for (WebSocketSession targetSession : activeSessions) { - if (targetSession != null && targetSession.isOpen() && !targetSession.equals(session)) { - sendToUser(targetSession, broadcastMessage, null); - } - } - } - } - } catch (Exception e) { - log.info("处理WebSocket消息时发生异常: {}", e.getMessage(), e); - } - } - - /** - * 尝试解密消息(如果需要) - */ - private String decryptMessageIfNeeded(String payload, String nodeSecret) { - if (payload == null || payload.trim().isEmpty()) { - return payload; - } - - try { - // 尝试解析为加密消息格式 - EncryptedMessage encryptedMessage = JSON.parseObject(payload, EncryptedMessage.class); - - if (encryptedMessage.isEncrypted() && encryptedMessage.getData() != null) { - // 获取或创建加密器 - AESCrypto crypto = getOrCreateCrypto(nodeSecret); - if (crypto == null) { - log.info("⚠️ 收到加密消息但无法创建解密器,使用原始数据"); - return payload; - } - - // 解密数据 - String decryptedData = crypto.decryptString(encryptedMessage.getData()); - return decryptedData; - } - } catch (Exception e) { - // 解析失败,可能是非加密格式,直接返回原始数据 - log.info("WebSocket消息未加密或解密失败,使用原始数据: {}", e.getMessage()); - } - - return payload; - } - - /** - * 加密消息(如果可能) - */ - private static String encryptMessageIfPossible(String message, String nodeSecret) { - if (message == null || nodeSecret == null) { - return message; - } - - try { - AESCrypto crypto = getOrCreateCrypto(nodeSecret); - if (crypto != null) { - String encryptedData = crypto.encrypt(message); - - // 创建加密消息包装器 - JSONObject encryptedMessage = new JSONObject(); - encryptedMessage.put("encrypted", true); - encryptedMessage.put("data", encryptedData); - encryptedMessage.put("timestamp", System.currentTimeMillis()); - - return encryptedMessage.toJSONString(); - } - } catch (Exception e) { - log.info("⚠️ WebSocket消息加密失败,发送原始数据: {}", e.getMessage()); - } - - return message; - } - - /** - * 获取或创建加密器实例 - */ - private static AESCrypto getOrCreateCrypto(String secret) { - if (secret == null || secret.isEmpty()) { - return null; - } - return cryptoCache.computeIfAbsent(secret, AESCrypto::create); - } - - // 建立连接 - @Override - public void afterConnectionEstablished(WebSocketSession session) { - try { - String id = session.getAttributes().get("id").toString(); - String type = session.getAttributes().get("type").toString(); - - if (!Objects.equals(type, "1")) { - // 网页管理员连接 - activeSessions.add(session); - log.info("管理员连接建立,sessionId: {}", session.getId()); - } else { - // 客户端节点连接 - Long nodeId = Long.valueOf(id); - String version = (String) session.getAttributes().get("nodeVersion"); - String http = (String) session.getAttributes().get("http"); - String tls = (String) session.getAttributes().get("tls"); - String socks = (String) session.getAttributes().get("socks"); - - log.info("节点 {} 尝试连接,开始处理连接逻辑", nodeId); - log.info("屏蔽协议数据:http->{}, tls->{}, socks->{}", http, tls, socks); - - // 检查是否已有该节点的连接,如果有则记录日志但直接覆盖 - WebSocketSession existingSession = nodeSessions.get(nodeId); - if (existingSession != null && existingSession.isOpen()) { - log.info("节点 {} 已有连接存在: {},新连接将覆盖旧连接", nodeId, existingSession.getId()); - // 清理旧连接的锁对象 - sessionLocks.remove(existingSession.getId()); - } - - // 直接覆盖会话映射(不主动关闭旧连接,让它自然断开) - nodeSessions.put(nodeId, session); - - // 如果有旧连接,在覆盖映射后主动关闭它 - if (existingSession != null && existingSession.isOpen()) { - try { - log.info("主动关闭节点 {} 的旧连接: {}", nodeId, existingSession.getId()); - existingSession.close(); - } catch (Exception e) { - log.info("关闭节点 {} 旧连接失败: {}", nodeId, e.getMessage()); - } - } - - // 更新节点状态为在线 - Node node = nodeService.getById(nodeId); - if (node != null) { - // 更新状态和版本信息 - node.setStatus(1); - if (version != null) { - node.setVersion(version); - } - if (http != null) { - node.setHttp(Integer.parseInt(http)); - } - if (tls != null) { - node.setTls(Integer.parseInt(tls)); - } - if (socks != null) { - node.setSocks(Integer.parseInt(socks)); - } - - boolean updateResult = nodeService.updateById(node); - - if (updateResult) { - log.info("节点 {} 连接建立成功,状态更新为在线,版本: {}", nodeId, version); - - // 广播节点上线状态给所有管理员 - JSONObject res = new JSONObject(); - res.put("id", id); - res.put("type", "status"); - res.put("data", 1); - broadcastMessage(res.toJSONString()); - } else { - log.info("节点 {} 状态更新失败", nodeId); - } - } else { - log.info("节点 {} 不存在,无法更新状态", nodeId); - // 移除无效的会话 - nodeSessions.remove(nodeId); - } - } - - } catch (Exception e) { - log.info("建立连接时发生异常: {}", e.getMessage(), e); - // 异常情况下,确保清理会话 - try { - String id = session.getAttributes().get("id").toString(); - String type = session.getAttributes().get("type").toString(); - if (Objects.equals(type, "1")) { - Long nodeId = Long.valueOf(id); - nodeSessions.remove(nodeId); - log.info("由于异常,移除节点 {} 的会话", nodeId); - } - } catch (Exception cleanupException) { - log.info("清理异常会话时出错: {}", cleanupException.getMessage()); - } - } - } - - // 连接关闭后 - @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { - try { - String id = session.getAttributes().get("id").toString(); - String type = session.getAttributes().get("type").toString(); - String sessionId = session.getId(); - - log.info("连接关闭,ID: {}, 类型: {}, 状态: {}", id, type, status); - - if (!Objects.equals(type, "1")) { - // 管理员连接关闭 - boolean removed = activeSessions.remove(session); - log.info("管理员连接关闭,sessionId: {}, 移除结果: {}", sessionId, removed); - } else { - // 客户端节点连接关闭 - Long nodeId = Long.valueOf(id); - - // 验证当前会话是否还是活跃会话(关键:这里会自动过滤掉被覆盖的旧连接) - WebSocketSession currentSession = nodeSessions.get(nodeId); - if (currentSession == null || !currentSession.equals(session)) { - log.info("节点 {} 连接关闭,但已有新连接或会话不匹配,跳过状态更新", nodeId); - sessionLocks.remove(sessionId); - return; - } - - log.info("节点 {} 当前活跃连接关闭,开始验证并更新状态", nodeId); - - nodeSessions.remove(nodeId); - - // 更新节点状态为离线 - Node node = nodeService.getById(nodeId); - if (node != null) { - node.setStatus(0); - boolean updateResult = nodeService.updateById(node); - - if (updateResult) { - log.info("节点 {} 状态更新为离线成功", nodeId); - - JSONObject res = new JSONObject(); - res.put("id", id); - res.put("type", "status"); - res.put("data", 0); - broadcastMessage(res.toJSONString()); - } else { - log.info("节点 {} 状态更新为离线失败", nodeId); - } - } else { - log.info("节点 {} 不存在,无法更新离线状态", nodeId); - } - } - - // 清理session锁对象 - sessionLocks.remove(sessionId); - - } catch (Exception e) { - log.info("关闭连接时发生异常: {}", e.getMessage(), e); - } - } - - // 点对点发送消息 - @SneakyThrows - public static void sendToUser(WebSocketSession socketSession, String message) { - sendToUser(socketSession, message, null); - } - - // 点对点发送消息(支持加密) - @SneakyThrows - public static void sendToUser(WebSocketSession socketSession, String message, String nodeSecret) { - if (socketSession != null && socketSession.isOpen()) { - String sessionId = socketSession.getId(); - Object lock = sessionLocks.computeIfAbsent(sessionId, k -> new Object()); - - synchronized (lock) { - try { - if (socketSession.isOpen()) { - // 如果是节点连接且有密钥,尝试加密消息 - String finalMessage = message; - if (nodeSecret != null && !nodeSecret.isEmpty()) { - String type = (String) socketSession.getAttributes().get("type"); - if ("1".equals(type)) { // 节点连接 - finalMessage = encryptMessageIfPossible(message, nodeSecret); - } - } - socketSession.sendMessage(new TextMessage(finalMessage)); - } - } catch (Exception e) { - log.info("发送WebSocket消息失败 [sessionId={}]: {}", sessionId, e.getMessage()); - cleanupSession(socketSession); - } - } - } else { - cleanupSession(socketSession); - } - } - - /** - * 清理失效的session,自动识别是节点session还是管理员session - */ - private static void cleanupSession(WebSocketSession session) { - if (session == null) return; - - String sessionId = session.getId(); - - // 清理session锁 - sessionLocks.remove(sessionId); - - boolean removedFromAdmin = activeSessions.remove(session); - - if (!removedFromAdmin) { - nodeSessions.entrySet().removeIf(entry -> { - if (entry.getValue() == session) { - return true; - } - return false; - }); - } - } - - // 广播消息 - public static void broadcastMessage(String message) { - for (WebSocketSession session : activeSessions) { - sendToUser(session, message); - } - } - - - - public static GostDto send_msg(Long node_id, Object msg, String type) { - WebSocketSession nodeSession = nodeSessions.get(node_id); - - if (nodeSession == null) { - log.info("发送消息失败:节点 {} 不在线或会话不存在", node_id); - GostDto result = new GostDto(); - result.setMsg("节点不在线"); - return result; - } - - if (!nodeSession.isOpen()) { - log.info("发送消息失败:节点 {} 连接已断开,清理会话", node_id); - nodeSessions.remove(node_id); - sessionLocks.remove(nodeSession.getId()); - GostDto result = new GostDto(); - result.setMsg("节点连接已断开"); - return result; - } - - // 生成唯一的请求ID - String requestId = UUID.randomUUID().toString(); - - // 创建CompletableFuture用于等待响应 - CompletableFuture future = new CompletableFuture<>(); - pendingRequests.put(requestId, future); - - // 获取节点密钥用于加密 - String nodeSecret = (String) nodeSession.getAttributes().get("nodeSecret"); - - try { - JSONObject data = new JSONObject(); - data.put("type", type); - data.put("data", msg); - data.put("requestId", requestId); - sendToUser(nodeSession, data.toJSONString(), nodeSecret); - GostDto result = future.get(10, TimeUnit.SECONDS); - - log.info("成功发送消息到节点 {} 并收到响应: {}", node_id, result.getMsg()); - return result; - - } catch (Exception e) { - // 清理请求和映射关系 - pendingRequests.remove(requestId); - - GostDto result = new GostDto(); - if (e instanceof java.util.concurrent.TimeoutException) { - result.setMsg("等待响应超时"); - log.info("节点 {} 响应超时,可能存在连接问题", node_id); - } else { - result.setMsg("发送消息失败: " + e.getMessage()); - log.info("发送消息到节点 {} 失败: {}", node_id, e.getMessage(), e); - } - return result; - } - } - - -} diff --git a/springboot-backend/src/main/java/com/admin/config/CaptchaResourceConfiguration.java b/springboot-backend/src/main/java/com/admin/config/CaptchaResourceConfiguration.java deleted file mode 100644 index d06469a..0000000 --- a/springboot-backend/src/main/java/com/admin/config/CaptchaResourceConfiguration.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.admin.config; - -import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; -import cloud.tianai.captcha.resource.FontCache; -import cloud.tianai.captcha.resource.ResourceStore; -import cloud.tianai.captcha.resource.common.model.dto.Resource; -import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; -import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; - -import static cloud.tianai.captcha.common.constant.CommonConstant.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH; - -@Component -@RequiredArgsConstructor -public class CaptchaResourceConfiguration { - - private final ResourceStore resourceStore; - - - private static final String[] OPTIONS = { - "SLIDER", "WORD_IMAGE_CLICK", "ROTATE", "CONCAT" - }; - - @PostConstruct - public void init() { - // 添加自定义背景图片 - for (int i = 1; i <= 10; i++) { - ResourceMap template = new ResourceMap("default", 4); - template.put("active.png", new Resource(ClassPathResourceProvider.NAME, "slide/" + i + "/1.png")); - template.put("fixed.png", new Resource(ClassPathResourceProvider.NAME, "slide/" + i + "/2.png")); - resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template); - } - - - // 添加自定义背景图片 - for (String option : OPTIONS) { - for (int i = 1; i <= 26; i++) { - resourceStore.addResource(option, new Resource("classpath", "bgimages/" + i + ".jpg", "default")); - } - } - - - //添加自定义字体 - resourceStore.addResource(FontCache.FONT_TYPE, new Resource("classpath", "fonts/SIMSUN.TTC", "default")); - } -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/config/EncryptionConfig.java b/springboot-backend/src/main/java/com/admin/config/EncryptionConfig.java deleted file mode 100644 index 20764f5..0000000 --- a/springboot-backend/src/main/java/com/admin/config/EncryptionConfig.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.admin.config; - -import com.admin.common.utils.AESCrypto; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.concurrent.ConcurrentHashMap; - -/** - * 加密配置管理类 - * 统一管理AES加密功能,为HTTP上报和WebSocket通信提供加密支持 - */ -@Component -@Slf4j -public class EncryptionConfig { - - // 缓存加密器实例,避免重复创建 - private static final ConcurrentHashMap CRYPTO_CACHE = new ConcurrentHashMap<>(); - - /** - * 获取或创建AES加密器实例 - * - * @param secret 密钥字符串 - * @return AES加密器实例,如果创建失败返回null - */ - public static AESCrypto getOrCreateCrypto(String secret) { - if (secret == null || secret.isEmpty()) { - return null; - } - return CRYPTO_CACHE.computeIfAbsent(secret, AESCrypto::create); - } - - /** - * 检测消息是否为加密格式 - * - * @param message 消息内容 - * @return 如果是加密格式返回true,否则返回false - */ - public static boolean isEncryptedMessage(String message) { - if (message == null || message.trim().isEmpty()) { - return false; - } - - try { - // 简单检查是否包含加密标识 - return message.contains("\"encrypted\":true") || message.contains("\"encrypted\": true"); - } catch (Exception e) { - return false; - } - } - - /** - * 清理缓存的加密器实例 - * - * @param secret 要清理的密钥 - */ - public static void clearCrypto(String secret) { - if (secret != null) { - CRYPTO_CACHE.remove(secret); - log.info("已清理密钥对应的加密器实例"); - } - } - - /** - * 清理所有缓存的加密器实例 - */ - public static void clearAllCrypto() { - int size = CRYPTO_CACHE.size(); - CRYPTO_CACHE.clear(); - log.info("已清理所有加密器实例缓存,共清理 {} 个实例", size); - } - - /** - * 获取当前缓存的加密器数量 - * - * @return 缓存的加密器数量 - */ - public static int getCacheSize() { - return CRYPTO_CACHE.size(); - } -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/config/MybatisPlusConfig.java b/springboot-backend/src/main/java/com/admin/config/MybatisPlusConfig.java deleted file mode 100644 index 7ad53a5..0000000 --- a/springboot-backend/src/main/java/com/admin/config/MybatisPlusConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.admin.config; - -import com.baomidou.mybatisplus.annotation.DbType; -import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer; -import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; -import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; -import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -@MapperScan("com.admin.mapper") -public class MybatisPlusConfig { - - @Bean - public MybatisPlusInterceptor mybatisPlusInterceptor() { - MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); - // 配置分页插件,指定数据库类型为 SQLite - PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.SQLITE); - interceptor.addInnerInterceptor(paginationInterceptor); // 分页插件 - interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); // 防止全表更新插件 - return interceptor; - } - - @Bean - public ConfigurationCustomizer configurationCustomizer() { - return configuration -> configuration.setUseDeprecatedExecutor(false); - } -} diff --git a/springboot-backend/src/main/java/com/admin/config/RestTemplateConfig.java b/springboot-backend/src/main/java/com/admin/config/RestTemplateConfig.java deleted file mode 100644 index 46f529d..0000000 --- a/springboot-backend/src/main/java/com/admin/config/RestTemplateConfig.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.admin.config; - -import org.apache.http.HttpHost; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContexts; -import org.apache.http.ssl.TrustStrategy; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.web.client.RestTemplate; - -import javax.net.ssl.SSLContext; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; - -@Configuration -public class RestTemplateConfig { - - @Bean - public RestTemplate restTemplate(ClientHttpRequestFactory factory){ - return new RestTemplate(factory); - } - - // 10秒超时配置 - private static final int TIMEOUT_SECONDS = 10; - private static final int TIMEOUT_MILLISECONDS = TIMEOUT_SECONDS * 1000; - - - @Bean - public ClientHttpRequestFactory simpleClientHttpRequestFactory(){ - HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); - factory.setConnectTimeout(TIMEOUT_MILLISECONDS); - factory.setReadTimeout(TIMEOUT_MILLISECONDS); - return factory; - } - - // 禁用ssl证书校验 - public static HttpComponentsClientHttpRequestFactory generateHttpRequestFactory() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException{ - TrustStrategy acceptingTrustStrategy = (x509Certificates, authType) -> true; - SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build(); - SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); - HttpClientBuilder httpClientBuilder = HttpClients.custom(); - - httpClientBuilder.setSSLSocketFactory(connectionSocketFactory); - CloseableHttpClient httpClient = httpClientBuilder.build(); - HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); - factory.setHttpClient(httpClient); - // 添加超时配置 - factory.setConnectTimeout(TIMEOUT_MILLISECONDS); - factory.setReadTimeout(TIMEOUT_MILLISECONDS); - return factory; - } - - // 禁用ssl证书校验 添加代理ip post请求 - public static HttpComponentsClientHttpRequestFactory post(String url, Integer port) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException{ - TrustStrategy acceptingTrustStrategy = (x509Certificates, authType) -> true; - SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build(); - SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); - HttpClientBuilder httpClientBuilder = HttpClients.custom(); - httpClientBuilder.setProxy(new HttpHost(url, port, "http")); - - httpClientBuilder.setSSLSocketFactory(connectionSocketFactory); - CloseableHttpClient httpClient = httpClientBuilder.build(); - HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); - factory.setHttpClient(httpClient); - // 添加超时配置 - factory.setConnectTimeout(TIMEOUT_MILLISECONDS); - factory.setReadTimeout(TIMEOUT_MILLISECONDS); - return factory; - } - -} diff --git a/springboot-backend/src/main/java/com/admin/config/SQLiteConfig.java b/springboot-backend/src/main/java/com/admin/config/SQLiteConfig.java deleted file mode 100644 index 550cc22..0000000 --- a/springboot-backend/src/main/java/com/admin/config/SQLiteConfig.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.admin.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import javax.annotation.PreDestroy; -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.Statement; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Pattern; - -/** - * SQLite 数据库配置 - * 启用 WAL (Write-Ahead Logging) 模式以提高并发性能 - * 添加定期 checkpoint 和优雅关闭处理 - */ -@Slf4j -@Component -@EnableScheduling -public class SQLiteConfig implements ApplicationRunner { - - private final DataSource dataSource; - - public SQLiteConfig(DataSource dataSource) { - this.dataSource = dataSource; - } - - @Override - public void run(ApplicationArguments args) throws Exception { - try (Connection connection = dataSource.getConnection(); - Statement statement = connection.createStatement()) { - - statement.execute("PRAGMA journal_mode=WAL;"); - statement.execute("PRAGMA synchronous=NORMAL;"); - statement.execute("PRAGMA cache_size=-64000;"); // 64MB 缓存 - statement.execute("PRAGMA temp_store=MEMORY;"); - statement.execute("PRAGMA busy_timeout=5000;"); // 5秒超时 - statement.execute("PRAGMA wal_autocheckpoint=1000;"); // 每1000页自动checkpoint - - ensureNodeDualStackColumns(connection); - - log.info("SQLite WAL mode configured successfully"); - } catch (Exception e) { - log.error("Failed to configure SQLite database", e); - throw e; - } - } - - private void ensureNodeDualStackColumns(Connection connection) throws Exception { - Set cols = getTableColumns(connection, "node"); - if (cols.isEmpty()) { - return; - } - - ensureColumnIfMissing(connection, cols, "node", "server_ip_v4", "VARCHAR(100)"); - ensureColumnIfMissing(connection, cols, "node", "server_ip_v6", "VARCHAR(100)"); - - backfillNodeDualStackColumns(connection); - } - - private void backfillNodeDualStackColumns(Connection connection) throws Exception { - Pattern ipv4 = Pattern.compile("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"); - - try (Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery("SELECT id, server_ip, server_ip_v4, server_ip_v6 FROM node;"); - PreparedStatement updV4 = connection.prepareStatement("UPDATE node SET server_ip_v4 = ? WHERE id = ?;"); - PreparedStatement updV6 = connection.prepareStatement("UPDATE node SET server_ip_v6 = ? WHERE id = ?;") - ) { - while (rs.next()) { - long id = rs.getLong("id"); - String serverIp = rs.getString("server_ip"); - String v4 = rs.getString("server_ip_v4"); - String v6 = rs.getString("server_ip_v6"); - - if (serverIp == null || serverIp.isBlank()) { - continue; - } - if ((v4 != null && !v4.isBlank()) || (v6 != null && !v6.isBlank())) { - continue; - } - - String trimmed = serverIp.trim(); - - if (ipv4.matcher(trimmed).matches()) { - updV4.setString(1, trimmed); - updV4.setLong(2, id); - updV4.executeUpdate(); - } else { - long colonCount = trimmed.chars().filter(ch -> ch == ':').count(); - if (colonCount >= 2) { - updV6.setString(1, trimmed); - updV6.setLong(2, id); - updV6.executeUpdate(); - } - } - } - } - } - - private Set getTableColumns(Connection connection, String table) throws Exception { - Set cols = new HashSet<>(); - try (Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery("PRAGMA table_info(" + table + ");")) { - while (rs.next()) { - String name = rs.getString("name"); - if (name != null && !name.isBlank()) { - cols.add(name); - } - } - } - return cols; - } - - private void ensureColumnIfMissing( - Connection connection, - Set existingColumns, - String table, - String column, - String type - ) throws Exception { - if (existingColumns.contains(column)) { - return; - } - - try (Statement statement = connection.createStatement()) { - statement.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + type + ";"); - } - - log.info("SQLite schema updated: added {}.{}", table, column); - } - - /** - * 定期执行 checkpoint,确保 WAL 文件内容写入主数据库 - * 每5分钟执行一次 - */ - @Scheduled(fixedDelay = 300000, initialDelay = 300000) - public void performCheckpoint() { - try (Connection connection = dataSource.getConnection(); - Statement statement = connection.createStatement()) { - - statement.execute("PRAGMA wal_checkpoint(TRUNCATE);"); - log.debug("SQLite WAL checkpoint completed"); - } catch (Exception e) { - log.error("Failed to perform SQLite checkpoint", e); - } - } - - /** - * 应用关闭前执行最终的 checkpoint,确保所有数据都写入主数据库文件 - */ - @PreDestroy - public void onShutdown() { - log.info("Performing final SQLite checkpoint before shutdown..."); - try (Connection connection = dataSource.getConnection(); - Statement statement = connection.createStatement()) { - - // 强制执行 checkpoint,将所有 WAL 内容写入主数据库 - statement.execute("PRAGMA wal_checkpoint(TRUNCATE);"); - log.info("Final SQLite checkpoint completed successfully"); - } catch (Exception e) { - log.error("Failed to perform final SQLite checkpoint", e); - } - } -} diff --git a/springboot-backend/src/main/java/com/admin/config/WebMvcConfig.java b/springboot-backend/src/main/java/com/admin/config/WebMvcConfig.java deleted file mode 100644 index b72324d..0000000 --- a/springboot-backend/src/main/java/com/admin/config/WebMvcConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.admin.config; - -import com.admin.common.interceptor.JwtInterceptor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.filter.CorsFilter; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - - -@Configuration -@EnableWebMvc -public class WebMvcConfig implements WebMvcConfigurer { - - private CorsConfiguration buildConfig() { - CorsConfiguration corsConfiguration = new CorsConfiguration(); - corsConfiguration.addAllowedOrigin("*"); - corsConfiguration.addAllowedHeader("*"); - corsConfiguration.addAllowedMethod("*"); - corsConfiguration.addExposedHeader("Authorization"); - return corsConfiguration; - } - - @Bean - public CorsFilter corsFilter() { - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", buildConfig()); - return new CorsFilter(source); - } - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins("*") - .allowedMethods("GET", "POST", "DELETE", "PUT") - .maxAge(3600); - } - - /** - * JWT拦截器 - */ - @Bean - public JwtInterceptor jwtInterceptor() { - return new JwtInterceptor(); - } - - /** - * 添加JWT拦截器 - */ - @Override - public void addInterceptors(InterceptorRegistry registry) { - // 添加JWT拦截器,不拦截登录接口 - registry.addInterceptor(jwtInterceptor()) - .addPathPatterns("/api/**") - .excludePathPatterns("/flow/**") - .excludePathPatterns("/api/v1/open_api/**") - .excludePathPatterns("/api/v1/config/get") - .excludePathPatterns("/api/v1/user/login") - .excludePathPatterns("/api/v1/captcha/**"); - } -} diff --git a/springboot-backend/src/main/java/com/admin/config/WebSocketConfig.java b/springboot-backend/src/main/java/com/admin/config/WebSocketConfig.java deleted file mode 100644 index 51035f0..0000000 --- a/springboot-backend/src/main/java/com/admin/config/WebSocketConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.admin.config; - -import com.admin.common.utils.WebSocketServer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.config.annotation.EnableWebSocket; -import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; -import org.springframework.boot.web.servlet.ServletContextInitializer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.annotation.Resource; - - -@Configuration -@EnableWebSocket -public class WebSocketConfig implements WebSocketConfigurer { - - @Resource - private WebSocketInterceptor webSocketInterceptor; - - @Override - public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) { - webSocketHandlerRegistry - .addHandler(myHandler(), "/system-info") - .setAllowedOrigins("*") - .addInterceptors(webSocketInterceptor); - } - - - @Bean - public WebSocketHandler myHandler() { - return new WebSocketServer(); - } - -} diff --git a/springboot-backend/src/main/java/com/admin/config/WebSocketInterceptor.java b/springboot-backend/src/main/java/com/admin/config/WebSocketInterceptor.java deleted file mode 100644 index 7a7964c..0000000 --- a/springboot-backend/src/main/java/com/admin/config/WebSocketInterceptor.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.admin.config; - - -import com.admin.common.utils.IpUtils; -import com.admin.common.utils.JwtUtil; -import com.admin.entity.Node; -import com.admin.service.NodeService; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; - -import javax.annotation.Resource; -import java.net.InetSocketAddress; -import java.util.Map; -import java.util.Objects; - - -@Configuration -@Slf4j -public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor { - - @Resource - NodeService nodeService; - - @Override - public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { - - } - - @Override - public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { - ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request; - String secret = serverHttpRequest.getServletRequest().getParameter("secret"); - String type = serverHttpRequest.getServletRequest().getParameter("type"); - String version = serverHttpRequest.getServletRequest().getParameter("version"); - String http = serverHttpRequest.getServletRequest().getParameter("http"); - String tls = serverHttpRequest.getServletRequest().getParameter("tls"); - String socks = serverHttpRequest.getServletRequest().getParameter("socks"); - if (Objects.equals(type, "1")) { - System.out.println("type: " + type + " - version: " + version + " - secret: " + secret + " - IP: " + getClientIp(request)); - Node node = nodeService.getOne(new QueryWrapper().eq("secret", secret)); - if (node == null) { - log.info("节点验证失败:未找到匹配的secret"); - return false; - } - attributes.put("id", node.getId()); - attributes.put("nodeSecret", secret); - attributes.put("nodeVersion", version); - attributes.put("http",http); - attributes.put("tls",tls); - attributes.put("socks",socks); - log.info("节点 {} 通过验证,版本: {}", node.getId(), version); - // 不在这里更新状态,等到连接建立后再统一更新 - }else { - boolean b = JwtUtil.validateToken(secret); - if (!b) return false; - attributes.put("id", JwtUtil.getUserIdFromToken(secret)); - } - attributes.put("type", type); - return true; - } - - public String getClientIp(ServerHttpRequest request) { - InetSocketAddress remoteAddress = request.getRemoteAddress(); - if (remoteAddress != null) { - return remoteAddress.getAddress().getHostAddress(); - } - return null; - } - - -} diff --git a/springboot-backend/src/main/java/com/admin/controller/BaseController.java b/springboot-backend/src/main/java/com/admin/controller/BaseController.java deleted file mode 100644 index 1c07ea4..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/BaseController.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.admin.controller; - -import com.admin.service.*; -import org.springframework.beans.factory.annotation.Autowired; - -public class BaseController { - - @Autowired - UserService userService; - - @Autowired - NodeService nodeService; - - @Autowired - UserTunnelService userTunnelService; - - @Autowired - TunnelService tunnelService; - - @Autowired - ForwardService forwardService; - - @Autowired - ViteConfigService viteConfigService; - -} diff --git a/springboot-backend/src/main/java/com/admin/controller/CaptchaController.java b/springboot-backend/src/main/java/com/admin/controller/CaptchaController.java deleted file mode 100644 index 26f25ca..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/CaptchaController.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.admin.controller; - -import cloud.tianai.captcha.application.ImageCaptchaApplication; -import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; -import cloud.tianai.captcha.common.response.ApiResponse; -import cloud.tianai.captcha.application.vo.ImageCaptchaVO; -import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack; -import cloud.tianai.captcha.validator.common.model.dto.MatchParam; -import com.admin.common.dto.CaptchaVerifyDto; -import com.admin.common.lang.R; -import com.admin.entity.ViteConfig; -import com.admin.service.ViteConfigService; -import com.alibaba.fastjson.JSONObject; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; -import cloud.tianai.captcha.application.vo.CaptchaResponse; - -import javax.annotation.Resource; -import javax.validation.Valid; -import java.util.Collections; -import java.util.Objects; -import java.util.concurrent.ThreadLocalRandom; - -/** - * 验证码控制器 - */ -@Slf4j -@RestController -@CrossOrigin -@RequestMapping("/api/v1/captcha") -public class CaptchaController extends BaseController { - - @Resource - ImageCaptchaApplication application; - - private static final String[] OPTIONS = { - "SLIDER", "WORD_IMAGE_CLICK", "ROTATE", "CONCAT" - }; - - @PostMapping("/check") - public R check() { - ViteConfig viteConfig = viteConfigService.getOne(new QueryWrapper().eq("name", "captcha_enabled")); - if (viteConfig == null) return R.ok(0); - if (!Objects.equals(viteConfig.getValue(), "true")) return R.ok(0); - return R.ok(1); - } - - - @PostMapping("/generate") - public CaptchaResponse genCaptcha() { - ViteConfig viteConfig = viteConfigService.getOne(new QueryWrapper().eq("name", "captcha_type")); - String captchaType; - if (viteConfig == null || Objects.equals(viteConfig.getValue(), "RANDOM")) { - captchaType = getRandomOption(); - }else { - captchaType = viteConfig.getValue(); - } - return application.generateCaptcha(captchaType); - } - - - @PostMapping("/verify") - public ApiResponse verify(@Valid @RequestBody CaptchaVerifyDto verifyDto) { - ApiResponse response = application.matching(verifyDto.getId(), verifyDto.getData()); - if (response.isSuccess()) { - return ApiResponse.ofSuccess(Collections.singletonMap("validToken", verifyDto.getId())); - } - return response; - } - - public static String getRandomOption() { - int index = ThreadLocalRandom.current().nextInt(OPTIONS.length); - return OPTIONS[index]; - } -} diff --git a/springboot-backend/src/main/java/com/admin/controller/CustomErrorController.java b/springboot-backend/src/main/java/com/admin/controller/CustomErrorController.java deleted file mode 100644 index 05ff38b..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/CustomErrorController.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.admin.controller; - -import org.springframework.boot.web.servlet.error.ErrorController; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; - -import javax.servlet.RequestDispatcher; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - -@Controller -public class CustomErrorController implements ErrorController { - - @RequestMapping("/error") - public void handleError(HttpServletRequest request, HttpServletResponse response) throws IOException { - Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); - - response.setContentType("text/html; charset=UTF-8"); - - if (status != null) { - int statusCode = Integer.valueOf(status.toString()); - - if (statusCode == HttpStatus.NOT_FOUND.value()) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - String html = generateErrorHtml( - "404", - "你推开了后端的大门,却发现里面只有寂寞。", - request.getRequestURI() - ); - response.getWriter().write(html); - } - } - } - - private String generateErrorHtml(String statusCode, String title, String path) { - return "" + - "" + - "" + - " " + - " " + - " 错误 " + statusCode + "" + - " " + - "" + - "" + - "
" + statusCode + "
" + - "
" + title + "
" + - "" + - ""; - } -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/controller/FlowController.java b/springboot-backend/src/main/java/com/admin/controller/FlowController.java deleted file mode 100644 index 7760ae7..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/FlowController.java +++ /dev/null @@ -1,389 +0,0 @@ -package com.admin.controller; - -import com.admin.common.aop.LogAnnotation; -import com.admin.common.dto.FlowDto; -import com.admin.common.dto.GostConfigDto; -import com.admin.common.task.CheckGostConfigAsync; -import com.admin.common.utils.AESCrypto; -import com.admin.common.utils.GostUtil; -import com.admin.entity.*; -import com.admin.service.ChainTunnelService; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; -import org.springframework.context.annotation.Lazy; -import org.springframework.web.bind.annotation.*; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.Resource; -import java.math.BigDecimal; -import java.util.Date; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 流量上报控制器 - * 处理节点上报的流量数据,更新用户和隧道的流量统计 - *

- * 主要功能: - * 1. 接收并处理节点上报的流量数据 - * 2. 更新转发、用户和隧道的流量统计 - * 3. 检查用户总流量限制,超限时暂停所有服务 - * 4. 检查隧道流量限制,超限时暂停对应服务 - * 5. 检查用户到期时间,到期时暂停所有服务 - * 6. 检查隧道权限到期时间,到期时暂停对应服务 - * 7. 检查用户状态,状态不为1时暂停所有服务 - * 8. 检查转发状态,状态不为1时暂停对应转发 - * 9. 检查用户隧道权限状态,状态不为1时暂停对应转发 - *

- * 并发安全解决方案: - * 1. 使用UpdateWrapper进行数据库层面的原子更新操作,避免读取-修改-写入的竞态条件 - * 2. 使用synchronized锁确保同一用户/隧道的流量更新串行执行 - * 3. 这样可以避免相同用户相同隧道不同转发同时上报时流量统计丢失的问题 - */ -@RestController -@RequestMapping("/flow") -@CrossOrigin -@Slf4j -public class FlowController extends BaseController { - - // 常量定义 - private static final String SUCCESS_RESPONSE = "ok"; - private static final String DEFAULT_USER_TUNNEL_ID = "0"; - private static final long BYTES_TO_GB = 1024L * 1024L * 1024L; - - // 用于同步相同用户和隧道的流量更新操作 - private static final ConcurrentHashMap USER_LOCKS = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap TUNNEL_LOCKS = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap FORWARD_LOCKS = new ConcurrentHashMap<>(); - - // 缓存加密器实例,避免重复创建 - private static final ConcurrentHashMap CRYPTO_CACHE = new ConcurrentHashMap<>(); - - @Resource - CheckGostConfigAsync checkGostConfigAsync; - - @Resource - @Lazy - ChainTunnelService chainTunnelService; - - /** - * 加密消息包装器 - */ - public static class EncryptedMessage { - private boolean encrypted; - private String data; - private Long timestamp; - - // getters and setters - public boolean isEncrypted() { - return encrypted; - } - - public void setEncrypted(boolean encrypted) { - this.encrypted = encrypted; - } - - public String getData() { - return data; - } - - public void setData(String data) { - this.data = data; - } - - public Long getTimestamp() { - return timestamp; - } - - public void setTimestamp(Long timestamp) { - this.timestamp = timestamp; - } - } - - @PostMapping("/config") - @LogAnnotation - public String config(@RequestBody String rawData, String secret) { - Node node = nodeService.getOne(new QueryWrapper().eq("secret", secret)); - if (node == null) return SUCCESS_RESPONSE; - - try { - // 尝试解密数据 - String decryptedData = decryptIfNeeded(rawData, secret); - - // 解析为GostConfigDto - GostConfigDto gostConfigDto = JSON.parseObject(decryptedData, GostConfigDto.class); - checkGostConfigAsync.cleanNodeConfigs(node.getId().toString(), gostConfigDto); - - log.info("🔓 节点 {} 配置数据接收成功{}", node.getId(), isEncryptedMessage(rawData) ? "(已解密)" : ""); - - } catch (Exception e) { - log.error("处理节点 {} 配置数据失败: {}", node.getId(), e.getMessage()); - } - - return SUCCESS_RESPONSE; - } - - @RequestMapping("/test") - @LogAnnotation - public String test() { - return "test"; - } - - /** - * 处理流量数据上报 - * - * @param rawData 原始数据(可能是加密的) - * @param secret 节点密钥 - * @return 处理结果 - */ - @RequestMapping("/upload") - @LogAnnotation - public String uploadFlowData(@RequestBody String rawData, String secret) { - // 1. 验证节点权限 - if (!isValidNode(secret)) { - return SUCCESS_RESPONSE; - } - - // 2. 尝试解密数据 - String decryptedData = decryptIfNeeded(rawData, secret); - - // 3. 解析为FlowDto列表 - JSONArray flowDataList = JSONObject.parseArray(decryptedData); - log.info("节点上报流量数据{}", flowDataList); - for (int i = 0; i < flowDataList.size(); i++) { - String jsonObject = flowDataList.getJSONObject(i).toJSONString(); - FlowDto flowDto = JSONObject.parseObject(jsonObject, FlowDto.class); - if (!Objects.equals(flowDto.getN(), "web_api")) { - processFlowData(flowDto); - } - } - return SUCCESS_RESPONSE; - - } - - /** - * 检测消息是否为加密格式 - */ - private boolean isEncryptedMessage(String data) { - try { - JSONObject json = JSON.parseObject(data); - return json.getBooleanValue("encrypted"); - } catch (Exception e) { - return false; - } - } - - /** - * 根据需要解密数据 - */ - private String decryptIfNeeded(String rawData, String secret) { - if (rawData == null || rawData.trim().isEmpty()) { - throw new IllegalArgumentException("数据不能为空"); - } - - try { - // 尝试解析为加密消息格式 - EncryptedMessage encryptedMessage = JSON.parseObject(rawData, EncryptedMessage.class); - - if (encryptedMessage.isEncrypted() && encryptedMessage.getData() != null) { - // 获取或创建加密器 - AESCrypto crypto = getOrCreateCrypto(secret); - if (crypto == null) { - log.info("⚠️ 收到加密消息但无法创建解密器,使用原始数据"); - return rawData; - } - - // 解密数据 - String decryptedData = crypto.decryptString(encryptedMessage.getData()); - return decryptedData; - } - } catch (Exception e) { - // 解析失败,可能是非加密格式,直接返回原始数据 - log.info("数据未加密或解密失败,使用原始数据: {}", e.getMessage()); - } - - return rawData; - } - - /** - * 获取或创建加密器实例 - */ - private AESCrypto getOrCreateCrypto(String secret) { - return CRYPTO_CACHE.computeIfAbsent(secret, AESCrypto::create); - } - - /** - * 处理流量数据的核心逻辑 - */ - private void processFlowData(FlowDto flowDataList) { - String[] serviceIds = parseServiceName(flowDataList.getN()); - String forwardId = serviceIds[0]; - String userId = serviceIds[1]; - String userTunnelId = serviceIds[2]; - - Forward forward = forwardService.getById(forwardId); - if (forward != null){ - Tunnel tunnel = tunnelService.getById(forward.getTunnelId()); - - // 处理流量倍率及单双向计算 - BigDecimal trafficRatio = tunnel.getTrafficRatio(); - BigDecimal originalD = BigDecimal.valueOf(flowDataList.getD()); - BigDecimal originalU = BigDecimal.valueOf(flowDataList.getU()); - BigDecimal newD = originalD.multiply(trafficRatio); - BigDecimal newU = originalU.multiply(trafficRatio); - flowDataList.setD(newD.longValue() * tunnel.getFlow()); - flowDataList.setU(newU.longValue() * tunnel.getFlow()); - } - - // 先更新所有流量统计 - 确保流量数据的一致性 - updateForwardFlow(forwardId, flowDataList); - updateUserFlow(userId, flowDataList); - updateUserTunnelFlow(userTunnelId, flowDataList); - - // 7. 检查和服务暂停操作 - String name = buildServiceName(forwardId, userId, userTunnelId); - if (!Objects.equals(userTunnelId, DEFAULT_USER_TUNNEL_ID)) { // 非管理员的转发需要检测流量限制 - checkUserRelatedLimits(userId, name); - checkUserTunnelRelatedLimits(userTunnelId, name, userId); - } - - } - - private void checkUserRelatedLimits(String userId, String name) { - - // 重新查询用户以获取最新的流量数据 - User updatedUser = userService.getById(userId); - if (updatedUser == null) return; - - // 检查用户总流量限制 - long userFlowLimit = updatedUser.getFlow() * BYTES_TO_GB; - long userCurrentFlow = updatedUser.getInFlow() + updatedUser.getOutFlow(); - if (userFlowLimit < userCurrentFlow) { - pauseAllUserServices(userId, name); - return; - } - - // 检查用户到期时间 - if (updatedUser.getExpTime() != null && updatedUser.getExpTime() <= new Date().getTime()) { - pauseAllUserServices(userId, name); - return; - } - - // 检查用户状态 - if (updatedUser.getStatus() != 1) { - pauseAllUserServices(userId, name); - } - } - - public void pauseAllUserServices(String userId, String name) { - List forwardList = forwardService.list(new QueryWrapper().eq("user_id", userId)); - pauseService(forwardList, name); - } - - public void checkUserTunnelRelatedLimits(String userTunnelId, String name, String userId) { - - UserTunnel userTunnel = userTunnelService.getById(userTunnelId); - if (userTunnel == null) return; - long flow = userTunnel.getInFlow() + userTunnel.getOutFlow(); - if (flow >= userTunnel.getFlow() * BYTES_TO_GB) { - pauseSpecificForward(userTunnel.getTunnelId(), name, userId); - return; - } - - if (userTunnel.getExpTime() != null && userTunnel.getExpTime() <= System.currentTimeMillis()) { - pauseSpecificForward(userTunnel.getTunnelId(), name, userId); - return; - } - - if (userTunnel.getStatus() != 1) { - pauseSpecificForward(userTunnel.getTunnelId(), name, userId); - } - - - } - - private void pauseSpecificForward(Integer tunnelId, String name, String userId) { - List forwardList = forwardService.list(new QueryWrapper().eq("tunnel_id", tunnelId).eq("user_id", userId)); - pauseService(forwardList, name); - } - - public void pauseService(List forwardList, String name) { - for (Forward forward : forwardList) { - List chainTunnels = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", forward.getTunnelId()).eq("chain_type", 1)); - for (ChainTunnel chainTunnel : chainTunnels) { - GostUtil.PauseAndResumeService(chainTunnel.getNodeId(), name, "PauseService"); - } - forward.setStatus(0); - forwardService.updateById(forward); - } - } - - private void updateForwardFlow(String forwardId, FlowDto flowStats) { - // 对相同转发的流量更新进行同步,避免并发覆盖 - synchronized (getForwardLock(forwardId)) { - UpdateWrapper updateWrapper = new UpdateWrapper<>(); - updateWrapper.eq("id", forwardId); - updateWrapper.setSql("in_flow = in_flow + " + flowStats.getD()); - updateWrapper.setSql("out_flow = out_flow + " + flowStats.getU()); - - forwardService.update(null, updateWrapper); - } - } - - private void updateUserFlow(String userId, FlowDto flowStats) { - // 对相同用户的流量更新进行同步,避免并发覆盖 - synchronized (getUserLock(userId)) { - UpdateWrapper updateWrapper = new UpdateWrapper<>(); - updateWrapper.eq("id", userId); - - updateWrapper.setSql("in_flow = in_flow + " + flowStats.getD()); - updateWrapper.setSql("out_flow = out_flow + " + flowStats.getU()); - - userService.update(null, updateWrapper); - } - } - - private void updateUserTunnelFlow(String userTunnelId, FlowDto flowStats) { - if (Objects.equals(userTunnelId, DEFAULT_USER_TUNNEL_ID)) { - return; // 默认隧道不需要更新,返回成功 - } - - // 对相同用户隧道的流量更新进行同步,避免并发覆盖 - synchronized (getTunnelLock(userTunnelId)) { - UpdateWrapper updateWrapper = new UpdateWrapper<>(); - updateWrapper.eq("id", userTunnelId); - updateWrapper.setSql("in_flow = in_flow + " + flowStats.getD()); - updateWrapper.setSql("out_flow = out_flow + " + flowStats.getU()); - userTunnelService.update(null, updateWrapper); - } - } - - private Object getUserLock(String userId) { - return USER_LOCKS.computeIfAbsent(userId, k -> new Object()); - } - - private Object getTunnelLock(String userTunnelId) { - return TUNNEL_LOCKS.computeIfAbsent(userTunnelId, k -> new Object()); - } - - private Object getForwardLock(String forwardId) { - return FORWARD_LOCKS.computeIfAbsent(forwardId, k -> new Object()); - } - - private boolean isValidNode(String secret) { - int nodeCount = nodeService.count(new QueryWrapper().eq("secret", secret)); - return nodeCount > 0; - } - - private String[] parseServiceName(String serviceName) { - return serviceName.split("_"); - } - - private String buildServiceName(String forwardId, String userId, String userTunnelId) { - return forwardId + "_" + userId + "_" + userTunnelId; - } -} diff --git a/springboot-backend/src/main/java/com/admin/controller/ForwardController.java b/springboot-backend/src/main/java/com/admin/controller/ForwardController.java deleted file mode 100644 index b8c55c3..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/ForwardController.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.admin.controller; - -import com.admin.common.aop.LogAnnotation; -import com.admin.common.annotation.RequireRole; -import com.admin.common.dto.BatchDeleteDto; -import com.admin.common.dto.BatchRedeployDto; -import com.admin.common.dto.BatchChangeTunnelDto; -import com.admin.common.dto.ForwardDto; -import com.admin.common.dto.ForwardUpdateDto; -import com.admin.common.lang.R; -import com.admin.service.ForwardService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; - -/** - *

- * 前端控制器 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@RestController -@CrossOrigin -@RequestMapping("/api/v1/forward") -public class ForwardController extends BaseController { - - @Autowired - private ForwardService forwardService; - - @LogAnnotation - @PostMapping("/create") - public R create(@Validated @RequestBody ForwardDto forwardDto) { - return forwardService.createForward(forwardDto); - } - - @LogAnnotation - @PostMapping("/list") - public R readAll() { - return forwardService.getAllForwards(); - } - - @LogAnnotation - @PostMapping("/update") - public R update(@Validated @RequestBody ForwardUpdateDto forwardUpdateDto) { - return forwardService.updateForward(forwardUpdateDto); - } - - @LogAnnotation - @PostMapping("/delete") - public R delete(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return forwardService.deleteForward(id); - } - - @LogAnnotation - @PostMapping("/force-delete") - public R forceDelete(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return forwardService.forceDeleteForward(id); - } - - @LogAnnotation - @PostMapping("/pause") - public R pause(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return forwardService.pauseForward(id); - } - - @LogAnnotation - @PostMapping("/resume") - public R resume(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return forwardService.resumeForward(id); - } - - /** - * 转发诊断功能 - * @param params 包含forwardId的参数 - * @return 诊断结果 - */ - @LogAnnotation - @PostMapping("/diagnose") - public R diagnoseForward(@RequestBody Map params) { - Long forwardId = Long.valueOf(params.get("forwardId").toString()); - return forwardService.diagnoseForward(forwardId); - } - - /** - * 更新转发排序 - * @param params 包含forwards数组的参数,每个元素包含id和inx - * @return 更新结果 - */ - @LogAnnotation - @PostMapping("/update-order") - public R updateForwardOrder(@RequestBody Map params) { - return forwardService.updateForwardOrder(params); - } - - @LogAnnotation - @PostMapping("/batch-delete") - public R batchDelete(@Validated @RequestBody BatchDeleteDto batchDeleteDto) { - return forwardService.batchDeleteForwards(batchDeleteDto); - } - - @LogAnnotation - @PostMapping("/batch-pause") - public R batchPause(@Validated @RequestBody BatchDeleteDto batchDeleteDto) { - return forwardService.batchPauseForwards(batchDeleteDto); - } - - @LogAnnotation - @PostMapping("/batch-resume") - public R batchResume(@Validated @RequestBody BatchDeleteDto batchDeleteDto) { - return forwardService.batchResumeForwards(batchDeleteDto); - } - - @LogAnnotation - @PostMapping("/batch-redeploy") - public R batchRedeploy(@Validated @RequestBody BatchRedeployDto batchRedeployDto) { - return forwardService.batchRedeployForwards(batchRedeployDto); - } - - @LogAnnotation - @PostMapping("/batch-change-tunnel") - public R batchChangeTunnel(@Validated @RequestBody BatchChangeTunnelDto batchChangeTunnelDto) { - return forwardService.batchChangeTunnel(batchChangeTunnelDto); - } - -} diff --git a/springboot-backend/src/main/java/com/admin/controller/GroupController.java b/springboot-backend/src/main/java/com/admin/controller/GroupController.java deleted file mode 100644 index 63845d4..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/GroupController.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.admin.controller; - -import com.admin.common.aop.LogAnnotation; -import com.admin.common.annotation.RequireRole; -import com.admin.common.dto.GroupCreateDto; -import com.admin.common.dto.GroupPermissionAssignDto; -import com.admin.common.dto.GroupUpdateDto; -import com.admin.common.dto.TunnelGroupAssignTunnelsDto; -import com.admin.common.dto.UserGroupAssignUsersDto; -import com.admin.common.lang.R; -import com.admin.service.GroupService; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.util.Map; - -@RestController -@CrossOrigin -@RequestMapping("/api/v1/group") -public class GroupController { - - @Resource - private GroupService groupService; - - @LogAnnotation - @RequireRole - @PostMapping("/tunnel/list") - public R tunnelGroupList() { - return groupService.getTunnelGroups(); - } - - @LogAnnotation - @RequireRole - @PostMapping("/tunnel/create") - public R createTunnelGroup(@Validated @RequestBody GroupCreateDto dto) { - return groupService.createTunnelGroup(dto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/tunnel/update") - public R updateTunnelGroup(@Validated @RequestBody GroupUpdateDto dto) { - return groupService.updateTunnelGroup(dto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/tunnel/delete") - public R deleteTunnelGroup(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return groupService.deleteTunnelGroup(id); - } - - @LogAnnotation - @RequireRole - @PostMapping("/tunnel/assign") - public R assignTunnels(@Validated @RequestBody TunnelGroupAssignTunnelsDto dto) { - return groupService.assignTunnelsToGroup(dto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/user/list") - public R userGroupList() { - return groupService.getUserGroups(); - } - - @LogAnnotation - @RequireRole - @PostMapping("/user/create") - public R createUserGroup(@Validated @RequestBody GroupCreateDto dto) { - return groupService.createUserGroup(dto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/user/update") - public R updateUserGroup(@Validated @RequestBody GroupUpdateDto dto) { - return groupService.updateUserGroup(dto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/user/delete") - public R deleteUserGroup(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return groupService.deleteUserGroup(id); - } - - @LogAnnotation - @RequireRole - @PostMapping("/user/assign") - public R assignUsers(@Validated @RequestBody UserGroupAssignUsersDto dto) { - return groupService.assignUsersToGroup(dto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/permission/list") - public R listPermissions() { - return groupService.getGroupPermissions(); - } - - @LogAnnotation - @RequireRole - @PostMapping("/permission/assign") - public R assignPermission(@Validated @RequestBody GroupPermissionAssignDto dto) { - return groupService.assignGroupPermission(dto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/permission/remove") - public R removePermission(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return groupService.removeGroupPermission(id); - } -} diff --git a/springboot-backend/src/main/java/com/admin/controller/NodeController.java b/springboot-backend/src/main/java/com/admin/controller/NodeController.java deleted file mode 100644 index 131a6e8..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/NodeController.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.admin.controller; - - -import com.admin.common.annotation.RequireRole; -import com.admin.common.aop.LogAnnotation; -import com.admin.common.dto.BatchDeleteDto; -import com.admin.common.dto.NodeDto; -import com.admin.common.dto.NodeUpdateDto; -import com.admin.common.lang.R; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; - -/** - *

- * 前端控制器 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@RestController -@CrossOrigin -@RequestMapping("/api/v1/node") -public class NodeController extends BaseController { - - @LogAnnotation - @RequireRole - @PostMapping("/create") - public R create(@Validated @RequestBody NodeDto nodeDto) { - return nodeService.createNode(nodeDto); - } - - - @LogAnnotation - @RequireRole - @PostMapping("/list") - public R list() { - return nodeService.getAllNodes(); - } - - @LogAnnotation - @RequireRole - @PostMapping("/update") - public R update(@Validated @RequestBody NodeUpdateDto nodeUpdateDto) { - return nodeService.updateNode(nodeUpdateDto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/delete") - public R delete(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return nodeService.deleteNode(id); - } - - @LogAnnotation - @RequireRole - @PostMapping("/install") - public R getInstallCommand(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return nodeService.getInstallCommand(id); - } - - /** - * 更新节点排序 - * @param params 包含nodes数组的参数,每个元素包含id和inx - * @return 更新结果 - */ - @LogAnnotation - @RequireRole - @PostMapping("/update-order") - public R updateNodeOrder(@RequestBody Map params) { - return nodeService.updateNodeOrder(params); - } - - @LogAnnotation - @RequireRole - @PostMapping("/batch-delete") - public R batchDelete(@Validated @RequestBody BatchDeleteDto batchDeleteDto) { - return nodeService.batchDeleteNodes(batchDeleteDto); - } - -} diff --git a/springboot-backend/src/main/java/com/admin/controller/OpenApiController.java b/springboot-backend/src/main/java/com/admin/controller/OpenApiController.java deleted file mode 100644 index a0882de..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/OpenApiController.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.admin.controller; - - -import com.admin.common.aop.LogAnnotation; -import com.admin.common.lang.R; -import javax.servlet.http.HttpServletResponse; - -import com.admin.common.utils.Md5Util; -import com.admin.entity.User; -import com.admin.entity.UserTunnel; -import com.alibaba.fastjson.JSONObject; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import org.springframework.web.bind.annotation.*; - -import java.util.Objects; - -@RestController -@CrossOrigin -@RequestMapping("/api/v1/open_api") -public class OpenApiController extends BaseController { - - @LogAnnotation - @GetMapping("/sub_store") - public Object create( - @RequestParam("user") String user, - @RequestParam("pwd") String pwd, - @RequestParam(value = "tunnel", required = false, defaultValue = "-1") String tunnel, - HttpServletResponse response) { - JSONObject result = new JSONObject(); - result.put("upload", 0); - result.put("download", 0); - result.put("total", 0); - result.put("expire", 0); - // 校验 user 是否为空 - if (user == null || user.isEmpty()) { - return R.err("用户不能为空"); - } - if (pwd == null || pwd.isEmpty()) { - return R.err("密码不能为空"); - } - - User userInfo = userService.getOne(new QueryWrapper().eq("user", user)); - if (userInfo == null) { - return R.err("鉴权失败"); - } - - String pwdMd5 = Md5Util.md5(pwd); - if (!Objects.equals(pwdMd5, userInfo.getPwd())) { - return R.err("鉴权失败"); - } - - final long GIGA = 1024L * 1024L * 1024L; - String headerValue; - - if ("-1".equals(tunnel)) { - headerValue = buildSubscriptionHeader( - userInfo.getOutFlow(), - userInfo.getInFlow(), - userInfo.getFlow() * GIGA, - userInfo.getExpTime() / 1000 - ); - } else { - UserTunnel tunnelInfo = userTunnelService.getById(tunnel); - if (tunnelInfo == null) return R.err("隧道不存在"); - if (!tunnelInfo.getUserId().toString().equals(userInfo.getId().toString())) return R.err("隧道不存在"); - headerValue = buildSubscriptionHeader( - tunnelInfo.getOutFlow(), - tunnelInfo.getInFlow(), - tunnelInfo.getFlow() * GIGA, - tunnelInfo.getExpTime() / 1000 - ); - } - - response.setHeader("subscription-userinfo", headerValue); - return headerValue; - } - - - - private String buildSubscriptionHeader(long upload, long download, long total, long expire) { - return String.format("upload=%d; download=%d; total=%d; expire=%d", download, upload, total, expire); - } - - -} diff --git a/springboot-backend/src/main/java/com/admin/controller/SpeedLimitController.java b/springboot-backend/src/main/java/com/admin/controller/SpeedLimitController.java deleted file mode 100644 index c827854..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/SpeedLimitController.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.admin.controller; - -import com.admin.common.aop.LogAnnotation; -import com.admin.common.annotation.RequireRole; -import com.admin.common.dto.SpeedLimitDto; -import com.admin.common.dto.SpeedLimitUpdateDto; -import com.admin.common.lang.R; -import com.admin.service.SpeedLimitService; -import com.admin.service.TunnelService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; - -/** - *

- * 限速规则前端控制器 - *

- * - * @author QAQ - * @since 2025-06-04 - */ -@RestController -@RequestMapping("/api/v1/speed-limit") -@CrossOrigin -public class SpeedLimitController extends BaseController { - - @Autowired - private SpeedLimitService speedLimitService; - - @Autowired - private TunnelService tunnelService; - - @LogAnnotation - @RequireRole - @PostMapping("/create") - public R create(@Validated @RequestBody SpeedLimitDto speedLimitDto) { - return speedLimitService.createSpeedLimit(speedLimitDto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/list") - public R list() { - return speedLimitService.getAllSpeedLimits(); - } - - @LogAnnotation - @RequireRole - @PostMapping("/update") - public R update(@Validated @RequestBody SpeedLimitUpdateDto speedLimitUpdateDto) { - return speedLimitService.updateSpeedLimit(speedLimitUpdateDto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/delete") - public R delete(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return speedLimitService.deleteSpeedLimit(id); - } - - @LogAnnotation - @RequireRole - @PostMapping("/tunnels") - public R getTunnels() { - return tunnelService.getAllTunnels(); - } -} diff --git a/springboot-backend/src/main/java/com/admin/controller/TunnelController.java b/springboot-backend/src/main/java/com/admin/controller/TunnelController.java deleted file mode 100644 index 5c797d2..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/TunnelController.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.admin.controller; - -import com.admin.common.aop.LogAnnotation; -import com.admin.common.annotation.RequireRole; -import com.admin.common.dto.BatchDeleteDto; -import com.admin.common.dto.BatchRedeployDto; -import com.admin.common.dto.TunnelDto; -import com.admin.common.dto.TunnelUpdateDto; - -import com.admin.common.dto.UserTunnelBatchAssignDto; -import com.admin.common.dto.UserTunnelDto; -import com.admin.common.dto.UserTunnelQueryDto; -import com.admin.common.dto.UserTunnelUpdateDto; -import com.admin.common.lang.R; -import com.admin.service.TunnelService; -import com.admin.service.UserTunnelService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; - -/** - *

- * 隧道前端控制器 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@RestController -@CrossOrigin -@RequestMapping("/api/v1/tunnel") -public class TunnelController extends BaseController { - - @Autowired - private TunnelService tunnelService; - - @Autowired - private UserTunnelService userTunnelService; - - @LogAnnotation - @RequireRole - @PostMapping("/create") - public R create(@Validated @RequestBody TunnelDto tunnelDto) { - return tunnelService.createTunnel(tunnelDto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/list") - public R readAll() { - return tunnelService.getAllTunnels(); - } - - @LogAnnotation - @RequireRole - @PostMapping("/update") - public R update(@Validated @RequestBody TunnelUpdateDto tunnelUpdateDto) { - return tunnelService.updateTunnel(tunnelUpdateDto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/delete") - public R delete(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return tunnelService.deleteTunnel(id); - } - - // ============ 用户隧道权限管理相关方法 ============ - - /** - * 分配用户隧道权限 - * @param userTunnelDto 用户隧道权限数据 - * @return 操作结果 - */ - @LogAnnotation - @RequireRole - @PostMapping("/user/assign") - public R assignUserTunnel(@Validated @RequestBody UserTunnelDto userTunnelDto) { - return userTunnelService.assignUserTunnel(userTunnelDto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/user/batch-assign") - public R batchAssignUserTunnel(@Validated @RequestBody UserTunnelBatchAssignDto batchAssignDto) { - return userTunnelService.batchAssignUserTunnel(batchAssignDto); - } - - /** - * 查询用户隧道权限列表 - * @param queryDto 查询条件 - * @return 用户隧道权限列表 - */ - @LogAnnotation - @RequireRole - @PostMapping("/user/list") - public R getUserTunnelList(@RequestBody @Validated UserTunnelQueryDto queryDto) { - return userTunnelService.getUserTunnelList(queryDto); - } - - /** - * 删除用户隧道权限 - * @param params 包含userId和tunnelId的参数 - * @return 操作结果 - */ - @LogAnnotation - @RequireRole - @PostMapping("/user/remove") - public R removeUserTunnel(@RequestBody Map params) { - Integer id = Integer.valueOf(params.get("id").toString()); - return userTunnelService.removeUserTunnel(id); - } - - - /** - * 更新用户隧道权限(包含流量、流量重置时间、到期时间) - * @param updateDto 更新数据 - * @return 操作结果 - */ - @LogAnnotation - @RequireRole - @PostMapping("/user/update") - public R updateUserTunnel(@Validated @RequestBody UserTunnelUpdateDto updateDto) { - return userTunnelService.updateUserTunnel(updateDto); - } - - - @LogAnnotation - @PostMapping("/user/tunnel") - public R userTunnel() { - return tunnelService.userTunnel(); - } - - /** - * 隧道诊断功能 - * @param params 包含tunnelId的参数 - * @return 诊断结果 - */ - @LogAnnotation - @RequireRole - @PostMapping("/diagnose") - public R diagnoseTunnel(@RequestBody Map params) { - Long tunnelId = Long.valueOf(params.get("tunnelId").toString()); - return tunnelService.diagnoseTunnel(tunnelId); - } - - /** - * 更新隧道排序 - * @param params 包含tunnels数组的参数,每个元素包含id和inx - * @return 更新结果 - */ - @LogAnnotation - @RequireRole - @PostMapping("/update-order") - public R updateTunnelOrder(@RequestBody Map params) { - return tunnelService.updateTunnelOrder(params); - } - - @LogAnnotation - @RequireRole - @PostMapping("/batch-delete") - public R batchDelete(@Validated @RequestBody BatchDeleteDto batchDeleteDto) { - return tunnelService.batchDeleteTunnels(batchDeleteDto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/batch-redeploy") - public R batchRedeploy(@Validated @RequestBody BatchRedeployDto batchRedeployDto) { - return tunnelService.batchRedeployTunnels(batchRedeployDto); - } - -} diff --git a/springboot-backend/src/main/java/com/admin/controller/UserController.java b/springboot-backend/src/main/java/com/admin/controller/UserController.java deleted file mode 100644 index 9fcd7b0..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/UserController.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.admin.controller; - - -import com.admin.common.aop.LogAnnotation; -import com.admin.common.annotation.RequireRole; -import com.admin.common.dto.*; -import com.admin.common.lang.R; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; - -/** - *

- * 前端控制器 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@RestController -@CrossOrigin -@RequestMapping("/api/v1/user") -public class UserController extends BaseController { - - @LogAnnotation - @PostMapping("/login") - public R login(@Validated @RequestBody LoginDto loginDto) { - return userService.login(loginDto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/create") - public R create(@Validated @RequestBody UserDto userDto) { - return userService.createUser(userDto); - } - - - @LogAnnotation - @RequireRole - @PostMapping("/list") - public R readAll() { - return userService.getAllUsers(); - } - - @LogAnnotation - @RequireRole - @PostMapping("/update") - public R update(@Validated @RequestBody UserUpdateDto userUpdateDto) { - return userService.updateUser(userUpdateDto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/delete") - public R delete(@RequestBody Map params) { - Long id = Long.valueOf(params.get("id").toString()); - return userService.deleteUser(id); - } - - @LogAnnotation - @PostMapping("/package") - public R getUserPackageInfo() { - return userService.getUserPackageInfo(); - } - - @LogAnnotation - @PostMapping("/updatePassword") - public R updatePassword(@Validated @RequestBody ChangePasswordDto changePasswordDto) { - return userService.updatePassword(changePasswordDto); - } - - @LogAnnotation - @RequireRole - @PostMapping("/reset") - public R reset(@Validated @RequestBody ResetFlowDto resetFlowDto) { - return userService.reset(resetFlowDto); - } - - - -} diff --git a/springboot-backend/src/main/java/com/admin/controller/ViteConfigController.java b/springboot-backend/src/main/java/com/admin/controller/ViteConfigController.java deleted file mode 100644 index 19d1732..0000000 --- a/springboot-backend/src/main/java/com/admin/controller/ViteConfigController.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.admin.controller; - - -import com.admin.common.annotation.RequireRole; -import com.admin.common.aop.LogAnnotation; -import com.admin.common.lang.R; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; - -/** - *

- * 网站配置控制器 - *

- * - * @author QAQ - * @since 2025-07-24 - */ -@RestController -@CrossOrigin -@RequestMapping("/api/v1/config") -public class ViteConfigController extends BaseController { - - /** - * 获取所有网站配置 - * 前端无需权限即可访问,用于获取网站基本信息 - */ - @LogAnnotation - @PostMapping("/list") - public R getConfigs() { - return viteConfigService.getConfigs(); - } - - /** - * 根据配置名获取配置值 - * 前端无需权限即可访问,用于获取特定配置 - */ - @LogAnnotation - @PostMapping("/get") - public R getConfigByName(@RequestBody Map params) { - String name = params.get("name").toString(); - return viteConfigService.getConfigByName(name); - } - - /** - * 批量更新网站配置 - * 需要管理员权限 - */ - @LogAnnotation - @RequireRole - @PostMapping("/update") - public R updateConfigs(@RequestBody Map configMap) { - return viteConfigService.updateConfigs(configMap); - } - - /** - * 更新单个配置项 - * 需要管理员权限 - */ - @LogAnnotation - @RequireRole - @PostMapping("/update-single") - public R updateConfig(@RequestBody Map params) { - String name = params.get("name").toString(); - String value = params.get("value").toString(); - return viteConfigService.updateConfig(name, value); - } - -} diff --git a/springboot-backend/src/main/java/com/admin/entity/BaseEntity.java b/springboot-backend/src/main/java/com/admin/entity/BaseEntity.java deleted file mode 100644 index 1720fe0..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/BaseEntity.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.admin.entity; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import lombok.Data; - -import java.io.Serializable; - -/** - * 基础实体类,包含公共字段 - */ -@Data -public class BaseEntity implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 主键ID - */ - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - /** - * 创建时间(时间戳) - */ - private Long createdTime; - - /** - * 更新时间(时间戳) - */ - private Long updatedTime; - - /** - * 状态(0:正常,1:删除) - */ - private Integer status; -} \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/entity/ChainTunnel.java b/springboot-backend/src/main/java/com/admin/entity/ChainTunnel.java deleted file mode 100644 index 3338ebb..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/ChainTunnel.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.admin.entity; - - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.io.Serializable; - -@Data -@EqualsAndHashCode(callSuper = false) -public class ChainTunnel implements Serializable { - - private static final long serialVersionUID = 1L; - - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - // 关联的隧道ID - private Long tunnelId; - - // 记录所选节点的类型: 1是入口节点 2是转发链 3是出口节点 - private Integer chainType; - - // 记录所选节点的ID - private Long nodeId; - - // 记录节点使用的端口 入口没有次配置为null - private Integer port; - - // 负载模式 入口没有次配置为null - private String strategy; - - // 只有转发链有次参数 表示是第几条 - private Integer inx; - - // 使用的协议 入口没有次配置为null - private String protocol; - - -} diff --git a/springboot-backend/src/main/java/com/admin/entity/Forward.java b/springboot-backend/src/main/java/com/admin/entity/Forward.java deleted file mode 100644 index bf40fc5..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/Forward.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.admin.entity; - -import java.io.Serializable; -import java.util.List; - -import com.admin.common.dto.ForwardPortDto; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - *

- * - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@Data -@EqualsAndHashCode(callSuper = false) -@TableName(autoResultMap = true) -public class Forward extends BaseEntity{ - - private static final long serialVersionUID = 1L; - - private Integer userId; - - private String userName; - - private String name; - - private Integer tunnelId; - - private String remoteAddr; - - private String strategy; - - private Long inFlow; - - private Long outFlow; - - private Integer inx; -} diff --git a/springboot-backend/src/main/java/com/admin/entity/ForwardPort.java b/springboot-backend/src/main/java/com/admin/entity/ForwardPort.java deleted file mode 100644 index 84299ac..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/ForwardPort.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.admin.entity; - - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; - -@Data -@EqualsAndHashCode(callSuper = false) -@TableName(autoResultMap = true) -public class ForwardPort{ - - /** - * 主键ID - */ - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - private Long forwardId; - - private Long nodeId; - - private Integer port; -} diff --git a/springboot-backend/src/main/java/com/admin/entity/GroupPermission.java b/springboot-backend/src/main/java/com/admin/entity/GroupPermission.java deleted file mode 100644 index 542bde4..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/GroupPermission.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.admin.entity; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import lombok.Data; - -import java.io.Serializable; - -@Data -public class GroupPermission implements Serializable { - - private static final long serialVersionUID = 1L; - - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - private Long userGroupId; - - private Long tunnelGroupId; - - private Long createdTime; -} diff --git a/springboot-backend/src/main/java/com/admin/entity/GroupPermissionGrant.java b/springboot-backend/src/main/java/com/admin/entity/GroupPermissionGrant.java deleted file mode 100644 index 123813c..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/GroupPermissionGrant.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.admin.entity; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import lombok.Data; - -import java.io.Serializable; - -@Data -public class GroupPermissionGrant implements Serializable { - - private static final long serialVersionUID = 1L; - - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - private Long userGroupId; - - private Long tunnelGroupId; - - private Long userTunnelId; - - private Integer createdByGroup; - - private Long createdTime; -} diff --git a/springboot-backend/src/main/java/com/admin/entity/Node.java b/springboot-backend/src/main/java/com/admin/entity/Node.java deleted file mode 100644 index 32d21d8..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/Node.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.admin.entity; - -import java.io.Serializable; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - *

- * - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class Node extends BaseEntity { - - private static final long serialVersionUID = 1L; - - private String name; - - private String secret; - - private String serverIp; - - private String serverIpV4; - - private String serverIpV6; - - private String version; - - private String port; - - private Integer http; - - private Integer tls; - - private Integer socks; - - private String interfaceName; - - private String tcpListenAddr; - - private String udpListenAddr; - - /** - * Display/order index for drag-sorting in admin UI. - * Lower value appears first. - */ - private Integer inx; -} diff --git a/springboot-backend/src/main/java/com/admin/entity/SpeedLimit.java b/springboot-backend/src/main/java/com/admin/entity/SpeedLimit.java deleted file mode 100644 index dc7332f..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/SpeedLimit.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.admin.entity; - -import com.admin.entity.BaseEntity; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.io.Serializable; - -/** - *

- * - *

- * - * @author QAQ - * @since 2025-06-04 - */ -@Data -public class SpeedLimit implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 主键ID - */ - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - /** - * 创建时间(时间戳) - */ - private Long createdTime; - - /** - * 更新时间(时间戳) - */ - private Long updatedTime; - - /** - * 状态(0:正常,1:删除) - */ - private Integer status; - - private String name; - - private Integer speed; - - private Long tunnelId; - - private String tunnelName; - -} diff --git a/springboot-backend/src/main/java/com/admin/entity/StatisticsFlow.java b/springboot-backend/src/main/java/com/admin/entity/StatisticsFlow.java deleted file mode 100644 index 89626ee..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/StatisticsFlow.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.admin.entity; - -import com.admin.entity.BaseEntity; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - *

- * - *

- * - * @author QAQ - * @since 2025-08-14 - */ -@Data -public class StatisticsFlow { - - private static final long serialVersionUID = 1L; - @TableId(value = "id", type = IdType.AUTO) - private Long id; - private Long userId; - - private Long flow; - - private Long totalFlow; - - private String time; - - private Long createdTime; - - -} diff --git a/springboot-backend/src/main/java/com/admin/entity/Tunnel.java b/springboot-backend/src/main/java/com/admin/entity/Tunnel.java deleted file mode 100644 index 01ff83a..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/Tunnel.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.admin.entity; - -import java.io.Serializable; -import java.math.BigDecimal; -import java.util.List; - -import com.admin.common.dto.ChainNodesItems; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - *

- * 隧道实体类 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName(autoResultMap = true) -public class Tunnel extends BaseEntity { - - private static final long serialVersionUID = 1L; - - private String name; - - private Integer type; - - private int flow; - - private BigDecimal trafficRatio; - - private String inIp; - - /** - * Display/order index for drag-sorting in admin UI. - * Lower value appears first. - */ - private Integer inx; -} diff --git a/springboot-backend/src/main/java/com/admin/entity/TunnelGroup.java b/springboot-backend/src/main/java/com/admin/entity/TunnelGroup.java deleted file mode 100644 index d280af2..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/TunnelGroup.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.admin.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -@Data -@EqualsAndHashCode(callSuper = true) -public class TunnelGroup extends BaseEntity { - - private static final long serialVersionUID = 1L; - - private String name; -} diff --git a/springboot-backend/src/main/java/com/admin/entity/TunnelGroupTunnel.java b/springboot-backend/src/main/java/com/admin/entity/TunnelGroupTunnel.java deleted file mode 100644 index 530b7ed..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/TunnelGroupTunnel.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.admin.entity; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import lombok.Data; - -import java.io.Serializable; - -@Data -public class TunnelGroupTunnel implements Serializable { - - private static final long serialVersionUID = 1L; - - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - private Long tunnelGroupId; - - private Long tunnelId; - - private Long createdTime; -} diff --git a/springboot-backend/src/main/java/com/admin/entity/User.java b/springboot-backend/src/main/java/com/admin/entity/User.java deleted file mode 100644 index 432566e..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/User.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.admin.entity; - - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - *

- * - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class User extends BaseEntity { - - private static final long serialVersionUID = 1L; - - /** - * 主键ID - */ - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - /** - * 创建时间(时间戳) - */ - private Long createdTime; - - /** - * 更新时间(时间戳) - */ - private Long updatedTime; - - /** - * 状态(0:正常,1:删除) - */ - private Integer status; - - private String user; - - private String pwd; - - private Integer roleId; - - private Long expTime; - - private Long flow; - - private Long inFlow; - - private Long outFlow; - - private Integer num; - - private Long flowResetTime; - - -} diff --git a/springboot-backend/src/main/java/com/admin/entity/UserGroup.java b/springboot-backend/src/main/java/com/admin/entity/UserGroup.java deleted file mode 100644 index 89cf5e6..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/UserGroup.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.admin.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -@Data -@EqualsAndHashCode(callSuper = true) -public class UserGroup extends BaseEntity { - - private static final long serialVersionUID = 1L; - - private String name; -} diff --git a/springboot-backend/src/main/java/com/admin/entity/UserGroupUser.java b/springboot-backend/src/main/java/com/admin/entity/UserGroupUser.java deleted file mode 100644 index 3779fa8..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/UserGroupUser.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.admin.entity; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import lombok.Data; - -import java.io.Serializable; - -@Data -public class UserGroupUser implements Serializable { - - private static final long serialVersionUID = 1L; - - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - private Long userGroupId; - - private Long userId; - - private Long createdTime; -} diff --git a/springboot-backend/src/main/java/com/admin/entity/UserTunnel.java b/springboot-backend/src/main/java/com/admin/entity/UserTunnel.java deleted file mode 100644 index 39347d8..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/UserTunnel.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.admin.entity; - -import java.io.Serializable; - -import com.baomidou.mybatisplus.annotation.FieldStrategy; -import com.baomidou.mybatisplus.annotation.TableField; -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; - -/** - *

- * - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@Data -@EqualsAndHashCode(callSuper = false) -public class UserTunnel implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 主键ID - */ - @TableId(value = "id", type = IdType.AUTO) - private Integer id; - - private Integer userId; - - private Integer tunnelId; - - private Long flow; - - private Long inFlow; - - private Long outFlow; - - private Long flowResetTime; - - private Long expTime; - - @TableField(updateStrategy = FieldStrategy.IGNORED) - private Integer speedId; - - private Integer num; - - private Integer status; - -} diff --git a/springboot-backend/src/main/java/com/admin/entity/ViteConfig.java b/springboot-backend/src/main/java/com/admin/entity/ViteConfig.java deleted file mode 100644 index 69ca6c5..0000000 --- a/springboot-backend/src/main/java/com/admin/entity/ViteConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.admin.entity; - -import com.admin.entity.BaseEntity; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - *

- * - *

- * - * @author QAQ - * @since 2025-07-24 - */ -@Data -public class ViteConfig { - - private static final long serialVersionUID = 1L; - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - private String name; - - private String value; - - private Long time; - - -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/ChainTunnelMapper.java b/springboot-backend/src/main/java/com/admin/mapper/ChainTunnelMapper.java deleted file mode 100644 index e380434..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/ChainTunnelMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.ChainTunnel; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -public interface ChainTunnelMapper extends BaseMapper { -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/ForwardMapper.java b/springboot-backend/src/main/java/com/admin/mapper/ForwardMapper.java deleted file mode 100644 index 6afa058..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/ForwardMapper.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.Forward; -import com.admin.common.dto.ForwardWithTunnelDto; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - *

- * Mapper 接口 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -public interface ForwardMapper extends BaseMapper { - - /** - * 查询所有转发信息(包含隧道信息) - * @return 转发信息列表 - */ - List selectAllForwardsWithTunnel(); - - /** - * 根据用户ID查询转发信息(包含隧道信息) - * @param userId 用户ID - * @return 转发信息列表 - */ - List selectForwardsWithTunnelByUserId(@Param("userId") Integer userId); - - -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/ForwardPortMapper.java b/springboot-backend/src/main/java/com/admin/mapper/ForwardPortMapper.java deleted file mode 100644 index 1233e64..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/ForwardPortMapper.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.Forward; -import com.admin.entity.ForwardPort; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -public interface ForwardPortMapper extends BaseMapper { -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/GroupPermissionGrantMapper.java b/springboot-backend/src/main/java/com/admin/mapper/GroupPermissionGrantMapper.java deleted file mode 100644 index 4bbb846..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/GroupPermissionGrantMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.GroupPermissionGrant; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -public interface GroupPermissionGrantMapper extends BaseMapper { -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/GroupPermissionMapper.java b/springboot-backend/src/main/java/com/admin/mapper/GroupPermissionMapper.java deleted file mode 100644 index a10ac69..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/GroupPermissionMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.GroupPermission; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -public interface GroupPermissionMapper extends BaseMapper { -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/NodeMapper.java b/springboot-backend/src/main/java/com/admin/mapper/NodeMapper.java deleted file mode 100644 index 78385de..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/NodeMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.Node; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -/** - *

- * Mapper 接口 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -public interface NodeMapper extends BaseMapper { - -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/SpeedLimitMapper.java b/springboot-backend/src/main/java/com/admin/mapper/SpeedLimitMapper.java deleted file mode 100644 index 0887ac6..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/SpeedLimitMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.SpeedLimit; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -/** - *

- * Mapper 接口 - *

- * - * @author QAQ - * @since 2025-06-04 - */ -public interface SpeedLimitMapper extends BaseMapper { - -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/StatisticsFlowMapper.java b/springboot-backend/src/main/java/com/admin/mapper/StatisticsFlowMapper.java deleted file mode 100644 index f63d7a3..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/StatisticsFlowMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.StatisticsFlow; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -/** - *

- * Mapper 接口 - *

- * - * @author QAQ - * @since 2025-08-14 - */ -public interface StatisticsFlowMapper extends BaseMapper { - -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/TunnelGroupMapper.java b/springboot-backend/src/main/java/com/admin/mapper/TunnelGroupMapper.java deleted file mode 100644 index fd8852e..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/TunnelGroupMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.TunnelGroup; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -public interface TunnelGroupMapper extends BaseMapper { -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/TunnelGroupTunnelMapper.java b/springboot-backend/src/main/java/com/admin/mapper/TunnelGroupTunnelMapper.java deleted file mode 100644 index c937ea0..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/TunnelGroupTunnelMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.TunnelGroupTunnel; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -public interface TunnelGroupTunnelMapper extends BaseMapper { -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/TunnelMapper.java b/springboot-backend/src/main/java/com/admin/mapper/TunnelMapper.java deleted file mode 100644 index 333a79b..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/TunnelMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.Tunnel; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -/** - *

- * Mapper 接口 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -public interface TunnelMapper extends BaseMapper { - -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/UserGroupMapper.java b/springboot-backend/src/main/java/com/admin/mapper/UserGroupMapper.java deleted file mode 100644 index 2b3b1df..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/UserGroupMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.UserGroup; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -public interface UserGroupMapper extends BaseMapper { -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/UserGroupUserMapper.java b/springboot-backend/src/main/java/com/admin/mapper/UserGroupUserMapper.java deleted file mode 100644 index dfcf62f..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/UserGroupUserMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.UserGroupUser; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -public interface UserGroupUserMapper extends BaseMapper { -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/UserMapper.java b/springboot-backend/src/main/java/com/admin/mapper/UserMapper.java deleted file mode 100644 index 3ac3e9a..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/UserMapper.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.User; -import com.admin.common.dto.UserPackageDto; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/** - *

- * Mapper 接口 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -public interface UserMapper extends BaseMapper { - - /** - * 查询用户隧道权限详情 - * @param userId 用户ID - * @return 隧道权限列表 - */ - List getUserTunnelDetails(@Param("userId") Integer userId); - - /** - * 查询用户转发详情 - * @param userId 用户ID - * @return 转发列表 - */ - List getUserForwardDetails(@Param("userId") Integer userId); - -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/UserTunnelMapper.java b/springboot-backend/src/main/java/com/admin/mapper/UserTunnelMapper.java deleted file mode 100644 index 401edc2..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/UserTunnelMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.UserTunnel; -import com.admin.common.dto.UserTunnelWithDetailDto; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - *

- * Mapper 接口 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -public interface UserTunnelMapper extends BaseMapper { - - - List getUserTunnelWithDetails(@Param("userId") Integer userId); - -} diff --git a/springboot-backend/src/main/java/com/admin/mapper/ViteConfigMapper.java b/springboot-backend/src/main/java/com/admin/mapper/ViteConfigMapper.java deleted file mode 100644 index cf807cb..0000000 --- a/springboot-backend/src/main/java/com/admin/mapper/ViteConfigMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.mapper; - -import com.admin.entity.ViteConfig; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -/** - *

- * Mapper 接口 - *

- * - * @author QAQ - * @since 2025-07-24 - */ -public interface ViteConfigMapper extends BaseMapper { - -} diff --git a/springboot-backend/src/main/java/com/admin/service/ChainTunnelService.java b/springboot-backend/src/main/java/com/admin/service/ChainTunnelService.java deleted file mode 100644 index a6cbf97..0000000 --- a/springboot-backend/src/main/java/com/admin/service/ChainTunnelService.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.admin.service; - -import com.admin.entity.ChainTunnel; -import com.admin.entity.Forward; -import com.baomidou.mybatisplus.extension.service.IService; - -public interface ChainTunnelService extends IService { -} diff --git a/springboot-backend/src/main/java/com/admin/service/ForwardPortService.java b/springboot-backend/src/main/java/com/admin/service/ForwardPortService.java deleted file mode 100644 index d1b6e51..0000000 --- a/springboot-backend/src/main/java/com/admin/service/ForwardPortService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.admin.service; - -import com.admin.entity.ForwardPort; -import com.baomidou.mybatisplus.extension.service.IService; - -public interface ForwardPortService extends IService { -} diff --git a/springboot-backend/src/main/java/com/admin/service/ForwardService.java b/springboot-backend/src/main/java/com/admin/service/ForwardService.java deleted file mode 100644 index be53884..0000000 --- a/springboot-backend/src/main/java/com/admin/service/ForwardService.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.admin.service; - -import com.admin.common.dto.BatchDeleteDto; -import com.admin.common.dto.BatchRedeployDto; -import com.admin.common.dto.BatchChangeTunnelDto; -import com.admin.common.dto.ForwardDto; -import com.admin.common.dto.ForwardUpdateDto; -import com.admin.common.lang.R; -import com.admin.entity.Forward; -import com.baomidou.mybatisplus.extension.service.IService; -import java.util.Map; -import java.util.Set; - -/** - *

- * 服务类 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -public interface ForwardService extends IService { - - /** - * 创建端口转发 - * @param forwardDto 转发数据 - * @return 结果 - */ - R createForward(ForwardDto forwardDto); - - /** - * 获取端口转发列表 - * @return 结果 - */ - R getAllForwards(); - - /** - * 更新端口转发 - * @param forwardUpdateDto 更新数据 - * @return 结果 - */ - R updateForward(ForwardUpdateDto forwardUpdateDto); - - /** - * 删除端口转发 - * @param id 转发ID - * @return 结果 - */ - R deleteForward(Long id); - - /** - * 强制删除端口转发 - * 跳过GOST节点验证,直接删除数据库记录 - * @param id 转发ID - * @return 结果 - */ - R forceDeleteForward(Long id); - - /** - * 暂停转发服务 - * @param id 转发ID - * @return 结果 - */ - R pauseForward(Long id); - - /** - * 恢复转发服务 - * @param id 转发ID - * @return 结果 - */ - R resumeForward(Long id); - - /** - * 转发诊断功能 - * @param id 转发ID - * @return 诊断结果 - */ - R diagnoseForward(Long id); - - /** - * 更新转发排序 - * @param params 包含forwards数组的参数 - * @return 更新结果 - */ - R updateForwardOrder(Map params); - - R batchDeleteForwards(BatchDeleteDto batchDeleteDto); - - R batchPauseForwards(BatchDeleteDto batchDeleteDto); - - R batchResumeForwards(BatchDeleteDto batchDeleteDto); - - R batchRedeployForwards(BatchRedeployDto batchRedeployDto); - - R batchChangeTunnel(BatchChangeTunnelDto batchChangeTunnelDto); -} diff --git a/springboot-backend/src/main/java/com/admin/service/GroupService.java b/springboot-backend/src/main/java/com/admin/service/GroupService.java deleted file mode 100644 index d6f7918..0000000 --- a/springboot-backend/src/main/java/com/admin/service/GroupService.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.admin.service; - -import com.admin.common.dto.GroupCreateDto; -import com.admin.common.dto.GroupPermissionAssignDto; -import com.admin.common.dto.GroupUpdateDto; -import com.admin.common.dto.TunnelGroupAssignTunnelsDto; -import com.admin.common.dto.UserGroupAssignUsersDto; -import com.admin.common.lang.R; - -public interface GroupService { - - R getTunnelGroups(); - - R createTunnelGroup(GroupCreateDto dto); - - R updateTunnelGroup(GroupUpdateDto dto); - - R deleteTunnelGroup(Long id); - - R assignTunnelsToGroup(TunnelGroupAssignTunnelsDto dto); - - R getUserGroups(); - - R createUserGroup(GroupCreateDto dto); - - R updateUserGroup(GroupUpdateDto dto); - - R deleteUserGroup(Long id); - - R assignUsersToGroup(UserGroupAssignUsersDto dto); - - R getGroupPermissions(); - - R assignGroupPermission(GroupPermissionAssignDto dto); - - R removeGroupPermission(Long id); -} diff --git a/springboot-backend/src/main/java/com/admin/service/NodeService.java b/springboot-backend/src/main/java/com/admin/service/NodeService.java deleted file mode 100644 index 1299315..0000000 --- a/springboot-backend/src/main/java/com/admin/service/NodeService.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.admin.service; - -import com.admin.common.dto.BatchDeleteDto; -import com.admin.common.dto.NodeDto; -import com.admin.common.dto.NodeUpdateDto; -import com.admin.common.lang.R; -import com.admin.entity.Node; -import com.baomidou.mybatisplus.extension.service.IService; - -import java.util.Map; - -/** - *

- * 服务类 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -public interface NodeService extends IService { - - R createNode(NodeDto nodeDto); - - R getAllNodes(); - - R updateNode(NodeUpdateDto nodeUpdateDto); - - R deleteNode(Long id); - - R getInstallCommand(Long id); - - /** - * 更新节点排序(管理员) - * @param params 包含nodes数组的参数,每个元素包含id和inx - */ - R updateNodeOrder(Map params); - - R batchDeleteNodes(BatchDeleteDto batchDeleteDto); - -} diff --git a/springboot-backend/src/main/java/com/admin/service/SpeedLimitService.java b/springboot-backend/src/main/java/com/admin/service/SpeedLimitService.java deleted file mode 100644 index cac4848..0000000 --- a/springboot-backend/src/main/java/com/admin/service/SpeedLimitService.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.admin.service; - -import com.admin.common.dto.SpeedLimitDto; -import com.admin.common.dto.SpeedLimitUpdateDto; -import com.admin.common.lang.R; -import com.admin.entity.SpeedLimit; -import com.baomidou.mybatisplus.extension.service.IService; - -/** - *

- * 限速规则服务类 - *

- * - * @author QAQ - * @since 2025-06-04 - */ -public interface SpeedLimitService extends IService { - - /** - * 创建限速规则 - * @param speedLimitDto 限速规则数据 - * @return 结果 - */ - R createSpeedLimit(SpeedLimitDto speedLimitDto); - - /** - * 获取所有限速规则 - * @return 结果 - */ - R getAllSpeedLimits(); - - /** - * 更新限速规则 - * @param speedLimitUpdateDto 更新数据 - * @return 结果 - */ - R updateSpeedLimit(SpeedLimitUpdateDto speedLimitUpdateDto); - - /** - * 删除限速规则 - * @param id 限速规则ID - * @return 结果 - */ - R deleteSpeedLimit(Long id); -} diff --git a/springboot-backend/src/main/java/com/admin/service/StatisticsFlowService.java b/springboot-backend/src/main/java/com/admin/service/StatisticsFlowService.java deleted file mode 100644 index cf3a5b6..0000000 --- a/springboot-backend/src/main/java/com/admin/service/StatisticsFlowService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.admin.service; - -import com.admin.entity.StatisticsFlow; -import com.baomidou.mybatisplus.extension.service.IService; - -/** - *

- * 服务类 - *

- * - * @author QAQ - * @since 2025-08-14 - */ -public interface StatisticsFlowService extends 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 deleted file mode 100644 index ca83377..0000000 --- a/springboot-backend/src/main/java/com/admin/service/TunnelService.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.admin.service; - -import com.admin.common.dto.BatchDeleteDto; -import com.admin.common.dto.BatchRedeployDto; -import com.admin.common.dto.TunnelDto; -import com.admin.common.dto.TunnelUpdateDto; - -import com.admin.common.lang.R; -import com.admin.entity.Tunnel; -import com.baomidou.mybatisplus.extension.service.IService; - -import java.util.Map; - -/** - *

- * 隧道服务类 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -public interface TunnelService extends IService { - - /** - * 创建隧道 - * @param tunnelDto 隧道数据 - * @return 结果 - */ - R createTunnel(TunnelDto tunnelDto); - - /** - * 获取隧道列表 - * @return 结果 - */ - R getAllTunnels(); - - /** - * 更新隧道(只允许修改名称、流量计费、端口范围) - * @param tunnelUpdateDto 更新数据 - * @return 结果 - */ - R updateTunnel(TunnelUpdateDto tunnelUpdateDto); - - /** - * 删除隧道 - * @param id 隧道ID - * @return 结果 - */ - R deleteTunnel(Long id); - - /** - * 获取用户可用的隧道列表 - * @return 结果 - */ - R userTunnel(); - - /** - * 隧道诊断功能 - * @param tunnelId 隧道ID - * @return 诊断结果 - */ - R diagnoseTunnel(Long tunnelId); - - /** - * 更新隧道排序(管理员) - * @param params 包含tunnels数组的参数,每个元素包含id和inx - */ - R updateTunnelOrder(Map params); - - /** - * 批量删除隧道 - * @param batchDeleteDto 批量删除数据 - * @return 操作结果 - */ - R batchDeleteTunnels(BatchDeleteDto batchDeleteDto); - - /** - * 批量重新下发隧道配置 - * @param batchRedeployDto 批量重新下发数据 - * @return 操作结果 - */ - R batchRedeployTunnels(BatchRedeployDto batchRedeployDto); -} diff --git a/springboot-backend/src/main/java/com/admin/service/UserService.java b/springboot-backend/src/main/java/com/admin/service/UserService.java deleted file mode 100644 index 0738afa..0000000 --- a/springboot-backend/src/main/java/com/admin/service/UserService.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.admin.service; - -import com.admin.common.dto.*; -import com.admin.common.lang.R; -import com.admin.entity.User; -import com.baomidou.mybatisplus.extension.service.IService; - -/** - *

- * 服务类 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -public interface UserService extends IService { - - R login(LoginDto loginDto); - - R createUser(UserDto userDto); - - R getAllUsers(); - - R updateUser(UserUpdateDto userUpdateDto); - - R deleteUser(Long id); - - R getUserPackageInfo(); - - R updatePassword(ChangePasswordDto changePasswordDto); - - R reset(ResetFlowDto resetFlowDto); -} diff --git a/springboot-backend/src/main/java/com/admin/service/UserTunnelService.java b/springboot-backend/src/main/java/com/admin/service/UserTunnelService.java deleted file mode 100644 index 737b4bd..0000000 --- a/springboot-backend/src/main/java/com/admin/service/UserTunnelService.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.admin.service; - -import com.admin.common.dto.UserTunnelBatchAssignDto; -import com.admin.common.dto.UserTunnelDto; -import com.admin.common.dto.UserTunnelQueryDto; -import com.admin.common.dto.UserTunnelUpdateDto; -import com.admin.common.lang.R; -import com.admin.entity.UserTunnel; -import com.baomidou.mybatisplus.extension.service.IService; - -public interface UserTunnelService extends IService { - - R assignUserTunnel(UserTunnelDto userTunnelDto); - - R batchAssignUserTunnel(UserTunnelBatchAssignDto batchAssignDto); - - R getUserTunnelList(UserTunnelQueryDto queryDto); - - R removeUserTunnel(Integer id); - - R updateUserTunnel(UserTunnelUpdateDto updateDto); - -} diff --git a/springboot-backend/src/main/java/com/admin/service/ViteConfigService.java b/springboot-backend/src/main/java/com/admin/service/ViteConfigService.java deleted file mode 100644 index fbde030..0000000 --- a/springboot-backend/src/main/java/com/admin/service/ViteConfigService.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.admin.service; - -import com.admin.entity.ViteConfig; -import com.admin.common.lang.R; -import com.baomidou.mybatisplus.extension.service.IService; - -import java.util.Map; - -/** - *

- * 网站配置服务类 - *

- * - * @author QAQ - * @since 2025-07-24 - */ -public interface ViteConfigService extends IService { - - /** - * 获取所有网站配置 - * @return 配置Map,key为配置名,value为配置值 - */ - R getConfigs(); - - /** - * 根据配置名获取配置值 - * @param name 配置名 - * @return 配置值 - */ - R getConfigByName(String name); - - /** - * 批量更新网站配置 - * @param configMap 配置Map,key为配置名,value为新的配置值 - * @return 更新结果 - */ - R updateConfigs(Map configMap); - - /** - * 更新单个配置项 - * @param name 配置名 - * @param value 配置值 - * @return 更新结果 - */ - R updateConfig(String name, String value); - -} diff --git a/springboot-backend/src/main/java/com/admin/service/impl/ChainTunnelServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/ChainTunnelServiceImpl.java deleted file mode 100644 index 97929e6..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/ChainTunnelServiceImpl.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.admin.service.impl; - -import com.admin.common.dto.ForwardDto; -import com.admin.common.dto.ForwardUpdateDto; -import com.admin.common.dto.ForwardWithTunnelDto; -import com.admin.common.dto.GostDto; -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.ChainTunnelMapper; -import com.admin.mapper.ForwardMapper; -import com.admin.service.*; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.alibaba.fastjson.JSONObject; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.*; -import java.util.stream.Collectors; - -/** - *

- * 端口转发服务实现类 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@Slf4j -@Service -public class ChainTunnelServiceImpl extends ServiceImpl implements ChainTunnelService { - -} diff --git a/springboot-backend/src/main/java/com/admin/service/impl/ForwardPortServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/ForwardPortServiceImpl.java deleted file mode 100644 index beb9398..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/ForwardPortServiceImpl.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.admin.service.impl; - -import com.admin.entity.ChainTunnel; -import com.admin.entity.ForwardPort; -import com.admin.mapper.ChainTunnelMapper; -import com.admin.mapper.ForwardPortMapper; -import com.admin.service.ChainTunnelService; -import com.admin.service.ForwardPortService; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import org.springframework.stereotype.Service; - -@Service -public class ForwardPortServiceImpl extends ServiceImpl implements ForwardPortService { - -} 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 deleted file mode 100644 index 21d8c14..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/ForwardServiceImpl.java +++ /dev/null @@ -1,1512 +0,0 @@ -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.ForwardMapper; -import com.admin.service.*; -import com.alibaba.fastjson.JSONArray; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.alibaba.fastjson.JSONObject; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.annotation.Resource; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -/** - *

- * 端口转发服务实现类 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@Slf4j -@Service -public class ForwardServiceImpl extends ServiceImpl implements ForwardService { - - private static final long BYTES_TO_GB = 1024L * 1024L * 1024L; - - @Resource - @Lazy - private TunnelService tunnelService; - - @Resource - UserTunnelService userTunnelService; - - @Resource - UserService userService; - - @Resource - NodeService nodeService; - - @Resource - ChainTunnelService chainTunnelService; - - @Resource - ForwardPortService forwardPortService; - - @Override - public R getAllForwards() { - UserInfo currentUser = getCurrentUserInfo(); - List forwardList; - if (currentUser.getRoleId() != 0) { - forwardList = baseMapper.selectForwardsWithTunnelByUserId(currentUser.getUserId()); - } else { - forwardList = baseMapper.selectAllForwardsWithTunnel(); - } - - // 填充入口IP和端口信息 - for (ForwardWithTunnelDto forward : forwardList) { - // 获取隧道信息 - Tunnel tunnel = tunnelService.getById(forward.getTunnelId()); - if (tunnel == null) continue; - - // 获取该转发的所有ForwardPort记录 - List forwardPorts = forwardPortService.list( - new QueryWrapper().eq("forward_id", forward.getId()) - ); - - if (forwardPorts.isEmpty()) continue; - - // 判断是否使用隧道的inIp - boolean useTunnelInIp = tunnel.getInIp() != null && !tunnel.getInIp().trim().isEmpty(); - - Set ipPortSet = new LinkedHashSet<>(); - - if (useTunnelInIp) { - // 使用隧道的inIp(求笛卡尔积) - List ipList = new ArrayList<>(); - List portList = new ArrayList<>(); - - String[] tunnelInIps = tunnel.getInIp().split(","); - for (String ip : tunnelInIps) { - if (ip != null && !ip.trim().isEmpty()) { - ipList.add(ip.trim()); - } - } - - // 收集所有端口 - for (ForwardPort forwardPort : forwardPorts) { - if (forwardPort.getPort() != null) { - portList.add(forwardPort.getPort()); - } - } - - // 去重 - List uniqueIps = ipList.stream().distinct().toList(); - List uniquePorts = portList.stream().distinct().toList(); - - // 组合 IP:Port(笛卡尔积) - for (String ip : uniqueIps) { - for (Integer port : uniquePorts) { - ipPortSet.add(ip + ":" + port); - } - } - - // inPort设置为第一个端口(用于向后兼容) - if (!uniquePorts.isEmpty()) { - forward.setInPort(uniquePorts.getFirst()); - } - } else { - // 使用节点的serverIp(一对一,不求笛卡尔积) - for (ForwardPort forwardPort : forwardPorts) { - Node node = nodeService.getById(forwardPort.getNodeId()); - if (node != null && node.getServerIp() != null && forwardPort.getPort() != null) { - ipPortSet.add(node.getServerIp() + ":" + forwardPort.getPort()); - } - } - - // inPort设置为第一个端口(用于向后兼容) - if (!forwardPorts.isEmpty() && forwardPorts.getFirst().getPort() != null) { - forward.setInPort(forwardPorts.getFirst().getPort()); - } - } - - // 设置入口IP - if (!ipPortSet.isEmpty()) { - forward.setInIp(String.join(",", ipPortSet)); - } - } - - return R.ok(forwardList); - } - - @Override - public R createForward(ForwardDto forwardDto) { - UserInfo currentUser = getCurrentUserInfo(); - - Tunnel tunnel = validateTunnel(forwardDto.getTunnelId()); - if (tunnel == null) { - return R.err("隧道不存在"); - } - - if (tunnel.getStatus() != 1) { - return R.err("隧道已禁用,无法创建转发"); - } - - UserPermissionResult permissionResult = checkUserPermissions(currentUser, tunnel, null); - if (permissionResult.isHasError()) { - return R.err(permissionResult.getErrorMessage()); - } - Forward forward = new Forward(); - BeanUtils.copyProperties(forwardDto, forward); - forward.setStatus(1); - forward.setUserId(currentUser.getUserId()); - forward.setUserName(currentUser.getUserName()); - forward.setCreatedTime(System.currentTimeMillis()); - forward.setUpdatedTime(System.currentTimeMillis()); - List success = new ArrayList<>(); - List chainTunnels = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", tunnel.getId()).eq("chain_type", 1)); - chainTunnels = get_port(chainTunnels, forwardDto.getInPort(), 0L); - this.save(forward); - - for (ChainTunnel chainTunnel : chainTunnels) { - - ForwardPort forwardPort = new ForwardPort(); - forwardPort.setForwardId(forward.getId()); - forwardPort.setNodeId(chainTunnel.getNodeId()); - forwardPort.setPort(chainTunnel.getPort()); - forwardPortService.save(forwardPort); - String serviceName = buildServiceName(forward.getId(), forward.getUserId(), permissionResult.getUserTunnel()); - Integer limiter = permissionResult.getLimiter(); - - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node == null) { - return R.err("部分节点不存在"); - } - GostDto gostDto = GostUtil.AddAndUpdateService(serviceName, limiter, node, forward, forwardPort, tunnel, "AddService"); - if (Objects.equals(gostDto.getMsg(), "OK")) { - JSONObject data = new JSONObject(); - data.put("node_id", node.getId()); - data.put("name", serviceName); - success.add(data); - } else { - this.removeById(forward.getId()); - forwardPortService.remove(new QueryWrapper().eq("forward_id", forward.getId())); - for (JSONObject jsonObject : success) { - JSONArray se = new JSONArray(); - se.add(jsonObject.getString("name") + "_tcp"); - se.add(jsonObject.getString("name") + "_udp"); - GostUtil.DeleteService(jsonObject.getLong("node_id"), se); - return R.err(gostDto.getMsg()); - } - } - - } - return R.ok(); - } - - @Override - @Transactional - public R updateForward(ForwardUpdateDto forwardUpdateDto) { - UserInfo currentUser = getCurrentUserInfo(); - - Forward existForward = validateForwardExists(forwardUpdateDto.getId(), currentUser); - if (existForward == null) { - return R.err("转发不存在"); - } - - Integer oldTunnelId = existForward.getTunnelId(); - Integer newTunnelId = forwardUpdateDto.getTunnelId() != null ? forwardUpdateDto.getTunnelId() : oldTunnelId; - boolean tunnelChanged = !Objects.equals(oldTunnelId, newTunnelId); - - Tunnel oldTunnel = validateTunnel(oldTunnelId); - if (oldTunnel == null) { - return R.err("原隧道不存在"); - } - - UserTunnel oldUserTunnel; - if (currentUser.getRoleId() != 0) { - oldUserTunnel = getUserTunnel(currentUser.getUserId(), oldTunnelId); - if (oldUserTunnel == null) { - return R.err("你没有原隧道权限"); - } - } else { - oldUserTunnel = getUserTunnel(existForward.getUserId(), oldTunnelId); - } - - if (tunnelChanged) { - Tunnel newTunnel = validateTunnel(newTunnelId); - if (newTunnel == null) { - return R.err("新隧道不存在"); - } - if (newTunnel.getStatus() != 1) { - return R.err("新隧道已禁用,无法切换"); - } - - UserPermissionResult newPermResult = checkUserPermissions(currentUser, newTunnel, null); - if (newPermResult.isHasError()) { - return R.err(newPermResult.getErrorMessage()); - } - - UserTunnel newUserTunnel; - if (currentUser.getRoleId() != 0) { - newUserTunnel = getUserTunnel(currentUser.getUserId(), newTunnelId); - if (newUserTunnel == null) { - return R.err("你没有新隧道权限"); - } - } else { - newUserTunnel = getUserTunnel(existForward.getUserId(), newTunnelId); - } - - releaseOldTunnelResources(existForward, oldTunnel, oldUserTunnel); - - existForward.setTunnelId(newTunnelId); - existForward.setRemoteAddr(forwardUpdateDto.getRemoteAddr()); - existForward.setName(forwardUpdateDto.getName()); - existForward.setStrategy(forwardUpdateDto.getStrategy()); - existForward.setStatus(1); - existForward.setUpdatedTime(System.currentTimeMillis()); - this.updateById(existForward); - - R allocResult = allocateNewTunnelResources(existForward, newTunnel, forwardUpdateDto.getInPort(), newPermResult, newUserTunnel); - if (allocResult.getCode() != 0) { - throw new RuntimeException(allocResult.getMsg()); - } - - return R.ok(); - } - - UserPermissionResult permissionResult = checkUserPermissions(currentUser, oldTunnel, null); - if (permissionResult.isHasError()) { - return R.err(permissionResult.getErrorMessage()); - } - - existForward.setRemoteAddr(forwardUpdateDto.getRemoteAddr()); - existForward.setName(forwardUpdateDto.getName()); - existForward.setStrategy(forwardUpdateDto.getStrategy()); - existForward.setStatus(1); - existForward.setUpdatedTime(System.currentTimeMillis()); - this.updateById(existForward); - - List chainTunnels = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", oldTunnel.getId()).eq("chain_type", 1)); - chainTunnels = get_port(chainTunnels, forwardUpdateDto.getInPort(), existForward.getId()); - - for (ChainTunnel chainTunnel : chainTunnels) { - String serviceName = buildServiceName(existForward.getId(), existForward.getUserId(), oldUserTunnel); - Integer limiter = permissionResult.getLimiter(); - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node == null) { - return R.err("部分节点不存在"); - } - ForwardPort forwardPort = forwardPortService.getOne(new QueryWrapper().eq("forward_id", existForward.getId()).eq("node_id", node.getId())); - if (forwardPort == null) { - return R.err("部分节点不存在1"); - } - forwardPort.setPort(chainTunnel.getPort()); - forwardPortService.updateById(forwardPort); - GostDto gostDto = GostUtil.AddAndUpdateService(serviceName, limiter, node, existForward, forwardPort, oldTunnel, "UpdateService"); - if (!Objects.equals(gostDto.getMsg(), "OK")) return R.err(gostDto.getMsg()); - } - - return R.ok(); - } - - private void releaseOldTunnelResources(Forward forward, Tunnel oldTunnel, UserTunnel oldUserTunnel) { - List oldInNodes = chainTunnelService.list( - new QueryWrapper() - .eq("tunnel_id", oldTunnel.getId()) - .eq("chain_type", 1) - ); - - for (ChainTunnel chainTunnel : oldInNodes) { - String serviceName = buildServiceName(forward.getId(), forward.getUserId(), oldUserTunnel); - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node != null) { - JSONArray services = new JSONArray(); - services.add(serviceName + "_tcp"); - services.add(serviceName + "_udp"); - GostUtil.DeleteService(node.getId(), services); - } - } - - forwardPortService.remove(new QueryWrapper().eq("forward_id", forward.getId())); - } - - private R allocateNewTunnelResources(Forward forward, Tunnel newTunnel, Integer requestedPort, - UserPermissionResult permResult, UserTunnel newUserTunnel) { - List newInNodes = chainTunnelService.list( - new QueryWrapper() - .eq("tunnel_id", newTunnel.getId()) - .eq("chain_type", 1) - ); - - newInNodes = get_port(newInNodes, requestedPort, forward.getId()); - - List successServices = new ArrayList<>(); - - for (ChainTunnel chainTunnel : newInNodes) { - ForwardPort forwardPort = new ForwardPort(); - forwardPort.setForwardId(forward.getId()); - forwardPort.setNodeId(chainTunnel.getNodeId()); - forwardPort.setPort(chainTunnel.getPort()); - forwardPortService.save(forwardPort); - - String serviceName = buildServiceName(forward.getId(), forward.getUserId(), newUserTunnel); - Integer limiter = permResult.getLimiter(); - Node node = nodeService.getById(chainTunnel.getNodeId()); - - if (node == null) { - rollbackCreatedServices(successServices); - forwardPortService.remove(new QueryWrapper().eq("forward_id", forward.getId())); - return R.err("新隧道部分节点不存在"); - } - - GostDto gostDto = GostUtil.AddAndUpdateService(serviceName, limiter, node, forward, forwardPort, newTunnel, "AddService"); - - if (!Objects.equals(gostDto.getMsg(), "OK")) { - rollbackCreatedServices(successServices); - forwardPortService.remove(new QueryWrapper().eq("forward_id", forward.getId())); - return R.err("在新隧道创建服务失败: " + gostDto.getMsg()); - } - - JSONObject data = new JSONObject(); - data.put("node_id", node.getId()); - data.put("name", serviceName); - successServices.add(data); - } - - return R.ok(); - } - - private void rollbackCreatedServices(List created) { - for (JSONObject jsonObject : created) { - JSONArray se = new JSONArray(); - se.add(jsonObject.getString("name") + "_tcp"); - se.add(jsonObject.getString("name") + "_udp"); - GostUtil.DeleteService(jsonObject.getLong("node_id"), se); - } - } - - @Override - public R deleteForward(Long id) { - - // 1. 获取当前用户信息 - UserInfo currentUser = getCurrentUserInfo(); - - - // 2. 检查转发是否存在 - Forward forward = validateForwardExists(id, currentUser); - if (forward == null) { - return R.err("转发不存在"); - } - - - Tunnel tunnel = validateTunnel(forward.getTunnelId()); - if (tunnel == null) { - return R.err("隧道不存在"); - } - - UserPermissionResult permissionResult = checkUserPermissions(currentUser, tunnel, null); - if (permissionResult.isHasError()) { - return R.err(permissionResult.getErrorMessage()); - } - UserTunnel userTunnel = null; - if (currentUser.getRoleId() != 0) { - userTunnel = getUserTunnel(currentUser.getUserId(), tunnel.getId().intValue()); - if (userTunnel == null) { - return R.err("你没有该隧道权限"); - } - } else { - // 管理员用户也需要获取UserTunnel(如果存在的话),用于构建正确的服务名称 - // 通过forward记录获取原始的用户ID - userTunnel = getUserTunnel(forward.getUserId(), tunnel.getId().intValue()); - } - - List chainTunnels = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", tunnel.getId()).eq("chain_type", 1)); - for (ChainTunnel chainTunnel : chainTunnels) { - - String serviceName = buildServiceName(forward.getId(), forward.getUserId(), userTunnel); - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node == null) { - return R.err("部分节点不存在"); - } - - JSONArray services = new JSONArray(); - services.add(serviceName + "_tcp"); - services.add(serviceName + "_udp"); - GostUtil.DeleteService(node.getId(), services); - } - forwardPortService.remove(new QueryWrapper().eq("forward_id", id)); - this.removeById(id); - return R.ok(); - } - - @Override - public R pauseForward(Long id) { - return changeForwardStatus(id, 0, "PauseService"); - } - - @Override - public R resumeForward(Long id) { - return changeForwardStatus(id, 1, "ResumeService"); - } - - @Override - public R forceDeleteForward(Long id) { - UserInfo currentUser = getCurrentUserInfo(); - Forward forward = validateForwardExists(id, currentUser); - if (forward == null) { - return R.err("端口转发不存在"); - } - this.removeById(id); - forwardPortService.remove(new QueryWrapper().eq("forward_id", id)); - return R.ok(); - } - - @Override - public R diagnoseForward(Long id) { - // 1. 获取当前用户信息 - UserInfo currentUser = getCurrentUserInfo(); - - // 2. 检查转发是否存在且用户有权限访问 - Forward forward = validateForwardExists(id, currentUser); - if (forward == null) { - return R.err("转发不存在"); - } - - // 3. 获取隧道信息 - Tunnel tunnel = validateTunnel(forward.getTunnelId()); - if (tunnel == null) { - return R.err("隧道不存在"); - } - - // 4. 获取隧道的ChainTunnel信息 - List chainTunnels = chainTunnelService.list( - new QueryWrapper().eq("tunnel_id", tunnel.getId()) - ); - - if (chainTunnels.isEmpty()) { - return R.err("隧道配置不完整"); - } - - // 分类节点 - List inNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() == 1) - .toList(); - - Map> chainNodesMap = chainTunnels.stream() - .filter(ct -> ct.getChainType() == 2) - .collect(Collectors.groupingBy( - ct -> ct.getInx() != null ? ct.getInx() : 0, - Collectors.toList() - )); - - List> chainNodesList = chainNodesMap.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(Map.Entry::getValue) - .toList(); - - List outNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() == 3) - .toList(); - - List> futures = new ArrayList<>(); - String[] remoteAddresses = forward.getRemoteAddr().split(","); - - // 根据隧道类型执行不同的诊断策略(并行执行所有诊断任务) - if (tunnel.getType() == 1) { - // 端口转发:入口节点直接TCP ping目标地址 - for (ChainTunnel inNode : inNodes) { - Node node = nodeService.getById(inNode.getNodeId()); - if (node != null) { - for (String remoteAddress : remoteAddresses) { - String targetIp = extractIpFromAddress(remoteAddress); - int targetPort = extractPortFromAddress(remoteAddress); - if (targetIp != null && targetPort != -1) { - final Node finalNode = node; - final String finalTargetIp = targetIp; - final int finalTargetPort = targetPort; - final String finalRemoteAddress = remoteAddress; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalNode, finalTargetIp, finalTargetPort, - "入口(" + finalNode.getName() + ")->目标(" + finalRemoteAddress + ")" - ); - result.setFromChainType(1); - return result; - })); - } - } - } - } - } else if (tunnel.getType() == 2) { - // 隧道转发:测试完整链路 - // 1. 入口->第一跳(或出口) - 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) { - final Node finalFromNode = fromNode; - final Node finalToNode = toNode; - final ChainTunnel finalFirstChainNode = firstChainNode; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalFirstChainNode.getPort(), - "入口(" + finalFromNode.getName() + ")->第1跳(" + finalToNode.getName() + ")" - ); - result.setFromChainType(1); - result.setToChainType(2); - result.setToInx(finalFirstChainNode.getInx()); - return result; - })); - } - } - } else if (!outNodes.isEmpty()) { - for (ChainTunnel outNode : outNodes) { - Node toNode = nodeService.getById(outNode.getNodeId()); - if (toNode != null) { - final Node finalFromNode = fromNode; - final Node finalToNode = toNode; - final ChainTunnel finalOutNode = outNode; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalOutNode.getPort(), - "入口(" + finalFromNode.getName() + ")->出口(" + finalToNode.getName() + ")" - ); - result.setFromChainType(1); - result.setToChainType(3); - return result; - })); - } - } - } - } - } - - // 2. 链路测试 - for (int i = 0; i < chainNodesList.size(); i++) { - List currentHop = chainNodesList.get(i); - final int hopIndex = 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) { - final Node finalFromNode = fromNode; - final Node finalToNode = toNode; - final ChainTunnel finalCurrentNode = currentNode; - final ChainTunnel finalNextNode = nextNode; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalNextNode.getPort(), - "第" + (hopIndex + 1) + "跳(" + finalFromNode.getName() + ")->第" + (hopIndex + 2) + "跳(" + finalToNode.getName() + ")" - ); - result.setFromChainType(2); - result.setFromInx(finalCurrentNode.getInx()); - result.setToChainType(2); - result.setToInx(finalNextNode.getInx()); - return result; - })); - } - } - } else if (!outNodes.isEmpty()) { - for (ChainTunnel outNode : outNodes) { - Node toNode = nodeService.getById(outNode.getNodeId()); - if (toNode != null) { - final Node finalFromNode = fromNode; - final Node finalToNode = toNode; - final ChainTunnel finalCurrentNode = currentNode; - final ChainTunnel finalOutNode = outNode; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalOutNode.getPort(), - "第" + (hopIndex + 1) + "跳(" + finalFromNode.getName() + ")->出口(" + finalToNode.getName() + ")" - ); - result.setFromChainType(2); - result.setFromInx(finalCurrentNode.getInx()); - result.setToChainType(3); - return result; - })); - } - } - } - } - } - } - - // 3. 出口->目标地址 - for (ChainTunnel outNode : outNodes) { - Node node = nodeService.getById(outNode.getNodeId()); - if (node != null) { - for (String remoteAddress : remoteAddresses) { - String targetIp = extractIpFromAddress(remoteAddress); - int targetPort = extractPortFromAddress(remoteAddress); - if (targetIp != null && targetPort != -1) { - final Node finalNode = node; - final String finalTargetIp = targetIp; - final int finalTargetPort = targetPort; - final String finalRemoteAddress = remoteAddress; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalNode, finalTargetIp, finalTargetPort, - "出口(" + finalNode.getName() + ")->目标(" + finalRemoteAddress + ")" - ); - result.setFromChainType(3); - return result; - })); - } - } - } - } - } - - // 等待所有诊断任务完成并收集结果 - List results = futures.stream() - .map(CompletableFuture::join) - .collect(Collectors.toList()); - - // 构建诊断报告 - Map diagnosisReport = new HashMap<>(); - diagnosisReport.put("forwardId", id); - diagnosisReport.put("forwardName", forward.getName()); - diagnosisReport.put("tunnelType", tunnel.getType() == 1 ? "端口转发" : "隧道转发"); - diagnosisReport.put("results", results); - diagnosisReport.put("timestamp", System.currentTimeMillis()); - - return R.ok(diagnosisReport); - } - - @Override - @Transactional - public R updateForwardOrder(Map params) { - // 1. 获取当前用户信息 - UserInfo currentUser = getCurrentUserInfo(); - - // 2. 验证参数 - if (!params.containsKey("forwards")) { - return R.err("缺少forwards参数"); - } - - @SuppressWarnings("unchecked") - List> forwardsList = (List>) params.get("forwards"); - if (forwardsList == null || forwardsList.isEmpty()) { - return R.err("forwards参数不能为空"); - } - - // 3. 验证用户权限(只能更新自己的转发) - if (currentUser.getRoleId() != 0) { - // 普通用户只能更新自己的转发 - List forwardIds = forwardsList.stream() - .map(item -> Long.valueOf(item.get("id").toString())) - .collect(Collectors.toList()); - - // 检查所有转发是否属于当前用户 - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.in("id", forwardIds); - queryWrapper.eq("user_id", currentUser.getUserId()); - - long count = this.count(queryWrapper); - if (count != forwardIds.size()) { - return R.err("只能更新自己的转发排序"); - } - } - - // 4. 批量更新排序 - List forwardsToUpdate = new ArrayList<>(); - for (Map forwardData : forwardsList) { - Long id = Long.valueOf(forwardData.get("id").toString()); - Integer inx = Integer.valueOf(forwardData.get("inx").toString()); - - Forward forward = new Forward(); - forward.setId(id); - forward.setInx(inx); - forwardsToUpdate.add(forward); - } - - // 5. 执行批量更新 - this.updateBatchById(forwardsToUpdate); - return R.ok(); - - - } - - - private R changeForwardStatus(Long id, int targetStatus, String gostMethod) { - UserInfo currentUser = getCurrentUserInfo(); - if (currentUser.getRoleId() != 0) { - User user = userService.getById(currentUser.getUserId()); - if (user == null) return R.err("用户不存在"); - if (user.getStatus() == 0) return R.err("用户已到期或被禁用"); - } - Forward forward = validateForwardExists(id, currentUser); - if (forward == null) { - return R.err("转发不存在"); - } - - Tunnel tunnel = validateTunnel(forward.getTunnelId()); - if (tunnel == null) { - return R.err("隧道不存在"); - } - - UserTunnel userTunnel = null; - if (targetStatus == 1) { - if (tunnel.getStatus() != 1) { - return R.err("隧道已禁用,无法恢复服务"); - } - if (currentUser.getRoleId() != 0) { - R flowCheckResult = checkUserFlowLimits(currentUser.getUserId(), tunnel); - if (flowCheckResult.getCode() != 0) { - return flowCheckResult; - } - userTunnel = getUserTunnel(currentUser.getUserId(), tunnel.getId().intValue()); - if (userTunnel == null) { - return R.err("你没有该隧道权限"); - } - if (userTunnel.getStatus() != 1) { - return R.err("隧道被禁用"); - } - } - } - if (currentUser.getRoleId() != 0 && userTunnel == null) { - userTunnel = getUserTunnel(currentUser.getUserId(), tunnel.getId().intValue()); - if (userTunnel == null) { - return R.err("你没有该隧道权限"); - } - } - - if (userTunnel == null) { - userTunnel = getUserTunnel(forward.getUserId(), tunnel.getId().intValue()); - } - - List chainTunnels = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", tunnel.getId()).eq("chain_type", 1)); - - for (ChainTunnel chainTunnel : chainTunnels) { - String serviceName = buildServiceName(forward.getId(), forward.getUserId(), userTunnel); - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node == null) { - return R.err("部分节点不存在"); - } - GostDto gostDto = GostUtil.PauseAndResumeService(node.getId(), serviceName, gostMethod); - if (!Objects.equals(gostDto.getMsg(), "OK")) return R.err(gostDto.getMsg()); - } - forward.setStatus(targetStatus); - forward.setUpdatedTime(System.currentTimeMillis()); - this.updateById(forward); - return R.ok(); - } - - private String extractIpFromAddress(String address) { - if (address == null || address.trim().isEmpty()) { - return null; - } - - address = address.trim(); - - // IPv6格式: [ipv6]:port - if (address.startsWith("[")) { - int closeBracket = address.indexOf(']'); - if (closeBracket > 1) { - return address.substring(1, closeBracket); - } - } - - // IPv4或域名格式: ip:port 或 domain:port - int lastColon = address.lastIndexOf(':'); - if (lastColon > 0) { - return address.substring(0, lastColon); - } - - // 如果没有端口,直接返回地址 - return address; - } - - private int extractPortFromAddress(String address) { - if (address == null || address.trim().isEmpty()) { - return -1; - } - - address = address.trim(); - - // IPv6格式: [ipv6]:port - if (address.startsWith("[")) { - int closeBracket = address.indexOf(']'); - if (closeBracket > 1 && closeBracket + 1 < address.length() && address.charAt(closeBracket + 1) == ':') { - String portStr = address.substring(closeBracket + 2); - try { - return Integer.parseInt(portStr); - } catch (NumberFormatException e) { - return -1; - } - } - } - - // IPv4或域名格式: ip:port 或 domain:port - int lastColon = address.lastIndexOf(':'); - if (lastColon > 0 && lastColon + 1 < address.length()) { - String portStr = address.substring(lastColon + 1); - try { - return Integer.parseInt(portStr); - } catch (NumberFormatException e) { - return -1; - } - } - - // 如果没有端口,返回-1表示无法解析 - return -1; - } - - 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 UserInfo getCurrentUserInfo() { - Integer userId = JwtUtil.getUserIdFromToken(); - Integer roleId = JwtUtil.getRoleIdFromToken(); - String userName = JwtUtil.getNameFromToken(); - return new UserInfo(userId, roleId, userName); - } - - private Tunnel validateTunnel(Integer tunnelId) { - return tunnelService.getById(tunnelId); - } - - private Forward validateForwardExists(Long forwardId, UserInfo currentUser) { - Forward forward = this.getById(forwardId); - if (forward == null) { - return null; - } - - // 普通用户只能操作自己的转发 - if (currentUser.getRoleId() != 0 && - !Objects.equals(currentUser.getUserId(), forward.getUserId())) { - return null; - } - - return forward; - } - - private UserPermissionResult checkUserPermissions(UserInfo currentUser, Tunnel tunnel, Long excludeForwardId) { - if (currentUser.getRoleId() == 0) { - return UserPermissionResult.success(null, null); - } - - // 获取用户信息 - User userInfo = userService.getById(currentUser.getUserId()); - if (userInfo.getExpTime() != null && userInfo.getExpTime() <= System.currentTimeMillis()) { - return UserPermissionResult.error("当前账号已到期"); - } - - // 检查用户隧道权限 - UserTunnel userTunnel = getUserTunnel(currentUser.getUserId(), tunnel.getId().intValue()); - if (userTunnel == null) { - return UserPermissionResult.error("你没有该隧道权限"); - } - - if (userTunnel.getStatus() != 1) { - return UserPermissionResult.error("隧道被禁用"); - } - - // 检查隧道权限到期时间 - if (userTunnel.getExpTime() != null && userTunnel.getExpTime() <= System.currentTimeMillis()) { - return UserPermissionResult.error("该隧道权限已到期"); - } - - // 流量限制检查 - if (userInfo.getFlow() <= 0) { - return UserPermissionResult.error("用户总流量已用完"); - } - if (userTunnel.getFlow() <= 0) { - return UserPermissionResult.error("该隧道流量已用完"); - } - - // 转发数量限制检查 - R quotaCheckResult = checkForwardQuota(currentUser.getUserId(), tunnel.getId().intValue(), userTunnel, userInfo, excludeForwardId); - if (quotaCheckResult.getCode() != 0) { - return UserPermissionResult.error(quotaCheckResult.getMsg()); - } - - return UserPermissionResult.success(userTunnel.getSpeedId(), userTunnel); - } - - private R checkForwardQuota(Integer userId, Integer tunnelId, UserTunnel userTunnel, User userInfo, Long excludeForwardId) { - // 检查用户总转发数量限制 - long userForwardCount = this.count(new QueryWrapper().eq("user_id", userId)); - if (userForwardCount >= userInfo.getNum()) { - return R.err("用户总转发数量已达上限,当前限制:" + userInfo.getNum() + "个"); - } - - // 检查用户在该隧道的转发数量限制 - QueryWrapper tunnelQuery = new QueryWrapper() - .eq("user_id", userId) - .eq("tunnel_id", tunnelId); - - if (excludeForwardId != null) { - tunnelQuery.ne("id", excludeForwardId); - } - - long tunnelForwardCount = this.count(tunnelQuery); - if (tunnelForwardCount >= userTunnel.getNum()) { - return R.err("该隧道转发数量已达上限,当前限制:" + userTunnel.getNum() + "个"); - } - - return R.ok(); - } - - private R checkUserFlowLimits(Integer userId, Tunnel tunnel) { - User userInfo = userService.getById(userId); - if (userInfo.getExpTime() != null && userInfo.getExpTime() <= System.currentTimeMillis()) { - return R.err("当前账号已到期"); - } - - UserTunnel userTunnel = getUserTunnel(userId, tunnel.getId().intValue()); - if (userTunnel == null) { - return R.err("你没有该隧道权限"); - } - - // 检查隧道权限到期时间 - if (userTunnel.getExpTime() != null && userTunnel.getExpTime() <= System.currentTimeMillis()) { - return R.err("该隧道权限已到期,无法恢复服务"); - } - - // 检查用户总流量限制 - if (userInfo.getFlow() * BYTES_TO_GB <= userInfo.getInFlow() + userInfo.getOutFlow()) { - return R.err("用户总流量已用完,无法恢复服务"); - } - - // 检查隧道流量限制 - // 数据库中的流量已按计费类型处理,直接使用总和 - long tunnelFlow = userTunnel.getInFlow() + userTunnel.getOutFlow(); - - if (userTunnel.getFlow() * BYTES_TO_GB <= tunnelFlow) { - return R.err("该隧道流量已用完,无法恢复服务"); - } - - return R.ok(); - } - - private UserTunnel getUserTunnel(Integer userId, Integer tunnelId) { - return userTunnelService.getOne(new QueryWrapper() - .eq("user_id", userId) - .eq("tunnel_id", tunnelId)); - } - - private String buildServiceName(Long forwardId, Integer userId, UserTunnel userTunnel) { - int userTunnelId = (userTunnel != null) ? userTunnel.getId() : 0; - return forwardId + "_" + userId + "_" + userTunnelId; - } - - - public List get_port(List chainTunnelList, Integer in_port, Long forward_id) { - List> list = new ArrayList<>(); - - // 获取每个节点的端口列表 - for (ChainTunnel tunnel : chainTunnelList) { - List nodePort = getNodePort(tunnel.getNodeId(), forward_id); - if (nodePort.isEmpty()) { - throw new RuntimeException("暂无可用端口"); - } - list.add(nodePort); - } - - // ========== 如果指定了 in_port,优先检查公有 ========== - if (in_port != null) { - for (List ports : list) { - if (!ports.contains(in_port)) { - throw new RuntimeException("指定端口 " + in_port + " 不可用(并非所有节点都有此端口)"); - } - } - - // 所有节点都有该端口 设置回 ChainTunnel - for (ChainTunnel tunnel : chainTunnelList) { - tunnel.setPort(in_port); - } - return chainTunnelList; - } - - // ========== 未指定 in_port 查找最小的共同端口 ========== - Set intersection = new HashSet<>(list.getFirst()); - for (int i = 1; i < list.size(); i++) { - intersection.retainAll(list.get(i)); - } - - if (!intersection.isEmpty()) { - // 找最小端口 - Integer commonMin = intersection.stream().min(Integer::compareTo).orElseThrow(); - - // 设置到所有节点 - for (ChainTunnel tunnel : chainTunnelList) { - tunnel.setPort(commonMin); - } - - return chainTunnelList; - } - - // ========== 没有共同端口取各自第一个可用端口 ========== - for (int i = 0; i < chainTunnelList.size(); i++) { - List ports = list.get(i); - Integer first = ports.getFirst(); - chainTunnelList.get(i).setPort(first); - } - - return chainTunnelList; - } - - public List getNodePort(Long nodeId, Long forward_id) { - - Node node = nodeService.getById(nodeId); - if (node == null) { - throw new RuntimeException("节点不存在"); - } - - // 1. 查询隧道转发链占用的端口 - List chainTunnels = chainTunnelService.list( - new QueryWrapper().eq("node_id", nodeId) - ); - Set usedPorts = chainTunnels.stream() - .map(ChainTunnel::getPort) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - - List list = forwardPortService.list(new QueryWrapper().eq("node_id", nodeId).ne("forward_id", forward_id)); - Set forwardUsedPorts = new HashSet<>(); - for (ForwardPort forwardPort : list) { - forwardUsedPorts.add(forwardPort.getPort()); - } - usedPorts.addAll(forwardUsedPorts); - - List parsedPorts = TunnelServiceImpl.parsePorts(node.getPort()); - return parsedPorts.stream() - .filter(p -> !usedPorts.contains(p)) - .toList(); - } - - - - // ========== 内部数据类 ========== - - @Data - private static class UserInfo { - private final Integer userId; - private final Integer roleId; - private final String userName; - - public UserInfo(Integer userId, Integer roleId, String userName) { - this.userId = userId; - this.roleId = roleId; - this.userName = userName; - } - } - - @Data - private static class UserPermissionResult { - private final boolean hasError; - private final String errorMessage; - private final Integer limiter; - private final UserTunnel userTunnel; - - private UserPermissionResult(boolean hasError, String errorMessage, Integer limiter, UserTunnel userTunnel) { - this.hasError = hasError; - this.errorMessage = errorMessage; - this.limiter = limiter; - this.userTunnel = userTunnel; - } - - public static UserPermissionResult success(Integer limiter, UserTunnel userTunnel) { - return new UserPermissionResult(false, null, limiter, userTunnel); - } - - public static UserPermissionResult error(String errorMessage) { - return new UserPermissionResult(true, errorMessage, null, null); - } - } - - @Data - public static class DiagnosisResult { - private Long nodeId; - private String nodeName; - private String targetIp; - private Integer targetPort; - private String description; - private boolean success; - private String message; - private double averageTime; - private double packetLoss; - private long timestamp; - - // 链路类型相关字段 - private Integer fromChainType; // 1: 入口, 2: 链, 3: 出口 - private Integer fromInx; - private Integer toChainType; - private Integer toInx; - } - - @Override - @Transactional - public R batchDeleteForwards(BatchDeleteDto batchDeleteDto) { - UserInfo currentUser = getCurrentUserInfo(); - BatchOperationResultDto result = new BatchOperationResultDto(); - - for (Long id : batchDeleteDto.getIds()) { - try { - Forward forward = validateForwardExists(id, currentUser); - if (forward == null) { - result.addFailedItem(id, "转发不存在或无权限"); - continue; - } - - Tunnel tunnel = validateTunnel(forward.getTunnelId()); - if (tunnel == null) { - result.addFailedItem(id, "隧道不存在"); - continue; - } - - UserTunnel userTunnel = null; - if (currentUser.getRoleId() != 0) { - userTunnel = getUserTunnel(currentUser.getUserId(), tunnel.getId().intValue()); - if (userTunnel == null) { - result.addFailedItem(id, "没有该隧道权限"); - continue; - } - } else { - userTunnel = getUserTunnel(forward.getUserId(), tunnel.getId().intValue()); - } - - List chainTunnels = chainTunnelService.list( - new QueryWrapper().eq("tunnel_id", tunnel.getId()).eq("chain_type", 1) - ); - - boolean deleteSuccess = true; - for (ChainTunnel chainTunnel : chainTunnels) { - String serviceName = buildServiceName(forward.getId(), forward.getUserId(), userTunnel); - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node != null) { - JSONArray services = new JSONArray(); - services.add(serviceName + "_tcp"); - services.add(serviceName + "_udp"); - GostUtil.DeleteService(node.getId(), services); - } - } - - if (deleteSuccess) { - forwardPortService.remove(new QueryWrapper().eq("forward_id", id)); - this.removeById(id); - result.incrementSuccess(); - } - } catch (Exception e) { - result.addFailedItem(id, e.getMessage()); - } - } - - return R.ok(result); - } - - @Override - @Transactional - public R batchPauseForwards(BatchDeleteDto batchDeleteDto) { - return batchChangeForwardStatus(batchDeleteDto.getIds(), 0, "PauseService"); - } - - @Override - @Transactional - public R batchResumeForwards(BatchDeleteDto batchDeleteDto) { - return batchChangeForwardStatus(batchDeleteDto.getIds(), 1, "ResumeService"); - } - - private R batchChangeForwardStatus(List ids, int targetStatus, String gostMethod) { - BatchOperationResultDto result = new BatchOperationResultDto(); - - for (Long id : ids) { - try { - R changeResult = changeForwardStatus(id, targetStatus, gostMethod); - if (changeResult.getCode() == 0) { - result.incrementSuccess(); - } else { - result.addFailedItem(id, changeResult.getMsg()); - } - } catch (Exception e) { - result.addFailedItem(id, e.getMessage()); - } - } - - return R.ok(result); - } - - @Override - @Transactional - public R batchRedeployForwards(BatchRedeployDto batchRedeployDto) { - UserInfo currentUser = getCurrentUserInfo(); - BatchOperationResultDto result = new BatchOperationResultDto(); - - for (Long id : batchRedeployDto.getIds()) { - try { - Forward forward = validateForwardExists(id, currentUser); - if (forward == null) { - result.addFailedItem(id, "转发不存在或无权限"); - continue; - } - - Tunnel tunnel = validateTunnel(forward.getTunnelId()); - if (tunnel == null) { - result.addFailedItem(id, "隧道不存在"); - continue; - } - - if (tunnel.getStatus() != 1) { - result.addFailedItem(id, "隧道已禁用"); - continue; - } - - UserPermissionResult permissionResult = checkUserPermissions(currentUser, tunnel, id); - if (permissionResult.isHasError()) { - result.addFailedItem(id, permissionResult.getErrorMessage()); - continue; - } - - List forwardPorts = forwardPortService.list( - new QueryWrapper().eq("forward_id", id) - ); - - for (ForwardPort forwardPort : forwardPorts) { - String serviceName = buildServiceName(forward.getId(), forward.getUserId(), permissionResult.getUserTunnel()); - Node node = nodeService.getById(forwardPort.getNodeId()); - if (node != null) { - GostUtil.AddAndUpdateService(serviceName, permissionResult.getLimiter(), - node, forward, forwardPort, tunnel, "UpdateService"); - } - } - - result.incrementSuccess(); - } catch (Exception e) { - result.addFailedItem(id, e.getMessage()); - } - } - - return R.ok(result); - } - - @Override - @Transactional - public R batchChangeTunnel(BatchChangeTunnelDto batchChangeTunnelDto) { - UserInfo currentUser = getCurrentUserInfo(); - BatchOperationResultDto result = new BatchOperationResultDto(); - - Long targetTunnelId = batchChangeTunnelDto.getTargetTunnelId(); - Tunnel targetTunnel = tunnelService.getById(targetTunnelId); - if (targetTunnel == null) { - return R.err("目标隧道不存在"); - } - if (targetTunnel.getStatus() != 1) { - return R.err("目标隧道已禁用"); - } - - for (Long forwardId : batchChangeTunnelDto.getForwardIds()) { - try { - Forward forward = validateForwardExists(forwardId, currentUser); - if (forward == null) { - result.addFailedItem(forwardId, "转发不存在或无权限"); - continue; - } - - if (forward.getTunnelId().equals(targetTunnelId.intValue())) { - result.addFailedItem(forwardId, "已是目标隧道"); - continue; - } - - List existingForwardPorts = forwardPortService.list( - new QueryWrapper().eq("forward_id", forwardId).orderByAsc("id") - ); - Integer originalInPort = existingForwardPorts.stream() - .map(ForwardPort::getPort) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); - - Tunnel oldTunnel = validateTunnel(forward.getTunnelId()); - if (oldTunnel != null) { - UserTunnel oldUserTunnel = null; - if (currentUser.getRoleId() != 0) { - oldUserTunnel = getUserTunnel(currentUser.getUserId(), oldTunnel.getId().intValue()); - } else { - oldUserTunnel = getUserTunnel(forward.getUserId(), oldTunnel.getId().intValue()); - } - - List oldChainTunnels = chainTunnelService.list( - new QueryWrapper().eq("tunnel_id", oldTunnel.getId()).eq("chain_type", 1) - ); - for (ChainTunnel chainTunnel : oldChainTunnels) { - String serviceName = buildServiceName(forward.getId(), forward.getUserId(), oldUserTunnel); - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node != null) { - JSONArray services = new JSONArray(); - services.add(serviceName + "_tcp"); - services.add(serviceName + "_udp"); - GostUtil.DeleteService(node.getId(), services); - } - } - } - - forwardPortService.remove(new QueryWrapper().eq("forward_id", forwardId)); - - forward.setTunnelId(targetTunnelId.intValue()); - forward.setUpdatedTime(System.currentTimeMillis()); - this.updateById(forward); - - UserPermissionResult permissionResult = checkUserPermissions(currentUser, targetTunnel, forwardId); - if (permissionResult.isHasError()) { - result.addFailedItem(forwardId, "切换成功但无法下发: " + permissionResult.getErrorMessage()); - continue; - } - - List newChainTunnels = chainTunnelService.list( - new QueryWrapper().eq("tunnel_id", targetTunnel.getId()).eq("chain_type", 1) - ); - List chainTunnelsWithPort = allocatePortsForBatchTunnelChange(newChainTunnels, originalInPort, forwardId); - - for (ChainTunnel chainTunnel : chainTunnelsWithPort) { - ForwardPort forwardPort = new ForwardPort(); - forwardPort.setForwardId(forwardId); - forwardPort.setNodeId(chainTunnel.getNodeId()); - forwardPort.setPort(chainTunnel.getPort()); - forwardPortService.save(forwardPort); - - String serviceName = buildServiceName(forward.getId(), forward.getUserId(), permissionResult.getUserTunnel()); - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node != null) { - GostUtil.AddAndUpdateService(serviceName, permissionResult.getLimiter(), - node, forward, forwardPort, targetTunnel, "AddService"); - } - } - - result.incrementSuccess(); - } catch (Exception e) { - result.addFailedItem(forwardId, e.getMessage()); - } - } - - return R.ok(result); - } - - private List allocatePortsForBatchTunnelChange(List newChainTunnels, Integer originalInPort, Long forwardId) { - if (originalInPort == null) { - return get_port(newChainTunnels, null, forwardId); - } - - try { - return get_port(newChainTunnels, originalInPort, forwardId); - } catch (RuntimeException originalPortError) { - try { - return get_port(newChainTunnels, null, forwardId); - } catch (RuntimeException autoAllocateError) { - throw new RuntimeException( - "原入口端口 " + originalInPort + " 在目标隧道不可用,自动分配新端口也失败: " + autoAllocateError.getMessage(), - autoAllocateError - ); - } - } - } - -} diff --git a/springboot-backend/src/main/java/com/admin/service/impl/GroupServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/GroupServiceImpl.java deleted file mode 100644 index fd70de8..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/GroupServiceImpl.java +++ /dev/null @@ -1,633 +0,0 @@ -package com.admin.service.impl; - -import com.admin.common.dto.GroupCreateDto; -import com.admin.common.dto.GroupPermissionAssignDto; -import com.admin.common.dto.GroupPermissionDetailDto; -import com.admin.common.dto.GroupUpdateDto; -import com.admin.common.dto.TunnelGroupAssignTunnelsDto; -import com.admin.common.dto.TunnelGroupDetailDto; -import com.admin.common.dto.UserGroupAssignUsersDto; -import com.admin.common.dto.UserGroupDetailDto; -import com.admin.common.lang.R; -import com.admin.entity.GroupPermission; -import com.admin.entity.GroupPermissionGrant; -import com.admin.entity.Tunnel; -import com.admin.entity.TunnelGroup; -import com.admin.entity.TunnelGroupTunnel; -import com.admin.entity.User; -import com.admin.entity.UserGroup; -import com.admin.entity.UserGroupUser; -import com.admin.entity.UserTunnel; -import com.admin.mapper.GroupPermissionGrantMapper; -import com.admin.mapper.GroupPermissionMapper; -import com.admin.mapper.TunnelGroupMapper; -import com.admin.mapper.TunnelGroupTunnelMapper; -import com.admin.mapper.UserGroupMapper; -import com.admin.mapper.UserGroupUserMapper; -import com.admin.service.GroupService; -import com.admin.service.TunnelService; -import com.admin.service.UserService; -import com.admin.service.UserTunnelService; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -@Service -public class GroupServiceImpl implements GroupService { - - @Resource - private TunnelGroupMapper tunnelGroupMapper; - - @Resource - private UserGroupMapper userGroupMapper; - - @Resource - private TunnelGroupTunnelMapper tunnelGroupTunnelMapper; - - @Resource - private UserGroupUserMapper userGroupUserMapper; - - @Resource - private GroupPermissionMapper groupPermissionMapper; - - @Resource - private GroupPermissionGrantMapper groupPermissionGrantMapper; - - @Resource - private TunnelService tunnelService; - - @Resource - private UserService userService; - - @Resource - private UserTunnelService userTunnelService; - - @Override - public R getTunnelGroups() { - List groups = tunnelGroupMapper.selectList(new QueryWrapper().orderByAsc("id")); - List mappings = tunnelGroupTunnelMapper.selectList(new QueryWrapper()); - - Map> mappingByGroupId = mappings.stream() - .collect(Collectors.groupingBy(TunnelGroupTunnel::getTunnelGroupId)); - - Set tunnelIds = mappings.stream().map(TunnelGroupTunnel::getTunnelId).collect(Collectors.toSet()); - Map tunnelNameMap = buildTunnelNameMap(tunnelIds); - - List result = new ArrayList<>(); - for (TunnelGroup group : groups) { - TunnelGroupDetailDto dto = new TunnelGroupDetailDto(); - dto.setId(group.getId()); - dto.setName(group.getName()); - dto.setStatus(group.getStatus()); - dto.setCreatedTime(group.getCreatedTime()); - dto.setUpdatedTime(group.getUpdatedTime()); - - List groupMappings = mappingByGroupId.getOrDefault(group.getId(), Collections.emptyList()); - List ids = groupMappings.stream().map(TunnelGroupTunnel::getTunnelId).collect(Collectors.toList()); - List names = ids.stream().map(tunnelNameMap::get).filter(name -> name != null && !name.isBlank()).collect(Collectors.toList()); - dto.setTunnelIds(ids); - dto.setTunnelNames(names); - result.add(dto); - } - return R.ok(result); - } - - @Override - public R createTunnelGroup(GroupCreateDto dto) { - String name = dto.getName().trim(); - int count = tunnelGroupMapper.selectCount(new QueryWrapper().eq("name", name)); - if (count > 0) { - return R.err("隧道分组名称已存在"); - } - - long now = System.currentTimeMillis(); - TunnelGroup group = new TunnelGroup(); - group.setName(name); - group.setStatus(normalizeStatus(dto.getStatus())); - group.setCreatedTime(now); - group.setUpdatedTime(now); - tunnelGroupMapper.insert(group); - return R.ok(); - } - - @Override - public R updateTunnelGroup(GroupUpdateDto dto) { - TunnelGroup group = tunnelGroupMapper.selectById(dto.getId()); - if (group == null) { - return R.err("隧道分组不存在"); - } - String name = dto.getName().trim(); - int count = tunnelGroupMapper.selectCount(new QueryWrapper().eq("name", name).ne("id", dto.getId())); - if (count > 0) { - return R.err("隧道分组名称已存在"); - } - - group.setName(name); - if (dto.getStatus() != null) { - group.setStatus(dto.getStatus()); - } - group.setUpdatedTime(System.currentTimeMillis()); - tunnelGroupMapper.updateById(group); - return R.ok(); - } - - @Override - public R deleteTunnelGroup(Long id) { - TunnelGroup group = tunnelGroupMapper.selectById(id); - if (group == null) { - return R.err("隧道分组不存在"); - } - - List grants = groupPermissionGrantMapper.selectList(new QueryWrapper().eq("tunnel_group_id", id)); - revokeGrantRecords(grants); - tunnelGroupTunnelMapper.delete(new QueryWrapper().eq("tunnel_group_id", id)); - groupPermissionMapper.delete(new QueryWrapper().eq("tunnel_group_id", id)); - tunnelGroupMapper.deleteById(id); - return R.ok(); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public R assignTunnelsToGroup(TunnelGroupAssignTunnelsDto dto) { - TunnelGroup group = tunnelGroupMapper.selectById(dto.getGroupId()); - if (group == null) { - return R.err("隧道分组不存在"); - } - - Set tunnelIds = new LinkedHashSet<>(dto.getTunnelIds()); - if (!tunnelIds.isEmpty()) { - List tunnels = tunnelService.list(new QueryWrapper().in("id", tunnelIds)); - if (tunnels.size() != tunnelIds.size()) { - return R.err("隧道列表中存在无效ID"); - } - } - - tunnelGroupTunnelMapper.delete(new QueryWrapper().eq("tunnel_group_id", dto.getGroupId())); - - if (!tunnelIds.isEmpty()) { - long now = System.currentTimeMillis(); - for (Long tunnelId : tunnelIds) { - TunnelGroupTunnel relation = new TunnelGroupTunnel(); - relation.setTunnelGroupId(dto.getGroupId()); - relation.setTunnelId(tunnelId); - relation.setCreatedTime(now); - tunnelGroupTunnelMapper.insert(relation); - } - } - - syncByTunnelGroup(dto.getGroupId()); - return R.ok(); - } - - @Override - public R getUserGroups() { - List groups = userGroupMapper.selectList(new QueryWrapper().orderByAsc("id")); - List mappings = userGroupUserMapper.selectList(new QueryWrapper()); - - Map> mappingByGroupId = mappings.stream() - .collect(Collectors.groupingBy(UserGroupUser::getUserGroupId)); - - Set userIds = mappings.stream().map(UserGroupUser::getUserId).collect(Collectors.toSet()); - Map userNameMap = buildUserNameMap(userIds); - - List result = new ArrayList<>(); - for (UserGroup group : groups) { - UserGroupDetailDto dto = new UserGroupDetailDto(); - dto.setId(group.getId()); - dto.setName(group.getName()); - dto.setStatus(group.getStatus()); - dto.setCreatedTime(group.getCreatedTime()); - dto.setUpdatedTime(group.getUpdatedTime()); - - List groupMappings = mappingByGroupId.getOrDefault(group.getId(), Collections.emptyList()); - List ids = groupMappings.stream().map(UserGroupUser::getUserId).collect(Collectors.toList()); - List names = ids.stream().map(userNameMap::get).filter(name -> name != null && !name.isBlank()).collect(Collectors.toList()); - dto.setUserIds(ids); - dto.setUserNames(names); - result.add(dto); - } - return R.ok(result); - } - - @Override - public R createUserGroup(GroupCreateDto dto) { - String name = dto.getName().trim(); - int count = userGroupMapper.selectCount(new QueryWrapper().eq("name", name)); - if (count > 0) { - return R.err("用户分组名称已存在"); - } - - long now = System.currentTimeMillis(); - UserGroup group = new UserGroup(); - group.setName(name); - group.setStatus(normalizeStatus(dto.getStatus())); - group.setCreatedTime(now); - group.setUpdatedTime(now); - userGroupMapper.insert(group); - return R.ok(); - } - - @Override - public R updateUserGroup(GroupUpdateDto dto) { - UserGroup group = userGroupMapper.selectById(dto.getId()); - if (group == null) { - return R.err("用户分组不存在"); - } - String name = dto.getName().trim(); - int count = userGroupMapper.selectCount(new QueryWrapper().eq("name", name).ne("id", dto.getId())); - if (count > 0) { - return R.err("用户分组名称已存在"); - } - - group.setName(name); - if (dto.getStatus() != null) { - group.setStatus(dto.getStatus()); - } - group.setUpdatedTime(System.currentTimeMillis()); - userGroupMapper.updateById(group); - return R.ok(); - } - - @Override - public R deleteUserGroup(Long id) { - UserGroup group = userGroupMapper.selectById(id); - if (group == null) { - return R.err("用户分组不存在"); - } - - List grants = groupPermissionGrantMapper.selectList(new QueryWrapper().eq("user_group_id", id)); - revokeGrantRecords(grants); - userGroupUserMapper.delete(new QueryWrapper().eq("user_group_id", id)); - groupPermissionMapper.delete(new QueryWrapper().eq("user_group_id", id)); - userGroupMapper.deleteById(id); - return R.ok(); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public R assignUsersToGroup(UserGroupAssignUsersDto dto) { - UserGroup group = userGroupMapper.selectById(dto.getGroupId()); - if (group == null) { - return R.err("用户分组不存在"); - } - - Set userIds = new LinkedHashSet<>(dto.getUserIds()); - if (!userIds.isEmpty()) { - List users = userService.list(new QueryWrapper().in("id", userIds).ne("role_id", 0)); - if (users.size() != userIds.size()) { - return R.err("用户列表中存在无效ID"); - } - } - - userGroupUserMapper.delete(new QueryWrapper().eq("user_group_id", dto.getGroupId())); - - if (!userIds.isEmpty()) { - long now = System.currentTimeMillis(); - for (Long userId : userIds) { - UserGroupUser relation = new UserGroupUser(); - relation.setUserGroupId(dto.getGroupId()); - relation.setUserId(userId); - relation.setCreatedTime(now); - userGroupUserMapper.insert(relation); - } - } - - syncByUserGroup(dto.getGroupId()); - return R.ok(); - } - - @Override - public R getGroupPermissions() { - List permissions = groupPermissionMapper.selectList(new QueryWrapper().orderByDesc("id")); - if (permissions.isEmpty()) { - return R.ok(new ArrayList()); - } - - Set userGroupIds = permissions.stream().map(GroupPermission::getUserGroupId).collect(Collectors.toSet()); - Set tunnelGroupIds = permissions.stream().map(GroupPermission::getTunnelGroupId).collect(Collectors.toSet()); - - Map userGroupNameMap = userGroupMapper.selectList(new QueryWrapper().in("id", userGroupIds)).stream() - .collect(Collectors.toMap(UserGroup::getId, UserGroup::getName)); - Map tunnelGroupNameMap = tunnelGroupMapper.selectList(new QueryWrapper().in("id", tunnelGroupIds)).stream() - .collect(Collectors.toMap(TunnelGroup::getId, TunnelGroup::getName)); - - List result = new ArrayList<>(); - for (GroupPermission permission : permissions) { - GroupPermissionDetailDto dto = new GroupPermissionDetailDto(); - dto.setId(permission.getId()); - dto.setUserGroupId(permission.getUserGroupId()); - dto.setTunnelGroupId(permission.getTunnelGroupId()); - dto.setCreatedTime(permission.getCreatedTime()); - dto.setUserGroupName(userGroupNameMap.get(permission.getUserGroupId())); - dto.setTunnelGroupName(tunnelGroupNameMap.get(permission.getTunnelGroupId())); - result.add(dto); - } - - return R.ok(result); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public R assignGroupPermission(GroupPermissionAssignDto dto) { - UserGroup userGroup = userGroupMapper.selectById(dto.getUserGroupId()); - if (userGroup == null) { - return R.err("用户分组不存在"); - } - TunnelGroup tunnelGroup = tunnelGroupMapper.selectById(dto.getTunnelGroupId()); - if (tunnelGroup == null) { - return R.err("隧道分组不存在"); - } - - int existing = groupPermissionMapper.selectCount(new QueryWrapper() - .eq("user_group_id", dto.getUserGroupId()) - .eq("tunnel_group_id", dto.getTunnelGroupId())); - if (existing == 0) { - GroupPermission permission = new GroupPermission(); - permission.setUserGroupId(dto.getUserGroupId()); - permission.setTunnelGroupId(dto.getTunnelGroupId()); - permission.setCreatedTime(System.currentTimeMillis()); - groupPermissionMapper.insert(permission); - } - - reconcilePermission(dto.getUserGroupId(), dto.getTunnelGroupId()); - return existing > 0 ? R.ok("权限已存在,已完成同步") : R.ok(); - } - - @Override - public R removeGroupPermission(Long id) { - GroupPermission permission = groupPermissionMapper.selectById(id); - if (permission == null) { - return R.err("权限记录不存在"); - } - - revokeByPermissionPair(permission.getUserGroupId(), permission.getTunnelGroupId()); - groupPermissionMapper.deleteById(id); - return R.ok(); - } - - private void syncByUserGroup(Long userGroupId) { - List permissions = groupPermissionMapper.selectList(new QueryWrapper().eq("user_group_id", userGroupId)); - for (GroupPermission permission : permissions) { - reconcilePermission(permission.getUserGroupId(), permission.getTunnelGroupId()); - } - } - - private void syncByTunnelGroup(Long tunnelGroupId) { - List permissions = groupPermissionMapper.selectList(new QueryWrapper().eq("tunnel_group_id", tunnelGroupId)); - for (GroupPermission permission : permissions) { - reconcilePermission(permission.getUserGroupId(), permission.getTunnelGroupId()); - } - } - - private void reconcilePermission(Long userGroupId, Long tunnelGroupId) { - Set userIds = userGroupUserMapper.selectList(new QueryWrapper().eq("user_group_id", userGroupId)).stream() - .map(UserGroupUser::getUserId) - .collect(Collectors.toCollection(LinkedHashSet::new)); - - Set tunnelIds = tunnelGroupTunnelMapper.selectList(new QueryWrapper().eq("tunnel_group_id", tunnelGroupId)).stream() - .map(TunnelGroupTunnel::getTunnelId) - .collect(Collectors.toCollection(LinkedHashSet::new)); - - Set desiredKeys = new HashSet<>(); - for (Long userId : userIds) { - for (Long tunnelId : tunnelIds) { - desiredKeys.add(permissionKey(userId, tunnelId)); - } - } - - List currentGrants = groupPermissionGrantMapper.selectList( - new QueryWrapper() - .eq("user_group_id", userGroupId) - .eq("tunnel_group_id", tunnelGroupId) - ); - - Set grantUserTunnelIds = currentGrants.stream().map(GroupPermissionGrant::getUserTunnelId).collect(Collectors.toSet()); - Map grantUserTunnelMap = new HashMap<>(); - if (!grantUserTunnelIds.isEmpty()) { - List userTunnels = userTunnelService.list(new QueryWrapper().in("id", grantUserTunnelIds)); - grantUserTunnelMap = userTunnels.stream().collect(Collectors.toMap(ut -> ut.getId().longValue(), Function.identity())); - } - - Map pairUserTunnelMap = new HashMap<>(); - if (!userIds.isEmpty() && !tunnelIds.isEmpty()) { - List pairUserTunnels = userTunnelService.list(new QueryWrapper() - .in("user_id", userIds) - .in("tunnel_id", tunnelIds)); - for (UserTunnel userTunnel : pairUserTunnels) { - pairUserTunnelMap.putIfAbsent(permissionKey(userTunnel.getUserId().longValue(), userTunnel.getTunnelId().longValue()), userTunnel); - } - } - - Set pairUserTunnelIds = pairUserTunnelMap.values().stream() - .map(ut -> ut.getId().longValue()) - .collect(Collectors.toSet()); - Map totalGrantCountMap = buildGrantCountMap(pairUserTunnelIds); - Set groupManagedUserTunnelIds = buildGroupManagedUserTunnelIds(pairUserTunnelIds); - - Set currentGrantUserTunnelIds = currentGrants.stream().map(GroupPermissionGrant::getUserTunnelId).collect(Collectors.toSet()); - if (!desiredKeys.isEmpty()) { - Map userMap = userService.list(new QueryWrapper().in("id", userIds)).stream() - .collect(Collectors.toMap(User::getId, Function.identity())); - - long now = System.currentTimeMillis(); - for (Long userId : userIds) { - User user = userMap.get(userId); - if (user == null) { - continue; - } - for (Long tunnelId : tunnelIds) { - String pairKey = permissionKey(userId, tunnelId); - UserTunnel userTunnel = pairUserTunnelMap.get(pairKey); - if (userTunnel == null) { - userTunnel = createGroupManagedUserTunnel(userId, tunnelId, user); - Long userTunnelId = resolveUserTunnelId(userTunnel, userId, tunnelId); - pairUserTunnelMap.put(pairKey, userTunnel); - createGrant(userGroupId, tunnelGroupId, userTunnelId, true, now); - currentGrantUserTunnelIds.add(userTunnelId); - totalGrantCountMap.put(userTunnelId, 1L); - groupManagedUserTunnelIds.add(userTunnelId); - continue; - } - - Long userTunnelId = userTunnel.getId().longValue(); - if (currentGrantUserTunnelIds.contains(userTunnelId)) { - continue; - } - - long existingGrantCount = totalGrantCountMap.getOrDefault(userTunnelId, 0L); - boolean createdByGroup = groupManagedUserTunnelIds.contains(userTunnelId); - createGrant(userGroupId, tunnelGroupId, userTunnelId, createdByGroup, now); - currentGrantUserTunnelIds.add(userTunnelId); - totalGrantCountMap.put(userTunnelId, existingGrantCount + 1L); - } - } - } - - List staleGrants = new ArrayList<>(); - for (GroupPermissionGrant grant : currentGrants) { - UserTunnel userTunnel = grantUserTunnelMap.get(grant.getUserTunnelId()); - boolean keep = false; - if (userTunnel != null) { - String key = permissionKey(userTunnel.getUserId().longValue(), userTunnel.getTunnelId().longValue()); - keep = desiredKeys.contains(key); - } - - if (!keep) { - staleGrants.add(grant); - } - } - revokeGrantRecords(staleGrants); - } - - private UserTunnel createGroupManagedUserTunnel(Long userId, Long tunnelId, User user) { - UserTunnel userTunnel = new UserTunnel(); - userTunnel.setUserId(userId.intValue()); - userTunnel.setTunnelId(tunnelId.intValue()); - userTunnel.setStatus(1); - userTunnel.setInFlow(0L); - userTunnel.setOutFlow(0L); - userTunnel.setFlow(user.getFlow()); - userTunnel.setNum(user.getNum()); - userTunnel.setFlowResetTime(user.getFlowResetTime()); - userTunnel.setExpTime(user.getExpTime()); - boolean saved = userTunnelService.save(userTunnel); - if (!saved) { - throw new IllegalStateException("创建用户隧道权限失败: userId=" + userId + ", tunnelId=" + tunnelId); - } - return userTunnel; - } - - private Long resolveUserTunnelId(UserTunnel userTunnel, Long userId, Long tunnelId) { - if (userTunnel.getId() != null) { - return userTunnel.getId().longValue(); - } - - UserTunnel persisted = userTunnelService.getOne( - new QueryWrapper() - .eq("user_id", userId.intValue()) - .eq("tunnel_id", tunnelId.intValue()) - .orderByDesc("id") - .last("LIMIT 1") - ); - if (persisted == null || persisted.getId() == null) { - throw new IllegalStateException("获取用户隧道权限ID失败: userId=" + userId + ", tunnelId=" + tunnelId); - } - userTunnel.setId(persisted.getId()); - return persisted.getId().longValue(); - } - - private void createGrant(Long userGroupId, Long tunnelGroupId, Long userTunnelId, boolean createdByGroup, long createdTime) { - int exists = groupPermissionGrantMapper.selectCount(new QueryWrapper() - .eq("user_group_id", userGroupId) - .eq("tunnel_group_id", tunnelGroupId) - .eq("user_tunnel_id", userTunnelId)); - if (exists > 0) { - return; - } - - GroupPermissionGrant grant = new GroupPermissionGrant(); - grant.setUserGroupId(userGroupId); - grant.setTunnelGroupId(tunnelGroupId); - grant.setUserTunnelId(userTunnelId); - grant.setCreatedByGroup(createdByGroup ? 1 : 0); - grant.setCreatedTime(createdTime); - groupPermissionGrantMapper.insert(grant); - } - - private void revokeByPermissionPair(Long userGroupId, Long tunnelGroupId) { - List grants = groupPermissionGrantMapper.selectList(new QueryWrapper() - .eq("user_group_id", userGroupId) - .eq("tunnel_group_id", tunnelGroupId)); - revokeGrantRecords(grants); - } - - private void revokeGrantRecords(List grants) { - if (grants == null || grants.isEmpty()) { - return; - } - - Set candidateUserTunnelIds = new HashSet<>(); - Set groupManagedCandidates = new HashSet<>(); - for (GroupPermissionGrant grant : grants) { - candidateUserTunnelIds.add(grant.getUserTunnelId()); - if (grant.getCreatedByGroup() != null && grant.getCreatedByGroup() == 1) { - groupManagedCandidates.add(grant.getUserTunnelId()); - } - groupPermissionGrantMapper.deleteById(grant.getId()); - } - - Set stillGrantedUserTunnelIds = groupPermissionGrantMapper.selectList( - new QueryWrapper().in("user_tunnel_id", candidateUserTunnelIds) - ).stream() - .map(GroupPermissionGrant::getUserTunnelId) - .collect(Collectors.toSet()); - - for (Long userTunnelId : candidateUserTunnelIds) { - if (!stillGrantedUserTunnelIds.contains(userTunnelId) && groupManagedCandidates.contains(userTunnelId)) { - userTunnelService.removeUserTunnel(userTunnelId.intValue()); - } - } - } - - private Set buildGroupManagedUserTunnelIds(Set userTunnelIds) { - if (userTunnelIds.isEmpty()) { - return new HashSet<>(); - } - - return groupPermissionGrantMapper.selectList(new QueryWrapper() - .in("user_tunnel_id", userTunnelIds) - .eq("created_by_group", 1)) - .stream() - .map(GroupPermissionGrant::getUserTunnelId) - .collect(Collectors.toSet()); - } - - private Map buildGrantCountMap(Set userTunnelIds) { - if (userTunnelIds.isEmpty()) { - return new HashMap<>(); - } - - List grants = groupPermissionGrantMapper.selectList(new QueryWrapper().in("user_tunnel_id", userTunnelIds)); - Map countMap = new HashMap<>(); - for (GroupPermissionGrant grant : grants) { - countMap.merge(grant.getUserTunnelId(), 1L, Long::sum); - } - return countMap; - } - - private Map buildTunnelNameMap(Set tunnelIds) { - if (tunnelIds.isEmpty()) { - return Collections.emptyMap(); - } - return tunnelService.list(new QueryWrapper().in("id", tunnelIds)).stream() - .collect(Collectors.toMap(Tunnel::getId, Tunnel::getName)); - } - - private Map buildUserNameMap(Set userIds) { - if (userIds.isEmpty()) { - return Collections.emptyMap(); - } - return userService.list(new QueryWrapper().in("id", userIds)).stream() - .collect(Collectors.toMap(User::getId, User::getUser)); - } - - private String permissionKey(Long userId, Long tunnelId) { - return userId + "_" + tunnelId; - } - - private int normalizeStatus(Integer status) { - return status == null ? 1 : status; - } -} 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 deleted file mode 100644 index 3ae1f3e..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/NodeServiceImpl.java +++ /dev/null @@ -1,535 +0,0 @@ -package com.admin.service.impl; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import com.admin.common.dto.BatchDeleteDto; -import com.admin.common.dto.BatchOperationResultDto; -import com.admin.common.dto.GostDto; -import com.admin.common.dto.NodeDto; -import com.admin.common.dto.NodeUpdateDto; -import com.admin.common.lang.R; -import com.admin.common.utils.GostUtil; -import com.admin.common.utils.WebSocketServer; -import com.admin.entity.*; -import com.admin.mapper.NodeMapper; -import com.admin.mapper.TunnelMapper; -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.core.conditions.update.LambdaUpdateWrapper; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.HashMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.regex.Pattern; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Slf4j -public class NodeServiceImpl extends ServiceImpl implements NodeService { - - - @Resource - @Lazy - private TunnelService tunnelService; - - @Resource - ViteConfigService viteConfigService; - - @Resource - ChainTunnelService chainTunnelService; - - @Resource - ForwardPortService forwardPortService; - - - @Override - public R createNode(NodeDto nodeDto) { - validatePortRange(nodeDto.getPort()); - - String normalizedV4 = normalizeV4(nodeDto.getServerIpV4(), nodeDto.getServerIp()); - String normalizedV6 = normalizeV6(nodeDto.getServerIpV6(), nodeDto.getServerIp()); - String primaryServerIp = pickPrimaryServerIp(normalizedV4, normalizedV6, nodeDto.getServerIp()); - - Node node = new Node(); - node.setSecret(IdUtil.simpleUUID()); - node.setStatus(0); - node.setPort(nodeDto.getPort()); - node.setName(nodeDto.getName()); - node.setServerIp(primaryServerIp); - node.setServerIpV4(normalizedV4); - node.setServerIpV6(normalizedV6); - long currentTime = System.currentTimeMillis(); - node.setCreatedTime(currentTime); - node.setUpdatedTime(currentTime); - node.setInterfaceName(nodeDto.getInterfaceName()); - this.save(node); - return R.ok(); - } - - @Override - public R getAllNodes() { - List nodeList = this.list(new QueryWrapper().orderByAsc("inx").orderByAsc("id")); - nodeList.forEach(node -> node.setSecret(null)); - return R.ok(nodeList); - } - - @Override - @Transactional - public R updateNodeOrder(Map params) { - if (!params.containsKey("nodes")) { - return R.err("缺少nodes参数"); - } - - @SuppressWarnings("unchecked") - List> nodesList = (List>) params.get("nodes"); - if (nodesList == null || nodesList.isEmpty()) { - return R.err("nodes参数不能为空"); - } - - List nodesToUpdate = new ArrayList<>(); - for (Map nodeData : nodesList) { - Long id = Long.valueOf(nodeData.get("id").toString()); - Integer inx = Integer.valueOf(nodeData.get("inx").toString()); - - Node node = new Node(); - node.setId(id); - node.setInx(inx); - nodesToUpdate.add(node); - } - - this.updateBatchById(nodesToUpdate); - return R.ok(); - } - - @Override - public R updateNode(NodeUpdateDto nodeUpdateDto) { - Node node = this.getById(nodeUpdateDto.getId()); - if (node == null) { - return R.err("节点不存在"); - } - - boolean online = node.getStatus() != null && node.getStatus() == 1; - Integer newHttp = nodeUpdateDto.getHttp(); - Integer newTls = nodeUpdateDto.getTls(); - Integer newSocks = nodeUpdateDto.getSocks(); - - boolean httpChanged = newHttp != null && !newHttp.equals(node.getHttp()); - boolean tlsChanged = newTls != null && !newTls.equals(node.getTls()); - boolean socksChanged = newSocks != null && !newSocks.equals(node.getSocks()); - - if (online && (httpChanged || tlsChanged || socksChanged)) { - JSONObject req = new JSONObject(); - req.put("http", newHttp); - req.put("tls", newTls); - req.put("socks", newSocks); - - GostDto gostResult = WebSocketServer.send_msg(node.getId(), req, "SetProtocol"); - if (!Objects.equals(gostResult.getMsg(), "OK")){ - return R.err(gostResult.getMsg()); - } - } - - - - Node updateNode = buildUpdateNode(nodeUpdateDto); - // Use LambdaUpdateWrapper to explicitly set nullable fields (serverIpV4/V6) - // because updateById() skips null fields by default - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(Node::getId, updateNode.getId()) - .set(Node::getName, updateNode.getName()) - .set(Node::getServerIp, updateNode.getServerIp()) - .set(Node::getServerIpV4, updateNode.getServerIpV4()) - .set(Node::getServerIpV6, updateNode.getServerIpV6()) - .set(Node::getPort, updateNode.getPort()) - .set(Node::getHttp, updateNode.getHttp()) - .set(Node::getTls, updateNode.getTls()) - .set(Node::getSocks, updateNode.getSocks()) - .set(Node::getInterfaceName, updateNode.getInterfaceName()) - .set(Node::getTcpListenAddr, updateNode.getTcpListenAddr()) - .set(Node::getUdpListenAddr, updateNode.getUdpListenAddr()) - .set(Node::getUpdatedTime, updateNode.getUpdatedTime()); - this.update(wrapper); - return R.ok(); - } - - @Override - public R deleteNode(Long id) { - Node node = this.getById(id); - if (node == null) { - return R.err("节点不存在"); - } - - List affected = chainTunnelService.list(new QueryWrapper().eq("node_id", id)); - Map> byTunnelId = affected.stream() - .filter(ct -> ct.getTunnelId() != null) - .collect(Collectors.groupingBy(ChainTunnel::getTunnelId)); - - for (Map.Entry> entry : byTunnelId.entrySet()) { - Long tunnelId = entry.getKey(); - Tunnel tunnel = tunnelService.getById(tunnelId); - - List before = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", tunnelId)); - - // Remove the node from the tunnel definition (do NOT delete the tunnel). - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnelId).eq("node_id", id)); - - if (tunnel == null) { - continue; - } - - List after = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", tunnelId)); - Integer removedChainType = entry.getValue().isEmpty() ? null : entry.getValue().get(0).getChainType(); - - // Keep tunnel.inIp consistent when it was auto-derived from entry nodes. - String oldDerivedInIp = buildDerivedInIp(before); - String newDerivedInIp = buildDerivedInIp(after); - if (shouldUpdateTunnelInIp(tunnel.getInIp(), oldDerivedInIp)) { - updateTunnelInIp(tunnelId, newDerivedInIp); - } - - boolean valid = isTunnelConfigValid(tunnel, after); - if (!valid) { - disableTunnelAndCleanupGostIfNeeded(tunnel, after, "node-delete"); - continue; - } - - // For tunnel-forwarding (type=2), removing a chain/out node requires rebuilding config. - // Removing an entry node (chainType=1) does not affect remaining nodes' chain targets. - if (tunnel.getType() != null && tunnel.getType() == 2 && removedChainType != null && removedChainType != 1) { - try { - cleanupGostConfig(after, tunnelId); - rebuildGostConfig(after, tunnel); - } catch (Exception e) { - log.warn("Failed to rebuild gost config after node delete. tunnelId={}, nodeId={}, err={}", tunnelId, id, e.getMessage(), e); - disableTunnelAndCleanupGostIfNeeded(tunnel, after, "node-delete:rebuild-failed"); - } - } - } - - // Remove per-forward port allocations on this node (avoid orphan ForwardPort rows). - try { - forwardPortService.remove(new QueryWrapper().eq("node_id", id)); - } catch (Exception e) { - log.warn("Failed to cleanup forward ports when deleting node. nodeId={}, err={}", id, e.getMessage(), e); - } - - this.removeById(id); - return R.ok(); - } - - private boolean isTunnelConfigValid(Tunnel tunnel, List chainTunnels) { - if (tunnel == null || chainTunnels == null) { - return false; - } - - long inCount = chainTunnels.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 1) - .count(); - if (inCount <= 0) { - return false; - } - - if (tunnel.getType() != null && tunnel.getType() == 2) { - long outCount = chainTunnels.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 3) - .count(); - return outCount > 0; - } - - return true; - } - - private boolean shouldUpdateTunnelInIp(String currentInIp, String oldDerivedInIp) { - if (StrUtil.isBlank(currentInIp)) { - return true; - } - if (oldDerivedInIp == null) { - return false; - } - return Objects.equals(currentInIp, oldDerivedInIp); - } - - private void updateTunnelInIp(Long tunnelId, String derivedInIp) { - Tunnel update = new Tunnel(); - update.setId(tunnelId); - update.setInIp(derivedInIp == null ? "" : derivedInIp); - update.setUpdatedTime(System.currentTimeMillis()); - tunnelService.updateById(update); - } - - private String buildDerivedInIp(List chainTunnels) { - if (chainTunnels == null) { - return ""; - } - List inNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 1) - .collect(Collectors.toList()); - if (inNodes.isEmpty()) { - return ""; - } - - StringBuilder inIp = new StringBuilder(); - for (ChainTunnel inNode : inNodes) { - Node n = this.getById(inNode.getNodeId()); - if (n == null || StrUtil.isBlank(n.getServerIp())) { - return null; - } - inIp.append(n.getServerIp()).append(","); - } - inIp.deleteCharAt(inIp.length() - 1); - return inIp.toString(); - } - - private void disableTunnelAndCleanupGostIfNeeded(Tunnel tunnel, List remaining, String reason) { - try { - Tunnel update = new Tunnel(); - update.setId(tunnel.getId()); - update.setStatus(0); - update.setUpdatedTime(System.currentTimeMillis()); - tunnelService.updateById(update); - } catch (Exception e) { - log.warn("Failed to disable tunnel. tunnelId={}, reason={}, err={}", tunnel.getId(), reason, e.getMessage(), e); - } - - if (tunnel.getType() != null && tunnel.getType() == 2) { - try { - cleanupGostConfig(remaining, tunnel.getId()); - } catch (Exception e) { - log.warn("Failed to cleanup gost config when disabling tunnel. tunnelId={}, reason={}, err={}", tunnel.getId(), reason, e.getMessage(), e); - } - } - } - - private void cleanupGostConfig(List chainTunnels, Long tunnelId) { - if (chainTunnels == null) { - return; - } - for (ChainTunnel chainTunnel : chainTunnels) { - if (chainTunnel.getChainType() == null) { - continue; - } - 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 void rebuildGostConfig(List chainTunnels, Tunnel tunnel) { - if (tunnel == null || chainTunnels == null) { - return; - } - - Map nodes = new HashMap<>(); - for (ChainTunnel ct : chainTunnels) { - Node n = this.getById(ct.getNodeId()); - if (n != null) { - nodes.put(n.getId(), n); - } - } - - List inNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 1) - .collect(Collectors.toList()); - - Map> chainNodesMap = chainTunnels.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 2) - .collect(Collectors.groupingBy(ct -> ct.getInx() != null ? ct.getInx() : 0)); - - List> chainNodesList = chainNodesMap.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(Map.Entry::getValue) - .collect(Collectors.toList()); - - List outNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 3) - .collect(Collectors.toList()); - - if (tunnel.getType() != null && 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 chainNode : chainNodesList.get(i)) { - if (i + 1 >= chainNodesList.size()) { - GostUtil.AddChains(chainNode.getNodeId(), outNodes, nodes); - } else { - GostUtil.AddChains(chainNode.getNodeId(), chainNodesList.get(i + 1), nodes); - } - GostUtil.AddChainService(chainNode.getNodeId(), chainNode, nodes); - } - } - - for (ChainTunnel outNode : outNodes) { - GostUtil.AddChainService(outNode.getNodeId(), outNode, nodes); - } - } - } - - - @Override - public R getInstallCommand(Long id) { - Node node = this.getById(id); - if (node == null) { - return R.err("节点不存在"); - } - ViteConfig viteConfig = viteConfigService.getOne(new QueryWrapper().eq("name", "ip")); - if (viteConfig == null) return R.err("请先前往网站配置中设置ip"); - StringBuilder command = new StringBuilder(); - command.append("curl -L https://github.com/Sagit-chu/flux-panel/releases/latest/download/install.sh") - .append(" -o ./install.sh && chmod +x ./install.sh && "); - String processedServerAddr = GostUtil.processServerAddress(viteConfig.getValue()); - command.append("./install.sh") - .append(" -a ").append(processedServerAddr) // 服务器地址 - .append(" -s ").append(node.getSecret()); // 节点密钥 - return R.ok(command); - - } - - - private Node buildUpdateNode(NodeUpdateDto nodeUpdateDto) { - validatePortRange(nodeUpdateDto.getPort()); - - String normalizedV4 = normalizeV4(nodeUpdateDto.getServerIpV4(), nodeUpdateDto.getServerIp()); - String normalizedV6 = normalizeV6(nodeUpdateDto.getServerIpV6(), nodeUpdateDto.getServerIp()); - String primaryServerIp = pickPrimaryServerIp(normalizedV4, normalizedV6, nodeUpdateDto.getServerIp()); - - Node node = new Node(); - node.setId(nodeUpdateDto.getId()); - node.setName(nodeUpdateDto.getName()); - node.setServerIp(primaryServerIp); - node.setServerIpV4(normalizedV4); - node.setServerIpV6(normalizedV6); - node.setPort(nodeUpdateDto.getPort()); - node.setHttp(nodeUpdateDto.getHttp()); - node.setTls(nodeUpdateDto.getTls()); - node.setSocks(nodeUpdateDto.getSocks()); - node.setUpdatedTime(System.currentTimeMillis()); - node.setInterfaceName(nodeUpdateDto.getInterfaceName()); - node.setTcpListenAddr(nodeUpdateDto.getTcpListenAddr()); - node.setUdpListenAddr(nodeUpdateDto.getUdpListenAddr()); - return node; - } - - private String pickPrimaryServerIp(String serverIpV4, String serverIpV6, String fallback) { - if (StrUtil.isNotBlank(serverIpV4)) { - return serverIpV4.trim(); - } - if (StrUtil.isNotBlank(serverIpV6)) { - return serverIpV6.trim(); - } - return fallback != null ? fallback.trim() : null; - } - - private String normalizeV4(String serverIpV4, String legacyServerIp) { - if (StrUtil.isNotBlank(serverIpV4)) { - return serverIpV4.trim(); - } - if (StrUtil.isNotBlank(legacyServerIp) && looksLikeIpv4(legacyServerIp.trim())) { - return legacyServerIp.trim(); - } - return null; - } - - private String normalizeV6(String serverIpV6, String legacyServerIp) { - if (StrUtil.isNotBlank(serverIpV6)) { - return serverIpV6.trim(); - } - if (StrUtil.isNotBlank(legacyServerIp) && looksLikeIpv6(legacyServerIp.trim())) { - return legacyServerIp.trim(); - } - return null; - } - - private boolean looksLikeIpv4(String value) { - // 仅用于判定地址族(不解析域名) - Pattern ipv4 = Pattern.compile("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"); - return ipv4.matcher(value).matches(); - } - - private boolean looksLikeIpv6(String value) { - // 粗略判定 IPv6(与 GostUtil.processServerAddress 一致思路) - long colonCount = value.chars().filter(ch -> ch == ':').count(); - return colonCount >= 2; - } - - - private void validatePortRange(String port) { - Pattern PORT_PATTERN = Pattern.compile( "([0-9]{1,5})(-([0-9]{1,5}))?"); - if (port == null || port.isEmpty()) { - throw new RuntimeException("可用端口不合法"); - } - String[] parts = port.split(","); - for (String part : parts) { - part = part.trim(); - if (!PORT_PATTERN.matcher(part).matches()) { - throw new RuntimeException("可用端口不合法"); - } - if (part.contains("-")) { - String[] range = part.split("-"); - int start = Integer.parseInt(range[0]); - int end = Integer.parseInt(range[1]); - if (start < 0 || end < 0 || end > 65535 || start > end) { - throw new RuntimeException("可用端口不合法"); - } - } else { - int ports = Integer.parseInt(part); - if (ports < 0 || ports > 65535) { - throw new RuntimeException("可用端口不合法"); - } - } - } - } - - @Override - @Transactional - public R batchDeleteNodes(BatchDeleteDto batchDeleteDto) { - BatchOperationResultDto result = new BatchOperationResultDto(); - - for (Long id : batchDeleteDto.getIds()) { - try { - R deleteResult = deleteNode(id); - if (deleteResult.getCode() == 0) { - result.incrementSuccess(); - } else { - result.addFailedItem(id, deleteResult.getMsg()); - } - } catch (Exception e) { - result.addFailedItem(id, e.getMessage()); - } - } - - return R.ok(result); - } - - - - -} diff --git a/springboot-backend/src/main/java/com/admin/service/impl/SpeedLimitServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/SpeedLimitServiceImpl.java deleted file mode 100644 index d39da81..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/SpeedLimitServiceImpl.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.admin.service.impl; - -import com.admin.common.dto.GostDto; -import com.admin.common.dto.SpeedLimitDto; -import com.admin.common.dto.SpeedLimitUpdateDto; -import com.admin.common.lang.R; -import com.admin.common.utils.GostUtil; -import com.admin.entity.*; -import com.admin.mapper.SpeedLimitMapper; -import com.admin.service.*; -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.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -/** - *

- * 限速规则服务实现类 - * 提供限速规则的增删改查功能,包括与Gost服务的集成 - * 支持限速器的创建、更新、删除和查询操作 - *

- * - * @author QAQ - * @since 2025-06-04 - */ -@Service -public class SpeedLimitServiceImpl extends ServiceImpl implements SpeedLimitService { - - @Resource - @Lazy - TunnelService tunnelService; - - @Resource - NodeService nodeService; - - @Resource - UserTunnelService userTunnelService; - - @Resource - ChainTunnelService chainTunnelService; - - - @Override - public R createSpeedLimit(SpeedLimitDto speedLimitDto) { - Tunnel tunnel = tunnelService.getById(speedLimitDto.getTunnelId()); - if (tunnel == null) return R.err("隧道不存在"); - - SpeedLimit speedLimit = new SpeedLimit(); - BeanUtils.copyProperties(speedLimitDto, speedLimit); - long currentTime = System.currentTimeMillis(); - speedLimit.setCreatedTime(currentTime); - speedLimit.setUpdatedTime(currentTime); - speedLimit.setStatus(1); - this.save(speedLimit); - - String speedInMBps = convertBitsToMBps(speedLimit.getSpeed()); - - List limit_success = new ArrayList<>(); - - List tunnelList = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", speedLimit.getTunnelId())); - for (ChainTunnel chainTunnel : tunnelList) { - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node != null) { - GostDto gostDto = GostUtil.AddLimiters(node.getId(), speedLimit.getId(), speedInMBps); - if (Objects.equals(gostDto.getMsg(), "OK")){ - limit_success.add(node.getId()); - }else { - this.removeById(speedLimit.getId()); - for (Long node_id : limit_success) { - GostDto deleteLimiters = GostUtil.DeleteLimiters(node_id, speedLimit.getId()); - System.out.println(deleteLimiters); - } - return R.err(gostDto.getMsg()); - } - } - } - return R.ok(); - } - - @Override - public R getAllSpeedLimits() { - List speedLimits = this.list(); - return R.ok(speedLimits); - } - - @Override - public R updateSpeedLimit(SpeedLimitUpdateDto speedLimitUpdateDto) { - SpeedLimit speedLimit = this.getById(speedLimitUpdateDto.getId()); - if (speedLimit == null) return R.err("限速不存在"); - - BeanUtils.copyProperties(speedLimitUpdateDto, speedLimit); - speedLimit.setUpdatedTime(System.currentTimeMillis()); - - String speedInMBps = convertBitsToMBps(speedLimit.getSpeed()); - List tunnelList = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", speedLimit.getTunnelId())); - for (ChainTunnel chainTunnel : tunnelList) { - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node != null) { - GostDto gostDto = GostUtil.UpdateLimiters(node.getId(), speedLimit.getId(), speedInMBps); - if (!Objects.equals(gostDto.getMsg(), "OK")) return R.err(gostDto.getMsg()); - } - } - this.updateById(speedLimit); - return R.ok(); - } - - @Override - public R deleteSpeedLimit(Long id) { - // 1. 验证限速规则是否存在 - SpeedLimit speedLimit = this.getById(id); - if (speedLimit == null) return R.err("限速规则不存在"); - - - int userCount = userTunnelService.count(new QueryWrapper().eq("speed_id", speedLimit.getId())); - if (userCount != 0) return R.err("该限速规则还有用户在使用 请先取消分配"); - - - List tunnelList = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", speedLimit.getTunnelId())); - for (ChainTunnel chainTunnel : tunnelList) { - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node != null) { - GostDto gostDto = GostUtil.DeleteLimiters(node.getId(), speedLimit.getId()); - if (!Objects.equals(gostDto.getMsg(), "OK"))return R.err(gostDto.getMsg()); - } - } - this.removeById(id); - return R.ok(); - } - - private String convertBitsToMBps(Integer speedInBits) { - double mbs = speedInBits / 8.0; - BigDecimal bd = new BigDecimal(mbs).setScale(1, RoundingMode.HALF_UP); - return bd.doubleValue() + ""; - } -} diff --git a/springboot-backend/src/main/java/com/admin/service/impl/StatisticsFlowServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/StatisticsFlowServiceImpl.java deleted file mode 100644 index c290185..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/StatisticsFlowServiceImpl.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.admin.service.impl; - -import com.admin.entity.StatisticsFlow; -import com.admin.mapper.StatisticsFlowMapper; -import com.admin.service.StatisticsFlowService; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import org.springframework.stereotype.Service; - -/** - *

- * 服务实现类 - *

- * - * @author QAQ - * @since 2025-08-14 - */ -@Service -public class StatisticsFlowServiceImpl extends ServiceImpl implements StatisticsFlowService { - -} 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 deleted file mode 100644 index 982f8a8..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java +++ /dev/null @@ -1,1448 +0,0 @@ -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.GroupPermissionGrantMapper; -import com.admin.mapper.TunnelGroupTunnelMapper; -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.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -/** - * - * @author QAQ - * @since 2025-06-03 - */ -@Service -public class TunnelServiceImpl extends ServiceImpl implements TunnelService { - - - @Resource - UserTunnelMapper userTunnelMapper; - - @Resource - NodeService nodeService; - - @Resource - ForwardService forwardService; - - @Resource - UserTunnelService userTunnelService; - - @Resource - ChainTunnelService chainTunnelService; - - @Resource - ForwardPortService forwardPortService; - - @Resource - TunnelGroupTunnelMapper tunnelGroupTunnelMapper; - - @Resource - GroupPermissionGrantMapper groupPermissionGrantMapper; - - - @Override - public R createTunnel(TunnelDto tunnelDto) { - - int count = this.count(new QueryWrapper().eq("name", tunnelDto.getName())); - if (count > 0) return R.err("隧道名称重复"); - if (tunnelDto.getType() == 2 && tunnelDto.getOutNodeId() == null) return R.err("出口不能为空"); - - - List chainTunnels = new ArrayList<>(); - Map nodes = new HashMap<>(); - - List node_ids = new ArrayList<>(); - for (ChainTunnel in_node : tunnelDto.getInNodeId()) { - node_ids.add(in_node.getNodeId()); - chainTunnels.add(in_node); - - Node node = nodeService.getById(in_node.getNodeId()); - if (node == null) return R.err("节点不存在"); - nodes.put(node.getId(), node); - } - - if (tunnelDto.getType() == 2) { - // 处理转发链节点,为每一跳设置inx - int inx = 1; - for (List chainNode : tunnelDto.getChainNodes()) { - for (ChainTunnel chain_node : chainNode) { - node_ids.add(chain_node.getNodeId()); - Node node = nodeService.getById(chain_node.getNodeId()); - if (node == null) return R.err("节点不存在"); - nodes.put(node.getId(), node); - Integer nodePort = getNodePort(chain_node.getNodeId()); - chain_node.setPort(nodePort); - chain_node.setInx(inx); // 设置转发链序号 - chainTunnels.add(chain_node); - } - inx++; // 每一跳递增 - } - for (ChainTunnel out_node : tunnelDto.getOutNodeId()) { - node_ids.add(out_node.getNodeId()); - Node node = nodeService.getById(out_node.getNodeId()); - if (node == null) return R.err("节点不存在"); - nodes.put(node.getId(), node); - Integer nodePort = getNodePort(out_node.getNodeId()); - out_node.setPort(nodePort); - chainTunnels.add(out_node); - } - - } - Set set = new HashSet<>(node_ids); - boolean hasDuplicate = set.size() != node_ids.size(); - if (hasDuplicate) return R.err("节点重复"); - - List list = nodeService.list(new QueryWrapper().in("id", node_ids)); - if (list.size() != node_ids.size()) return R.err("部分节点不存在"); - for (Node node : list) { - if (node.getStatus() != 1) return R.err("部分节点不在线"); - } - - - Tunnel tunnel = new Tunnel(); - BeanUtils.copyProperties(tunnelDto, tunnel); - tunnel.setStatus(1); - long currentTime = System.currentTimeMillis(); - tunnel.setCreatedTime(currentTime); - tunnel.setUpdatedTime(currentTime); - - // When tunnels are ordered via `inx`, new tunnels should be appended. - // Only apply this when an order already exists (max `inx` > 0) to avoid - // changing behavior for deployments still relying on local ordering. - Tunnel lastByInx = this.getOne(new QueryWrapper() - .select("inx") - .orderByDesc("inx") - .orderByDesc("id") - .last("LIMIT 1")); - Integer maxInx = lastByInx == null ? null : lastByInx.getInx(); - if (maxInx != null && maxInx > 0) { - tunnel.setInx(maxInx + 1); - } - if (StringUtils.isEmpty(tunnel.getInIp())){ - java.util.LinkedHashSet inIps = new java.util.LinkedHashSet<>(); - for (ChainTunnel chainTunnel : tunnelDto.getInNodeId()) { - Node node = nodes.get(chainTunnel.getNodeId()); - if (node == null) continue; - if (cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIpV4())) { - inIps.add(node.getServerIpV4().trim()); - } - if (cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIpV6())) { - inIps.add(node.getServerIpV6().trim()); - } - if (cn.hutool.core.util.StrUtil.isBlank(node.getServerIpV4()) - && cn.hutool.core.util.StrUtil.isBlank(node.getServerIpV6()) - && cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIp())) { - inIps.add(node.getServerIp().trim()); - } - } - if (!inIps.isEmpty()) { - tunnel.setInIp(String.join(",", inIps)); - } - } - - this.save(tunnel); - for (ChainTunnel chainTunnel : chainTunnels) { - chainTunnel.setTunnelId(tunnel.getId()); - } - chainTunnelService.saveBatch(chainTunnels); - - List chain_success = new ArrayList<>(); - List service_success = new ArrayList<>(); - - - - if (tunnel.getType() == 2) { - - for (ChainTunnel in_node : tunnelDto.getInNodeId()) { - // 创建Chain, 指向chainNode的第一跳。如果chainNode为空就是指向出口 - if (tunnelDto.getChainNodes().isEmpty()) { // 指向出口 - GostDto gostDto; - try { - gostDto = GostUtil.AddChains(in_node.getNodeId(), tunnelDto.getOutNodeId(), nodes); - } catch (RuntimeException e) { - this.removeById(tunnel.getId()); - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); - return R.err(e.getMessage()); - } - if (!Objects.equals(gostDto.getMsg(), "OK")) { - this.removeById(tunnel.getId()); - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); - return R.err(gostDto.getMsg()); - } - - } else { - GostDto gostDto; - try { - gostDto = GostUtil.AddChains(in_node.getNodeId(), tunnelDto.getChainNodes().getFirst(), nodes);// 指向第一跳 - } catch (RuntimeException e) { - this.removeById(tunnel.getId()); - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); - for (JSONObject chainSuccess : chain_success) { - GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); - System.out.println(deleteChains); - } - return R.err(e.getMessage()); - } - if (Objects.equals(gostDto.getMsg(), "OK")){ - JSONObject data = new JSONObject(); - data.put("node_id", in_node.getNodeId()); - data.put("name", "chains_" + tunnel.getId()); - chain_success.add(data); - }else { - this.removeById(tunnel.getId()); - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); - for (JSONObject chainSuccess : chain_success) { - GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); - System.out.println(deleteChains); - } - return R.err(gostDto.getMsg()); - } - } - } - - for (int i = 0; i < tunnelDto.getChainNodes().size(); i++) { - // 创建Chain和Service。每一条的Chain都是指向下一跳。最后一跳指向出口, Service是监听端口 - List chainTunnels1 = tunnelDto.getChainNodes().get(i); - for (ChainTunnel chainTunnel : chainTunnels1) { - int inx = i+1; - if (inx >= tunnelDto.getChainNodes().size()) { // 指向出口 - GostDto gostDto; - try { - gostDto = GostUtil.AddChains(chainTunnel.getNodeId(), tunnelDto.getOutNodeId(), nodes); - } catch (RuntimeException e) { - this.removeById(tunnel.getId()); - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); - for (JSONObject chainSuccess : chain_success) { - GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); - System.out.println(deleteChains); - } - return R.err(e.getMessage()); - } - if (Objects.equals(gostDto.getMsg(), "OK")){ - JSONObject data = new JSONObject(); - data.put("node_id", chainTunnel.getNodeId()); - data.put("name", "chains_" + tunnel.getId()); - chain_success.add(data); - }else { - this.removeById(tunnel.getId()); - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); - for (JSONObject chainSuccess : chain_success) { - GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); - System.out.println(deleteChains); - } - return R.err(gostDto.getMsg()); - } - } else { - GostDto gostDto; - try { - gostDto = GostUtil.AddChains(chainTunnel.getNodeId(), tunnelDto.getChainNodes().get(inx), nodes); - } catch (RuntimeException e) { - this.removeById(tunnel.getId()); - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); - for (JSONObject chainSuccess : chain_success) { - GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); - System.out.println(deleteChains); - } - return R.err(e.getMessage()); - } - if (Objects.equals(gostDto.getMsg(), "OK")){ - JSONObject data = new JSONObject(); - data.put("node_id", chainTunnel.getNodeId()); - data.put("name", "chains_" + tunnel.getId()); - chain_success.add(data); - }else { - this.removeById(tunnel.getId()); - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); - for (JSONObject chainSuccess : chain_success) { - GostDto deleteChains = GostUtil.DeleteChains(chainSuccess.getLong("node_id"), chainSuccess.getString("name")); - System.out.println(deleteChains); - } - return R.err(gostDto.getMsg()); - } - } - - GostDto gostDto = GostUtil.AddChainService(chainTunnel.getNodeId(), chainTunnel, nodes); - if (Objects.equals(gostDto.getMsg(), "OK")){ - JSONObject data = new JSONObject(); - data.put("node_id", chainTunnel.getNodeId()); - data.put("name", tunnel.getId() + "_tls"); - service_success.add(data); - }else { - this.removeById(tunnel.getId()); - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); - for (JSONObject serviceSuccess : service_success) { - JSONArray jsonArray = new JSONArray(); - jsonArray.add(serviceSuccess.getString("name")); - GostDto deleteService = GostUtil.DeleteService(serviceSuccess.getLong("node_id"), jsonArray); - System.out.println(deleteService); - } - return R.err(gostDto.getMsg()); - } - } - - } - - - for (ChainTunnel out_node : tunnelDto.getOutNodeId()) { - GostDto gostDto = GostUtil.AddChainService(out_node.getNodeId(), out_node, nodes); - if (Objects.equals(gostDto.getMsg(), "OK")){ - JSONObject data = new JSONObject(); - data.put("node_id", out_node.getNodeId()); - data.put("name", tunnel.getId() + "_tls"); - service_success.add(data); - }else { - this.removeById(tunnel.getId()); - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", tunnel.getId())); - for (JSONObject serviceSuccess : service_success) { - JSONArray jsonArray = new JSONArray(); - jsonArray.add(serviceSuccess.getString("name")); - GostDto deleteService = GostUtil.DeleteService(serviceSuccess.getLong("node_id"), jsonArray); - System.out.println(deleteService); - } - return R.err(gostDto.getMsg()); - } - } - - } - return R.ok(); - } - - - @Override - public R getAllTunnels() { - List tunnelList = this.list(new QueryWrapper().orderByAsc("inx").orderByAsc("id")); - - // 查询所有隧道的ChainTunnel信息 - List tunnelIds = tunnelList.stream() - .map(Tunnel::getId) - .collect(Collectors.toList()); - - if (tunnelIds.isEmpty()) { - return R.ok(new ArrayList()); - } - - // 批量查询所有ChainTunnel记录 - List allChainTunnels = chainTunnelService.list( - new QueryWrapper().in("tunnel_id", tunnelIds) - ); - - // 按tunnelId分组 - Map> chainTunnelMap = allChainTunnels.stream() - .collect(Collectors.groupingBy(ChainTunnel::getTunnelId)); - - // 转换为TunnelDetailDto列表 - List detailDtoList = tunnelList.stream() - .map(tunnel -> { - TunnelDetailDto detailDto = new TunnelDetailDto(); - BeanUtils.copyProperties(tunnel, detailDto); - - List chainTunnels = chainTunnelMap.getOrDefault(tunnel.getId(), new ArrayList<>()); - - // 按chainType分类节点 - // 入口节点 (chainType = 1) - List inNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 1) - .collect(Collectors.toList()); - detailDto.setInNodeId(inNodes); - - detailDto.setInIp(tunnel.getInIp()); - - // 转发链节点 (chainType = 2) - 按inx分组 - Map> chainNodesMap = chainTunnels.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 2) - .collect(Collectors.groupingBy( - ct -> ct.getInx() != null ? ct.getInx() : 0, - Collectors.toList() - )); - - // 将Map转换为按inx排序的二维列表 - List> chainNodesList = chainNodesMap.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(Map.Entry::getValue) - .collect(Collectors.toList()); - detailDto.setChainNodes(chainNodesList); - - // 出口节点 (chainType = 3) - List outNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 3) - .collect(Collectors.toList()); - detailDto.setOutNodeId(outNodes); - - return detailDto; - }) - .collect(Collectors.toList()); - - return R.ok(detailDtoList); - } - - @Override - @Transactional - public R updateTunnelOrder(Map params) { - if (!params.containsKey("tunnels")) { - return R.err("缺少tunnels参数"); - } - - @SuppressWarnings("unchecked") - List> tunnelsList = (List>) params.get("tunnels"); - if (tunnelsList == null || tunnelsList.isEmpty()) { - return R.err("tunnels参数不能为空"); - } - - List tunnelsToUpdate = new ArrayList<>(); - for (Map tunnelData : tunnelsList) { - Long id = Long.valueOf(tunnelData.get("id").toString()); - Integer inx = Integer.valueOf(tunnelData.get("inx").toString()); - - Tunnel tunnel = new Tunnel(); - tunnel.setId(id); - tunnel.setInx(inx); - tunnelsToUpdate.add(tunnel); - } - - this.updateBatchById(tunnelsToUpdate); - return R.ok(); - } - - - @Override - public R updateTunnel(TunnelUpdateDto tunnelUpdateDto) { - Tunnel existingTunnel = this.getById(tunnelUpdateDto.getId()); - if (existingTunnel == null) return R.err("隧道不存在"); - - List oldChainTunnels = chainTunnelService.list( - new QueryWrapper().eq("tunnel_id", tunnelUpdateDto.getId()) - ); - - boolean hasNodeChanges = detectNodeChanges(oldChainTunnels, tunnelUpdateDto); - - if (hasNodeChanges && tunnelUpdateDto.getInNodeId() != null) { - List backupChains = deepCopyChainTunnels(oldChainTunnels); - - Set oldEntryNodeIds = oldChainTunnels.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 1) - .map(ChainTunnel::getNodeId) - .collect(Collectors.toSet()); - - Set newEntryNodeIds = tunnelUpdateDto.getInNodeId().stream() - .map(ChainTunnel::getNodeId) - .collect(Collectors.toSet()); - - List nodeIds = new ArrayList<>(); - Map nodes = new HashMap<>(); - - for (ChainTunnel inNode : tunnelUpdateDto.getInNodeId()) { - nodeIds.add(inNode.getNodeId()); - Node node = nodeService.getById(inNode.getNodeId()); - if (node == null) return R.err("入口节点不存在: " + inNode.getNodeId()); - if (node.getStatus() != 1) return R.err("入口节点不在线: " + node.getName()); - nodes.put(node.getId(), node); - } - - List newChainTunnels = new ArrayList<>(); - for (ChainTunnel inNode : tunnelUpdateDto.getInNodeId()) { - inNode.setTunnelId(existingTunnel.getId()); - inNode.setChainType(1); - newChainTunnels.add(inNode); - } - - if (existingTunnel.getType() == 2) { - if (tunnelUpdateDto.getOutNodeId() == null || tunnelUpdateDto.getOutNodeId().isEmpty()) { - return R.err("隧道转发类型必须配置出口节点"); - } - - List> chainNodes = tunnelUpdateDto.getChainNodes() == null ? - new ArrayList<>() : tunnelUpdateDto.getChainNodes(); - - int inx = 1; - for (List hop : chainNodes) { - for (ChainTunnel chainNode : hop) { - nodeIds.add(chainNode.getNodeId()); - Node node = nodeService.getById(chainNode.getNodeId()); - if (node == null) return R.err("转发链节点不存在: " + chainNode.getNodeId()); - if (node.getStatus() != 1) return R.err("转发链节点不在线: " + node.getName()); - nodes.put(node.getId(), node); - - Integer port = getNodePort(chainNode.getNodeId()); - chainNode.setPort(port); - chainNode.setInx(inx); - chainNode.setChainType(2); - chainNode.setTunnelId(existingTunnel.getId()); - newChainTunnels.add(chainNode); - } - inx++; - } - - for (ChainTunnel outNode : tunnelUpdateDto.getOutNodeId()) { - nodeIds.add(outNode.getNodeId()); - Node node = nodeService.getById(outNode.getNodeId()); - if (node == null) return R.err("出口节点不存在: " + outNode.getNodeId()); - if (node.getStatus() != 1) return R.err("出口节点不在线: " + node.getName()); - nodes.put(node.getId(), node); - - Integer port = getNodePort(outNode.getNodeId()); - outNode.setPort(port); - outNode.setChainType(3); - outNode.setTunnelId(existingTunnel.getId()); - newChainTunnels.add(outNode); - } - } - - Set nodeIdSet = new HashSet<>(nodeIds); - if (nodeIdSet.size() != nodeIds.size()) { - return R.err("节点配置重复"); - } - - try { - cleanupGostConfig(oldChainTunnels, existingTunnel.getId()); - - chainTunnelService.remove( - new QueryWrapper().eq("tunnel_id", existingTunnel.getId()) - ); - - R applyResult = applyNewGostConfig(tunnelUpdateDto, existingTunnel, nodes); - if (applyResult.getCode() != 0) { - chainTunnelService.saveBatch(backupChains); - rebuildGostConfig(backupChains, existingTunnel); - return R.err("更新失败,已回滚: " + applyResult.getMsg()); - } - - chainTunnelService.saveBatch(newChainTunnels); - - syncForwardsForEntryNodeChanges(existingTunnel.getId(), oldEntryNodeIds, newEntryNodeIds); - - } 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()); - - boolean forceRegenerateInIp = hasNodeChanges && tunnelUpdateDto.getInNodeId() != null; - if (StringUtils.isEmpty(tunnel.getInIp()) || forceRegenerateInIp) { - java.util.LinkedHashSet inIps = new java.util.LinkedHashSet<>(); - List chainTunnels = chainTunnelService.list( - new QueryWrapper().eq("tunnel_id", tunnel.getId()).eq("chain_type", 1) - ); - for (ChainTunnel chainTunnel : chainTunnels) { - Node node = nodeService.getById(chainTunnel.getNodeId()); - if (node == null) return R.err("隧道节点数据错误,部分节点不存在"); - - if (cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIpV4())) { - inIps.add(node.getServerIpV4().trim()); - } - if (cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIpV6())) { - inIps.add(node.getServerIpV6().trim()); - } - if (cn.hutool.core.util.StrUtil.isBlank(node.getServerIpV4()) - && cn.hutool.core.util.StrUtil.isBlank(node.getServerIpV6()) - && cn.hutool.core.util.StrUtil.isNotBlank(node.getServerIp())) { - inIps.add(node.getServerIp().trim()); - } - } - if (!inIps.isEmpty()) { - tunnel.setInIp(String.join(",", inIps)); - } - } - - tunnel.setUpdatedTime(System.currentTimeMillis()); - this.updateById(tunnel); - return R.ok(); - } - - - @Override - public R deleteTunnel(Long id) { - Tunnel tunnel = this.getById(id); - if (tunnel == null) return R.err("隧道不存在"); - List forwardList = forwardService.list(new QueryWrapper().eq("tunnel_id", id)); - for (Forward forward : forwardList) { - forwardService.deleteForward(forward.getId()); - } - - List userTunnels = userTunnelService.list(new QueryWrapper().eq("tunnel_id", id)); - if (!userTunnels.isEmpty()) { - List userTunnelIds = userTunnels.stream().map(UserTunnel::getId).toList(); - groupPermissionGrantMapper.delete(new QueryWrapper().in("user_tunnel_id", userTunnelIds)); - } - - forwardService.remove(new QueryWrapper().eq("tunnel_id", id)); - userTunnelService.remove(new QueryWrapper().eq("tunnel_id", id)); - tunnelGroupTunnelMapper.delete(new QueryWrapper().eq("tunnel_id", id)); - this.removeById(id); - - List chainTunnels = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", id)); - for (ChainTunnel chainTunnel : chainTunnels) { - if (chainTunnel.getChainType() == 1){ // 入口 - GostUtil.DeleteChains(chainTunnel.getNodeId(), "chains_" + chainTunnel.getTunnelId()); - } - else if (chainTunnel.getChainType() == 2){ // 链 - GostUtil.DeleteChains(chainTunnel.getNodeId(), "chains_" + chainTunnel.getTunnelId()); - JSONArray services = new JSONArray(); - services.add(chainTunnel.getTunnelId() + "_tls"); - GostUtil.DeleteService(chainTunnel.getNodeId(), services); - } - else { // 出口 - JSONArray services = new JSONArray(); - services.add(chainTunnel.getTunnelId() + "_tls"); - GostUtil.DeleteService(chainTunnel.getNodeId(), services); - } - } - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", id)); - return R.ok(); - } - - - @Override - public R userTunnel() { - List tunnelEntities; - Integer roleId = JwtUtil.getRoleIdFromToken(); - Integer userId = JwtUtil.getUserIdFromToken(); - if (roleId == 0) { - tunnelEntities = this.list(new QueryWrapper() - .eq("status", 1) - .orderByAsc("inx") - .orderByAsc("id")); - } else { - tunnelEntities = java.util.Collections.emptyList(); // 返回空列表 - List userTunnels = userTunnelMapper.selectList( - new QueryWrapper().eq("user_id", userId) - ); - if (!userTunnels.isEmpty()) { - List tunnelIds = userTunnels.stream() - .map(UserTunnel::getTunnelId) - .collect(Collectors.toList()); - tunnelEntities = this.list(new QueryWrapper() - .in("id", tunnelIds) - .eq("status", 1) - .orderByAsc("inx") - .orderByAsc("id")); - } - - } - return R.ok(tunnelEntities); - } - - - @Override - public R diagnoseTunnel(Long tunnelId) { - Tunnel tunnel = this.getById(tunnelId); - if (tunnel == null) { - return R.err("隧道不存在"); - } - - List chainTunnels = chainTunnelService.list( - new QueryWrapper().eq("tunnel_id", tunnelId) - ); - - if (chainTunnels.isEmpty()) { - return R.err("隧道配置不完整"); - } - - List inNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() == 1) - .toList(); - - Map> chainNodesMap = chainTunnels.stream() - .filter(ct -> ct.getChainType() == 2) - .collect(Collectors.groupingBy( - ct -> ct.getInx() != null ? ct.getInx() : 0, - Collectors.toList() - )); - - List> chainNodesList = chainNodesMap.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(Map.Entry::getValue) - .toList(); - - List outNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() == 3) - .toList(); - - List> futures = new ArrayList<>(); - - if (tunnel.getType() == 1) { - for (ChainTunnel inNode : inNodes) { - Node node = nodeService.getById(inNode.getNodeId()); - if (node != null) { - final Node finalNode = node; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalNode, "www.google.com", 443, "入口(" + finalNode.getName() + ")->外网" - ); - result.setFromChainType(1); - return 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) { - final Node finalFromNode = fromNode; - final Node finalToNode = toNode; - final ChainTunnel finalFirstChainNode = firstChainNode; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalFirstChainNode.getPort(), - "入口(" + finalFromNode.getName() + ")->第1跳(" + finalToNode.getName() + ")" - ); - result.setFromChainType(1); - result.setToChainType(2); - result.setToInx(finalFirstChainNode.getInx()); - return result; - })); - } - } - } else if (!outNodes.isEmpty()) { - for (ChainTunnel outNode : outNodes) { - Node toNode = nodeService.getById(outNode.getNodeId()); - if (toNode != null) { - final Node finalFromNode = fromNode; - final Node finalToNode = toNode; - final ChainTunnel finalOutNode = outNode; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalOutNode.getPort(), - "入口(" + finalFromNode.getName() + ")->出口(" + finalToNode.getName() + ")" - ); - result.setFromChainType(1); - result.setToChainType(3); - return result; - })); - } - } - } - } - } - - for (int i = 0; i < chainNodesList.size(); i++) { - List currentHop = chainNodesList.get(i); - final int hopIndex = 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) { - final Node finalFromNode = fromNode; - final Node finalToNode = toNode; - final ChainTunnel finalCurrentNode = currentNode; - final ChainTunnel finalNextNode = nextNode; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalNextNode.getPort(), - "第" + (hopIndex + 1) + "跳(" + finalFromNode.getName() + ")->第" + (hopIndex + 2) + "跳(" + finalToNode.getName() + ")" - ); - result.setFromChainType(2); - result.setFromInx(finalCurrentNode.getInx()); - result.setToChainType(2); - result.setToInx(finalNextNode.getInx()); - return result; - })); - } - } - } else if (!outNodes.isEmpty()) { - for (ChainTunnel outNode : outNodes) { - Node toNode = nodeService.getById(outNode.getNodeId()); - if (toNode != null) { - final Node finalFromNode = fromNode; - final Node finalToNode = toNode; - final ChainTunnel finalCurrentNode = currentNode; - final ChainTunnel finalOutNode = outNode; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalOutNode.getPort(), - "第" + (hopIndex + 1) + "跳(" + finalFromNode.getName() + ")->出口(" + finalToNode.getName() + ")" - ); - result.setFromChainType(2); - result.setFromInx(finalCurrentNode.getInx()); - result.setToChainType(3); - return result; - })); - } - } - } - } - } - } - for (ChainTunnel outNode : outNodes) { - Node node = nodeService.getById(outNode.getNodeId()); - if (node != null) { - final Node finalNode = node; - futures.add(CompletableFuture.supplyAsync(() -> { - DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck( - finalNode, "www.google.com", 443, "出口(" + finalNode.getName() + ")->外网" - ); - result.setFromChainType(3); - return result; - })); - } - } - } - - List results = futures.stream() - .map(CompletableFuture::join) - .collect(Collectors.toList()); - - Map diagnosisReport = new HashMap<>(); - diagnosisReport.put("tunnelId", tunnelId); - diagnosisReport.put("tunnelName", tunnel.getName()); - diagnosisReport.put("tunnelType", tunnel.getType() == 1 ? "端口转发" : "隧道转发"); - diagnosisReport.put("results", results); - diagnosisReport.put("timestamp", System.currentTimeMillis()); - - return R.ok(diagnosisReport); - } - - public Integer getNodePort(Long nodeId) { - - Node node = nodeService.getById(nodeId); - if (node == null){ - throw new RuntimeException("节点不存在"); - } - - // 1. 查询隧道转发链占用的端口 - List chainTunnels = chainTunnelService.list( - new QueryWrapper().eq("node_id", nodeId) - ); - Set usedPorts = chainTunnels.stream() - .map(ChainTunnel::getPort) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - - List list = forwardPortService.list(new QueryWrapper().eq("node_id", nodeId)); - Set forwardUsedPorts = new HashSet<>(); - for (ForwardPort forwardPort : list) { - forwardUsedPorts.add(forwardPort.getPort()); - } - usedPorts.addAll(forwardUsedPorts); - - // 3. 从可用端口范围中筛选未被占用的端口 - List parsedPorts = parsePorts(node.getPort()); - List availablePorts = parsedPorts.stream() - .filter(p -> !usedPorts.contains(p)) - .toList(); - - if (availablePorts.isEmpty()) { - throw new RuntimeException("节点端口已满,无可用端口"); - } - return availablePorts.getFirst(); - } - - public static List parsePorts(String input) { - Set set = new HashSet<>(); - String[] parts = input.split(","); - for (String part : parts) { - part = part.trim(); - if (part.contains("-")) { - String[] range = part.split("-"); - int start = Integer.parseInt(range[0]); - int end = Integer.parseInt(range[1]); - for (int i = start; i <= end; i++) { - set.add(i); - } - } else { - set.add(Integer.parseInt(part)); - } - } - return set.stream().sorted().collect(Collectors.toList()); - } - - private void isError(GostDto gostDto){ - if (gostDto == null) { - throw new RuntimeException("节点无响应"); - } - if (!Objects.equals(gostDto.getMsg(), "OK")) { - throw new RuntimeException(gostDto.getMsg()); - } - } - - private DiagnosisResult performTcpPingDiagnosis(Node node, String targetIp, int port, String description) { - try { - // 构建TCP ping请求数据 - JSONObject tcpPingData = new JSONObject(); - tcpPingData.put("ip", targetIp); - tcpPingData.put("port", port); - tcpPingData.put("count", 4); - tcpPingData.put("timeout", 5000); // 5秒超时 - - // 发送TCP ping命令到节点 - GostDto gostResult = WebSocketServer.send_msg(node.getId(), tcpPingData, "TcpPing"); - - DiagnosisResult result = new DiagnosisResult(); - result.setNodeId(node.getId()); - result.setNodeName(node.getName()); - result.setTargetIp(targetIp); - result.setTargetPort(port); - result.setDescription(description); - result.setTimestamp(System.currentTimeMillis()); - - if (gostResult != null && "OK".equals(gostResult.getMsg())) { - // 尝试解析TCP ping响应数据 - try { - if (gostResult.getData() != null) { - JSONObject tcpPingResponse = (JSONObject) gostResult.getData(); - boolean success = tcpPingResponse.getBooleanValue("success"); - - result.setSuccess(success); - if (success) { - result.setMessage("TCP连接成功"); - result.setAverageTime(tcpPingResponse.getDoubleValue("averageTime")); - result.setPacketLoss(tcpPingResponse.getDoubleValue("packetLoss")); - } else { - result.setMessage(tcpPingResponse.getString("errorMessage")); - result.setAverageTime(-1.0); - result.setPacketLoss(100.0); - } - } else { - // 没有详细数据,使用默认值 - result.setSuccess(true); - result.setMessage("TCP连接成功"); - result.setAverageTime(0.0); - result.setPacketLoss(0.0); - } - } catch (Exception e) { - // 解析响应数据失败,但TCP ping命令本身成功了 - result.setSuccess(true); - result.setMessage("TCP连接成功,但无法解析详细数据"); - result.setAverageTime(0.0); - result.setPacketLoss(0.0); - } - } else { - result.setSuccess(false); - result.setMessage(gostResult != null ? gostResult.getMsg() : "节点无响应"); - result.setAverageTime(-1.0); - result.setPacketLoss(100.0); - } - - return result; - } catch (Exception e) { - DiagnosisResult result = new DiagnosisResult(); - result.setNodeId(node.getId()); - result.setNodeName(node.getName()); - result.setTargetIp(targetIp); - result.setTargetPort(port); - result.setDescription(description); - result.setSuccess(false); - result.setMessage("诊断执行异常: " + e.getMessage()); - result.setTimestamp(System.currentTimeMillis()); - result.setAverageTime(-1.0); - result.setPacketLoss(100.0); - return result; - } - } - - private DiagnosisResult performTcpPingDiagnosisWithConnectionCheck(Node node, String targetIp, int port, String description) { - DiagnosisResult result = new DiagnosisResult(); - result.setNodeId(node.getId()); - result.setNodeName(node.getName()); - result.setTargetIp(targetIp); - result.setTargetPort(port); - result.setDescription(description); - result.setTimestamp(System.currentTimeMillis()); - - try { - return performTcpPingDiagnosis(node, targetIp, port, description); - } catch (Exception e) { - result.setSuccess(false); - result.setMessage("连接检查异常: " + e.getMessage()); - result.setAverageTime(-1.0); - result.setPacketLoss(100.0); - return result; - } - } - - private boolean detectNodeChanges(List oldChains, TunnelUpdateDto dto) { - if (dto.getInNodeId() == null) { - return false; - } - - List oldInNodes = oldChains.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 1) - .collect(Collectors.toList()); - List oldChainNodes = oldChains.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 2) - .collect(Collectors.toList()); - List oldOutNodes = oldChains.stream() - .filter(ct -> ct.getChainType() != null && ct.getChainType() == 3) - .collect(Collectors.toList()); - - Set oldInNodeIds = oldInNodes.stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet()); - Set newInNodeIds = dto.getInNodeId().stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet()); - if (!oldInNodeIds.equals(newInNodeIds)) { - return true; - } - - List flatNewChainNodes = (dto.getChainNodes() == null) ? new ArrayList<>() : - dto.getChainNodes().stream().flatMap(List::stream).collect(Collectors.toList()); - if (oldChainNodes.size() != flatNewChainNodes.size()) { - return true; - } - for (int i = 0; i < oldChainNodes.size(); i++) { - ChainTunnel oldCt = oldChainNodes.get(i); - boolean found = flatNewChainNodes.stream().anyMatch(newCt -> - Objects.equals(oldCt.getNodeId(), newCt.getNodeId()) && - Objects.equals(oldCt.getProtocol(), newCt.getProtocol()) && - Objects.equals(oldCt.getStrategy(), newCt.getStrategy()) && - Objects.equals(oldCt.getInx(), newCt.getInx()) - ); - if (!found) { - return true; - } - } - - List newOutNodes = (dto.getOutNodeId() == null) ? new ArrayList<>() : dto.getOutNodeId(); - if (oldOutNodes.size() != newOutNodes.size()) { - return true; - } - Set oldOutNodeIds = oldOutNodes.stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet()); - Set newOutNodeIds = newOutNodes.stream().map(ChainTunnel::getNodeId).collect(Collectors.toSet()); - if (!oldOutNodeIds.equals(newOutNodeIds)) { - return true; - } - for (ChainTunnel oldOut : oldOutNodes) { - boolean found = newOutNodes.stream().anyMatch(newOut -> - Objects.equals(oldOut.getNodeId(), newOut.getNodeId()) && - Objects.equals(oldOut.getProtocol(), newOut.getProtocol()) && - Objects.equals(oldOut.getStrategy(), newOut.getStrategy()) - ); - if (!found) { - return true; - } - } - - return false; - } - - private void cleanupGostConfig(List chainTunnels, Long tunnelId) { - for (ChainTunnel chainTunnel : chainTunnels) { - if (chainTunnel.getChainType() == 1) { - GostUtil.DeleteChains(chainTunnel.getNodeId(), "chains_" + tunnelId); - } else if (chainTunnel.getChainType() == 2) { - GostUtil.DeleteChains(chainTunnel.getNodeId(), "chains_" + tunnelId); - JSONArray services = new JSONArray(); - services.add(tunnelId + "_tls"); - GostUtil.DeleteService(chainTunnel.getNodeId(), services); - } else if (chainTunnel.getChainType() == 3) { - JSONArray services = new JSONArray(); - services.add(tunnelId + "_tls"); - GostUtil.DeleteService(chainTunnel.getNodeId(), services); - } - } - } - - private List deepCopyChainTunnels(List original) { - List copy = new ArrayList<>(); - for (ChainTunnel ct : original) { - ChainTunnel newCt = new ChainTunnel(); - newCt.setId(ct.getId()); - newCt.setTunnelId(ct.getTunnelId()); - newCt.setChainType(ct.getChainType()); - newCt.setNodeId(ct.getNodeId()); - newCt.setPort(ct.getPort()); - newCt.setStrategy(ct.getStrategy()); - newCt.setInx(ct.getInx()); - newCt.setProtocol(ct.getProtocol()); - copy.add(newCt); - } - return copy; - } - - private R applyNewGostConfig(TunnelUpdateDto dto, Tunnel tunnel, Map nodes) { - List chainSuccess = new ArrayList<>(); - List serviceSuccess = new ArrayList<>(); - - if (tunnel.getType() == 2) { - List> chainNodes = dto.getChainNodes() == null ? new ArrayList<>() : dto.getChainNodes(); - - for (ChainTunnel inNode : dto.getInNodeId()) { - GostDto gostDto; - if (chainNodes.isEmpty()) { - gostDto = GostUtil.AddChains(inNode.getNodeId(), dto.getOutNodeId(), nodes); - } else { - gostDto = GostUtil.AddChains(inNode.getNodeId(), chainNodes.get(0), nodes); - } - if (!Objects.equals(gostDto.getMsg(), "OK")) { - rollbackGostChanges(chainSuccess, serviceSuccess); - return R.err("创建入口Chain失败: " + gostDto.getMsg()); - } - JSONObject data = new JSONObject(); - data.put("node_id", inNode.getNodeId()); - data.put("name", "chains_" + tunnel.getId()); - chainSuccess.add(data); - } - - for (int i = 0; i < chainNodes.size(); i++) { - List currentHop = chainNodes.get(i); - for (ChainTunnel chainTunnel : currentHop) { - GostDto gostDto; - if (i + 1 >= chainNodes.size()) { - gostDto = GostUtil.AddChains(chainTunnel.getNodeId(), dto.getOutNodeId(), nodes); - } else { - gostDto = GostUtil.AddChains(chainTunnel.getNodeId(), chainNodes.get(i + 1), nodes); - } - if (!Objects.equals(gostDto.getMsg(), "OK")) { - rollbackGostChanges(chainSuccess, serviceSuccess); - return R.err("创建转发链Chain失败: " + gostDto.getMsg()); - } - JSONObject chainData = new JSONObject(); - chainData.put("node_id", chainTunnel.getNodeId()); - chainData.put("name", "chains_" + tunnel.getId()); - chainSuccess.add(chainData); - - GostDto serviceResult = GostUtil.AddChainService(chainTunnel.getNodeId(), chainTunnel, nodes); - if (!Objects.equals(serviceResult.getMsg(), "OK")) { - rollbackGostChanges(chainSuccess, serviceSuccess); - return R.err("创建转发链Service失败: " + serviceResult.getMsg()); - } - JSONObject serviceData = new JSONObject(); - serviceData.put("node_id", chainTunnel.getNodeId()); - serviceData.put("name", tunnel.getId() + "_tls"); - serviceSuccess.add(serviceData); - } - } - - for (ChainTunnel outNode : dto.getOutNodeId()) { - GostDto gostDto = GostUtil.AddChainService(outNode.getNodeId(), outNode, nodes); - if (!Objects.equals(gostDto.getMsg(), "OK")) { - rollbackGostChanges(chainSuccess, serviceSuccess); - return R.err("创建出口Service失败: " + gostDto.getMsg()); - } - JSONObject serviceData = new JSONObject(); - serviceData.put("node_id", outNode.getNodeId()); - serviceData.put("name", tunnel.getId() + "_tls"); - serviceSuccess.add(serviceData); - } - } - - return R.ok(); - } - - private void rollbackGostChanges(List chainSuccess, List serviceSuccess) { - for (JSONObject chain : chainSuccess) { - GostUtil.DeleteChains(chain.getLong("node_id"), chain.getString("name")); - } - for (JSONObject service : serviceSuccess) { - JSONArray services = new JSONArray(); - services.add(service.getString("name")); - GostUtil.DeleteService(service.getLong("node_id"), services); - } - } - - private void rebuildGostConfig(List chainTunnels, Tunnel tunnel) { - Map nodes = new HashMap<>(); - for (ChainTunnel ct : chainTunnels) { - Node node = nodeService.getById(ct.getNodeId()); - if (node != null) { - nodes.put(node.getId(), node); - } - } - - List inNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() == 1) - .collect(Collectors.toList()); - Map> chainNodesMap = chainTunnels.stream() - .filter(ct -> ct.getChainType() == 2) - .collect(Collectors.groupingBy(ct -> ct.getInx() != null ? ct.getInx() : 0)); - List> chainNodesList = chainNodesMap.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(Map.Entry::getValue) - .collect(Collectors.toList()); - List outNodes = chainTunnels.stream() - .filter(ct -> ct.getChainType() == 3) - .collect(Collectors.toList()); - - if (tunnel.getType() == 2) { - for (ChainTunnel inNode : inNodes) { - if (chainNodesList.isEmpty()) { - GostUtil.AddChains(inNode.getNodeId(), outNodes, nodes); - } else { - GostUtil.AddChains(inNode.getNodeId(), chainNodesList.get(0), nodes); - } - } - - for (int i = 0; i < chainNodesList.size(); i++) { - for (ChainTunnel chainTunnel : chainNodesList.get(i)) { - if (i + 1 >= chainNodesList.size()) { - GostUtil.AddChains(chainTunnel.getNodeId(), outNodes, nodes); - } else { - GostUtil.AddChains(chainTunnel.getNodeId(), chainNodesList.get(i + 1), nodes); - } - GostUtil.AddChainService(chainTunnel.getNodeId(), chainTunnel, nodes); - } - } - - for (ChainTunnel outNode : outNodes) { - GostUtil.AddChainService(outNode.getNodeId(), outNode, nodes); - } - } - } - - private void syncForwardsForEntryNodeChanges(Long tunnelId, Set oldEntryNodeIds, Set newEntryNodeIds) { - Set addedNodeIds = new HashSet<>(newEntryNodeIds); - addedNodeIds.removeAll(oldEntryNodeIds); - - Set removedNodeIds = new HashSet<>(oldEntryNodeIds); - removedNodeIds.removeAll(newEntryNodeIds); - - if (addedNodeIds.isEmpty() && removedNodeIds.isEmpty()) { - return; - } - - List forwards = forwardService.list( - new QueryWrapper().eq("tunnel_id", tunnelId.intValue()) - ); - - if (forwards.isEmpty()) { - return; - } - - Tunnel tunnel = this.getById(tunnelId); - if (tunnel == null) { - return; - } - - for (Forward forward : forwards) { - if (forward.getStatus() != 1) { - continue; - } - - UserTunnel userTunnel = userTunnelService.getOne( - new QueryWrapper() - .eq("user_id", forward.getUserId()) - .eq("tunnel_id", tunnelId.intValue()) - ); - - for (Long removedNodeId : removedNodeIds) { - ForwardPort forwardPort = forwardPortService.getOne( - new QueryWrapper() - .eq("forward_id", forward.getId()) - .eq("node_id", removedNodeId) - ); - - if (forwardPort != null) { - String serviceName = buildForwardServiceName(forward.getId(), forward.getUserId(), userTunnel); - JSONArray services = new JSONArray(); - services.add(serviceName + "_tcp"); - services.add(serviceName + "_udp"); - GostUtil.DeleteService(removedNodeId, services); - - forwardPortService.removeById(forwardPort.getId()); - } - } - - for (Long addedNodeId : addedNodeIds) { - ForwardPort existingPort = forwardPortService.getOne( - new QueryWrapper() - .eq("forward_id", forward.getId()) - .eq("node_id", addedNodeId) - ); - - if (existingPort != null) { - continue; - } - - List existingPorts = forwardPortService.list( - new QueryWrapper().eq("forward_id", forward.getId()) - ); - - Integer targetPort = null; - if (!existingPorts.isEmpty()) { - targetPort = existingPorts.get(0).getPort(); - } - - Integer allocatedPort = allocatePortForNode(addedNodeId, targetPort, forward.getId()); - if (allocatedPort == null) { - System.err.println("Failed to allocate port on node " + addedNodeId + " for forward " + forward.getId()); - continue; - } - - ForwardPort newForwardPort = new ForwardPort(); - newForwardPort.setForwardId(forward.getId()); - newForwardPort.setNodeId(addedNodeId); - newForwardPort.setPort(allocatedPort); - forwardPortService.save(newForwardPort); - - Node node = nodeService.getById(addedNodeId); - if (node != null) { - String serviceName = buildForwardServiceName(forward.getId(), forward.getUserId(), userTunnel); - Integer limiter = (userTunnel != null && userTunnel.getSpeedId() != null) ? userTunnel.getSpeedId() : null; - GostUtil.AddAndUpdateService(serviceName, limiter, node, forward, newForwardPort, tunnel, "AddService"); - } - } - } - } - - private Integer allocatePortForNode(Long nodeId, Integer preferredPort, Long forwardId) { - Node node = nodeService.getById(nodeId); - if (node == null || node.getPort() == null) { - return null; - } - - Set usedPorts = new HashSet<>(); - - List chainTunnels = chainTunnelService.list( - new QueryWrapper().eq("node_id", nodeId) - ); - for (ChainTunnel ct : chainTunnels) { - if (ct.getPort() != null) { - usedPorts.add(ct.getPort()); - } - } - - List forwardPorts = forwardPortService.list( - new QueryWrapper() - .eq("node_id", nodeId) - .ne("forward_id", forwardId) - ); - for (ForwardPort fp : forwardPorts) { - if (fp.getPort() != null) { - usedPorts.add(fp.getPort()); - } - } - - List availablePorts = parsePorts(node.getPort()); - - if (preferredPort != null && availablePorts.contains(preferredPort) && !usedPorts.contains(preferredPort)) { - return preferredPort; - } - - for (Integer port : availablePorts) { - if (!usedPorts.contains(port)) { - return port; - } - } - - return null; - } - - private String buildForwardServiceName(Long forwardId, Integer userId, UserTunnel userTunnel) { - int userTunnelId = (userTunnel != null) ? userTunnel.getId() : 0; - return forwardId + "_" + userId + "_" + userTunnelId; - } - - @Override - @Transactional - public R batchDeleteTunnels(BatchDeleteDto batchDeleteDto) { - BatchOperationResultDto result = new BatchOperationResultDto(); - - for (Long id : batchDeleteDto.getIds()) { - try { - Tunnel tunnel = this.getById(id); - if (tunnel == null) { - result.addFailedItem(id, "隧道不存在"); - continue; - } - - List forwardList = forwardService.list(new QueryWrapper().eq("tunnel_id", id)); - for (Forward forward : forwardList) { - forwardService.deleteForward(forward.getId()); - } - forwardService.remove(new QueryWrapper().eq("tunnel_id", id)); - userTunnelService.remove(new QueryWrapper().eq("tunnel_id", id)); - this.removeById(id); - - List chainTunnels = chainTunnelService.list(new QueryWrapper().eq("tunnel_id", id)); - for (ChainTunnel chainTunnel : chainTunnels) { - if (chainTunnel.getChainType() == 1) { - GostUtil.DeleteChains(chainTunnel.getNodeId(), "chains_" + chainTunnel.getTunnelId()); - } else if (chainTunnel.getChainType() == 2) { - GostUtil.DeleteChains(chainTunnel.getNodeId(), "chains_" + chainTunnel.getTunnelId()); - JSONArray services = new JSONArray(); - services.add(chainTunnel.getTunnelId() + "_tls"); - GostUtil.DeleteService(chainTunnel.getNodeId(), services); - } else { - JSONArray services = new JSONArray(); - services.add(chainTunnel.getTunnelId() + "_tls"); - GostUtil.DeleteService(chainTunnel.getNodeId(), services); - } - } - chainTunnelService.remove(new QueryWrapper().eq("tunnel_id", id)); - - result.incrementSuccess(); - } catch (Exception e) { - result.addFailedItem(id, e.getMessage()); - } - } - - return R.ok(result); - } - - @Override - @Transactional - public R batchRedeployTunnels(BatchRedeployDto batchRedeployDto) { - BatchOperationResultDto result = new BatchOperationResultDto(); - - for (Long id : batchRedeployDto.getIds()) { - try { - Tunnel tunnel = this.getById(id); - if (tunnel == null) { - result.addFailedItem(id, "隧道不存在"); - continue; - } - - if (tunnel.getType() != 2) { - result.incrementSuccess(); - continue; - } - - List chainTunnels = chainTunnelService.list( - new QueryWrapper().eq("tunnel_id", id) - ); - - if (chainTunnels.isEmpty()) { - result.addFailedItem(id, "隧道配置不完整"); - continue; - } - - cleanupGostConfig(chainTunnels, id); - rebuildGostConfig(chainTunnels, tunnel); - - result.incrementSuccess(); - } catch (Exception e) { - result.addFailedItem(id, e.getMessage()); - } - } - - return R.ok(result); - } - - -} 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 deleted file mode 100644 index d291faa..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java +++ /dev/null @@ -1,342 +0,0 @@ -package com.admin.service.impl; - -import cloud.tianai.captcha.application.ImageCaptchaApplication; -import cloud.tianai.captcha.spring.plugins.secondary.SecondaryVerificationApplication; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; -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.Md5Util; -import com.admin.entity.*; -import com.admin.mapper.UserMapper; -import com.admin.mapper.GroupPermissionGrantMapper; -import com.admin.mapper.UserGroupUserMapper; -import com.admin.service.*; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - - -@Slf4j -@Service -public class UserServiceImpl extends ServiceImpl implements UserService { - - @Resource - @Lazy - ForwardService forwardService; - - @Resource - UserMapper userMapper; - - @Resource - @Lazy - TunnelService tunnelService; - - @Resource - @Lazy - NodeService nodeService; - - @Resource - UserTunnelService userTunnelService; - - @Resource - ViteConfigService viteConfigService; - - @Resource - StatisticsFlowService statisticsFlowService; - - @Resource - GroupPermissionGrantMapper groupPermissionGrantMapper; - - @Resource - UserGroupUserMapper userGroupUserMapper; - - @Resource - @Lazy - ForwardPortService forwardPortService; - - @Resource - ImageCaptchaApplication application; - - - @Override - public R login(LoginDto loginDto) { - ViteConfig viteConfig = viteConfigService.getOne(new QueryWrapper().eq("name", "captcha_enabled")); - if (viteConfig != null && Objects.equals(viteConfig.getValue(), "true")) { - if (StringUtils.isBlank(loginDto.getCaptchaId())) return R.err("验证码校验失败"); - boolean valid = ((SecondaryVerificationApplication) application).secondaryVerification(loginDto.getCaptchaId()); - if (!valid) return R.err("验证码校验失败"); - } - - User user = this.getOne(new QueryWrapper().eq("user", loginDto.getUsername())); - if (user == null) return R.err("账号或密码错误"); - if (!user.getPwd().equals(Md5Util.md5(loginDto.getPassword()))) return R.err("账号或密码错误"); - if (user.getStatus() == 0) return R.err("账号被停用"); - String token = JwtUtil.generateToken(user); - boolean requirePasswordChange = Objects.equals(loginDto.getUsername(), "admin_user") || Objects.equals(loginDto.getPassword(), "admin_user"); - return R.ok(MapUtil.builder() - .put("token", token) - .put("name", user.getUser()) - .put("role_id", user.getRoleId()) - .put("requirePasswordChange", requirePasswordChange) - .build()); - } - - @Override - public R createUser(UserDto userDto) { - int count = this.count(new QueryWrapper().eq("user", userDto.getUser())); - if (count > 0) return R.err("用户名已存在"); - User user = new User(); - BeanUtils.copyProperties(userDto, user); - user.setPwd(Md5Util.md5(userDto.getPwd())); - user.setStatus(1); - user.setRoleId(1); - long currentTime = System.currentTimeMillis(); - user.setCreatedTime(currentTime); - user.setUpdatedTime(currentTime); - this.save(user); - return R.ok(); - } - - @Override - public R getAllUsers() { - List list = this.list(new QueryWrapper().ne("role_id", 0)); - return R.ok(list); - } - - @Override - public R updateUser(UserUpdateDto userUpdateDto) { - User user = this.getById(userUpdateDto.getId()); - if (user == null) return R.err("用户不存在"); - if (user.getRoleId() == 0) return R.err("请不要作死"); - - int count = this.count(new QueryWrapper().eq("user", userUpdateDto.getUser()).ne("id", userUpdateDto.getId())); - if (count > 0) return R.err("用户名已存在"); - - - User updateUser = new User(); - BeanUtils.copyProperties(userUpdateDto, updateUser); - if (StrUtil.isNotBlank(userUpdateDto.getPwd())) { - updateUser.setPwd(Md5Util.md5(userUpdateDto.getPwd())); - } else { - updateUser.setPwd(null); // 不更新密码字段 - } - updateUser.setUpdatedTime(System.currentTimeMillis()); - this.updateById(updateUser); - return R.ok(); - } - - @Override - public R deleteUser(Long id) { - User user = this.getById(id); - if (user == null) return R.err("用户不存在"); - if (user.getRoleId() == 0) return R.err("请不要作死"); - List forwardList = forwardService.list(new QueryWrapper().eq("user_id", id)); - for (Forward forward : forwardList) { - forwardService.deleteForward(forward.getId()); - } - - List userTunnels = userTunnelService.list(new QueryWrapper().eq("user_id", id)); - if (!userTunnels.isEmpty()) { - List userTunnelIds = userTunnels.stream().map(UserTunnel::getId).toList(); - groupPermissionGrantMapper.delete(new QueryWrapper().in("user_tunnel_id", userTunnelIds)); - } - - forwardService.remove(new QueryWrapper().eq("user_id", id)); - userTunnelService.remove(new QueryWrapper().eq("user_id", id)); - userGroupUserMapper.delete(new QueryWrapper().eq("user_id", id)); - statisticsFlowService.remove(new QueryWrapper().eq("user_id", id)); - this.removeById(id); - return R.ok(); - } - - @Override - public R getUserPackageInfo() { - Integer userId = JwtUtil.getUserIdFromToken(); - User user = this.getById(userId); - if (user == null) return R.err("用户不存在"); - UserPackageDto.UserInfoDto userInfo = buildUserInfoDto(user); - List tunnelPermissions = userMapper.getUserTunnelDetails(userId); - List forwards = userMapper.getUserForwardDetails(user.getId().intValue()); - fillForwardInIpAndPort(forwards); - List statisticsFlows = getLast24HoursFlowStatistics(user.getId()); - UserPackageDto packageDto = new UserPackageDto(); - packageDto.setUserInfo(userInfo); - packageDto.setTunnelPermissions(tunnelPermissions); - packageDto.setForwards(forwards); - packageDto.setStatisticsFlows(statisticsFlows); - return R.ok(packageDto); - } - - @Override - public R updatePassword(ChangePasswordDto changePasswordDto) { - Integer userId = JwtUtil.getUserIdFromToken(); - User user = this.getById(userId); - if (user == null) return R.err("用户不存在"); - if (!changePasswordDto.getNewPassword().equals(changePasswordDto.getConfirmPassword())) { - return R.err("新密码和确认密码不匹配"); - } - String currentPasswordMd5 = Md5Util.md5(changePasswordDto.getCurrentPassword()); - if (!user.getPwd().equals(currentPasswordMd5)) { - return R.err("当前密码错误"); - } - if (!user.getUser().equals(changePasswordDto.getNewUsername())) { - user.setPwd(Md5Util.md5(changePasswordDto.getNewPassword())); - int count = this.count(new QueryWrapper().eq("user", changePasswordDto.getNewUsername()).ne("id", user.getId())); - if (count > 0) return R.err("用户名已存在"); - } - User updateUser = new User(); - updateUser.setId(user.getId()); - updateUser.setUser(changePasswordDto.getNewUsername()); - updateUser.setPwd(Md5Util.md5(changePasswordDto.getNewPassword())); - updateUser.setUpdatedTime(System.currentTimeMillis()); - this.updateById(updateUser); - return R.ok(); - } - - @Override - public R reset(ResetFlowDto resetFlowDto) { - if (resetFlowDto.getType() == 1){ // 清零账号流量 - User user = this.getById(resetFlowDto.getId()); - if (user == null) return R.err("用户不存在"); - user.setInFlow(0L); - user.setOutFlow(0L); - this.updateById(user); - }else { // 清零隧道流量 - UserTunnel tunnel = userTunnelService.getById(resetFlowDto.getId()); - if (tunnel == null) return R.err("隧道不存在"); - tunnel.setInFlow(0L); - tunnel.setOutFlow(0L); - userTunnelService.updateById(tunnel); - } - return R.ok(); - } - - private UserPackageDto.UserInfoDto buildUserInfoDto(User user) { - UserPackageDto.UserInfoDto userInfo = new UserPackageDto.UserInfoDto(); - userInfo.setId(user.getId()); - userInfo.setUser(user.getUser()); - userInfo.setStatus(user.getStatus()); - userInfo.setFlow(user.getFlow()); - userInfo.setInFlow(user.getInFlow()); - userInfo.setOutFlow(user.getOutFlow()); - userInfo.setNum(user.getNum()); - userInfo.setExpTime(user.getExpTime()); - userInfo.setFlowResetTime(user.getFlowResetTime()); - userInfo.setCreatedTime(user.getCreatedTime()); - userInfo.setUpdatedTime(user.getUpdatedTime()); - return userInfo; - } - - private List getLast24HoursFlowStatistics(Long userId) { - List recentFlows = statisticsFlowService.list( - new QueryWrapper() - .eq("user_id", userId) - .orderByDesc("id") - .last("LIMIT 24") - ); - List result = new ArrayList<>(recentFlows); - if (result.size() < 24) { - int startHour = java.time.LocalDateTime.now().getHour(); - if (!result.isEmpty()) { - String lastTime = result.getLast().getTime(); - startHour = parseHour(lastTime) - 1; - } - while (result.size() < 24) { - if (startHour < 0) startHour = 23; - StatisticsFlow emptyFlow = new StatisticsFlow(); - emptyFlow.setUserId(userId); - emptyFlow.setFlow(0L); - emptyFlow.setTotalFlow(0L); - emptyFlow.setTime(String.format("%02d:00", startHour)); - result.add(emptyFlow); - startHour--; - } - } - return result; - } - - private int parseHour(String timeStr) { - if (timeStr != null && timeStr.contains(":")) { - return Integer.parseInt(timeStr.split(":")[0]); - } - return java.time.LocalDateTime.now().getHour(); - } - - private void fillForwardInIpAndPort(List forwards) { - for (UserPackageDto.UserForwardDetailDto forward : forwards) { - Tunnel tunnel = tunnelService.getById(forward.getTunnelId()); - if (tunnel == null) continue; - - List forwardPorts = forwardPortService.list( - new QueryWrapper().eq("forward_id", forward.getId()) - ); - if (forwardPorts.isEmpty()) continue; - - boolean useTunnelInIp = tunnel.getInIp() != null && !tunnel.getInIp().trim().isEmpty(); - java.util.Set ipPortSet = new java.util.LinkedHashSet<>(); - - if (useTunnelInIp) { - // 使用隧道的inIp(求笛卡尔积) - List ipList = new ArrayList<>(); - List portList = new ArrayList<>(); - - String[] tunnelInIps = tunnel.getInIp().split(","); - for (String ip : tunnelInIps) { - if (ip != null && !ip.trim().isEmpty()) { - ipList.add(ip.trim()); - } - } - - for (ForwardPort forwardPort : forwardPorts) { - if (forwardPort.getPort() != null) { - portList.add(forwardPort.getPort()); - } - } - - List uniqueIps = ipList.stream().distinct().toList(); - List uniquePorts = portList.stream().distinct().toList(); - - for (String ip : uniqueIps) { - for (Integer port : uniquePorts) { - ipPortSet.add(ip + ":" + port); - } - } - - if (!uniquePorts.isEmpty()) { - forward.setInPort(uniquePorts.getFirst()); - } - } else { - // 使用节点的serverIp(一对一,不求笛卡尔积) - for (ForwardPort forwardPort : forwardPorts) { - Node node = nodeService.getById(forwardPort.getNodeId()); - if (node != null && node.getServerIp() != null && forwardPort.getPort() != null) { - ipPortSet.add(node.getServerIp() + ":" + forwardPort.getPort()); - } - } - - if (!forwardPorts.isEmpty() && forwardPorts.getFirst().getPort() != null) { - forward.setInPort(forwardPorts.getFirst().getPort()); - } - } - - if (!ipPortSet.isEmpty()) { - forward.setInIp(String.join(",", ipPortSet)); - } - } - } - -} diff --git a/springboot-backend/src/main/java/com/admin/service/impl/UserTunnelServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/UserTunnelServiceImpl.java deleted file mode 100644 index 175933e..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/UserTunnelServiceImpl.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.admin.service.impl; - -import com.admin.common.dto.*; -import com.admin.common.lang.R; -import com.admin.entity.GroupPermissionGrant; -import com.admin.entity.User; -import com.admin.entity.UserTunnel; -import com.admin.mapper.GroupPermissionGrantMapper; -import com.admin.mapper.UserTunnelMapper; -import com.admin.service.UserService; -import com.admin.service.UserTunnelService; -import com.admin.service.ForwardService; -import com.admin.entity.Forward; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import org.springframework.beans.BeanUtils; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -@Service -public class UserTunnelServiceImpl extends ServiceImpl implements UserTunnelService { - - @Resource - @Lazy - private ForwardService forwardService; - - @Resource - @Lazy - private UserService userService; - - @Resource - private GroupPermissionGrantMapper groupPermissionGrantMapper; - - @Override - public R assignUserTunnel(UserTunnelDto userTunnelDto) { - int count = this.count(new QueryWrapper() - .eq("user_id", userTunnelDto.getUserId()) - .eq("tunnel_id", userTunnelDto.getTunnelId())); - if (count > 0) return R.err("该用户已拥有此隧道权限"); - - User user = userService.getById(userTunnelDto.getUserId()); - if (user == null) return R.err("用户不存在"); - - UserTunnel userTunnel = new UserTunnel(); - userTunnel.setUserId(userTunnelDto.getUserId()); - userTunnel.setTunnelId(userTunnelDto.getTunnelId()); - userTunnel.setSpeedId(userTunnelDto.getSpeedId()); - userTunnel.setStatus(1); - userTunnel.setInFlow(0L); - userTunnel.setOutFlow(0L); - - userTunnel.setFlow(userTunnelDto.getFlow() != null ? userTunnelDto.getFlow() : user.getFlow()); - userTunnel.setNum(userTunnelDto.getNum() != null ? userTunnelDto.getNum() : user.getNum()); - userTunnel.setFlowResetTime(userTunnelDto.getFlowResetTime() != null ? userTunnelDto.getFlowResetTime() : user.getFlowResetTime()); - userTunnel.setExpTime(userTunnelDto.getExpTime() != null ? userTunnelDto.getExpTime() : user.getExpTime()); - - this.save(userTunnel); - return R.ok(); - } - - @Override - public R batchAssignUserTunnel(UserTunnelBatchAssignDto batchAssignDto) { - User user = userService.getById(batchAssignDto.getUserId()); - if (user == null) return R.err("用户不存在"); - - Map uniqueTunnels = new LinkedHashMap<>(); - for (UserTunnelBatchAssignDto.TunnelAssignItem item : batchAssignDto.getTunnels()) { - uniqueTunnels.putIfAbsent(item.getTunnelId(), item); - } - - Set existingTunnelIds = this.list( - new QueryWrapper() - .eq("user_id", batchAssignDto.getUserId()) - .in("tunnel_id", uniqueTunnels.keySet()) - ).stream().map(UserTunnel::getTunnelId).collect(Collectors.toSet()); - - List toSave = new ArrayList<>(); - List skippedIds = new ArrayList<>(); - - for (UserTunnelBatchAssignDto.TunnelAssignItem item : uniqueTunnels.values()) { - if (existingTunnelIds.contains(item.getTunnelId())) { - skippedIds.add(item.getTunnelId()); - continue; - } - - UserTunnel ut = new UserTunnel(); - ut.setUserId(batchAssignDto.getUserId()); - ut.setTunnelId(item.getTunnelId()); - ut.setSpeedId(item.getSpeedId()); - ut.setStatus(1); - ut.setInFlow(0L); - ut.setOutFlow(0L); - ut.setFlow(user.getFlow()); - ut.setNum(user.getNum()); - ut.setFlowResetTime(user.getFlowResetTime()); - ut.setExpTime(user.getExpTime()); - toSave.add(ut); - } - - if (toSave.isEmpty()) { - return R.err("所选隧道用户均已拥有权限"); - } - - this.saveBatch(toSave); - - if (!skippedIds.isEmpty()) { - return R.ok("成功分配 " + toSave.size() + " 个隧道,跳过 " + skippedIds.size() + " 个已有权限的隧道"); - } - return R.ok(); - } - - @Override - public R getUserTunnelList(UserTunnelQueryDto queryDto) { - List userTunnelWithDetails = this.baseMapper.getUserTunnelWithDetails(queryDto.getUserId()); - return R.ok(userTunnelWithDetails); - } - - @Override - public R removeUserTunnel(Integer id) { - UserTunnel userTunnel = this.getById(id); - if (userTunnel == null) return R.err("未找到对应的用户隧道权限记录"); - - int grantCount = groupPermissionGrantMapper.selectCount( - new QueryWrapper().eq("user_tunnel_id", id) - ); - if (grantCount > 0) { - return R.err("该隧道权限由分组授权,请先调整分组权限或分组成员"); - } - - List forwardList = forwardService.list(new QueryWrapper() - .eq("user_id", userTunnel.getUserId()) - .eq("tunnel_id", userTunnel.getTunnelId())); - for (Forward forward : forwardList) { - forwardService.deleteForward(forward.getId()); - } - this.removeById(id); - return R.ok(); - } - - @Override - public R updateUserTunnel(UserTunnelUpdateDto updateDto) { - UserTunnel userTunnel = this.getById(updateDto.getId()); - if (userTunnel == null) return R.err("隧道不存在"); - boolean speedChanged = hasSpeedChanged(userTunnel.getSpeedId(), updateDto.getSpeedId()); - userTunnel.setFlow(updateDto.getFlow()); - userTunnel.setNum(updateDto.getNum()); - updateOptionalProperty(userTunnel::setFlowResetTime, updateDto.getFlowResetTime()); - updateOptionalProperty(userTunnel::setExpTime, updateDto.getExpTime()); - updateOptionalProperty(userTunnel::setStatus, updateDto.getStatus()); - userTunnel.setSpeedId(updateDto.getSpeedId()); - this.updateById(userTunnel); - if (speedChanged) { - List forwardList = forwardService.list(new QueryWrapper() - .eq("user_id", userTunnel.getUserId()) - .eq("tunnel_id", userTunnel.getTunnelId())); - for (Forward forward : forwardList) { - ForwardUpdateDto forwardUpdateDto = new ForwardUpdateDto(); - forwardUpdateDto.setId(forward.getId()); - forwardUpdateDto.setUserId(forward.getUserId()); - forwardUpdateDto.setName(forward.getName()); - forwardUpdateDto.setRemoteAddr(forward.getRemoteAddr()); - forwardUpdateDto.setStrategy(forward.getStrategy()); - forwardService.updateForward(forwardUpdateDto); - } - } - return R.ok(); - } - - private void updateOptionalProperty(java.util.function.Consumer setter, T value) { - if (value != null) { - setter.accept(value); - } - } - - private boolean hasSpeedChanged(Integer oldSpeedId, Integer newSpeedId) { - if (oldSpeedId == null && newSpeedId == null) { - return false; - } - if (oldSpeedId == null || newSpeedId == null) { - return true; - } - return !oldSpeedId.equals(newSpeedId); - } - -} diff --git a/springboot-backend/src/main/java/com/admin/service/impl/ViteConfigServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/ViteConfigServiceImpl.java deleted file mode 100644 index 108ebbf..0000000 --- a/springboot-backend/src/main/java/com/admin/service/impl/ViteConfigServiceImpl.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.admin.service.impl; - -import com.admin.entity.ViteConfig; -import com.admin.mapper.ViteConfigMapper; -import com.admin.service.ViteConfigService; -import com.admin.common.lang.R; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - *

- * 网站配置服务实现类 - *

- * - * @author QAQ - * @since 2025-07-24 - */ -@Service -public class ViteConfigServiceImpl extends ServiceImpl implements ViteConfigService { - - - @Override - public R getConfigs() { - List configList = this.list(); - Map configMap = new HashMap<>(); - - for (ViteConfig config : configList) { - configMap.put(config.getName(), config.getValue()); - } - - return R.ok(configMap); - } - - - @Override - public R getConfigByName(String name) { - if (!StringUtils.hasText(name)) return R.err("配置名称不能为空"); - - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("name", name); - ViteConfig config = this.getOne(queryWrapper); - - if (config == null) return R.err("配置不存在"); - - return R.ok(config); - } - - - @Override - public R updateConfigs(Map configMap) { - if (configMap == null || configMap.isEmpty()) return R.err("配置数据不能为空"); - - for (Map.Entry entry : configMap.entrySet()) { - String name = entry.getKey(); - String value = entry.getValue(); - - if (!StringUtils.hasText(name)) { - continue; - } - - updateOrCreateConfig(name, value); - } - return R.ok(); - } - - - @Override - public R updateConfig(String name, String value) { - if (!StringUtils.hasText(name)) return R.err("配置名称不能为空"); - if (!StringUtils.hasText(value)) return R.err("配置值不能为空"); - updateOrCreateConfig(name, value); - return R.ok(); - } - - - private void updateOrCreateConfig(String name, String value) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("name", name); - ViteConfig existingConfig = this.getOne(queryWrapper); - - if (existingConfig != null) { - // 更新现有配置 - existingConfig.setValue(value); - existingConfig.setTime(System.currentTimeMillis()); - this.updateById(existingConfig); - } else { - // 创建新配置 - ViteConfig newConfig = new ViteConfig(); - newConfig.setName(name); - newConfig.setValue(value); - newConfig.setTime(System.currentTimeMillis()); - this.save(newConfig); - } - } - -} diff --git a/springboot-backend/src/main/resources/application.yml b/springboot-backend/src/main/resources/application.yml deleted file mode 100644 index 4f5af55..0000000 --- a/springboot-backend/src/main/resources/application.yml +++ /dev/null @@ -1,51 +0,0 @@ -spring: - datasource: - driver-class-name: org.sqlite.JDBC - url: jdbc:sqlite:${DB_PATH:/app/data/gost.db} - hikari: - max-lifetime: 500000 - connection-timeout: 30000 - idle-timeout: 600000 - max-pool-size: 20 - minimum-idle: 5 - pool-name: HikariCP - auto-commit: true - sql: - init: - mode: always - platform: sqlite - continue-on-error: false - servlet: - multipart: - max-file-size: 50MB - max-request-size: 50MB - lifecycle: - timeout-per-shutdown-phase: 30s - -server: - port: 6365 - tomcat: - uri-encoding: UTF-8 - max-thread: 800 - max-connections: 2000 - shutdown: graceful - error: - whitelabel: - enabled: false - include-message: always - include-binding-errors: always - -mybatis-plus: - mapper-locations: classpath*:/mapper/**Mapper.xml -# configuration: -# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - -jwt-secret: ${JWT_SECRET} -log-dir: ${LOG_DIR} - - -captcha: - init-default-resource: true - secondary: - enabled: true - expire: 120000 diff --git a/springboot-backend/src/main/resources/bgimages/1.jpg b/springboot-backend/src/main/resources/bgimages/1.jpg deleted file mode 100644 index f96ca1f..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/1.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/10.jpg b/springboot-backend/src/main/resources/bgimages/10.jpg deleted file mode 100644 index 73f62d9..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/10.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/11.jpg b/springboot-backend/src/main/resources/bgimages/11.jpg deleted file mode 100644 index 40b8c69..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/11.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/12.jpg b/springboot-backend/src/main/resources/bgimages/12.jpg deleted file mode 100644 index 54f1454..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/12.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/13.jpg b/springboot-backend/src/main/resources/bgimages/13.jpg deleted file mode 100644 index 405f5f7..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/13.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/14.jpg b/springboot-backend/src/main/resources/bgimages/14.jpg deleted file mode 100644 index 52ca868..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/14.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/15.jpg b/springboot-backend/src/main/resources/bgimages/15.jpg deleted file mode 100644 index b36d89f..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/15.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/16.jpg b/springboot-backend/src/main/resources/bgimages/16.jpg deleted file mode 100644 index be2aa6b..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/16.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/17.jpg b/springboot-backend/src/main/resources/bgimages/17.jpg deleted file mode 100644 index dafdcb4..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/17.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/18.jpg b/springboot-backend/src/main/resources/bgimages/18.jpg deleted file mode 100644 index efdd342..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/18.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/19.jpg b/springboot-backend/src/main/resources/bgimages/19.jpg deleted file mode 100644 index 80f1358..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/19.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/2.jpg b/springboot-backend/src/main/resources/bgimages/2.jpg deleted file mode 100644 index a5bac6f..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/2.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/20.jpg b/springboot-backend/src/main/resources/bgimages/20.jpg deleted file mode 100644 index 931f4cc..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/20.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/21.jpg b/springboot-backend/src/main/resources/bgimages/21.jpg deleted file mode 100644 index 0963e68..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/21.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/22.jpg b/springboot-backend/src/main/resources/bgimages/22.jpg deleted file mode 100644 index d7bda2c..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/22.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/23.jpg b/springboot-backend/src/main/resources/bgimages/23.jpg deleted file mode 100644 index 287e6d3..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/23.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/24.jpg b/springboot-backend/src/main/resources/bgimages/24.jpg deleted file mode 100644 index 4adf515..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/24.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/25.jpg b/springboot-backend/src/main/resources/bgimages/25.jpg deleted file mode 100644 index 8aee7b0..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/25.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/26.jpg b/springboot-backend/src/main/resources/bgimages/26.jpg deleted file mode 100644 index 2b630f6..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/26.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/3.jpg b/springboot-backend/src/main/resources/bgimages/3.jpg deleted file mode 100644 index 350c729..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/3.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/4.jpg b/springboot-backend/src/main/resources/bgimages/4.jpg deleted file mode 100644 index c7fe08b..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/4.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/5.jpg b/springboot-backend/src/main/resources/bgimages/5.jpg deleted file mode 100644 index 98ad3d2..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/5.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/6.jpg b/springboot-backend/src/main/resources/bgimages/6.jpg deleted file mode 100644 index 1d0cc0b..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/6.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/7.jpg b/springboot-backend/src/main/resources/bgimages/7.jpg deleted file mode 100644 index 26ead24..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/7.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/8.jpg b/springboot-backend/src/main/resources/bgimages/8.jpg deleted file mode 100644 index f2591df..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/8.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/bgimages/9.jpg b/springboot-backend/src/main/resources/bgimages/9.jpg deleted file mode 100644 index 9cc4dc1..0000000 Binary files a/springboot-backend/src/main/resources/bgimages/9.jpg and /dev/null differ diff --git a/springboot-backend/src/main/resources/data.sql b/springboot-backend/src/main/resources/data.sql deleted file mode 100644 index 7d73855..0000000 --- a/springboot-backend/src/main/resources/data.sql +++ /dev/null @@ -1,7 +0,0 @@ - -INSERT OR IGNORE INTO user (id, user, pwd, role_id, exp_time, flow, in_flow, out_flow, flow_reset_time, num, created_time, updated_time, status) -VALUES (1, 'admin_user', '3c85cdebade1c51cf64ca9f3c09d182d', 0, 2727251700000, 99999, 0, 0, 1, 99999, 1748914865000, 1754011744252, 1); - - -INSERT OR IGNORE INTO vite_config (id, name, value, time) -VALUES (1, 'app_name', 'flux', 1755147963000); diff --git a/springboot-backend/src/main/resources/fonts/SIMSUN.TTC b/springboot-backend/src/main/resources/fonts/SIMSUN.TTC deleted file mode 100644 index 40e9693..0000000 Binary files a/springboot-backend/src/main/resources/fonts/SIMSUN.TTC and /dev/null differ diff --git a/springboot-backend/src/main/resources/logback-spring.xml b/springboot-backend/src/main/resources/logback-spring.xml deleted file mode 100644 index 121728a..0000000 --- a/springboot-backend/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - logback - - - - - - - - %d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n - - - - - - true - - ${FILE_PATH} - 7 - - - - %d{yyyy-MM-dd HH:mm:ss} ^ %-5level ^ %logger{36} ^ %msg%n - - - - - - - - - - - - - - - - - - - diff --git a/springboot-backend/src/main/resources/mapper/ForwardMapper.xml b/springboot-backend/src/main/resources/mapper/ForwardMapper.xml deleted file mode 100644 index 56b2ed8..0000000 --- a/springboot-backend/src/main/resources/mapper/ForwardMapper.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - diff --git a/springboot-backend/src/main/resources/mapper/NodeMapper.xml b/springboot-backend/src/main/resources/mapper/NodeMapper.xml deleted file mode 100644 index 30543b3..0000000 --- a/springboot-backend/src/main/resources/mapper/NodeMapper.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/springboot-backend/src/main/resources/mapper/SpeedLimitMapper.xml b/springboot-backend/src/main/resources/mapper/SpeedLimitMapper.xml deleted file mode 100644 index 52dff37..0000000 --- a/springboot-backend/src/main/resources/mapper/SpeedLimitMapper.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/springboot-backend/src/main/resources/mapper/StatisticsFlowMapper.xml b/springboot-backend/src/main/resources/mapper/StatisticsFlowMapper.xml deleted file mode 100644 index 73b986c..0000000 --- a/springboot-backend/src/main/resources/mapper/StatisticsFlowMapper.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/springboot-backend/src/main/resources/mapper/TunnelMapper.xml b/springboot-backend/src/main/resources/mapper/TunnelMapper.xml deleted file mode 100644 index 0d767da..0000000 --- a/springboot-backend/src/main/resources/mapper/TunnelMapper.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/springboot-backend/src/main/resources/mapper/UserMapper.xml b/springboot-backend/src/main/resources/mapper/UserMapper.xml deleted file mode 100644 index 523c0a7..0000000 --- a/springboot-backend/src/main/resources/mapper/UserMapper.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/springboot-backend/src/main/resources/mapper/UserTunnelMapper.xml b/springboot-backend/src/main/resources/mapper/UserTunnelMapper.xml deleted file mode 100644 index 0b1092b..0000000 --- a/springboot-backend/src/main/resources/mapper/UserTunnelMapper.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - diff --git a/springboot-backend/src/main/resources/mapper/ViteConfigMapper.xml b/springboot-backend/src/main/resources/mapper/ViteConfigMapper.xml deleted file mode 100644 index 6a35c4e..0000000 --- a/springboot-backend/src/main/resources/mapper/ViteConfigMapper.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/springboot-backend/src/main/resources/schema.sql b/springboot-backend/src/main/resources/schema.sql deleted file mode 100644 index 9330f04..0000000 --- a/springboot-backend/src/main/resources/schema.sql +++ /dev/null @@ -1,182 +0,0 @@ --- SQLite Auto-generated schema --- This will be executed automatically on startup if tables don't exist - -CREATE TABLE IF NOT EXISTS forward ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - user_name VARCHAR(100) NOT NULL, - name VARCHAR(100) NOT NULL, - tunnel_id INTEGER NOT NULL, - remote_addr TEXT NOT NULL, - strategy VARCHAR(100) NOT NULL DEFAULT 'fifo', - in_flow INTEGER NOT NULL DEFAULT 0, - out_flow INTEGER NOT NULL DEFAULT 0, - created_time INTEGER NOT NULL, - updated_time INTEGER NOT NULL, - status INTEGER NOT NULL, - inx INTEGER NOT NULL DEFAULT 0 -); - -CREATE TABLE IF NOT EXISTS forward_port ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - forward_id INTEGER NOT NULL, - node_id INTEGER NOT NULL, - port INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS node ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(100) NOT NULL, - secret VARCHAR(100) NOT NULL, - server_ip VARCHAR(100) NOT NULL, - server_ip_v4 VARCHAR(100), - server_ip_v6 VARCHAR(100), - port TEXT NOT NULL, - interface_name VARCHAR(200), - version VARCHAR(100), - http INTEGER NOT NULL DEFAULT 0, - tls INTEGER NOT NULL DEFAULT 0, - socks INTEGER NOT NULL DEFAULT 0, - created_time INTEGER NOT NULL, - updated_time INTEGER, - status INTEGER NOT NULL, - tcp_listen_addr VARCHAR(100) NOT NULL DEFAULT '[::]', - udp_listen_addr VARCHAR(100) NOT NULL DEFAULT '[::]', - inx INTEGER NOT NULL DEFAULT 0 -); - -CREATE TABLE IF NOT EXISTS speed_limit ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(100) NOT NULL, - speed INTEGER NOT NULL, - tunnel_id INTEGER NOT NULL, - tunnel_name VARCHAR(100) NOT NULL, - created_time INTEGER NOT NULL, - updated_time INTEGER, - status INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS statistics_flow ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - flow INTEGER NOT NULL, - total_flow INTEGER NOT NULL, - time VARCHAR(100) NOT NULL, - created_time INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS tunnel ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(100) NOT NULL, - traffic_ratio REAL NOT NULL DEFAULT 1.0, - type INTEGER NOT NULL, - protocol VARCHAR(10) NOT NULL DEFAULT 'tls', - flow INTEGER NOT NULL, - created_time INTEGER NOT NULL, - updated_time INTEGER NOT NULL, - status INTEGER NOT NULL, - in_ip TEXT, - inx INTEGER NOT NULL DEFAULT 0 -); - -CREATE TABLE IF NOT EXISTS chain_tunnel ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - tunnel_id INTEGER NOT NULL , - chain_type VARCHAR(10) NOT NULL, - node_id INTEGER NOT NULL , - port INTEGER, - strategy VARCHAR(10), - inx INTEGER, - protocol VARCHAR(10) -); - - -CREATE TABLE IF NOT EXISTS user ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user VARCHAR(100) NOT NULL, - pwd VARCHAR(100) NOT NULL, - role_id INTEGER NOT NULL, - exp_time INTEGER NOT NULL, - flow INTEGER NOT NULL, - in_flow INTEGER NOT NULL DEFAULT 0, - out_flow INTEGER NOT NULL DEFAULT 0, - flow_reset_time INTEGER NOT NULL, - num INTEGER NOT NULL, - created_time INTEGER NOT NULL, - updated_time INTEGER, - status INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS user_tunnel ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - tunnel_id INTEGER NOT NULL, - speed_id INTEGER, - num INTEGER NOT NULL, - flow INTEGER NOT NULL, - in_flow INTEGER NOT NULL DEFAULT 0, - out_flow INTEGER NOT NULL DEFAULT 0, - flow_reset_time INTEGER NOT NULL, - exp_time INTEGER NOT NULL, - status INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS tunnel_group ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(100) NOT NULL, - created_time INTEGER NOT NULL, - updated_time INTEGER NOT NULL, - status INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS user_group ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(100) NOT NULL, - created_time INTEGER NOT NULL, - updated_time INTEGER NOT NULL, - status INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS tunnel_group_tunnel ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - tunnel_group_id INTEGER NOT NULL, - tunnel_id INTEGER NOT NULL, - created_time INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS user_group_user ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_group_id INTEGER NOT NULL, - user_id INTEGER NOT NULL, - created_time INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS group_permission ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_group_id INTEGER NOT NULL, - tunnel_group_id INTEGER NOT NULL, - created_time INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS group_permission_grant ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_group_id INTEGER NOT NULL, - tunnel_group_id INTEGER NOT NULL, - user_tunnel_id INTEGER NOT NULL, - created_by_group INTEGER NOT NULL DEFAULT 0, - created_time INTEGER NOT NULL -); - -CREATE UNIQUE INDEX IF NOT EXISTS idx_tunnel_group_name ON tunnel_group(name); -CREATE UNIQUE INDEX IF NOT EXISTS idx_user_group_name ON user_group(name); -CREATE UNIQUE INDEX IF NOT EXISTS idx_tunnel_group_tunnel_unique ON tunnel_group_tunnel(tunnel_group_id, tunnel_id); -CREATE UNIQUE INDEX IF NOT EXISTS idx_user_group_user_unique ON user_group_user(user_group_id, user_id); -CREATE UNIQUE INDEX IF NOT EXISTS idx_group_permission_unique ON group_permission(user_group_id, tunnel_group_id); -CREATE UNIQUE INDEX IF NOT EXISTS idx_group_permission_grant_unique ON group_permission_grant(user_group_id, tunnel_group_id, user_tunnel_id); - -CREATE TABLE IF NOT EXISTS vite_config ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(200) NOT NULL UNIQUE, - value VARCHAR(200) NOT NULL, - time INTEGER NOT NULL -); diff --git a/springboot-backend/src/main/resources/slide/1/1.png b/springboot-backend/src/main/resources/slide/1/1.png deleted file mode 100644 index d684aa2..0000000 Binary files a/springboot-backend/src/main/resources/slide/1/1.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/1/2.png b/springboot-backend/src/main/resources/slide/1/2.png deleted file mode 100644 index 6a599bb..0000000 Binary files a/springboot-backend/src/main/resources/slide/1/2.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/10/1.png b/springboot-backend/src/main/resources/slide/10/1.png deleted file mode 100644 index cd720b4..0000000 Binary files a/springboot-backend/src/main/resources/slide/10/1.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/10/2.png b/springboot-backend/src/main/resources/slide/10/2.png deleted file mode 100644 index 1e67338..0000000 Binary files a/springboot-backend/src/main/resources/slide/10/2.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/2/1.png b/springboot-backend/src/main/resources/slide/2/1.png deleted file mode 100644 index 9e93069..0000000 Binary files a/springboot-backend/src/main/resources/slide/2/1.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/2/2.png b/springboot-backend/src/main/resources/slide/2/2.png deleted file mode 100644 index c3eb8b7..0000000 Binary files a/springboot-backend/src/main/resources/slide/2/2.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/3/1.png b/springboot-backend/src/main/resources/slide/3/1.png deleted file mode 100644 index 2f8e777..0000000 Binary files a/springboot-backend/src/main/resources/slide/3/1.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/3/2.png b/springboot-backend/src/main/resources/slide/3/2.png deleted file mode 100644 index 39ee879..0000000 Binary files a/springboot-backend/src/main/resources/slide/3/2.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/4/1.png b/springboot-backend/src/main/resources/slide/4/1.png deleted file mode 100644 index 9577cc1..0000000 Binary files a/springboot-backend/src/main/resources/slide/4/1.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/4/2.png b/springboot-backend/src/main/resources/slide/4/2.png deleted file mode 100644 index a9b91a7..0000000 Binary files a/springboot-backend/src/main/resources/slide/4/2.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/5/1.png b/springboot-backend/src/main/resources/slide/5/1.png deleted file mode 100644 index e4cdfa1..0000000 Binary files a/springboot-backend/src/main/resources/slide/5/1.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/5/2.png b/springboot-backend/src/main/resources/slide/5/2.png deleted file mode 100644 index 14ac9af..0000000 Binary files a/springboot-backend/src/main/resources/slide/5/2.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/6/1.png b/springboot-backend/src/main/resources/slide/6/1.png deleted file mode 100644 index 5e78f38..0000000 Binary files a/springboot-backend/src/main/resources/slide/6/1.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/6/2.png b/springboot-backend/src/main/resources/slide/6/2.png deleted file mode 100644 index 55abcbe..0000000 Binary files a/springboot-backend/src/main/resources/slide/6/2.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/7/1.png b/springboot-backend/src/main/resources/slide/7/1.png deleted file mode 100644 index 909b9dd..0000000 Binary files a/springboot-backend/src/main/resources/slide/7/1.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/7/2.png b/springboot-backend/src/main/resources/slide/7/2.png deleted file mode 100644 index 0013e76..0000000 Binary files a/springboot-backend/src/main/resources/slide/7/2.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/8/1.png b/springboot-backend/src/main/resources/slide/8/1.png deleted file mode 100644 index 91b279a..0000000 Binary files a/springboot-backend/src/main/resources/slide/8/1.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/8/2.png b/springboot-backend/src/main/resources/slide/8/2.png deleted file mode 100644 index f124e99..0000000 Binary files a/springboot-backend/src/main/resources/slide/8/2.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/9/1.png b/springboot-backend/src/main/resources/slide/9/1.png deleted file mode 100644 index 9ec99a1..0000000 Binary files a/springboot-backend/src/main/resources/slide/9/1.png and /dev/null differ diff --git a/springboot-backend/src/main/resources/slide/9/2.png b/springboot-backend/src/main/resources/slide/9/2.png deleted file mode 100644 index 72e2ae5..0000000 Binary files a/springboot-backend/src/main/resources/slide/9/2.png and /dev/null differ diff --git a/springboot-backend/src/test/java/com/admin/AdminApplicationTests.java b/springboot-backend/src/test/java/com/admin/AdminApplicationTests.java deleted file mode 100644 index 1171e48..0000000 --- a/springboot-backend/src/test/java/com/admin/AdminApplicationTests.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.admin; - -import com.admin.common.dto.GostDto; -import com.admin.common.utils.GostUtil; -import com.admin.entity.Forward; -import com.admin.entity.Tunnel; -import com.admin.entity.User; -import com.admin.entity.UserTunnel; -import com.admin.service.ForwardService; -import com.admin.service.TunnelService; -import com.admin.service.UserService; -import com.admin.service.UserTunnelService; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -import javax.annotation.Resource; -import java.util.Date; -import java.util.List; - - -@SpringBootTest -class AdminApplicationTests { - - - - @Test - public void test(){ - - } - - - - -} diff --git a/springboot-backend/src/test/java/com/admin/OrderingUpdateTests.java b/springboot-backend/src/test/java/com/admin/OrderingUpdateTests.java deleted file mode 100644 index d412d34..0000000 --- a/springboot-backend/src/test/java/com/admin/OrderingUpdateTests.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.admin; - -import com.admin.common.lang.R; -import com.admin.entity.Node; -import com.admin.entity.Tunnel; -import com.admin.service.NodeService; -import com.admin.service.TunnelService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.jdbc.core.JdbcTemplate; - -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 static org.junit.jupiter.api.Assertions.*; - -@SpringBootTest(properties = { - // use a local sqlite file for tests (resolved via ${DB_PATH} placeholder) - "DB_PATH=./target/test-gost-ordering.db", -}) -class OrderingUpdateTests { - - @Resource - private JdbcTemplate jdbcTemplate; - - @Resource - private NodeService nodeService; - - @Resource - private TunnelService tunnelService; - - @BeforeEach - void cleanup() { - // keep it simple; other tables may have foreign references in real runs - jdbcTemplate.execute("DELETE FROM node"); - jdbcTemplate.execute("DELETE FROM tunnel"); - } - - @Test - void updateNodeOrder_updatesInx() { - Node n1 = new Node(); - n1.setName("n1"); - n1.setSecret("s1"); - n1.setServerIp("127.0.0.1"); - n1.setPort("1000-2000"); - n1.setInterfaceName(""); - n1.setHttp(0); - n1.setTls(0); - n1.setSocks(0); - n1.setTcpListenAddr("[::]"); - n1.setUdpListenAddr("[::]"); - n1.setStatus(0); - n1.setInx(0); - long now = System.currentTimeMillis(); - n1.setCreatedTime(now); - n1.setUpdatedTime(now); - assertTrue(nodeService.save(n1)); - - Node n2 = new Node(); - n2.setName("n2"); - n2.setSecret("s2"); - n2.setServerIp("127.0.0.2"); - n2.setPort("1000-2000"); - n2.setInterfaceName(""); - n2.setHttp(0); - n2.setTls(0); - n2.setSocks(0); - n2.setTcpListenAddr("[::]"); - n2.setUdpListenAddr("[::]"); - n2.setStatus(0); - n2.setInx(0); - n2.setCreatedTime(now); - n2.setUpdatedTime(now); - assertTrue(nodeService.save(n2)); - - Node n3 = new Node(); - n3.setName("n3"); - n3.setSecret("s3"); - n3.setServerIp("127.0.0.3"); - n3.setPort("1000-2000"); - n3.setInterfaceName(""); - n3.setHttp(0); - n3.setTls(0); - n3.setSocks(0); - n3.setTcpListenAddr("[::]"); - n3.setUdpListenAddr("[::]"); - n3.setStatus(0); - n3.setInx(0); - n3.setCreatedTime(now); - n3.setUpdatedTime(now); - assertTrue(nodeService.save(n3)); - - List> nodes = new ArrayList<>(); - nodes.add(mapIdInx(n2.getId(), 0)); - nodes.add(mapIdInx(n1.getId(), 1)); - nodes.add(mapIdInx(n3.getId(), 2)); - - Map params = new HashMap<>(); - params.put("nodes", nodes); - - R res = nodeService.updateNodeOrder(params); - assertEquals(0, res.getCode()); - - assertEquals(1, nodeService.getById(n1.getId()).getInx()); - assertEquals(0, nodeService.getById(n2.getId()).getInx()); - assertEquals(2, nodeService.getById(n3.getId()).getInx()); - } - - @Test - void updateTunnelOrder_updatesInx() { - long now = System.currentTimeMillis(); - - Tunnel t1 = new Tunnel(); - t1.setName("t1"); - t1.setType(1); - t1.setFlow(1); - t1.setTrafficRatio(new BigDecimal("1.0")); - t1.setInIp(""); - t1.setStatus(1); - t1.setInx(0); - t1.setCreatedTime(now); - t1.setUpdatedTime(now); - assertTrue(tunnelService.save(t1)); - - Tunnel t2 = new Tunnel(); - t2.setName("t2"); - t2.setType(2); - t2.setFlow(2); - t2.setTrafficRatio(new BigDecimal("1.0")); - t2.setInIp(""); - t2.setStatus(1); - t2.setInx(0); - t2.setCreatedTime(now); - t2.setUpdatedTime(now); - assertTrue(tunnelService.save(t2)); - - List> tunnels = new ArrayList<>(); - tunnels.add(mapIdInx(t2.getId(), 0)); - tunnels.add(mapIdInx(t1.getId(), 1)); - - Map params = new HashMap<>(); - params.put("tunnels", tunnels); - - R res = tunnelService.updateTunnelOrder(params); - assertEquals(0, res.getCode()); - - assertEquals(1, tunnelService.getById(t1.getId()).getInx()); - assertEquals(0, tunnelService.getById(t2.getId()).getInx()); - } - - private static Map mapIdInx(Long id, int inx) { - Map m = new HashMap<>(); - m.put("id", id); - m.put("inx", inx); - return m; - } -}