This commit is contained in:
qaq
2025-06-17 16:24:47 +08:00
parent 08cb8a4b53
commit ce6f5917c2
5 changed files with 59 additions and 15 deletions
+3
View File
@@ -237,6 +237,9 @@ go-gost/gost.json
gitee/ gitee/
# 错误位置的Java构建产物
java/
# ========================================== # ==========================================
# 其他 # 其他
# ========================================== # ==========================================
@@ -34,9 +34,6 @@ public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class) @ExceptionHandler(value = Exception.class)
public R Exception(Exception e){ public R Exception(Exception e){
if (e instanceof ClientAbortException) {
return null; // 不需要返回值
}
log.error("异常:----------------{}", e.getMessage()); log.error("异常:----------------{}", e.getMessage());
return R.err(-2, "异常错误"); return R.err(-2, "异常错误");
} }
@@ -28,6 +28,7 @@ import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.util.StreamUtils;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@@ -100,16 +101,34 @@ public class HttpUtils implements ApplicationContextAware {
} }
/** /**
* 自定义错误处理器,不抛出异常 * 自定义错误处理器,不抛出异常但记录错误信息
*/ */
private static class NoOpResponseErrorHandler implements ResponseErrorHandler { private static class NoOpResponseErrorHandler implements ResponseErrorHandler {
@Override @Override
public boolean hasError(ClientHttpResponse response) throws IOException { 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 @Override
public void handleError(ClientHttpResponse response) throws IOException { 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);
} }
} }
+28 -4
View File
@@ -177,13 +177,13 @@
<el-form-item label="远程地址" prop="remoteAddr"> <el-form-item label="远程地址" prop="remoteAddr">
<el-input <el-input
v-model="forwardForm.remoteAddr" v-model="forwardForm.remoteAddr"
placeholder="例如: 192.168.1.100:8080" placeholder="例如: 192.168.1.100:8080 或 [2001:db8::1]:8080"
clearable clearable
> >
<template slot="prepend">目标</template> <template slot="prepend">目标</template>
</el-input> </el-input>
<div class="form-hint"> <div class="form-hint">
格式: IP:端口 或 域名:端口 格式: IPv4(IP:端口)、IPv6([IPv6]:端口)或域名:端口
</div> </div>
</el-form-item> </el-form-item>
@@ -254,8 +254,32 @@ export default {
remoteAddr: [ remoteAddr: [
{ required: true, message: '请输入远程地址', trigger: 'blur' }, { required: true, message: '请输入远程地址', trigger: 'blur' },
{ {
pattern: /^[\w\.-]+:\d+$/, validator: (rule, value, callback) => {
message: '请输入正确的地址格式,如: 192.168.1.100:8080', 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' trigger: 'blur'
} }
] ]
+7 -6
View File
@@ -213,7 +213,7 @@
<el-form-item v-if="isEdit" label="节点IP" prop="ip"> <el-form-item v-if="isEdit" label="节点IP" prop="ip">
<el-input <el-input
v-model="nodeForm.ip" v-model="nodeForm.ip"
placeholder="请输入节点IP地址" placeholder="请输入节点IP地址,如: 192.168.1.1 或 2001:db8::1"
clearable clearable
></el-input> ></el-input>
</el-form-item> </el-form-item>
@@ -312,13 +312,14 @@ export default {
if (value) { if (value) {
// IPv4格式验证 // 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]?)$/; 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)) { // IPv6格式验证(完整版,支持各种压缩格式)
callback(new Error('请输入有效的IP地址(支持IPv4和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}))|:)))$/;
} else {
if (ipv4Regex.test(value) || ipv6Regex.test(value)) {
callback(); callback();
} else {
callback(new Error('请输入有效的IP地址(支持IPv4和IPv6格式,如: 192.168.1.1 或 2001:db8::1)'));
} }
} else { } else {
callback(); callback();