From 70022f15d6d03d6a502ad6cfdb87578ee78ac08e Mon Sep 17 00:00:00 2001 From: chiyi Date: Thu, 8 Oct 2026 19:54:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B5=8B=E6=B4=BB=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E4=BB=A3=E7=90=86=EF=BC=88=E5=90=AB=E6=AD=BB?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=EF=BC=89=EF=BC=8C=E6=8B=A8=E6=B4=BB=E5=8D=B3?= =?UTF-8?q?=E5=A4=8D=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ListProxiesDue 去掉 alive=1 限制:死代理每小时也重测,协议级握手 成功就 MarkProxyAlive 复活(fail_count 清零),不再只能等同步时 重新出现在源列表才复活。 附带:提取页加「带协议前缀」开关,复制链接自动带 fmt=url。 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- README.md | 4 ++-- backend/internal/db/queries.sql | 2 +- backend/internal/db/queries.sql.go | 2 +- backend/internal/service/proxy.go | 4 ++-- frontend/src/views/ProxyExtract.vue | 8 ++++++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5404db0..065187b 100644 --- a/README.md +++ b/README.md @@ -187,9 +187,9 @@ make build # go build + vite build - 同步:启动时若无数据或上次超过 20h 补一次,之后每日 00:00(本地时区)全量 diff—— 源上消失即删、已存在刷新指标复活、新增入库;`proxy_syncs` 记录 added/removed/updated 与明细 (各最多 200 个地址); -- 测活:每小时一轮,对距上次检测 >55min 的存活代理做**协议级握手**(SOCKS5 greeting+CONNECT / +- 测活:每小时一轮,对距上次检测 >55min 的**全部**代理做**协议级握手**(SOCKS5 greeting+CONNECT / SOCKS4 / HTTP GET 转发——裸 TCP 连通不算数,防透明网关误判),并发 128、4s 超时; - 失败标 alive=0,死记录 7 天后物理清除; + 失败标 alive=0,拨活即复活(alive=1、fail_count 清零),死记录 7 天后物理清除; - 页面 `/proxies`:左栏大洲→地区分类(存活数),右侧筛选/搜索/分页列表,底部同步记录可展开变更明细; - `ProxySvc`:List/Regions/Stats 登录即可,Syncs/SyncNow/CheckNow 仅 admin。 diff --git a/backend/internal/db/queries.sql b/backend/internal/db/queries.sql index 0fb4942..69f4185 100644 --- a/backend/internal/db/queries.sql +++ b/backend/internal/db/queries.sql @@ -208,7 +208,7 @@ WHERE id = ?; DELETE FROM proxies WHERE id = ?; -- name: ListProxiesDue :many -SELECT id, ip, port, protocols, fail_count FROM proxies WHERE alive = 1 AND last_checked_at < ? ORDER BY last_checked_at LIMIT 20000; +SELECT id, ip, port, protocols, fail_count FROM proxies WHERE last_checked_at < ? ORDER BY last_checked_at LIMIT 20000; -- name: MarkProxyAlive :exec UPDATE proxies SET last_checked_at = ?, alive = 1, fail_count = 0, last_alive_at = ?, latency = ? WHERE id = ?; diff --git a/backend/internal/db/queries.sql.go b/backend/internal/db/queries.sql.go index 2f8655b..9eed618 100644 --- a/backend/internal/db/queries.sql.go +++ b/backend/internal/db/queries.sql.go @@ -1887,7 +1887,7 @@ func (q *Queries) ListPendingPayOrders(ctx context.Context) ([]PayOrder, error) } const listProxiesDue = `-- name: ListProxiesDue :many -SELECT id, ip, port, protocols, fail_count FROM proxies WHERE alive = 1 AND last_checked_at < ? ORDER BY last_checked_at LIMIT 20000 +SELECT id, ip, port, protocols, fail_count FROM proxies WHERE last_checked_at < ? ORDER BY last_checked_at LIMIT 20000 ` type ListProxiesDueRow struct { diff --git a/backend/internal/service/proxy.go b/backend/internal/service/proxy.go index f607709..08921b7 100644 --- a/backend/internal/service/proxy.go +++ b/backend/internal/service/proxy.go @@ -297,8 +297,8 @@ func runProxySync(ctx context.Context, st *store.Store) { sync.Total, sync.Added, sync.Updated, sync.Removed, failed) } -// runProxyCheck 测活一轮:取距上次检测超过阈值的存活代理,并发 TCP 拨测; -// 连不上即标记为死(alive=0),死记录超过保留期后物理清除。 +// runProxyCheck 测活一轮:取距上次检测超过阈值的全部代理(含死记录,拨活即复活), +// 并发协议级拨测;连不上即标记为死(alive=0),死记录超过保留期后物理清除。 func runProxyCheck(ctx context.Context, st *store.Store) { if !proxyChecking.CompareAndSwap(false, true) { return diff --git a/frontend/src/views/ProxyExtract.vue b/frontend/src/views/ProxyExtract.vue index f1161f3..a1798e3 100644 --- a/frontend/src/views/ProxyExtract.vue +++ b/frontend/src/views/ProxyExtract.vue @@ -61,8 +61,10 @@ async function create() { } } +const withScheme = ref(false) + function extractUrl(k: proxyKeyView): string { - return `${location.origin}/api/v1/proxy?key=${k.key}` + return `${location.origin}/api/v1/proxy?key=${k.key}${withScheme.value ? '&fmt=url' : ''}` } async function copyUrl(k: proxyKeyView) { @@ -100,6 +102,7 @@ function regionLabel(k: proxyKeyView): string {
提取密钥 {{ keys.length }} 个 + 带协议前缀 生成密钥 @@ -128,7 +131,7 @@ function regionLabel(k: proxyKeyView): string {
使用方式 -
GET /api/v1/proxy?key=<密钥>
+
GET /api/v1/proxy?key=<密钥>{{ withScheme ? '&fmt=url' : '' }}
每次请求实时探活,返回一个可用的 ip:port(纯文本);加 &fmt=url 返回带协议的 scheme://ip:port(socks5h/http/socks4)。地区由密钥绑定,「全部地区」密钥随机返回任意国家。
@@ -175,6 +178,7 @@ 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 {