feat(proxy): 集中代理 URL 验证并实现全局 fail-fast

提取 proxyurl.Parse() 公共包,将分散在 6 处的代理 URL 验证逻辑
统一收敛,确保无效代理配置在创建时立即失败,永不静默回退直连。

主要变更:
- 新增 proxyurl 包:统一 TrimSpace → url.Parse → Host 校验 → Scheme 白名单
- socks5:// 自动升级为 socks5h://,防止 DNS 泄漏(大小写不敏感)
- antigravity: http.ProxyURL → proxyutil.ConfigureTransportProxy 支持 SOCKS5
- openai_oauth: 删除 newOpenAIOAuthHTTPClient,收编至 httpclient.GetClient
- 移除未使用的 ProxyStrict 字段(fail-fast 已是全局默认行为)
- 补充 15 个 proxyurl 测试 + pricing/usage fail-fast 测试
This commit is contained in:
QTom
2026-03-02 15:53:26 +08:00
parent 445bfdf242
commit fdcbf7aacf
31 changed files with 633 additions and 157 deletions

View File

@@ -6,6 +6,8 @@ import (
"sync"
"time"
"github.com/Wei-Shaw/sub2api/internal/pkg/proxyurl"
"github.com/imroc/req/v3"
)
@@ -33,11 +35,11 @@ var sharedReqClients sync.Map
// getSharedReqClient 获取共享的 req 客户端实例
// 性能优化:相同配置复用同一客户端,避免重复创建
func getSharedReqClient(opts reqClientOptions) *req.Client {
func getSharedReqClient(opts reqClientOptions) (*req.Client, error) {
key := buildReqClientKey(opts)
if cached, ok := sharedReqClients.Load(key); ok {
if c, ok := cached.(*req.Client); ok {
return c
return c, nil
}
}
@@ -48,15 +50,19 @@ func getSharedReqClient(opts reqClientOptions) *req.Client {
if opts.Impersonate {
client = client.ImpersonateChrome()
}
if strings.TrimSpace(opts.ProxyURL) != "" {
client.SetProxyURL(strings.TrimSpace(opts.ProxyURL))
trimmed, _, err := proxyurl.Parse(opts.ProxyURL)
if err != nil {
return nil, err
}
if trimmed != "" {
client.SetProxyURL(trimmed)
}
actual, _ := sharedReqClients.LoadOrStore(key, client)
if c, ok := actual.(*req.Client); ok {
return c
return c, nil
}
return client
return client, nil
}
func buildReqClientKey(opts reqClientOptions) string {