Files
hubproxy/src/token_cache.go
2025-06-11 14:49:29 +08:00

160 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"crypto/md5"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
)
// CachedItem 通用缓存项支持Token和Manifest
type CachedItem struct {
Data []byte // 缓存数据(token字符串或manifest字节)
ContentType string // 内容类型
Headers map[string]string // 额外的响应头
ExpiresAt time.Time // 过期时间
}
// UniversalCache 通用缓存支持Token和Manifest
type UniversalCache struct {
cache sync.Map // 线程安全的并发映射
}
var globalCache = &UniversalCache{}
// Get 获取缓存项如果不存在或过期返回nil
func (c *UniversalCache) Get(key string) *CachedItem {
if v, ok := c.cache.Load(key); ok {
if cached := v.(*CachedItem); time.Now().Before(cached.ExpiresAt) {
return cached
}
// 自动清理过期项,保持内存整洁
c.cache.Delete(key)
}
return nil
}
// Set 设置缓存项
func (c *UniversalCache) Set(key string, data []byte, contentType string, headers map[string]string, ttl time.Duration) {
c.cache.Store(key, &CachedItem{
Data: data,
ContentType: contentType,
Headers: headers,
ExpiresAt: time.Now().Add(ttl),
})
}
// GetToken 获取缓存的token(向后兼容)
func (c *UniversalCache) GetToken(key string) string {
if item := c.Get(key); item != nil {
return string(item.Data)
}
return ""
}
// SetToken 设置token缓存(向后兼容)
func (c *UniversalCache) SetToken(key, token string, ttl time.Duration) {
c.Set(key, []byte(token), "application/json", nil, ttl)
}
// buildCacheKey 构建稳定的缓存key
func buildCacheKey(prefix, query string) string {
// 使用MD5确保key的一致性和简洁性
return fmt.Sprintf("%s:%x", prefix, md5.Sum([]byte(query)))
}
// buildTokenCacheKey 构建token缓存key(向后兼容)
func buildTokenCacheKey(query string) string {
return buildCacheKey("token", query)
}
// buildManifestCacheKey 构建manifest缓存key
func buildManifestCacheKey(imageRef, reference string) string {
key := fmt.Sprintf("%s:%s", imageRef, reference)
return buildCacheKey("manifest", key)
}
// getManifestTTL 根据引用类型智能确定TTL
func getManifestTTL(reference string) time.Duration {
cfg := GetConfig()
defaultTTL := 30 * time.Minute
if cfg.TokenCache.DefaultTTL != "" {
if parsed, err := time.ParseDuration(cfg.TokenCache.DefaultTTL); err == nil {
defaultTTL = parsed
}
}
// 智能TTL策略
if strings.HasPrefix(reference, "sha256:") {
// immutable digest: 长期缓存
return 24 * time.Hour
}
// mutable tag的智能判断
if reference == "latest" || reference == "main" || reference == "master" ||
reference == "dev" || reference == "develop" {
// 热门可变标签: 短期缓存
return 10 * time.Minute
}
// 普通tag: 中等缓存时间
return defaultTTL
}
// extractTTLFromResponse 从响应中智能提取TTL
func extractTTLFromResponse(responseBody []byte) time.Duration {
var tokenResp struct {
ExpiresIn int `json:"expires_in"`
}
// 默认30分钟TTL确保稳定性
defaultTTL := 30 * time.Minute
if json.Unmarshal(responseBody, &tokenResp) == nil && tokenResp.ExpiresIn > 0 {
// 使用响应中的过期时间但提前5分钟过期确保安全边际
safeTTL := time.Duration(tokenResp.ExpiresIn-300) * time.Second
if safeTTL > 5*time.Minute { // 确保至少有5分钟的缓存时间
return safeTTL
}
}
return defaultTTL
}
// writeTokenResponse 写入token响应(向后兼容)
func writeTokenResponse(c *gin.Context, cachedBody string) {
// 直接返回缓存的完整响应体,保持格式一致性
c.Header("Content-Type", "application/json")
c.String(200, cachedBody)
}
// writeCachedResponse 写入缓存响应
func writeCachedResponse(c *gin.Context, item *CachedItem) {
// 设置内容类型
if item.ContentType != "" {
c.Header("Content-Type", item.ContentType)
}
// 设置额外的响应头
for key, value := range item.Headers {
c.Header(key, value)
}
// 返回数据
c.Data(200, item.ContentType, item.Data)
}
// isCacheEnabled 检查缓存是否启用
func isCacheEnabled() bool {
cfg := GetConfig()
return cfg.TokenCache.Enabled
}
// isTokenCacheEnabled 检查token缓存是否启用(向后兼容)
func isTokenCacheEnabled() bool {
return isCacheEnabled()
}