mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-18 00:21:56 +08:00
添加ExtensionMatcher缓存机制,优化缓存管理器和302跳转处理逻辑,增强规则服务集成,提升代码可读性和性能。
This commit is contained in:
parent
1c9d5bc326
commit
605b26b883
216
internal/cache/extension_matcher.go
vendored
Normal file
216
internal/cache/extension_matcher.go
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"proxy-go/internal/config"
|
||||
"proxy-go/internal/utils"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ExtensionMatcherCacheItem 扩展名匹配器缓存项
|
||||
type ExtensionMatcherCacheItem struct {
|
||||
Matcher *utils.ExtensionMatcher
|
||||
Hash string // 配置的哈希值,用于检测配置变化
|
||||
CreatedAt time.Time
|
||||
LastUsed time.Time
|
||||
UseCount int64
|
||||
}
|
||||
|
||||
// ExtensionMatcherCache 扩展名匹配器缓存管理器
|
||||
type ExtensionMatcherCache struct {
|
||||
cache sync.Map
|
||||
maxAge time.Duration
|
||||
cleanupTick time.Duration
|
||||
stopCleanup chan struct{}
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewExtensionMatcherCache 创建新的扩展名匹配器缓存
|
||||
func NewExtensionMatcherCache() *ExtensionMatcherCache {
|
||||
emc := &ExtensionMatcherCache{
|
||||
maxAge: 10 * time.Minute, // 缓存10分钟
|
||||
cleanupTick: 2 * time.Minute, // 每2分钟清理一次
|
||||
stopCleanup: make(chan struct{}),
|
||||
}
|
||||
|
||||
// 启动清理协程
|
||||
go emc.startCleanup()
|
||||
|
||||
return emc
|
||||
}
|
||||
|
||||
// generateConfigHash 生成配置的哈希值
|
||||
func (emc *ExtensionMatcherCache) generateConfigHash(rules []config.ExtensionRule) string {
|
||||
// 将规则序列化为JSON
|
||||
data, err := json.Marshal(rules)
|
||||
if err != nil {
|
||||
// 如果序列化失败,使用时间戳作为哈希
|
||||
return hex.EncodeToString([]byte(time.Now().String()))
|
||||
}
|
||||
|
||||
// 计算SHA256哈希
|
||||
hash := sha256.Sum256(data)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// GetOrCreate 获取或创建扩展名匹配器
|
||||
func (emc *ExtensionMatcherCache) GetOrCreate(pathKey string, rules []config.ExtensionRule) *utils.ExtensionMatcher {
|
||||
// 如果没有规则,直接创建新的匹配器
|
||||
if len(rules) == 0 {
|
||||
return utils.NewExtensionMatcher(rules)
|
||||
}
|
||||
|
||||
// 生成配置哈希
|
||||
configHash := emc.generateConfigHash(rules)
|
||||
|
||||
// 尝试从缓存获取
|
||||
if value, ok := emc.cache.Load(pathKey); ok {
|
||||
item := value.(*ExtensionMatcherCacheItem)
|
||||
|
||||
// 检查配置是否变化
|
||||
if item.Hash == configHash {
|
||||
// 配置未变化,更新使用信息
|
||||
emc.mu.Lock()
|
||||
item.LastUsed = time.Now()
|
||||
item.UseCount++
|
||||
emc.mu.Unlock()
|
||||
|
||||
log.Printf("[ExtensionMatcherCache] HIT %s (使用次数: %d)", pathKey, item.UseCount)
|
||||
return item.Matcher
|
||||
} else {
|
||||
// 配置已变化,删除旧缓存
|
||||
emc.cache.Delete(pathKey)
|
||||
log.Printf("[ExtensionMatcherCache] CONFIG_CHANGED %s", pathKey)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新的匹配器
|
||||
matcher := utils.NewExtensionMatcher(rules)
|
||||
|
||||
// 创建缓存项
|
||||
item := &ExtensionMatcherCacheItem{
|
||||
Matcher: matcher,
|
||||
Hash: configHash,
|
||||
CreatedAt: time.Now(),
|
||||
LastUsed: time.Now(),
|
||||
UseCount: 1,
|
||||
}
|
||||
|
||||
// 存储到缓存
|
||||
emc.cache.Store(pathKey, item)
|
||||
log.Printf("[ExtensionMatcherCache] NEW %s (规则数量: %d)", pathKey, len(rules))
|
||||
|
||||
return matcher
|
||||
}
|
||||
|
||||
// InvalidatePath 使指定路径的缓存失效
|
||||
func (emc *ExtensionMatcherCache) InvalidatePath(pathKey string) {
|
||||
if _, ok := emc.cache.LoadAndDelete(pathKey); ok {
|
||||
log.Printf("[ExtensionMatcherCache] INVALIDATED %s", pathKey)
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidateAll 清空所有缓存
|
||||
func (emc *ExtensionMatcherCache) InvalidateAll() {
|
||||
count := 0
|
||||
emc.cache.Range(func(key, value interface{}) bool {
|
||||
emc.cache.Delete(key)
|
||||
count++
|
||||
return true
|
||||
})
|
||||
log.Printf("[ExtensionMatcherCache] INVALIDATED_ALL (清理了 %d 个缓存项)", count)
|
||||
}
|
||||
|
||||
// GetStats 获取缓存统计信息
|
||||
func (emc *ExtensionMatcherCache) GetStats() ExtensionMatcherCacheStats {
|
||||
stats := ExtensionMatcherCacheStats{
|
||||
MaxAge: int64(emc.maxAge.Minutes()),
|
||||
CleanupTick: int64(emc.cleanupTick.Minutes()),
|
||||
}
|
||||
|
||||
emc.cache.Range(func(key, value interface{}) bool {
|
||||
item := value.(*ExtensionMatcherCacheItem)
|
||||
stats.TotalItems++
|
||||
stats.TotalUseCount += item.UseCount
|
||||
|
||||
// 计算平均年龄
|
||||
age := time.Since(item.CreatedAt)
|
||||
stats.AverageAge += int64(age.Minutes())
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if stats.TotalItems > 0 {
|
||||
stats.AverageAge /= int64(stats.TotalItems)
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// ExtensionMatcherCacheStats 扩展名匹配器缓存统计信息
|
||||
type ExtensionMatcherCacheStats struct {
|
||||
TotalItems int `json:"total_items"` // 缓存项数量
|
||||
TotalUseCount int64 `json:"total_use_count"` // 总使用次数
|
||||
AverageAge int64 `json:"average_age"` // 平均年龄(分钟)
|
||||
MaxAge int64 `json:"max_age"` // 最大缓存时间(分钟)
|
||||
CleanupTick int64 `json:"cleanup_tick"` // 清理间隔(分钟)
|
||||
}
|
||||
|
||||
// startCleanup 启动清理协程
|
||||
func (emc *ExtensionMatcherCache) startCleanup() {
|
||||
ticker := time.NewTicker(emc.cleanupTick)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
emc.cleanup()
|
||||
case <-emc.stopCleanup:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup 清理过期的缓存项
|
||||
func (emc *ExtensionMatcherCache) cleanup() {
|
||||
now := time.Now()
|
||||
expiredKeys := make([]interface{}, 0)
|
||||
|
||||
// 收集过期的键
|
||||
emc.cache.Range(func(key, value interface{}) bool {
|
||||
item := value.(*ExtensionMatcherCacheItem)
|
||||
if now.Sub(item.LastUsed) > emc.maxAge {
|
||||
expiredKeys = append(expiredKeys, key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// 删除过期的缓存项
|
||||
for _, key := range expiredKeys {
|
||||
emc.cache.Delete(key)
|
||||
}
|
||||
|
||||
if len(expiredKeys) > 0 {
|
||||
log.Printf("[ExtensionMatcherCache] CLEANUP 清理了 %d 个过期缓存项", len(expiredKeys))
|
||||
}
|
||||
}
|
||||
|
||||
// Stop 停止缓存管理器
|
||||
func (emc *ExtensionMatcherCache) Stop() {
|
||||
close(emc.stopCleanup)
|
||||
}
|
||||
|
||||
// UpdateConfig 更新缓存配置
|
||||
func (emc *ExtensionMatcherCache) UpdateConfig(maxAge, cleanupTick time.Duration) {
|
||||
emc.mu.Lock()
|
||||
defer emc.mu.Unlock()
|
||||
|
||||
emc.maxAge = maxAge
|
||||
emc.cleanupTick = cleanupTick
|
||||
|
||||
log.Printf("[ExtensionMatcherCache] CONFIG_UPDATED maxAge=%v cleanupTick=%v", maxAge, cleanupTick)
|
||||
}
|
58
internal/cache/manager.go
vendored
58
internal/cache/manager.go
vendored
@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"proxy-go/internal/config"
|
||||
"proxy-go/internal/utils"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -80,6 +81,9 @@ type CacheManager struct {
|
||||
bytesSaved atomic.Int64 // 节省的带宽
|
||||
cleanupTimer *time.Ticker // 添加清理定时器
|
||||
stopCleanup chan struct{} // 添加停止信号通道
|
||||
|
||||
// ExtensionMatcher缓存
|
||||
extensionMatcherCache *ExtensionMatcherCache
|
||||
}
|
||||
|
||||
// NewCacheManager 创建新的缓存管理器
|
||||
@ -94,6 +98,9 @@ func NewCacheManager(cacheDir string) (*CacheManager, error) {
|
||||
cleanupTick: 5 * time.Minute,
|
||||
maxCacheSize: 10 * 1024 * 1024 * 1024, // 10GB
|
||||
stopCleanup: make(chan struct{}),
|
||||
|
||||
// 初始化ExtensionMatcher缓存
|
||||
extensionMatcherCache: NewExtensionMatcherCache(),
|
||||
}
|
||||
|
||||
cm.enabled.Store(true) // 默认启用缓存
|
||||
@ -591,3 +598,54 @@ func (cm *CacheManager) loadConfig() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExtensionMatcher 获取缓存的ExtensionMatcher
|
||||
func (cm *CacheManager) GetExtensionMatcher(pathKey string, rules []config.ExtensionRule) *utils.ExtensionMatcher {
|
||||
if cm.extensionMatcherCache == nil {
|
||||
return utils.NewExtensionMatcher(rules)
|
||||
}
|
||||
return cm.extensionMatcherCache.GetOrCreate(pathKey, rules)
|
||||
}
|
||||
|
||||
// InvalidateExtensionMatcherPath 使指定路径的ExtensionMatcher缓存失效
|
||||
func (cm *CacheManager) InvalidateExtensionMatcherPath(pathKey string) {
|
||||
if cm.extensionMatcherCache != nil {
|
||||
cm.extensionMatcherCache.InvalidatePath(pathKey)
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidateAllExtensionMatchers 清空所有ExtensionMatcher缓存
|
||||
func (cm *CacheManager) InvalidateAllExtensionMatchers() {
|
||||
if cm.extensionMatcherCache != nil {
|
||||
cm.extensionMatcherCache.InvalidateAll()
|
||||
}
|
||||
}
|
||||
|
||||
// GetExtensionMatcherStats 获取ExtensionMatcher缓存统计信息
|
||||
func (cm *CacheManager) GetExtensionMatcherStats() ExtensionMatcherCacheStats {
|
||||
if cm.extensionMatcherCache != nil {
|
||||
return cm.extensionMatcherCache.GetStats()
|
||||
}
|
||||
return ExtensionMatcherCacheStats{}
|
||||
}
|
||||
|
||||
// UpdateExtensionMatcherConfig 更新ExtensionMatcher缓存配置
|
||||
func (cm *CacheManager) UpdateExtensionMatcherConfig(maxAge, cleanupTick time.Duration) {
|
||||
if cm.extensionMatcherCache != nil {
|
||||
cm.extensionMatcherCache.UpdateConfig(maxAge, cleanupTick)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop 停止缓存管理器(包括ExtensionMatcher缓存)
|
||||
func (cm *CacheManager) Stop() {
|
||||
// 停止主缓存清理
|
||||
if cm.cleanupTimer != nil {
|
||||
cm.cleanupTimer.Stop()
|
||||
}
|
||||
close(cm.stopCleanup)
|
||||
|
||||
// 停止ExtensionMatcher缓存
|
||||
if cm.extensionMatcherCache != nil {
|
||||
cm.extensionMatcherCache.Stop()
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"proxy-go/internal/cache"
|
||||
"proxy-go/internal/config"
|
||||
"proxy-go/internal/metrics"
|
||||
"proxy-go/internal/service"
|
||||
"proxy-go/internal/utils"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -53,7 +54,8 @@ type ProxyHandler struct {
|
||||
auth *authManager
|
||||
errorHandler ErrorHandler
|
||||
Cache *cache.CacheManager
|
||||
redirectHandler *RedirectHandler // 添加302跳转处理器
|
||||
redirectHandler *RedirectHandler // 添加302跳转处理器
|
||||
ruleService *service.RuleService // 添加规则服务
|
||||
}
|
||||
|
||||
// 前缀匹配器结构体
|
||||
@ -154,6 +156,9 @@ func NewProxyHandler(cfg *config.Config) *ProxyHandler {
|
||||
log.Printf("[Cache] Failed to initialize cache manager: %v", err)
|
||||
}
|
||||
|
||||
// 初始化规则服务
|
||||
ruleService := service.NewRuleService(cacheManager)
|
||||
|
||||
handler := &ProxyHandler{
|
||||
pathMap: cfg.MAP,
|
||||
prefixTree: newPrefixMatcher(cfg.MAP), // 初始化前缀匹配树
|
||||
@ -171,7 +176,8 @@ func NewProxyHandler(cfg *config.Config) *ProxyHandler {
|
||||
config: cfg,
|
||||
auth: newAuthManager(),
|
||||
Cache: cacheManager,
|
||||
redirectHandler: NewRedirectHandler(), // 初始化302跳转处理器
|
||||
ruleService: ruleService,
|
||||
redirectHandler: NewRedirectHandler(ruleService), // 初始化302跳转处理器
|
||||
errorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
log.Printf("[Error] %s %s -> %v from %s", r.Method, r.URL.Path, err, utils.GetRequestSource(r))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@ -246,7 +252,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// 使用统一的路由选择逻辑
|
||||
targetBase, usedAltTarget := utils.GetTargetURL(h.client, r, pathConfig, decodedPath)
|
||||
targetBase, usedAltTarget := h.ruleService.GetTargetURL(h.client, r, pathConfig, decodedPath)
|
||||
|
||||
// 重新编码路径,保留 '/'
|
||||
parts := strings.Split(decodedPath, "/")
|
||||
|
@ -5,16 +5,21 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"proxy-go/internal/config"
|
||||
"proxy-go/internal/service"
|
||||
"proxy-go/internal/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RedirectHandler 处理302跳转逻辑
|
||||
type RedirectHandler struct{}
|
||||
type RedirectHandler struct {
|
||||
ruleService *service.RuleService
|
||||
}
|
||||
|
||||
// NewRedirectHandler 创建新的跳转处理器
|
||||
func NewRedirectHandler() *RedirectHandler {
|
||||
return &RedirectHandler{}
|
||||
func NewRedirectHandler(ruleService *service.RuleService) *RedirectHandler {
|
||||
return &RedirectHandler{
|
||||
ruleService: ruleService,
|
||||
}
|
||||
}
|
||||
|
||||
// HandleRedirect 处理302跳转请求
|
||||
@ -33,8 +38,8 @@ func (rh *RedirectHandler) HandleRedirect(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// shouldRedirect 判断是否应该进行302跳转,并返回目标URL(优化版本)
|
||||
func (rh *RedirectHandler) shouldRedirect(r *http.Request, pathConfig config.PathConfig, targetPath string, client *http.Client) (bool, string) {
|
||||
// 使用优化的规则选择函数
|
||||
result := utils.SelectRuleForRedirect(client, pathConfig, targetPath)
|
||||
// 使用service包的规则选择函数
|
||||
result := rh.ruleService.SelectRuleForRedirect(client, pathConfig, targetPath)
|
||||
|
||||
if result.ShouldRedirect {
|
||||
// 构建完整的目标URL
|
||||
|
217
internal/service/rule_service.go
Normal file
217
internal/service/rule_service.go
Normal file
@ -0,0 +1,217 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"proxy-go/internal/config"
|
||||
"proxy-go/internal/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RuleService 规则选择服务
|
||||
type RuleService struct {
|
||||
cacheManager CacheManager
|
||||
}
|
||||
|
||||
// CacheManager 缓存管理器接口
|
||||
type CacheManager interface {
|
||||
GetExtensionMatcher(pathKey string, rules []config.ExtensionRule) *utils.ExtensionMatcher
|
||||
}
|
||||
|
||||
// NewRuleService 创建规则选择服务
|
||||
func NewRuleService(cacheManager CacheManager) *RuleService {
|
||||
return &RuleService{
|
||||
cacheManager: cacheManager,
|
||||
}
|
||||
}
|
||||
|
||||
// SelectBestRule 选择最合适的规则
|
||||
func (rs *RuleService) SelectBestRule(client *http.Client, pathConfig config.PathConfig, path string) (*config.ExtensionRule, bool, bool) {
|
||||
// 如果没有扩展名规则,返回nil
|
||||
if len(pathConfig.ExtRules) == 0 {
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// 提取扩展名
|
||||
ext := extractExtension(path)
|
||||
|
||||
var matcher *utils.ExtensionMatcher
|
||||
|
||||
// 尝试使用缓存管理器
|
||||
if rs.cacheManager != nil {
|
||||
pathKey := fmt.Sprintf("path_%p", &pathConfig)
|
||||
matcher = rs.cacheManager.GetExtensionMatcher(pathKey, pathConfig.ExtRules)
|
||||
} else {
|
||||
// 直接创建新的匹配器
|
||||
matcher = utils.NewExtensionMatcher(pathConfig.ExtRules)
|
||||
}
|
||||
|
||||
// 获取匹配的规则
|
||||
matchingRules := matcher.GetMatchingRules(ext)
|
||||
if len(matchingRules) == 0 {
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// 获取文件大小
|
||||
contentLength, err := utils.GetFileSize(client, pathConfig.DefaultTarget+path)
|
||||
if err != nil {
|
||||
log.Printf("[SelectRule] %s -> 获取文件大小出错: %v", path, err)
|
||||
// 如果无法获取文件大小,返回第一个匹配的规则
|
||||
if len(matchingRules) > 0 {
|
||||
log.Printf("[SelectRule] %s -> 基于扩展名直接匹配规则", path)
|
||||
return matchingRules[0], true, true
|
||||
}
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// 根据文件大小找出最匹配的规则(规则已经预排序)
|
||||
for _, rule := range matchingRules {
|
||||
// 检查文件大小是否在阈值范围内
|
||||
if contentLength >= rule.SizeThreshold && contentLength <= rule.MaxSize {
|
||||
// 找到匹配的规则
|
||||
log.Printf("[SelectRule] %s -> 选中规则 (文件大小: %s, 在区间 %s 到 %s 之间)",
|
||||
path, utils.FormatBytes(contentLength),
|
||||
utils.FormatBytes(rule.SizeThreshold), utils.FormatBytes(rule.MaxSize))
|
||||
|
||||
// 检查目标是否可访问
|
||||
if utils.IsTargetAccessible(client, rule.Target+path) {
|
||||
return rule, true, true
|
||||
} else {
|
||||
log.Printf("[SelectRule] %s -> 规则目标不可访问,继续查找", path)
|
||||
// 继续查找下一个匹配的规则
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 没有找到合适的规则
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// RuleSelectionResult 规则选择结果
|
||||
type RuleSelectionResult struct {
|
||||
Rule *config.ExtensionRule
|
||||
Found bool
|
||||
UsedAltTarget bool
|
||||
TargetURL string
|
||||
ShouldRedirect bool
|
||||
}
|
||||
|
||||
// SelectRuleForRedirect 专门为302跳转优化的规则选择函数
|
||||
func (rs *RuleService) SelectRuleForRedirect(client *http.Client, pathConfig config.PathConfig, path string) *RuleSelectionResult {
|
||||
result := &RuleSelectionResult{}
|
||||
|
||||
// 快速检查:如果没有任何302跳转配置,直接返回
|
||||
if !pathConfig.RedirectMode && len(pathConfig.ExtRules) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
// 如果默认目标配置了302跳转,优先使用
|
||||
if pathConfig.RedirectMode {
|
||||
result.Found = true
|
||||
result.ShouldRedirect = true
|
||||
result.TargetURL = pathConfig.DefaultTarget
|
||||
return result
|
||||
}
|
||||
|
||||
// 检查扩展名规则
|
||||
if len(pathConfig.ExtRules) > 0 {
|
||||
ext := extractExtension(path)
|
||||
|
||||
var matcher *utils.ExtensionMatcher
|
||||
|
||||
// 尝试使用缓存管理器
|
||||
if rs.cacheManager != nil {
|
||||
pathKey := fmt.Sprintf("redirect_%p", &pathConfig)
|
||||
matcher = rs.cacheManager.GetExtensionMatcher(pathKey, pathConfig.ExtRules)
|
||||
} else {
|
||||
matcher = utils.NewExtensionMatcher(pathConfig.ExtRules)
|
||||
}
|
||||
|
||||
// 快速检查:如果没有任何302跳转规则,跳过复杂逻辑
|
||||
if !matcher.HasRedirectRule() {
|
||||
return result
|
||||
}
|
||||
|
||||
// 尝试选择最佳规则
|
||||
if rule, found, usedAlt := rs.SelectBestRule(client, pathConfig, path); found && rule != nil && rule.RedirectMode {
|
||||
result.Rule = rule
|
||||
result.Found = found
|
||||
result.UsedAltTarget = usedAlt
|
||||
result.ShouldRedirect = true
|
||||
result.TargetURL = rule.Target
|
||||
return result
|
||||
}
|
||||
|
||||
// 回退到简单的扩展名匹配
|
||||
if rule, found := pathConfig.GetProcessedExtRule(ext); found && rule.RedirectMode {
|
||||
result.Rule = rule
|
||||
result.Found = found
|
||||
result.UsedAltTarget = true
|
||||
result.ShouldRedirect = true
|
||||
result.TargetURL = rule.Target
|
||||
return result
|
||||
}
|
||||
|
||||
// 检查通配符规则
|
||||
if rule, found := pathConfig.GetProcessedExtRule("*"); found && rule.RedirectMode {
|
||||
result.Rule = rule
|
||||
result.Found = found
|
||||
result.UsedAltTarget = true
|
||||
result.ShouldRedirect = true
|
||||
result.TargetURL = rule.Target
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetTargetURL 根据路径和配置决定目标URL
|
||||
func (rs *RuleService) GetTargetURL(client *http.Client, r *http.Request, pathConfig config.PathConfig, path string) (string, bool) {
|
||||
// 默认使用默认目标
|
||||
targetBase := pathConfig.DefaultTarget
|
||||
usedAltTarget := false
|
||||
|
||||
// 如果没有扩展名规则,直接返回默认目标
|
||||
if len(pathConfig.ExtRules) == 0 {
|
||||
ext := extractExtension(path)
|
||||
if ext == "" {
|
||||
log.Printf("[Route] %s -> %s (无扩展名)", path, targetBase)
|
||||
}
|
||||
return targetBase, false
|
||||
}
|
||||
|
||||
// 使用新的统一规则选择逻辑
|
||||
rule, found, usedAlt := rs.SelectBestRule(client, pathConfig, path)
|
||||
if found && rule != nil {
|
||||
targetBase = rule.Target
|
||||
usedAltTarget = usedAlt
|
||||
log.Printf("[Route] %s -> %s (使用选中的规则)", path, targetBase)
|
||||
} else {
|
||||
// 如果无法获取文件大小,尝试使用扩展名直接匹配
|
||||
ext := extractExtension(path)
|
||||
if altTarget, exists := pathConfig.GetProcessedExtTarget(ext); exists {
|
||||
usedAltTarget = true
|
||||
targetBase = altTarget
|
||||
log.Printf("[Route] %s -> %s (基于扩展名直接匹配)", path, targetBase)
|
||||
} else if altTarget, exists := pathConfig.GetProcessedExtTarget("*"); exists {
|
||||
// 尝试使用通配符
|
||||
usedAltTarget = true
|
||||
targetBase = altTarget
|
||||
log.Printf("[Route] %s -> %s (基于通配符匹配)", path, targetBase)
|
||||
}
|
||||
}
|
||||
|
||||
return targetBase, usedAltTarget
|
||||
}
|
||||
|
||||
// extractExtension 提取文件扩展名
|
||||
func extractExtension(path string) string {
|
||||
lastDotIndex := strings.LastIndex(path, ".")
|
||||
if lastDotIndex > 0 && lastDotIndex < len(path)-1 {
|
||||
return strings.ToLower(path[lastDotIndex+1:])
|
||||
}
|
||||
return ""
|
||||
}
|
@ -183,15 +183,6 @@ func GetFileSize(client *http.Client, url string) (int64, error) {
|
||||
return resp.ContentLength, nil
|
||||
}
|
||||
|
||||
// RuleSelectionResult 规则选择结果,用于缓存和传递结果
|
||||
type RuleSelectionResult struct {
|
||||
Rule *config.ExtensionRule
|
||||
Found bool
|
||||
UsedAltTarget bool
|
||||
TargetURL string
|
||||
ShouldRedirect bool
|
||||
}
|
||||
|
||||
// ExtensionMatcher 扩展名匹配器,用于优化扩展名匹配性能
|
||||
type ExtensionMatcher struct {
|
||||
exactMatches map[string][]*config.ExtensionRule // 精确匹配的扩展名
|
||||
@ -269,173 +260,8 @@ func (em *ExtensionMatcher) HasRedirectRule() bool {
|
||||
return em.hasRedirectRule
|
||||
}
|
||||
|
||||
// extractExtension 提取文件扩展名(优化版本)
|
||||
func extractExtension(path string) string {
|
||||
lastDotIndex := strings.LastIndex(path, ".")
|
||||
if lastDotIndex > 0 && lastDotIndex < len(path)-1 {
|
||||
return strings.ToLower(path[lastDotIndex+1:])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SelectBestRule 根据文件大小和扩展名选择最合适的规则(优化版本)
|
||||
// 返回值: (选中的规则, 是否找到匹配的规则, 是否使用了备用目标)
|
||||
func SelectBestRule(client *http.Client, pathConfig config.PathConfig, path string) (*config.ExtensionRule, bool, bool) {
|
||||
// 如果没有扩展名规则,返回nil
|
||||
if len(pathConfig.ExtRules) == 0 {
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// 提取扩展名
|
||||
ext := extractExtension(path)
|
||||
|
||||
// 创建扩展名匹配器(可以考虑缓存这个匹配器)
|
||||
matcher := NewExtensionMatcher(pathConfig.ExtRules)
|
||||
|
||||
// 获取匹配的规则
|
||||
matchingRules := matcher.GetMatchingRules(ext)
|
||||
if len(matchingRules) == 0 {
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// 获取文件大小
|
||||
contentLength, err := GetFileSize(client, pathConfig.DefaultTarget+path)
|
||||
if err != nil {
|
||||
log.Printf("[SelectRule] %s -> 获取文件大小出错: %v", path, err)
|
||||
// 如果无法获取文件大小,返回第一个匹配的规则
|
||||
if len(matchingRules) > 0 {
|
||||
log.Printf("[SelectRule] %s -> 基于扩展名直接匹配规则", path)
|
||||
return matchingRules[0], true, true
|
||||
}
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// 根据文件大小找出最匹配的规则(规则已经预排序)
|
||||
for _, rule := range matchingRules {
|
||||
// 检查文件大小是否在阈值范围内
|
||||
if contentLength >= rule.SizeThreshold && contentLength <= rule.MaxSize {
|
||||
// 找到匹配的规则
|
||||
log.Printf("[SelectRule] %s -> 选中规则 (文件大小: %s, 在区间 %s 到 %s 之间)",
|
||||
path, FormatBytes(contentLength),
|
||||
FormatBytes(rule.SizeThreshold), FormatBytes(rule.MaxSize))
|
||||
|
||||
// 检查目标是否可访问(使用带缓存的检查)
|
||||
if isTargetAccessible(client, rule.Target+path) {
|
||||
return rule, true, true
|
||||
} else {
|
||||
log.Printf("[SelectRule] %s -> 规则目标不可访问,继续查找", path)
|
||||
// 继续查找下一个匹配的规则
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 没有找到合适的规则
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// SelectRuleForRedirect 专门为302跳转优化的规则选择函数
|
||||
func SelectRuleForRedirect(client *http.Client, pathConfig config.PathConfig, path string) *RuleSelectionResult {
|
||||
result := &RuleSelectionResult{}
|
||||
|
||||
// 快速检查:如果没有任何302跳转配置,直接返回
|
||||
if !pathConfig.RedirectMode && len(pathConfig.ExtRules) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
// 如果默认目标配置了302跳转,优先使用
|
||||
if pathConfig.RedirectMode {
|
||||
result.Found = true
|
||||
result.ShouldRedirect = true
|
||||
result.TargetURL = pathConfig.DefaultTarget
|
||||
return result
|
||||
}
|
||||
|
||||
// 检查扩展名规则
|
||||
if len(pathConfig.ExtRules) > 0 {
|
||||
ext := extractExtension(path)
|
||||
matcher := NewExtensionMatcher(pathConfig.ExtRules)
|
||||
|
||||
// 快速检查:如果没有任何302跳转规则,跳过复杂逻辑
|
||||
if !matcher.HasRedirectRule() {
|
||||
return result
|
||||
}
|
||||
|
||||
// 尝试选择最佳规则
|
||||
if rule, found, usedAlt := SelectBestRule(client, pathConfig, path); found && rule != nil && rule.RedirectMode {
|
||||
result.Rule = rule
|
||||
result.Found = found
|
||||
result.UsedAltTarget = usedAlt
|
||||
result.ShouldRedirect = true
|
||||
result.TargetURL = rule.Target
|
||||
return result
|
||||
}
|
||||
|
||||
// 回退到简单的扩展名匹配
|
||||
if rule, found := pathConfig.GetProcessedExtRule(ext); found && rule.RedirectMode {
|
||||
result.Rule = rule
|
||||
result.Found = found
|
||||
result.UsedAltTarget = true
|
||||
result.ShouldRedirect = true
|
||||
result.TargetURL = rule.Target
|
||||
return result
|
||||
}
|
||||
|
||||
// 检查通配符规则
|
||||
if rule, found := pathConfig.GetProcessedExtRule("*"); found && rule.RedirectMode {
|
||||
result.Rule = rule
|
||||
result.Found = found
|
||||
result.UsedAltTarget = true
|
||||
result.ShouldRedirect = true
|
||||
result.TargetURL = rule.Target
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetTargetURL 根据路径和配置决定目标URL(优化版本)
|
||||
func GetTargetURL(client *http.Client, r *http.Request, pathConfig config.PathConfig, path string) (string, bool) {
|
||||
// 默认使用默认目标
|
||||
targetBase := pathConfig.DefaultTarget
|
||||
usedAltTarget := false
|
||||
|
||||
// 如果没有扩展名规则,直接返回默认目标
|
||||
if len(pathConfig.ExtRules) == 0 {
|
||||
ext := extractExtension(path)
|
||||
if ext == "" {
|
||||
log.Printf("[Route] %s -> %s (无扩展名)", path, targetBase)
|
||||
}
|
||||
return targetBase, false
|
||||
}
|
||||
|
||||
// 使用新的统一规则选择逻辑
|
||||
rule, found, usedAlt := SelectBestRule(client, pathConfig, path)
|
||||
if found && rule != nil {
|
||||
targetBase = rule.Target
|
||||
usedAltTarget = usedAlt
|
||||
log.Printf("[Route] %s -> %s (使用选中的规则)", path, targetBase)
|
||||
} else {
|
||||
// 如果无法获取文件大小,尝试使用扩展名直接匹配(优化点)
|
||||
ext := extractExtension(path)
|
||||
if altTarget, exists := pathConfig.GetProcessedExtTarget(ext); exists {
|
||||
usedAltTarget = true
|
||||
targetBase = altTarget
|
||||
log.Printf("[Route] %s -> %s (基于扩展名直接匹配)", path, targetBase)
|
||||
} else if altTarget, exists := pathConfig.GetProcessedExtTarget("*"); exists {
|
||||
// 尝试使用通配符
|
||||
usedAltTarget = true
|
||||
targetBase = altTarget
|
||||
log.Printf("[Route] %s -> %s (基于通配符匹配)", path, targetBase)
|
||||
}
|
||||
}
|
||||
|
||||
return targetBase, usedAltTarget
|
||||
}
|
||||
|
||||
// isTargetAccessible 检查目标URL是否可访问
|
||||
func isTargetAccessible(client *http.Client, targetURL string) bool {
|
||||
// IsTargetAccessible 检查目标URL是否可访问
|
||||
func IsTargetAccessible(client *http.Client, targetURL string) bool {
|
||||
// 先查缓存
|
||||
if cache, ok := accessCache.Load(targetURL); ok {
|
||||
cacheItem := cache.(accessibilityCache)
|
||||
|
Loading…
x
Reference in New Issue
Block a user