Files
nofx/proxy/proxy_manager.go
T
2025-11-05 21:41:36 -05:00

347 lines
8.7 KiB
Go

package proxy
import (
"crypto/tls"
"fmt"
"log"
"math/rand"
"net/http"
"net/url"
"sync"
"time"
)
// ProxyManager 代理管理器
type ProxyManager struct {
config *Config
provider IPProvider
// IP池管理
ipList []ProxyIP
blacklist map[int]string // ProxyID -> IP
ipBlacklist map[string]int // IP -> 剩余TTL
mutex sync.RWMutex // 读写锁,保证线程安全
// 刷新控制
stopRefresh chan struct{}
}
var (
globalProxyManager *ProxyManager
once sync.Once
)
// InitGlobalProxyManager 初始化全局代理管理器
func InitGlobalProxyManager(config *Config) error {
var err error
once.Do(func() {
globalProxyManager, err = NewProxyManager(config)
if err == nil && config.Enabled && config.RefreshInterval > 0 {
globalProxyManager.StartAutoRefresh()
}
})
return err
}
// GetGlobalProxyManager 获取全局代理管理器
func GetGlobalProxyManager() *ProxyManager {
if globalProxyManager == nil {
// 如果未初始化,使用默认配置(禁用代理)
_ = InitGlobalProxyManager(&Config{Enabled: false})
}
return globalProxyManager
}
// NewProxyManager 创建代理管理器
func NewProxyManager(config *Config) (*ProxyManager, error) {
if config == nil {
config = &Config{Enabled: false}
}
// 设置默认值
if config.Timeout == 0 {
config.Timeout = 30 * time.Second
}
if config.BlacklistTTL == 0 {
config.BlacklistTTL = 5 // 默认 TTL 为 5 次刷新
}
if config.RefreshInterval == 0 && config.Mode == "brightdata" {
config.RefreshInterval = 30 * time.Minute // 默认 30 分钟刷新一次
}
m := &ProxyManager{
config: config,
blacklist: make(map[int]string),
ipBlacklist: make(map[string]int),
stopRefresh: make(chan struct{}),
}
// 如果未启用代理,直接返回
if !config.Enabled {
log.Printf("🌐 HTTP 代理未启用,使用直连")
return m, nil
}
// 根据模式选择IP提供者
switch config.Mode {
case "single":
// 单个代理模式
if config.ProxyURL == "" {
return nil, fmt.Errorf("single模式下必须配置proxy_url")
}
m.provider = NewSingleProxyProvider(config.ProxyURL)
log.Printf("🌐 HTTP 代理已启用 (单代理模式): %s", config.ProxyURL)
case "pool":
// 代理池模式(固定列表)
if len(config.ProxyList) == 0 {
return nil, fmt.Errorf("pool模式下必须配置proxy_list")
}
m.provider = NewFixedIPProvider(config.ProxyList)
log.Printf("🌐 HTTP 代理已启用 (代理池模式): %d个代理", len(config.ProxyList))
case "brightdata":
// Bright Data动态获取模式
if config.BrightDataEndpoint == "" {
return nil, fmt.Errorf("brightdata模式下必须配置brightdata_endpoint")
}
m.provider = NewBrightDataProvider(config.BrightDataEndpoint, config.BrightDataToken, config.BrightDataZone)
log.Printf("🌐 HTTP 代理已启用 (Bright Data模式): %s", config.BrightDataEndpoint)
default:
// 默认使用single模式
if config.ProxyURL == "" {
return nil, fmt.Errorf("未知的proxy模式: %s", config.Mode)
}
m.provider = NewSingleProxyProvider(config.ProxyURL)
log.Printf("🌐 HTTP 代理已启用 (默认模式): %s", config.ProxyURL)
}
// 初始化IP列表
if err := m.RefreshIPList(); err != nil {
return nil, fmt.Errorf("初始化IP列表失败: %w", err)
}
return m, nil
}
// RefreshIPList 刷新IP列表(线程安全)
func (m *ProxyManager) RefreshIPList() error {
if m.provider == nil {
return nil
}
ips, err := m.provider.RefreshIPList()
if err != nil {
return err
}
m.mutex.Lock()
defer m.mutex.Unlock()
// 清理黑名单,TTL倒计时
validIPs := make([]ProxyIP, 0, len(ips))
newBlacklist := make(map[int]string)
for _, ip := range ips {
if ttl, inBlacklist := m.ipBlacklist[ip.IP]; inBlacklist {
// TTL 倒计时
m.ipBlacklist[ip.IP] = ttl - 1
if ttl > 0 {
// 仍在黑名单中,跳过
continue
}
// TTL 归零,从黑名单移除
delete(m.ipBlacklist, ip.IP)
log.Printf("✓ 代理IP已从黑名单恢复: %s", ip.IP)
}
validIPs = append(validIPs, ip)
}
m.ipList = validIPs
m.blacklist = newBlacklist
log.Printf("✓ 刷新代理IP列表: 总计%d个,黑名单%d个,可用%d个",
len(ips), len(m.ipBlacklist), len(validIPs))
return nil
}
// StartAutoRefresh 启动自动刷新
func (m *ProxyManager) StartAutoRefresh() {
if m.config.RefreshInterval <= 0 {
return
}
go func() {
ticker := time.NewTicker(m.config.RefreshInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := m.RefreshIPList(); err != nil {
log.Printf("⚠️ 自动刷新IP列表失败: %v", err)
}
case <-m.stopRefresh:
return
}
}
}()
log.Printf("✓ 已启动代理IP自动刷新 (间隔: %v)", m.config.RefreshInterval)
}
// StopAutoRefresh 停止自动刷新
func (m *ProxyManager) StopAutoRefresh() {
close(m.stopRefresh)
}
// getRandomProxy 随机获取一个可用代理(线程安全 - 读锁,确保不越界)
func (m *ProxyManager) getRandomProxy() (int, *ProxyIP, error) {
m.mutex.RLock()
defer m.mutex.RUnlock()
if len(m.ipList) == 0 {
return -1, nil, fmt.Errorf("代理IP列表为空")
}
// 找到所有未被黑名单的索引
availableIndices := make([]int, 0, len(m.ipList))
for i := range m.ipList {
if _, inBlacklist := m.blacklist[i]; !inBlacklist {
availableIndices = append(availableIndices, i)
}
}
if len(availableIndices) == 0 {
return -1, nil, fmt.Errorf("所有代理IP都在黑名单中")
}
// 随机选择一个(确保不越界)
randomIdx := availableIndices[rand.Intn(len(availableIndices))]
// 二次检查,确保索引有效(防御性编程)
if randomIdx < 0 || randomIdx >= len(m.ipList) {
return -1, nil, fmt.Errorf("代理索引越界: %d (总数: %d)", randomIdx, len(m.ipList))
}
return randomIdx, &m.ipList[randomIdx], nil
}
// buildProxyURL 构建代理URL
func (m *ProxyManager) buildProxyURL(ip *ProxyIP) string {
if m.config.ProxyHost != "" && m.config.ProxyUser != "" {
// 使用配置的代理主机和认证信息
user := m.config.ProxyUser
if m.config.ProxyUser != "" && ip.IP != "" {
// 支持%s占位符替换IP
user = fmt.Sprintf(m.config.ProxyUser, ip.IP)
}
protocol := ip.Protocol
if protocol == "" {
protocol = "http"
}
if m.config.ProxyPassword != "" {
return fmt.Sprintf("%s://%s:%s@%s", protocol, user, m.config.ProxyPassword, m.config.ProxyHost)
}
return fmt.Sprintf("%s://%s@%s", protocol, user, m.config.ProxyHost)
}
// 直接使用IP信息
return ip.IP
}
// GetProxyClient 获取代理客户端(线程安全)
func (m *ProxyManager) GetProxyClient() (*ProxyClient, error) {
if !m.config.Enabled {
// 未启用代理,返回普通HTTP客户端
return &ProxyClient{
ProxyID: -1, // -1 表示未使用代理
IP: "direct",
Client: &http.Client{
Timeout: m.config.Timeout,
},
}, nil
}
// 获取随机代理(使用读锁,确保不越界)
proxyID, proxyIP, err := m.getRandomProxy()
if err != nil {
return nil, err
}
// 构建代理URL
proxyURLStr := m.buildProxyURL(proxyIP)
proxyURL, err := url.Parse(proxyURLStr)
if err != nil {
return nil, fmt.Errorf("解析代理URL失败: %w", err)
}
// 创建Transport
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false,
},
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
}
return &ProxyClient{
ProxyID: proxyID,
IP: proxyIP.IP,
Client: &http.Client{
Transport: transport,
Timeout: m.config.Timeout,
},
}, nil
}
// AddBlacklist 将代理IP添加到黑名单(线程安全 - 写锁)
func (m *ProxyManager) AddBlacklist(proxyID int) {
m.mutex.Lock()
defer m.mutex.Unlock()
// 检查 proxyID 有效性,防止越界
if proxyID < 0 || proxyID >= len(m.ipList) {
log.Printf("⚠️ 无效的 ProxyID: %d (有效范围: 0-%d)", proxyID, len(m.ipList)-1)
return
}
ip := m.ipList[proxyID].IP
m.blacklist[proxyID] = ip
m.ipBlacklist[ip] = m.config.BlacklistTTL
log.Printf("⚠️ 代理IP已加入黑名单: %s (ProxyID: %d, TTL: %d)", ip, proxyID, m.config.BlacklistTTL)
}
// GetBlacklistStatus 获取黑名单状态(线程安全 - 读锁)
func (m *ProxyManager) GetBlacklistStatus() (total int, blacklisted int, available int) {
m.mutex.RLock()
defer m.mutex.RUnlock()
total = len(m.ipList)
blacklisted = len(m.ipBlacklist)
available = total - len(m.blacklist)
return
}
// IsEnabled 检查代理是否启用
func IsEnabled() bool {
return GetGlobalProxyManager().config.Enabled
}
// RefreshIPList 刷新全局代理IP列表
func RefreshIPList() error {
return GetGlobalProxyManager().RefreshIPList()
}
// AddBlacklist 将代理IP添加到全局黑名单
func AddBlacklist(proxyID int) {
GetGlobalProxyManager().AddBlacklist(proxyID)
}