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

686 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` 配置段:
```json
{
"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` 或初始化代码中:
```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请求的地方
```go
// 获取代理客户端包含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. 黑名单管理
```go
// 添加失败的代理到黑名单
proxy.AddBlacklist(proxyClient.ProxyID)
// 获取黑名单状态
total, blacklisted, available := proxy.GetGlobalProxyManager().GetBlacklistStatus()
log.Printf("代理状态: 总计%d个黑名单%d个可用%d个", total, blacklisted, available)
```
### 4. 手动刷新IP列表
```go
err := proxy.RefreshIPList()
if err != nil {
log.Printf("刷新IP列表失败: %v", err)
}
```
### 5. 检查代理是否启用
```go
if proxy.IsEnabled() {
log.Println("代理已启用")
} else {
log.Println("代理未启用,使用直连")
}
```
## 三种模式详解
### Mode 1: Single单代理模式
适用场景本地代理工具如Clash、V2Ray或单个固定代理服务器
```json
{
"proxy": {
"enabled": true,
"mode": "single",
"proxy_url": "http://127.0.0.1:7890"
}
}
```
特点:
- 简单直接,适合本地开发和测试
- 所有请求通过同一个代理
- 不需要刷新和轮换
### Mode 2: Pool代理池模式
适用场景:拥有多个固定代理服务器,需要轮换使用
```json
{
"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的动态代理服务
```json
{
"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
### 全局函数
```go
// 初始化全局代理管理器(只执行一次)
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 方法
```go
// 获取代理客户端
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自动从黑名单移除重新可用
### 线程安全保证
```go
// 添加黑名单使用写锁
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请求单代理模式
```go
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)
}
```
### 示例2OI数据获取代理池模式 + 黑名单)
```go
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
```go
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. 错误处理
```go
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. 调试技巧
```go
// 获取当前代理状态
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返回空列表或请求失败
**解决方案**
```bash
# 检查配置文件
cat config.json | grep -A 15 "proxy"
# 检查日志,查看初始化信息
# 应该看到类似:🌐 HTTP 代理已启用 (xxx模式)
```
### 问题2所有代理都在黑名单中
**原因**请求持续失败所有IP被加入黑名单
**解决方案**
```go
// 方案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参数不正确
**解决方案**
```bash
# 手动测试API
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://api.brightdata.com/zones/get_ips?zone=residential"
# 检查返回格式是否符合:
# {"ips": [{"ip": "1.2.3.4", ...}, ...]}
```
### 问题4代理连接超时
**原因**:代理服务器响应慢或网络不稳定
**解决方案**
```json
{
"proxy": {
"timeout": 60 // 增加超时时间(秒)
}
}
```
## 扩展开发
### 添加新的Provider
实现 `IPProvider` 接口即可:
```go
// 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.go``NewProxyManager` 中添加新模式:
```go
case "custom":
m.provider = NewCustomProvider(config.CustomEndpoint)
log.Printf("🌐 HTTP 代理已启用 (自定义模式)")
```
## 更新日志
### v1.0.0 (当前版本)
- ✅ 支持三种代理模式single、pool、brightdata
- ✅ 线程安全的IP轮换和黑名单管理
- ✅ 自动刷新机制30分钟默认
- ✅ TTL黑名单自动恢复
- ✅ 防越界保护
- ✅ ProxyID追踪机制
## 技术支持
如有问题或建议,请联系项目维护者 @hzb1115