From 605b26b88379a9dd2cbbe86897b287160ac77ce6 Mon Sep 17 00:00:00 2001 From: wood chen Date: Mon, 2 Jun 2025 07:18:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0ExtensionMatcher=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=9C=BA=E5=88=B6=EF=BC=8C=E4=BC=98=E5=8C=96=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E7=AE=A1=E7=90=86=E5=99=A8=E5=92=8C302=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E8=A7=84=E5=88=99=E6=9C=8D=E5=8A=A1=E9=9B=86=E6=88=90?= =?UTF-8?q?=EF=BC=8C=E6=8F=90=E5=8D=87=E4=BB=A3=E7=A0=81=E5=8F=AF=E8=AF=BB?= =?UTF-8?q?=E6=80=A7=E5=92=8C=E6=80=A7=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/cache/extension_matcher.go | 216 +++++++++++++++++++++++++++ internal/cache/manager.go | 58 ++++++++ internal/handler/proxy.go | 12 +- internal/handler/redirect.go | 15 +- internal/service/rule_service.go | 217 ++++++++++++++++++++++++++++ internal/utils/utils.go | 178 +---------------------- 6 files changed, 512 insertions(+), 184 deletions(-) create mode 100644 internal/cache/extension_matcher.go create mode 100644 internal/service/rule_service.go diff --git a/internal/cache/extension_matcher.go b/internal/cache/extension_matcher.go new file mode 100644 index 0000000..1d90c8d --- /dev/null +++ b/internal/cache/extension_matcher.go @@ -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) +} diff --git a/internal/cache/manager.go b/internal/cache/manager.go index c43aba2..5c40e36 100644 --- a/internal/cache/manager.go +++ b/internal/cache/manager.go @@ -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() + } +} diff --git a/internal/handler/proxy.go b/internal/handler/proxy.go index 38f34b3..78be286 100644 --- a/internal/handler/proxy.go +++ b/internal/handler/proxy.go @@ -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, "/") diff --git a/internal/handler/redirect.go b/internal/handler/redirect.go index 4739136..08d1fa9 100644 --- a/internal/handler/redirect.go +++ b/internal/handler/redirect.go @@ -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 diff --git a/internal/service/rule_service.go b/internal/service/rule_service.go new file mode 100644 index 0000000..41f1398 --- /dev/null +++ b/internal/service/rule_service.go @@ -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 "" +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index ee76a7e..dbf64f2 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -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)