diff --git a/.gitignore b/.gitignore index 73014b6..b36f060 100644 --- a/.gitignore +++ b/.gitignore @@ -237,6 +237,9 @@ go-gost/gost.json gitee/ +# 错误位置的Java构建产物 +java/ + # ========================================== # 其他 # ========================================== 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 index 41f1bbb..012c2d7 100644 --- a/springboot-backend/src/main/java/com/admin/common/exception/GlobalExceptionHandler.java +++ b/springboot-backend/src/main/java/com/admin/common/exception/GlobalExceptionHandler.java @@ -34,9 +34,6 @@ public class GlobalExceptionHandler { @ExceptionHandler(value = Exception.class) public R Exception(Exception e){ - if (e instanceof ClientAbortException) { - return null; // 不需要返回值 - } log.error("异常:----------------{}", e.getMessage()); return R.err(-2, "异常错误"); } 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 index 13094f0..3d2c417 100644 --- a/springboot-backend/src/main/java/com/admin/common/utils/HttpUtils.java +++ b/springboot-backend/src/main/java/com/admin/common/utils/HttpUtils.java @@ -28,6 +28,7 @@ import org.springframework.http.client.ClientHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestTemplate; +import org.springframework.util.StreamUtils; import java.io.IOException; import java.net.URI; @@ -100,16 +101,34 @@ public class HttpUtils implements ApplicationContextAware { } /** - * 自定义错误处理器,不抛出异常 + * 自定义错误处理器,不抛出异常但记录错误信息 */ private static class NoOpResponseErrorHandler implements ResponseErrorHandler { @Override public boolean hasError(ClientHttpResponse response) throws IOException { - return false; + HttpStatus statusCode = response.getStatusCode(); + return statusCode.series() == HttpStatus.Series.CLIENT_ERROR || + statusCode.series() == HttpStatus.Series.SERVER_ERROR; } @Override public void handleError(ClientHttpResponse response) throws IOException { + HttpStatus statusCode = response.getStatusCode(); + String statusText = response.getStatusText(); + String responseBody = ""; + + try { + // 使用Spring的StreamUtils来读取响应体 + responseBody = new String( + StreamUtils.copyToByteArray(response.getBody()), + StandardCharsets.UTF_8 + ); + } catch (Exception e) { + responseBody = "无法读取响应体: " + e.getMessage(); + } + + logger.error("HTTP请求错误 - 状态码: {} {}, 响应体: {}", + statusCode.value(), statusText, responseBody); } } diff --git a/vue-frontend/src/views/Forward.vue b/vue-frontend/src/views/Forward.vue index 5428b5d..5b6f125 100644 --- a/vue-frontend/src/views/Forward.vue +++ b/vue-frontend/src/views/Forward.vue @@ -177,13 +177,13 @@
- 格式: IP:端口 或 域名:端口 + 格式: IPv4(IP:端口)、IPv6([IPv6]:端口)或域名:端口
@@ -254,8 +254,32 @@ export default { remoteAddr: [ { required: true, message: '请输入远程地址', trigger: 'blur' }, { - pattern: /^[\w\.-]+:\d+$/, - message: '请输入正确的地址格式,如: 192.168.1.100:8080', + validator: (rule, value, callback) => { + if (value) { + // IPv4格式:192.168.1.1:8080 + const ipv4Pattern = /^(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]?):\d+$/; + + // IPv6格式(带方括号):[2001:db8::1]:8080 + const ipv6BracketPattern = /^\[([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}\]:\d+$/; + + // IPv6格式(简化版本,包括::1, ::, ::ffff:192.0.2.1等) + const ipv6SimplifiedPattern = /^\[(::1|::|::ffff:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|([0-9a-fA-F]{1,4}:){1,7}:|:([0-9a-fA-F]{1,4}:){1,6}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,6}:([0-9a-fA-F]{1,4}:){1,6}[0-9a-fA-F]{1,4})\]:\d+$/; + + // 域名格式:example.com:8080 + const domainPattern = /^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*:\d+$/; + + // 更完整的IPv6正则(支持各种压缩格式) + const ipv6FullPattern = /^\[((([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:))|(([0-9a-fA-F]{1,4}:){6}(:[0-9a-fA-F]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-fA-F]{1,4}:){5}(((:[0-9a-fA-F]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-fA-F]{1,4}:){4}(((:[0-9a-fA-F]{1,4}){1,3})|((:[0-9a-fA-F]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-fA-F]{1,4}:){3}(((:[0-9a-fA-F]{1,4}){1,4})|((:[0-9a-fA-F]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-fA-F]{1,4}:){2}(((:[0-9a-fA-F]{1,4}){1,5})|((:[0-9a-fA-F]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-fA-F]{1,4}:){1}(((:[0-9a-fA-F]{1,4}){1,6})|((:[0-9a-fA-F]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-fA-F]{1,4}){1,7})|((:[0-9a-fA-F]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))\]:\d+$/; + + if (ipv4Pattern.test(value) || ipv6FullPattern.test(value) || domainPattern.test(value)) { + callback(); + } else { + callback(new Error('请输入正确的地址格式,如: 192.168.1.100:8080 或 [2001:db8::1]:8080 或 example.com:8080')); + } + } else { + callback(); + } + }, trigger: 'blur' } ] diff --git a/vue-frontend/src/views/node.vue b/vue-frontend/src/views/node.vue index 8a3d7f7..1ad8462 100644 --- a/vue-frontend/src/views/node.vue +++ b/vue-frontend/src/views/node.vue @@ -213,7 +213,7 @@ @@ -312,13 +312,14 @@ export default { if (value) { // IPv4格式验证 const ipv4Regex = /^(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]?)$/; - // IPv6格式验证(简化版) - const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$/; - if (!ipv4Regex.test(value) && !ipv6Regex.test(value)) { - callback(new Error('请输入有效的IP地址(支持IPv4和IPv6)')); - } else { + // IPv6格式验证(完整版,支持各种压缩格式) + const ipv6Regex = /^((([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:))|(([0-9a-fA-F]{1,4}:){6}(:[0-9a-fA-F]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-fA-F]{1,4}:){5}(((:[0-9a-fA-F]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-fA-F]{1,4}:){4}(((:[0-9a-fA-F]{1,4}){1,3})|((:[0-9a-fA-F]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-fA-F]{1,4}:){3}(((:[0-9a-fA-F]{1,4}){1,4})|((:[0-9a-fA-F]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-fA-F]{1,4}:){2}(((:[0-9a-fA-F]{1,4}){1,5})|((:[0-9a-fA-F]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-fA-F]{1,4}:){1}(((:[0-9a-fA-F]{1,4}){1,6})|((:[0-9a-fA-F]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-fA-F]{1,4}){1,7})|((:[0-9a-fA-F]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/; + + if (ipv4Regex.test(value) || ipv6Regex.test(value)) { callback(); + } else { + callback(new Error('请输入有效的IP地址(支持IPv4和IPv6格式,如: 192.168.1.1 或 2001:db8::1)')); } } else { callback();