Files
nofx/proxy/README.md
T
2025-11-05 21:41:36 -05:00

17 KiB
Raw Blame History

HTTP 代理模块

概述

这是一个高度解耦的HTTP代理管理模块,专为解决高频API请求被限流/封禁问题而设计。支持单代理、代理池和动态IP获取三种模式,提供线程安全的IP轮换和智能黑名单管理机制。

功能特性

  • 三种工作模式:单代理、固定代理池、Bright Data API动态获取
  • 线程安全:所有操作使用读写锁保护,支持并发访问
  • 智能黑名单:失败的代理IP手动加入黑名单,TTL机制自动恢复
  • 自动刷新:支持定时刷新代理IP列表(默认30分钟)
  • 随机轮换:从可用IP池中随机选择,避免单点压力
  • 防越界保护:多层数组边界检查,确保运行时安全
  • 可选启用:未配置或禁用时自动使用直连,不影响独立客户

架构设计

proxy/
├── README.md                    # 本文档
├── types.go                     # 核心数据结构定义
├── provider.go                  # IP提供者接口定义
├── single_provider.go           # 单代理实现
├── fixed_provider.go            # 固定代理池实现
├── brightdata_provider.go       # Bright Data API实现
└── proxy_manager.go             # 代理管理器(核心逻辑)

设计原则

  1. 接口抽象:通过 IPProvider 接口实现不同代理源的统一管理
  2. 策略模式:三种Provider实现可灵活切换
  3. 单例模式:全局ProxyManager确保资源统一管理
  4. 防御性编程:多层边界检查,优雅处理异常情况

配置说明

config.json 中添加 proxy 配置段:

{
  "proxy": {
    "enabled": true,
    "mode": "single",
    "timeout": 30,
    "proxy_url": "http://127.0.0.1:7890",
    "proxy_list": [],
    "brightdata_endpoint": "",
    "brightdata_token": "",
    "brightdata_zone": "",
    "proxy_host": "",
    "proxy_user": "",
    "proxy_password": "",
    "refresh_interval": 1800,
    "blacklist_ttl": 5
  }
}

配置字段详解

字段 类型 必填 说明
enabled bool 是否启用代理(false时使用直连)
mode string 代理模式:single/pool/brightdata
timeout int HTTP请求超时时间(秒),默认30
proxy_url string single模式必填 单个代理地址,如 http://127.0.0.1:7890
proxy_list []string pool模式必填 代理列表,支持 http://https://socks5://
brightdata_endpoint string brightdata模式必填 Bright Data API端点
brightdata_token string brightdata模式可选 Bright Data访问令牌
brightdata_zone string brightdata模式可选 Bright Data区域参数
proxy_host string 代理主机(用于认证代理)
proxy_user string 代理用户名模板,支持 %s 占位符替换IP
proxy_password string 代理密码
refresh_interval int IP列表刷新间隔(秒),brightdata模式默认180030分钟)
blacklist_ttl int 黑名单IP的TTL(刷新次数),默认5

使用方法

1. 初始化代理管理器

main.go 或初始化代码中:

import (
    "nofx/proxy"
    "time"
)

// 方式1:使用配置结构体初始化
proxyConfig := &proxy.Config{
    Enabled: true,
    Mode: "single",
    Timeout: 30 * time.Second,
    ProxyURL: "http://127.0.0.1:7890",
    BlacklistTTL: 5,
}

err := proxy.InitGlobalProxyManager(proxyConfig)
if err != nil {
    log.Fatalf("初始化代理管理器失败: %v", err)
}

2. 获取代理HTTP客户端

在需要发送HTTP请求的地方:

// 获取代理客户端(包含ProxyID用于黑名单管理)
proxyClient, err := proxy.GetProxyHTTPClient()
if err != nil {
    log.Printf("获取代理客户端失败: %v", err)
    return
}

// 使用代理客户端发送请求
resp, err := proxyClient.Client.Get("https://api.example.com/data")
if err != nil {
    // 请求失败,将此代理加入黑名单
    proxy.AddBlacklist(proxyClient.ProxyID)
    log.Printf("请求失败,代理IP %s 已加入黑名单", proxyClient.IP)
    return
}
defer resp.Body.Close()

// 处理响应...

3. 黑名单管理

// 添加失败的代理到黑名单
proxy.AddBlacklist(proxyClient.ProxyID)

// 获取黑名单状态
total, blacklisted, available := proxy.GetGlobalProxyManager().GetBlacklistStatus()
log.Printf("代理状态: 总计%d个,黑名单%d个,可用%d个", total, blacklisted, available)

4. 手动刷新IP列表

err := proxy.RefreshIPList()
if err != nil {
    log.Printf("刷新IP列表失败: %v", err)
}

5. 检查代理是否启用

if proxy.IsEnabled() {
    log.Println("代理已启用")
} else {
    log.Println("代理未启用,使用直连")
}

三种模式详解

Mode 1: Single(单代理模式)

适用场景:本地代理工具(如Clash、V2Ray)或单个固定代理服务器

{
  "proxy": {
    "enabled": true,
    "mode": "single",
    "proxy_url": "http://127.0.0.1:7890"
  }
}

特点:

  • 简单直接,适合本地开发和测试
  • 所有请求通过同一个代理
  • 不需要刷新和轮换

Mode 2: Pool(代理池模式)

适用场景:拥有多个固定代理服务器,需要轮换使用

{
  "proxy": {
    "enabled": true,
    "mode": "pool",
    "proxy_list": [
      "http://proxy1.example.com:8080",
      "http://user:pass@proxy2.example.com:8080",
      "socks5://proxy3.example.com:1080"
    ],
    "blacklist_ttl": 5
  }
}

特点:

  • 支持多协议:HTTP、HTTPS、SOCKS5
  • 随机选择代理,分散请求压力
  • 失败的代理自动加入黑名单
  • 黑名单IP经过TTL次刷新后自动恢复

Mode 3: BrightData(动态IP模式)

适用场景:使用Bright Data等提供API的动态代理服务

{
  "proxy": {
    "enabled": true,
    "mode": "brightdata",
    "brightdata_endpoint": "https://api.brightdata.com/zones/get_ips",
    "brightdata_token": "your_api_token",
    "brightdata_zone": "residential",
    "proxy_host": "brd.superproxy.io:22225",
    "proxy_user": "brd-customer-xxx-zone-residential-ip-%s",
    "proxy_password": "your_password",
    "refresh_interval": 1800,
    "blacklist_ttl": 5
  }
}

特点:

  • 从API动态获取可用IP列表
  • 自动定时刷新(默认30分钟)
  • 支持用户名模板(%s 替换为IP地址)
  • 黑名单TTL机制避免频繁切换

用户名模板说明

proxy_user: "brd-customer-xxx-zone-residential-ip-%s"
                                                    ↑
                                             自动替换为IP地址

核心API

全局函数

// 初始化全局代理管理器(只执行一次)
func InitGlobalProxyManager(config *Config) error

// 获取全局代理管理器实例
func GetGlobalProxyManager() *ProxyManager

// 获取代理HTTP客户端(包含ProxyID和IP信息)
func GetProxyHTTPClient() (*ProxyClient, error)

// 将代理IP添加到黑名单
func AddBlacklist(proxyID int)

// 刷新IP列表
func RefreshIPList() error

// 检查代理是否启用
func IsEnabled() bool

ProxyManager 方法

// 获取代理客户端
func (m *ProxyManager) GetProxyClient() (*ProxyClient, error)

// 刷新IP列表
func (m *ProxyManager) RefreshIPList() error

// 添加到黑名单
func (m *ProxyManager) AddBlacklist(proxyID int)

// 获取黑名单状态
func (m *ProxyManager) GetBlacklistStatus() (total, blacklisted, available int)

// 启动自动刷新
func (m *ProxyManager) StartAutoRefresh()

// 停止自动刷新
func (m *ProxyManager) StopAutoRefresh()

黑名单机制

工作原理

  1. 添加黑名单:当代理请求失败时,调用 AddBlacklist(proxyID) 将该IP加入黑名单
  2. TTL倒计时:每次刷新IP列表时,黑名单中的IP的TTL减1
  3. 自动恢复:当TTL归零时,IP自动从黑名单移除,重新可用

线程安全保证

// 添加黑名单使用写锁
func (m *ProxyManager) AddBlacklist(proxyID int) {
    m.mutex.Lock()
    defer m.mutex.Unlock()

    // 防越界检查
    if proxyID < 0 || proxyID >= len(m.ipList) {
        log.Printf("⚠️  无效的 ProxyID: %d", proxyID)
        return
    }

    ip := m.ipList[proxyID].IP
    m.blacklist[proxyID] = ip
    m.ipBlacklist[ip] = m.config.BlacklistTTL
}

// 获取代理使用读锁(支持并发)
func (m *ProxyManager) getRandomProxy() (int, *ProxyIP, error) {
    m.mutex.RLock()
    defer m.mutex.RUnlock()
    // ... 读取操作
}

示例流程

初始状态:5个代理IPTTL=3
IP列表: [IP1, IP2, IP3, IP4, IP5]
黑名单: {}

第1次失败:IP2请求失败
IP列表: [IP1, IP2, IP3, IP4, IP5]
黑名单: {IP2: TTL=3}

第1次刷新:TTL-1
黑名单: {IP2: TTL=2}

第2次刷新:TTL-1
黑名单: {IP2: TTL=1}

第3次刷新:TTL-1
黑名单: {IP2: TTL=0}  → 从黑名单移除

第3次刷新后:
IP列表: [IP1, IP2, IP3, IP4, IP5]
黑名单: {}  ← IP2已恢复可用

完整使用示例

示例1:币安API请求(单代理模式)

package main

import (
    "log"
    "nofx/proxy"
    "time"
)

func main() {
    // 初始化代理
    err := proxy.InitGlobalProxyManager(&proxy.Config{
        Enabled: true,
        Mode: "single",
        ProxyURL: "http://127.0.0.1:7890",
        Timeout: 30 * time.Second,
    })
    if err != nil {
        log.Fatalf("初始化代理失败: %v", err)
    }

    // 获取币安数据
    proxyClient, err := proxy.GetProxyHTTPClient()
    if err != nil {
        log.Fatalf("获取代理客户端失败: %v", err)
    }

    resp, err := proxyClient.Client.Get("https://fapi.binance.com/fapi/v1/ticker/24hr")
    if err != nil {
        log.Printf("请求失败: %v", err)
        return
    }
    defer resp.Body.Close()

    log.Printf("请求成功,使用代理: %s", proxyClient.IP)
}

示例2:OI数据获取(代理池模式 + 黑名单)

package main

import (
    "fmt"
    "io"
    "log"
    "nofx/proxy"
    "time"
)

func fetchOIData(symbol string) error {
    proxyClient, err := proxy.GetProxyHTTPClient()
    if err != nil {
        return fmt.Errorf("获取代理失败: %w", err)
    }

    url := fmt.Sprintf("https://fapi.binance.com/futures/data/openInterestHist?symbol=%s&period=5m&limit=1", symbol)
    resp, err := proxyClient.Client.Get(url)
    if err != nil {
        // 请求失败,加入黑名单
        proxy.AddBlacklist(proxyClient.ProxyID)
        return fmt.Errorf("请求失败 (代理: %s): %w", proxyClient.IP, err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        // 状态码异常,加入黑名单
        proxy.AddBlacklist(proxyClient.ProxyID)
        return fmt.Errorf("状态码异常: %d (代理: %s)", resp.StatusCode, proxyClient.IP)
    }

    body, _ := io.ReadAll(resp.Body)
    log.Printf("✓ 获取 %s OI数据成功 (代理: %s): %s", symbol, proxyClient.IP, string(body))
    return nil
}

func main() {
    // 初始化代理池
    err := proxy.InitGlobalProxyManager(&proxy.Config{
        Enabled: true,
        Mode: "pool",
        ProxyList: []string{
            "http://proxy1.example.com:8080",
            "http://proxy2.example.com:8080",
            "http://proxy3.example.com:8080",
        },
        Timeout: 30 * time.Second,
        BlacklistTTL: 5,
    })
    if err != nil {
        log.Fatalf("初始化代理失败: %v", err)
    }

    // 循环获取数据
    symbols := []string{"BTCUSDT", "ETHUSDT", "SOLUSDT"}
    for {
        for _, symbol := range symbols {
            if err := fetchOIData(symbol); err != nil {
                log.Printf("⚠️  %v", err)
            }
            time.Sleep(1 * time.Second)
        }
        time.Sleep(10 * time.Second)
    }
}

示例3Bright Data动态IP

package main

import (
    "log"
    "nofx/proxy"
    "time"
)

func main() {
    // 初始化Bright Data代理
    err := proxy.InitGlobalProxyManager(&proxy.Config{
        Enabled: true,
        Mode: "brightdata",
        BrightDataEndpoint: "https://api.brightdata.com/zones/get_ips",
        BrightDataToken: "your_token",
        BrightDataZone: "residential",
        ProxyHost: "brd.superproxy.io:22225",
        ProxyUser: "brd-customer-xxx-zone-residential-ip-%s",
        ProxyPassword: "your_password",
        RefreshInterval: 30 * time.Minute,
        Timeout: 30 * time.Second,
        BlacklistTTL: 5,
    })
    if err != nil {
        log.Fatalf("初始化代理失败: %v", err)
    }

    // 代理会自动每30分钟刷新IP列表
    log.Println("✓ Bright Data代理已启动,自动刷新已开启")

    // 获取并使用代理
    for i := 0; i < 10; i++ {
        proxyClient, err := proxy.GetProxyHTTPClient()
        if err != nil {
            log.Printf("获取代理失败: %v", err)
            continue
        }

        resp, err := proxyClient.Client.Get("https://api.ipify.org?format=json")
        if err != nil {
            proxy.AddBlacklist(proxyClient.ProxyID)
            log.Printf("请求失败,代理已加入黑名单: %s", proxyClient.IP)
            continue
        }
        resp.Body.Close()

        log.Printf("✓ 请求成功 (代理ID: %d, IP: %s)", proxyClient.ProxyID, proxyClient.IP)
        time.Sleep(2 * time.Second)
    }
}

注意事项

1. 模块解耦性

  • 代理模块完全独立,不依赖其他业务模块
  • 禁用代理时自动使用直连,对业务代码透明
  • 适合多租户/多客户环境,可按需启用

2. 线程安全

  • 所有公开方法都是线程安全的
  • 支持高并发场景下的代理获取和黑名单操作
  • 读写锁优化性能:读操作可并发,写操作独占

3. 错误处理

proxyClient, err := proxy.GetProxyHTTPClient()
if err != nil {
    // 可能的错误:
    // - 代理IP列表为空
    // - 所有代理都在黑名单中
    // - 代理URL解析失败
    log.Printf("获取代理失败: %v", err)

    // 建议:降级为直连或重试
    return
}

4. 性能优化建议

  • 对于高频请求,复用 http.Client 而不是每次创建新的
  • 合理设置 refresh_interval 避免频繁刷新
  • blacklist_ttl 建议设置为 3-10,平衡恢复速度和稳定性

5. 安全建议

  • 生产环境中代理密钥应使用环境变量或密钥管理服务
  • 避免在日志中打印完整的代理URL(包含密码)
  • TLS验证默认开启,如需跳过请谨慎评估风险

6. 调试技巧

// 获取当前代理状态
total, blacklisted, available := proxy.GetGlobalProxyManager().GetBlacklistStatus()
log.Printf("代理池状态: 总计=%d, 黑名单=%d, 可用=%d", total, blacklisted, available)

// 检查是否启用
if !proxy.IsEnabled() {
    log.Println("代理未启用,请检查配置")
}

故障排查

问题1:获取代理失败 - "代理IP列表为空"

原因

  • single 模式:未配置 proxy_url
  • pool 模式:proxy_list 为空
  • brightdata 模式:API返回空列表或请求失败

解决方案

# 检查配置文件
cat config.json | grep -A 15 "proxy"

# 检查日志,查看初始化信息
# 应该看到类似:🌐 HTTP 代理已启用 (xxx模式)

问题2:所有代理都在黑名单中

原因:请求持续失败,所有IP被加入黑名单

解决方案

// 方案1:手动刷新IP列表(会触发TTL倒计时)
proxy.RefreshIPList()

// 方案2:降低blacklist_ttl,加快恢复速度
// config.json: "blacklist_ttl": 2  (默认5)

// 方案3:检查代理本身是否可用
// 使用curl测试代理:
// curl -x http://proxy_url https://api.binance.com/api/v3/ping

问题3Bright Data模式无法获取IP

原因

  • API端点配置错误
  • Token无效或过期
  • Zone参数不正确

解决方案

# 手动测试API
curl -H "Authorization: Bearer YOUR_TOKEN" \
     "https://api.brightdata.com/zones/get_ips?zone=residential"

# 检查返回格式是否符合:
# {"ips": [{"ip": "1.2.3.4", ...}, ...]}

问题4:代理连接超时

原因:代理服务器响应慢或网络不稳定

解决方案

{
  "proxy": {
    "timeout": 60  // 增加超时时间(秒)
  }
}

扩展开发

添加新的Provider

实现 IPProvider 接口即可:

// custom_provider.go
package proxy

type CustomProvider struct {
    // 自定义字段
}

func NewCustomProvider(config string) *CustomProvider {
    return &CustomProvider{}
}

func (p *CustomProvider) GetIPList() ([]ProxyIP, error) {
    // 实现获取IP列表的逻辑
    return []ProxyIP{}, nil
}

func (p *CustomProvider) RefreshIPList() ([]ProxyIP, error) {
    // 实现刷新IP列表的逻辑
    return p.GetIPList()
}

然后在 proxy_manager.goNewProxyManager 中添加新模式:

case "custom":
    m.provider = NewCustomProvider(config.CustomEndpoint)
    log.Printf("🌐 HTTP 代理已启用 (自定义模式)")

更新日志

v1.0.0 (当前版本)

  • 支持三种代理模式:single、pool、brightdata
  • 线程安全的IP轮换和黑名单管理
  • 自动刷新机制(30分钟默认)
  • TTL黑名单自动恢复
  • 防越界保护
  • ProxyID追踪机制

技术支持

如有问题或建议,请联系项目维护者 @hzb1115 。