diff --git a/pkg/webserver/apache/proxy.go b/pkg/webserver/apache/proxy.go index 0e6631ff..b242bd7d 100644 --- a/pkg/webserver/apache/proxy.go +++ b/pkg/webserver/apache/proxy.go @@ -12,6 +12,38 @@ import ( "github.com/samber/lo" ) +// parseDurationToSeconds 将时长字符串转换为秒数 +// 支持格式: "10s", "5m", "1h", "1d" +func parseDurationToSeconds(duration string) int { + duration = strings.TrimSpace(duration) + if duration == "" { + return 600 // 默认 10 分钟 + } + + // 提取数字和单位 + re := regexp.MustCompile(`^(\d+)([smhd]?)$`) + matches := re.FindStringSubmatch(duration) + if matches == nil { + return 600 + } + + value, _ := strconv.Atoi(matches[1]) + unit := matches[2] + + switch unit { + case "s", "": + return value + case "m": + return value * 60 + case "h": + return value * 3600 + case "d": + return value * 86400 + default: + return value + } +} + // proxyFilePattern 匹配代理配置文件名 (200-299) var proxyFilePattern = regexp.MustCompile(`^(\d{3})-proxy\.conf$`) @@ -104,7 +136,24 @@ func parseProxyFile(filePath string) (*types.Proxy, error) { // 解析 CacheEnable if regexp.MustCompile(`CacheEnable`).MatchString(contentStr) { - proxy.Cache = true + proxy.Cache = &types.CacheConfig{ + Valid: make(map[string]string), + NoCacheConditions: []string{}, + UseStale: []string{}, + Methods: []string{}, + } + + // 解析 CacheDefaultExpire + expirePattern := regexp.MustCompile(`CacheDefaultExpire\s+(\d+)`) + if em := expirePattern.FindStringSubmatch(contentStr); em != nil { + seconds, _ := strconv.Atoi(em[1]) + minutes := seconds / 60 + if minutes > 0 { + proxy.Cache.Valid["any"] = fmt.Sprintf("%dm", minutes) + } else { + proxy.Cache.Valid["any"] = fmt.Sprintf("%ds", seconds) + } + } } // 解析 Substitute (响应内容替换) @@ -243,10 +292,20 @@ func generateProxyConfig(proxy types.Proxy) string { } // Cache 配置 - if proxy.Cache { + if proxy.Cache != nil { sb.WriteString(" \n") sb.WriteString(fmt.Sprintf(" CacheEnable disk %s\n", location)) - sb.WriteString(" CacheDefaultExpire 600\n") + + // 从 Valid 中获取默认过期时间 + expireSeconds := 600 // 默认 10 分钟 + if len(proxy.Cache.Valid) > 0 { + // 取第一个值作为默认过期时间 + for _, duration := range proxy.Cache.Valid { + expireSeconds = parseDurationToSeconds(duration) + break + } + } + sb.WriteString(fmt.Sprintf(" CacheDefaultExpire %d\n", expireSeconds)) sb.WriteString(" \n") } diff --git a/pkg/webserver/nginx/proxy.go b/pkg/webserver/nginx/proxy.go index 353cca7f..e8bacc2d 100644 --- a/pkg/webserver/nginx/proxy.go +++ b/pkg/webserver/nginx/proxy.go @@ -115,7 +115,70 @@ func parseProxyFile(filePath string) (*types.Proxy, error) { // 解析 proxy_cache cachePattern := regexp.MustCompile(`proxy_cache\s+(\S+);`) if cm := cachePattern.FindStringSubmatch(blockContent); cm != nil && cm[1] != "off" { - proxy.Cache = true + proxy.Cache = &types.CacheConfig{ + Valid: make(map[string]string), + NoCacheConditions: []string{}, + UseStale: []string{}, + Methods: []string{}, + } + + // 解析 proxy_cache_valid + cacheValidPattern := regexp.MustCompile(`proxy_cache_valid\s+([^;]+);`) + cacheValidMatches := cacheValidPattern.FindAllStringSubmatch(blockContent, -1) + for _, cvm := range cacheValidMatches { + parts := strings.Fields(cvm[1]) + if len(parts) >= 2 { + // 最后一个是时长,前面的是状态码 + duration := parts[len(parts)-1] + codes := strings.Join(parts[:len(parts)-1], " ") + proxy.Cache.Valid[codes] = duration + } else if len(parts) == 1 { + // 只有时长,表示 any + proxy.Cache.Valid["any"] = parts[0] + } + } + + // 解析 proxy_cache_bypass / proxy_no_cache + bypassPattern := regexp.MustCompile(`proxy_cache_bypass\s+([^;]+);`) + if bm := bypassPattern.FindStringSubmatch(blockContent); bm != nil { + proxy.Cache.NoCacheConditions = strings.Fields(bm[1]) + } + + // 解析 proxy_cache_use_stale + useStalePattern := regexp.MustCompile(`proxy_cache_use_stale\s+([^;]+);`) + if usm := useStalePattern.FindStringSubmatch(blockContent); usm != nil { + proxy.Cache.UseStale = strings.Fields(usm[1]) + } + + // 解析 proxy_cache_background_update + bgUpdatePattern := regexp.MustCompile(`proxy_cache_background_update\s+(on|off);`) + if bgm := bgUpdatePattern.FindStringSubmatch(blockContent); bgm != nil { + proxy.Cache.BackgroundUpdate = bgm[1] == "on" + } + + // 解析 proxy_cache_lock + lockPattern := regexp.MustCompile(`proxy_cache_lock\s+(on|off);`) + if lm := lockPattern.FindStringSubmatch(blockContent); lm != nil { + proxy.Cache.Lock = lm[1] == "on" + } + + // 解析 proxy_cache_min_uses + minUsesPattern := regexp.MustCompile(`proxy_cache_min_uses\s+(\d+);`) + if mum := minUsesPattern.FindStringSubmatch(blockContent); mum != nil { + proxy.Cache.MinUses, _ = strconv.Atoi(mum[1]) + } + + // 解析 proxy_cache_methods + methodsPattern := regexp.MustCompile(`proxy_cache_methods\s+([^;]+);`) + if mm := methodsPattern.FindStringSubmatch(blockContent); mm != nil { + proxy.Cache.Methods = strings.Fields(mm[1]) + } + + // 解析 proxy_cache_key + keyPattern := regexp.MustCompile(`proxy_cache_key\s+"?([^";]+)"?;`) + if km := keyPattern.FindStringSubmatch(blockContent); km != nil { + proxy.Cache.Key = strings.TrimSpace(km[1]) + } } // 解析 resolver @@ -272,10 +335,60 @@ func generateProxyConfig(proxy types.Proxy) string { sb.WriteString(fmt.Sprintf(" proxy_buffering %s;\n", lo.If(proxy.Buffering, "on").Else("off"))) // Cache 配置 - if proxy.Cache { + if proxy.Cache != nil { sb.WriteString(" proxy_cache cache_one;\n") - sb.WriteString(" proxy_cache_valid 200 302 10m;\n") - sb.WriteString(" proxy_cache_valid 404 1m;\n") + + // 缓存时长 + if len(proxy.Cache.Valid) > 0 { + for codes, duration := range proxy.Cache.Valid { + if codes == "any" { + sb.WriteString(fmt.Sprintf(" proxy_cache_valid %s;\n", duration)) + } else { + sb.WriteString(fmt.Sprintf(" proxy_cache_valid %s %s;\n", codes, duration)) + } + } + } else { + // 默认缓存时长 + sb.WriteString(" proxy_cache_valid 200 302 10m;\n") + sb.WriteString(" proxy_cache_valid 404 1m;\n") + } + + // 不缓存条件 + if len(proxy.Cache.NoCacheConditions) > 0 { + conditions := strings.Join(proxy.Cache.NoCacheConditions, " ") + sb.WriteString(fmt.Sprintf(" proxy_cache_bypass %s;\n", conditions)) + sb.WriteString(fmt.Sprintf(" proxy_no_cache %s;\n", conditions)) + } + + // 过期缓存使用策略 + if len(proxy.Cache.UseStale) > 0 { + sb.WriteString(fmt.Sprintf(" proxy_cache_use_stale %s;\n", strings.Join(proxy.Cache.UseStale, " "))) + } + + // 后台更新 + if proxy.Cache.BackgroundUpdate { + sb.WriteString(" proxy_cache_background_update on;\n") + } + + // 缓存锁 + if proxy.Cache.Lock { + sb.WriteString(" proxy_cache_lock on;\n") + } + + // 最小请求次数 + if proxy.Cache.MinUses > 0 { + sb.WriteString(fmt.Sprintf(" proxy_cache_min_uses %d;\n", proxy.Cache.MinUses)) + } + + // 缓存方法 + if len(proxy.Cache.Methods) > 0 { + sb.WriteString(fmt.Sprintf(" proxy_cache_methods %s;\n", strings.Join(proxy.Cache.Methods, " "))) + } + + // 自定义缓存键 + if proxy.Cache.Key != "" { + sb.WriteString(fmt.Sprintf(" proxy_cache_key \"%s\";\n", proxy.Cache.Key)) + } } // 自定义请求头 diff --git a/pkg/webserver/types/proxy.go b/pkg/webserver/types/proxy.go index 2c621bb5..82fe0c7c 100644 --- a/pkg/webserver/types/proxy.go +++ b/pkg/webserver/types/proxy.go @@ -2,13 +2,42 @@ package types import "time" +// CacheConfig 缓存配置 +type CacheConfig struct { + // 缓存时长,状态码 -> 时长,如: {"200 302": "10m", "404": "1m", "any": "5m"} + Valid map[string]string `form:"valid" json:"valid"` + + // 不缓存条件 (proxy_cache_bypass + proxy_no_cache) + // 常用值: "$cookie_nocache", "$arg_nocache", "$http_pragma", "$http_authorization" + NoCacheConditions []string `form:"no_cache_conditions" json:"no_cache_conditions"` + + // 过期缓存使用策略 (proxy_cache_use_stale) + // 可选值: "error", "timeout", "updating", "http_500", "http_502", "http_503", "http_504" + UseStale []string `form:"use_stale" json:"use_stale"` + + // 后台更新 (proxy_cache_background_update) + BackgroundUpdate bool `form:"background_update" json:"background_update"` + + // 缓存锁 (proxy_cache_lock),防止缓存击穿 + Lock bool `form:"lock" json:"lock"` + + // 最小请求次数 (proxy_cache_min_uses),请求 N 次后才缓存 + MinUses int `form:"min_uses" json:"min_uses"` + + // 缓存方法 (proxy_cache_methods),默认 GET HEAD + Methods []string `form:"methods" json:"methods"` + + // 自定义缓存键 (proxy_cache_key) + Key string `form:"key" json:"key"` +} + // Proxy 反向代理配置 type Proxy struct { Location string `form:"location" json:"location" validate:"required"` // 匹配路径,如: "/", "/api", "~ ^/api/v[0-9]+/" Pass string `form:"pass" json:"pass" validate:"required"` // 代理地址,如: "http://example.com", "http://backend" Host string `form:"host" json:"host"` // 代理 Host,如: "example.com" SNI string `form:"sni" json:"sni"` // 代理 SNI,如: "example.com" - Cache bool `form:"cache" json:"cache"` // 是否启用缓存 + Cache *CacheConfig `form:"cache" json:"cache"` // 缓存配置,nil 表示禁用缓存 Buffering bool `form:"buffering" json:"buffering"` // 是否启用缓冲 Resolver []string `form:"resolver" json:"resolver"` // 自定义 DNS 解析器配置,如: ["8.8.8.8", "ipv6=off"] ResolverTimeout time.Duration `form:"resolver_timeout" json:"resolver_timeout"` // DNS 解析超时时间,如: 5 * time.Second