package cache import ( "bytes" "crypto/md5" "encoding/hex" "io" "net/http" "strings" "sync" "time" ) // Cache 缓存接口 type Cache interface { // Get 获取缓存 Get(key string) (*http.Response, bool) // Set 设置缓存 Set(key string, resp *http.Response) // Delete 删除缓存 Delete(key string) // Clear 清空缓存 Clear() } // MemoryCache 内存缓存实现 type MemoryCache struct { // 缓存内容 items sync.Map // 过期时间 ttl time.Duration // 清理间隔 cleanupInterval time.Duration // 最大条目数 maxEntries int // 当前条目数 size int32 // 互斥锁 mu sync.Mutex } // CacheItem 缓存项 type CacheItem struct { response *http.Response responseBody []byte expiry time.Time } // NewMemoryCache 创建内存缓存 func NewMemoryCache(ttl, cleanupInterval time.Duration, maxEntries int) *MemoryCache { cache := &MemoryCache{ ttl: ttl, cleanupInterval: cleanupInterval, maxEntries: maxEntries, } // 启动过期清理 if cleanupInterval > 0 { go cache.startCleanup() } return cache } // Get 获取缓存 func (c *MemoryCache) Get(key string) (*http.Response, bool) { value, ok := c.items.Load(key) if !ok { return nil, false } item := value.(*CacheItem) if time.Now().After(item.expiry) { c.Delete(key) return nil, false } // 克隆响应,避免修改原始数据 resp := cloneResponse(item.response, item.responseBody) return resp, true } // Set 设置缓存 func (c *MemoryCache) Set(key string, resp *http.Response) { // 检查缓存是否已满 c.mu.Lock() if c.maxEntries > 0 && c.size >= int32(c.maxEntries) { c.mu.Unlock() return } c.size++ c.mu.Unlock() // 读取并保存响应体 var bodyBytes []byte if resp.Body != nil { bodyBytes, _ = io.ReadAll(resp.Body) resp.Body.Close() resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) } item := &CacheItem{ response: resp, responseBody: bodyBytes, expiry: time.Now().Add(c.ttl), } c.items.Store(key, item) } // Delete 删除缓存 func (c *MemoryCache) Delete(key string) { c.items.Delete(key) c.mu.Lock() c.size-- if c.size < 0 { c.size = 0 } c.mu.Unlock() } // Clear 清空缓存 func (c *MemoryCache) Clear() { c.items = sync.Map{} c.mu.Lock() c.size = 0 c.mu.Unlock() } // startCleanup 启动过期清理 func (c *MemoryCache) startCleanup() { ticker := time.NewTicker(c.cleanupInterval) defer ticker.Stop() for range ticker.C { now := time.Now() c.items.Range(func(key, value interface{}) bool { item := value.(*CacheItem) if now.After(item.expiry) { c.Delete(key.(string)) } return true }) } } // GenerateCacheKey 生成缓存键 func GenerateCacheKey(req *http.Request) string { // 忽略一些可变的头部 ignoredHeaders := map[string]bool{ "Connection": true, "Keep-Alive": true, "Proxy-Authenticate": true, "Proxy-Authorization": true, "TE": true, "Trailers": true, "Transfer-Encoding": true, "Upgrade": true, } // 提取缓存键组件 components := []string{ req.Method, req.URL.String(), } // 添加选择性头部 for key, values := range req.Header { if !ignoredHeaders[key] { for _, value := range values { components = append(components, key+":"+value) } } } // 连接并计算哈希 data := strings.Join(components, "|") hash := md5.New() hash.Write([]byte(data)) return hex.EncodeToString(hash.Sum(nil)) } // cloneResponse 克隆HTTP响应 func cloneResponse(resp *http.Response, body []byte) *http.Response { clone := *resp clone.Body = io.NopCloser(bytes.NewBuffer(body)) clone.Header = make(http.Header) for k, v := range resp.Header { clone.Header[k] = v } return &clone } // ShouldCache 判断请求是否应该缓存 func ShouldCache(req *http.Request, resp *http.Response) bool { // 只缓存GET请求 if req.Method != http.MethodGet { return false } // 检查响应状态码 if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotModified && resp.StatusCode != http.StatusMovedPermanently && resp.StatusCode != http.StatusPermanentRedirect { return false } // 检查Cache-Control头 cacheControl := resp.Header.Get("Cache-Control") if strings.Contains(cacheControl, "no-store") || strings.Contains(cacheControl, "no-cache") || strings.Contains(cacheControl, "private") { return false } return true }