mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-07 11:17:56 +08:00
347 lines
8.7 KiB
Go
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)
|
|
}
|