From 2ab39ecfab7dd2dea4f657fa7daf65c0adadb86f Mon Sep 17 00:00:00 2001 From: chiyi Date: Thu, 8 Oct 2026 20:39:06 +0800 Subject: [PATCH] proxy extract: always return scheme://ip:port The plain ip:port output forced consumers to guess the protocol. The probe already knows it from the handshake, so return the full URL directly (socks5h/http/socks4). Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- README.md | 2 +- backend/internal/service/proxy_routes.go | 10 ++-------- frontend/src/views/ProxyExtract.vue | 10 +++------- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 065187b..7b243ec 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ make build # go build + vite build - `/proxies/extract` 页生成密钥(`pk_xxx`,存 `proxy_keys` 表,绑定地区或「全部」),删除即吊销; - `GET /v1/proxy?key=<密钥>` 免登录:随机抽 15 个存活候选**并行**协议级探活(3s 超时), - 先活先回一行 `ip:port` 纯文本(加 `&fmt=url` 返回 `scheme://ip:port`,scheme 为握手实测的 + 先活先回一行 `scheme://ip:port`(握手实测协议)(加 `&fmt=url` 返回 `scheme://ip:port`,scheme 为握手实测的 socks5h/http/socks4);死的顺手标 `alive=0`,最多 4 批 / 20s 总超时,全死 503; - 每次成功提取 `used_count+1`、`last_used_at` 更新。 diff --git a/backend/internal/service/proxy_routes.go b/backend/internal/service/proxy_routes.go index 8df3840..5f3dba6 100644 --- a/backend/internal/service/proxy_routes.go +++ b/backend/internal/service/proxy_routes.go @@ -28,8 +28,7 @@ type extractResult struct { // ProxyExtractHandler GET /v1/proxy?key=xxx[&fmt=url] // 公开提取接口:key 落库校验,地区绑在 key 上(0=全部)。 -// 并行探测一批候选,谁先探活返回谁;默认纯文本 "ip:port", -// fmt=url 时带握手实测协议返回 "scheme://ip:port"(socks5h/http/socks4)。 +// 并行探测一批候选,谁先探活返回谁;返回带握手实测协议的 "scheme://ip:port"(socks5h/http/socks4)。 // 两批全死返回 503。 func ProxyExtractHandler(st *store.Store) fun.RouteHandler { return func(rc *fun.RouteCtx) error { @@ -44,7 +43,6 @@ func ProxyExtractHandler(st *store.Store) fun.RouteHandler { routeJSONErr(reqCtx, 401, "invalid key") return nil } - withScheme := strings.TrimSpace(string(reqCtx.QueryArgs().Peek("fmt"))) == "url" ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(extractDeadline)) defer cancel() @@ -61,11 +59,7 @@ func ProxyExtractHandler(st *store.Store) fun.RouteHandler { if p, proto, _, ok := probeBatch(ctx, st, items); ok { st.TouchProxyApiKey(k.Id) reqCtx.SetContentType("text/plain; charset=utf-8") - if withScheme { - reqCtx.SetBodyString(fmt.Sprintf("%s://%s:%d\n", urlScheme(proto), p.Ip, p.Port)) - } else { - reqCtx.SetBodyString(fmt.Sprintf("%s:%d\n", p.Ip, p.Port)) - } + reqCtx.SetBodyString(fmt.Sprintf("%s://%s:%d\n", urlScheme(proto), p.Ip, p.Port)) return nil } } diff --git a/frontend/src/views/ProxyExtract.vue b/frontend/src/views/ProxyExtract.vue index a1798e3..b23fafb 100644 --- a/frontend/src/views/ProxyExtract.vue +++ b/frontend/src/views/ProxyExtract.vue @@ -61,10 +61,8 @@ async function create() { } } -const withScheme = ref(false) - function extractUrl(k: proxyKeyView): string { - return `${location.origin}/api/v1/proxy?key=${k.key}${withScheme.value ? '&fmt=url' : ''}` + return `${location.origin}/api/v1/proxy?key=${k.key}` } async function copyUrl(k: proxyKeyView) { @@ -102,7 +100,6 @@ function regionLabel(k: proxyKeyView): string {
提取密钥 {{ keys.length }} 个 - 带协议前缀 生成密钥 @@ -131,8 +128,8 @@ function regionLabel(k: proxyKeyView): string {
使用方式 -
GET /api/v1/proxy?key=<密钥>{{ withScheme ? '&fmt=url' : '' }}
-
每次请求实时探活,返回一个可用的 ip:port(纯文本);加 &fmt=url 返回带协议的 scheme://ip:port(socks5h/http/socks4)。地区由密钥绑定,「全部地区」密钥随机返回任意国家。
+
GET /api/v1/proxy?key=<密钥>
+
每次请求实时探活,返回一个可用的 scheme://ip:port(协议是握手实测结果:socks5h/http/socks4)。地区由密钥绑定,「全部地区」密钥随机返回任意国家。
@@ -178,7 +175,6 @@ function regionLabel(k: proxyKeyView): string { .key-stat { font-size: 12px; font-weight: 700; text-align: right; flex: none; min-width: 90px; } .mono { font-family: Consolas, monospace; } .dim { color: var(--cb-ink-3); font-weight: 500; } -.fmt-toggle { display: inline-flex; align-items: center; gap: 6px; margin-left: 14px; font-size: 12px; font-weight: 700; color: var(--cb-ink-2); } .usage { margin-top: 16px; } .usage-line { margin: 8px 0 4px; font-size: 13px; } .panel-inset {