From 9e45b3e38ad70952791aea0e7d3cae68a9f6c016 Mon Sep 17 00:00:00 2001 From: wood chen Date: Mon, 2 Jun 2025 06:33:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=89=A9=E5=B1=95=E5=90=8D?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=99=A8=E5=92=8C=E7=BC=93=E5=AD=98=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=EF=BC=8C=E4=BC=98=E5=8C=96302=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E8=A7=84=E5=88=99=E9=80=89=E6=8B=A9=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E7=BC=93=E5=AD=98=E7=BB=9F=E8=AE=A1=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E7=A1=AE=E4=BF=9D=E9=85=8D=E7=BD=AE=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=97=B6=E6=B8=85=E7=90=86=E7=BC=93=E5=AD=98=E4=BB=A5?= =?UTF-8?q?=E6=8F=90=E9=AB=98=E6=80=A7=E8=83=BD=E5=92=8C=E5=87=86=E7=A1=AE?= =?UTF-8?q?=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/config/types.go | 32 ++ internal/handler/proxy.go | 4 + internal/handler/redirect.go | 45 +-- internal/initapp/config_migration_20250322.go | 5 + internal/utils/utils.go | 340 +++++++++++++----- 5 files changed, 295 insertions(+), 131 deletions(-) diff --git a/internal/config/types.go b/internal/config/types.go index a422dc6..1a53f3a 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -2,6 +2,7 @@ package config import ( "strings" + "sync" ) type Config struct { @@ -14,6 +15,11 @@ type PathConfig struct { ExtensionMap []ExtRuleConfig `json:"ExtensionMap"` // 扩展名映射规则 ExtRules []ExtensionRule `json:"-"` // 内部使用,存储处理后的扩展名规则 RedirectMode bool `json:"RedirectMode"` // 是否使用302跳转模式 + + // 缓存相关字段(不参与JSON序列化) + matcherCache interface{} `json:"-"` // 缓存的ExtensionMatcher,使用interface{}避免循环导入 + matcherCacheMux sync.RWMutex `json:"-"` // 缓存读写锁 + cacheValid bool `json:"-"` // 缓存是否有效 } // ExtensionRule 表示一个扩展名映射规则(内部使用) @@ -73,6 +79,32 @@ func (p *PathConfig) ProcessExtensionMap() { p.ExtRules = append(p.ExtRules, extRule) } } + + // 清除缓存,因为规则已经改变 + p.InvalidateMatcherCache() +} + +// InvalidateMatcherCache 清除ExtensionMatcher缓存 +func (p *PathConfig) InvalidateMatcherCache() { + p.matcherCacheMux.Lock() + defer p.matcherCacheMux.Unlock() + p.matcherCache = nil + p.cacheValid = false +} + +// GetMatcherCache 获取缓存的ExtensionMatcher +func (p *PathConfig) GetMatcherCache() (interface{}, bool) { + p.matcherCacheMux.RLock() + defer p.matcherCacheMux.RUnlock() + return p.matcherCache, p.cacheValid +} + +// SetMatcherCache 设置ExtensionMatcher缓存 +func (p *PathConfig) SetMatcherCache(matcher interface{}) { + p.matcherCacheMux.Lock() + defer p.matcherCacheMux.Unlock() + p.matcherCache = matcher + p.cacheValid = true } // GetProcessedExtTarget 快速获取扩展名对应的目标URL,如果存在返回true diff --git a/internal/handler/proxy.go b/internal/handler/proxy.go index 38f34b3..f880aa8 100644 --- a/internal/handler/proxy.go +++ b/internal/handler/proxy.go @@ -182,6 +182,10 @@ func NewProxyHandler(cfg *config.Config) *ProxyHandler { // 注册配置更新回调 config.RegisterUpdateCallback(func(newCfg *config.Config) { // 注意:config包已经在回调触发前处理了所有ExtRules,这里无需再次处理 + + // 清理所有ExtensionMatcher缓存,因为配置已更新 + utils.ClearAllMatcherCaches(newCfg.MAP) + handler.pathMap = newCfg.MAP handler.prefixTree.update(newCfg.MAP) // 更新前缀匹配树 handler.config = newCfg diff --git a/internal/handler/redirect.go b/internal/handler/redirect.go index 8842203..4739136 100644 --- a/internal/handler/redirect.go +++ b/internal/handler/redirect.go @@ -4,7 +4,6 @@ import ( "log" "net/http" "net/url" - "path/filepath" "proxy-go/internal/config" "proxy-go/internal/utils" "strings" @@ -32,43 +31,21 @@ func (rh *RedirectHandler) HandleRedirect(w http.ResponseWriter, r *http.Request return true } -// shouldRedirect 判断是否应该进行302跳转,并返回目标URL +// shouldRedirect 判断是否应该进行302跳转,并返回目标URL(优化版本) func (rh *RedirectHandler) shouldRedirect(r *http.Request, pathConfig config.PathConfig, targetPath string, client *http.Client) (bool, string) { - // 获取文件扩展名 - ext := strings.ToLower(filepath.Ext(targetPath)) - if ext != "" { - ext = ext[1:] // 去掉点号 - } + // 使用优化的规则选择函数 + result := utils.SelectRuleForRedirect(client, pathConfig, targetPath) - // 使用统一的规则选择逻辑,考虑文件大小 - if rule, found, _ := utils.SelectBestRule(client, pathConfig, targetPath); found && rule != nil && rule.RedirectMode { - // 使用选中规则的目标URL进行302跳转 - targetURL := rh.buildTargetURL(rule.Target, targetPath, r.URL.RawQuery) - log.Printf("[Redirect] %s -> 使用选中规则进行302跳转: %s", targetPath, targetURL) - return true, targetURL - } + if result.ShouldRedirect { + // 构建完整的目标URL + targetURL := rh.buildTargetURL(result.TargetURL, targetPath, r.URL.RawQuery) - // 检查默认目标是否配置为302跳转 - if pathConfig.RedirectMode { - // 使用默认目标URL进行302跳转 - targetURL := rh.buildTargetURL(pathConfig.DefaultTarget, targetPath, r.URL.RawQuery) - log.Printf("[Redirect] %s -> 使用默认目标进行302跳转: %s", targetPath, targetURL) - return true, targetURL - } + if result.Rule != nil { + log.Printf("[Redirect] %s -> 使用选中规则进行302跳转: %s", targetPath, targetURL) + } else { + log.Printf("[Redirect] %s -> 使用默认目标进行302跳转: %s", targetPath, targetURL) + } - // 如果默认目标没有配置302跳转,检查是否有简单的扩展名匹配(向后兼容) - if rule, found := pathConfig.GetProcessedExtRule(ext); found && rule.RedirectMode { - // 使用扩展名规则的目标URL进行302跳转 - targetURL := rh.buildTargetURL(rule.Target, targetPath, r.URL.RawQuery) - log.Printf("[Redirect] %s -> 使用扩展名规则进行302跳转: %s", targetPath, targetURL) - return true, targetURL - } - - // 检查通配符规则 - if rule, found := pathConfig.GetProcessedExtRule("*"); found && rule.RedirectMode { - // 使用通配符规则的目标URL进行302跳转 - targetURL := rh.buildTargetURL(rule.Target, targetPath, r.URL.RawQuery) - log.Printf("[Redirect] %s -> 使用通配符规则进行302跳转: %s", targetPath, targetURL) return true, targetURL } diff --git a/internal/initapp/config_migration_20250322.go b/internal/initapp/config_migration_20250322.go index d9a92c5..216c772 100644 --- a/internal/initapp/config_migration_20250322.go +++ b/internal/initapp/config_migration_20250322.go @@ -13,12 +13,14 @@ type OldPathConfig struct { SizeThreshold int64 `json:"SizeThreshold,omitempty"` MaxSize int64 `json:"MaxSize,omitempty"` Path string `json:"Path,omitempty"` + RedirectMode bool `json:"RedirectMode,omitempty"` } // 新配置结构 type NewPathConfig struct { DefaultTarget string `json:"DefaultTarget"` ExtensionMap []ExtRuleConfig `json:"ExtensionMap"` + RedirectMode bool `json:"RedirectMode"` } type ExtRuleConfig struct { @@ -26,6 +28,7 @@ type ExtRuleConfig struct { Target string `json:"Target"` SizeThreshold int64 `json:"SizeThreshold"` MaxSize int64 `json:"MaxSize"` + RedirectMode bool `json:"RedirectMode"` } type CompressionConfig struct { @@ -79,6 +82,7 @@ func MigrateConfig(configPath string) error { newPathConfig := NewPathConfig{ DefaultTarget: oldPathConfig.DefaultTarget, ExtensionMap: []ExtRuleConfig{}, + RedirectMode: oldPathConfig.RedirectMode, } // 检查ExtensionMap类型 @@ -94,6 +98,7 @@ func MigrateConfig(configPath string) error { Target: target, SizeThreshold: oldPathConfig.SizeThreshold, MaxSize: oldPathConfig.MaxSize, + RedirectMode: oldPathConfig.RedirectMode, } newPathConfig.ExtensionMap = append(newPathConfig.ExtensionMap, rule) } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 9d92a71..82a9675 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -14,6 +14,7 @@ import ( "sort" "strings" "sync" + "sync/atomic" "time" ) @@ -37,6 +38,10 @@ var ( cacheTTL = 5 * time.Minute accessTTL = 30 * time.Second maxCacheSize = 10000 // 最大缓存条目数 + + // 缓存统计 + matcherCacheHits int64 + matcherCacheMisses int64 ) // 清理过期缓存 @@ -81,6 +86,19 @@ func init() { }) } }() + + // 定期打印缓存统计信息 + go func() { + ticker := time.NewTicker(5 * time.Minute) + for range ticker.C { + hits, misses := GetMatcherCacheStats() + if hits+misses > 0 { + hitRate := float64(hits) / float64(hits+misses) * 100 + log.Printf("[Cache] ExtensionMatcher stats: hits=%d, misses=%d, hit_rate=%.2f%%", + hits, misses, hitRate) + } + } + }() } // GenerateRequestID 生成唯一的请求ID @@ -183,104 +201,168 @@ func GetFileSize(client *http.Client, url string) (int64, error) { return resp.ContentLength, nil } -// SelectBestRule 根据文件大小和扩展名选择最合适的规则 -// 返回值: (选中的规则, 是否找到匹配的规则, 是否使用了备用目标) -func SelectBestRule(client *http.Client, pathConfig config.PathConfig, path string) (*config.ExtensionRule, bool, bool) { - // 获取文件扩展名(使用优化的字符串处理) - ext := "" - lastDotIndex := strings.LastIndex(path, ".") - if lastDotIndex > 0 && lastDotIndex < len(path)-1 { - ext = strings.ToLower(path[lastDotIndex+1:]) +// RuleSelectionResult 规则选择结果,用于缓存和传递结果 +type RuleSelectionResult struct { + Rule *config.ExtensionRule + Found bool + UsedAltTarget bool + TargetURL string + ShouldRedirect bool +} + +// ExtensionMatcher 扩展名匹配器,用于优化扩展名匹配性能 +type ExtensionMatcher struct { + exactMatches map[string][]*config.ExtensionRule // 精确匹配的扩展名 + wildcardRules []*config.ExtensionRule // 通配符规则 + hasRedirectRule bool // 是否有任何302跳转规则 +} + +// NewExtensionMatcher 创建扩展名匹配器 +func NewExtensionMatcher(rules []config.ExtensionRule) *ExtensionMatcher { + matcher := &ExtensionMatcher{ + exactMatches: make(map[string][]*config.ExtensionRule), + wildcardRules: make([]*config.ExtensionRule, 0), } + for i := range rules { + rule := &rules[i] + + // 处理阈值默认值 + if rule.SizeThreshold < 0 { + rule.SizeThreshold = 0 + } + if rule.MaxSize <= 0 { + rule.MaxSize = 1<<63 - 1 + } + + // 检查是否有302跳转规则 + if rule.RedirectMode { + matcher.hasRedirectRule = true + } + + // 分类存储规则 + for _, ext := range rule.Extensions { + if ext == "*" { + matcher.wildcardRules = append(matcher.wildcardRules, rule) + } else { + if matcher.exactMatches[ext] == nil { + matcher.exactMatches[ext] = make([]*config.ExtensionRule, 0, 1) + } + matcher.exactMatches[ext] = append(matcher.exactMatches[ext], rule) + } + } + } + + // 预排序所有规则组 + for ext := range matcher.exactMatches { + sortRulesByThreshold(matcher.exactMatches[ext]) + } + sortRulesByThreshold(matcher.wildcardRules) + + return matcher +} + +// sortRulesByThreshold 按阈值排序规则 +func sortRulesByThreshold(rules []*config.ExtensionRule) { + sort.Slice(rules, func(i, j int) bool { + if rules[i].SizeThreshold == rules[j].SizeThreshold { + return rules[i].MaxSize > rules[j].MaxSize + } + return rules[i].SizeThreshold < rules[j].SizeThreshold + }) +} + +// GetMatchingRules 获取匹配的规则 +func (em *ExtensionMatcher) GetMatchingRules(ext string) []*config.ExtensionRule { + // 先查找精确匹配 + if rules, exists := em.exactMatches[ext]; exists { + return rules + } + // 返回通配符规则 + return em.wildcardRules +} + +// HasRedirectRule 检查是否有任何302跳转规则 +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 "" +} + +// GetMatcherCacheStats 获取缓存统计信息 +func GetMatcherCacheStats() (hits, misses int64) { + return atomic.LoadInt64(&matcherCacheHits), atomic.LoadInt64(&matcherCacheMisses) +} + +// ResetMatcherCacheStats 重置缓存统计 +func ResetMatcherCacheStats() { + atomic.StoreInt64(&matcherCacheHits, 0) + atomic.StoreInt64(&matcherCacheMisses, 0) +} + +// getOrCreateExtensionMatcher 获取或创建ExtensionMatcher(带缓存和统计) +func getOrCreateExtensionMatcher(pathConfig *config.PathConfig) *ExtensionMatcher { + // 尝试从缓存获取 + if cached, valid := pathConfig.GetMatcherCache(); valid && cached != nil { + if matcher, ok := cached.(*ExtensionMatcher); ok { + atomic.AddInt64(&matcherCacheHits, 1) + return matcher + } + } + + // 缓存未命中,创建新的匹配器 + atomic.AddInt64(&matcherCacheMisses, 1) + matcher := NewExtensionMatcher(pathConfig.ExtRules) + + // 存储到缓存 + pathConfig.SetMatcherCache(matcher) + + log.Printf("[Cache] ExtensionMatcher created and cached for %d rules", len(pathConfig.ExtRules)) + + return matcher +} + +// 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 := getOrCreateExtensionMatcher(&pathConfig) + + // 获取匹配的规则 + 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) - - // 如果无法获取文件大小,尝试使用扩展名直接匹配 - for _, rule := range pathConfig.ExtRules { - // 检查具体扩展名匹配 - for _, e := range rule.Extensions { - if e == ext { - log.Printf("[SelectRule] %s -> 基于扩展名直接匹配规则", path) - return &rule, true, true - } - } + // 如果无法获取文件大小,返回第一个匹配的规则 + if len(matchingRules) > 0 { + log.Printf("[SelectRule] %s -> 基于扩展名直接匹配规则", path) + return matchingRules[0], true, true } - - // 尝试使用通配符规则 - for _, rule := range pathConfig.ExtRules { - for _, e := range rule.Extensions { - if e == "*" { - log.Printf("[SelectRule] %s -> 基于通配符匹配规则", path) - return &rule, true, true - } - } - } - return nil, false, false } - // 获取匹配的扩展名规则 - matchingRules := []config.ExtensionRule{} - wildcardRules := []config.ExtensionRule{} // 存储通配符规则 - - // 找出所有匹配当前扩展名的规则 - for _, rule := range pathConfig.ExtRules { - // 处理阈值默认值 - if rule.SizeThreshold < 0 { - rule.SizeThreshold = 0 // 默认不限制 - } - - if rule.MaxSize <= 0 { - rule.MaxSize = 1<<63 - 1 // 设置为最大值表示不限制 - } - - // 检查是否包含通配符 - for _, e := range rule.Extensions { - if e == "*" { - wildcardRules = append(wildcardRules, rule) - break - } - } - - // 检查具体扩展名匹配 - for _, e := range rule.Extensions { - if e == ext { - matchingRules = append(matchingRules, rule) - break - } - } - } - - // 如果没有找到匹配的具体扩展名规则,使用通配符规则 - if len(matchingRules) == 0 { - if len(wildcardRules) > 0 { - log.Printf("[SelectRule] %s -> 使用通配符规则", path) - matchingRules = wildcardRules - } else { - return nil, false, false - } - } - - // 按阈值排序规则(优化点:使用更高效的排序) - sort.Slice(matchingRules, func(i, j int) bool { - if matchingRules[i].SizeThreshold == matchingRules[j].SizeThreshold { - return matchingRules[i].MaxSize > matchingRules[j].MaxSize - } - return matchingRules[i].SizeThreshold < matchingRules[j].SizeThreshold - }) - - // 根据文件大小找出最匹配的规则 - for i := range matchingRules { - rule := &matchingRules[i] - + // 根据文件大小找出最匹配的规则(规则已经预排序) + for _, rule := range matchingRules { // 检查文件大小是否在阈值范围内 if contentLength >= rule.SizeThreshold && contentLength <= rule.MaxSize { // 找到匹配的规则 @@ -303,33 +385,84 @@ func SelectBestRule(client *http.Client, pathConfig config.PathConfig, path stri return nil, false, false } -// GetTargetURL 根据路径和配置决定目标URL +// 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 := getOrCreateExtensionMatcher(&pathConfig) + + // 快速检查:如果没有任何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 - // 获取文件扩展名(使用优化的字符串处理) - ext := "" - lastDotIndex := strings.LastIndex(path, ".") - if lastDotIndex > 0 && lastDotIndex < len(path)-1 { - ext = strings.ToLower(path[lastDotIndex+1:]) - } - // 如果没有扩展名规则,直接返回默认目标 if len(pathConfig.ExtRules) == 0 { + ext := extractExtension(path) if ext == "" { log.Printf("[Route] %s -> %s (无扩展名)", path, targetBase) } return targetBase, false } - // 确保有扩展名规则 - if ext == "" { - log.Printf("[Route] %s -> %s (无扩展名)", path, targetBase) - // 即使没有扩展名,也要尝试匹配 * 通配符规则 - } - // 使用新的统一规则选择逻辑 rule, found, usedAlt := SelectBestRule(client, pathConfig, path) if found && rule != nil { @@ -338,6 +471,7 @@ func GetTargetURL(client *http.Client, r *http.Request, pathConfig config.PathCo log.Printf("[Route] %s -> %s (使用选中的规则)", path, targetBase) } else { // 如果无法获取文件大小,尝试使用扩展名直接匹配(优化点) + ext := extractExtension(path) if altTarget, exists := pathConfig.GetProcessedExtTarget(ext); exists { usedAltTarget = true targetBase = altTarget @@ -457,3 +591,15 @@ func ParseInt(s string, defaultValue int) int { } return result } + +// ClearAllMatcherCaches 清理所有ExtensionMatcher缓存(用于配置更新时) +func ClearAllMatcherCaches(configMap map[string]config.PathConfig) { + cleared := 0 + for path := range configMap { + pathConfig := configMap[path] + pathConfig.InvalidateMatcherCache() + configMap[path] = pathConfig + cleared++ + } + log.Printf("[Cache] Cleared %d ExtensionMatcher caches due to config update", cleared) +}