diff --git a/src/config.go b/src/config.go index 5b6ac3c..3a205a0 100644 --- a/src/config.go +++ b/src/config.go @@ -63,6 +63,12 @@ var ( appConfigLock sync.RWMutex isViperEnabled bool viperInstance *viper.Viper + + // ✅ 配置缓存变量 + cachedConfig *AppConfig + configCacheTime time.Time + configCacheTTL = 5 * time.Second + configCacheMutex sync.RWMutex ) // DefaultConfig 返回默认配置 @@ -141,21 +147,45 @@ func DefaultConfig() *AppConfig { // GetConfig 安全地获取配置副本 func GetConfig() *AppConfig { - appConfigLock.RLock() - defer appConfigLock.RUnlock() + // ✅ 快速缓存检查,减少深拷贝开销 + configCacheMutex.RLock() + if cachedConfig != nil && time.Since(configCacheTime) < configCacheTTL { + config := cachedConfig + configCacheMutex.RUnlock() + return config + } + configCacheMutex.RUnlock() - if appConfig == nil { - return DefaultConfig() + // 缓存过期,重新生成配置 + configCacheMutex.Lock() + defer configCacheMutex.Unlock() + + // 双重检查,防止重复生成 + if cachedConfig != nil && time.Since(configCacheTime) < configCacheTTL { + return cachedConfig } - // 返回配置的深拷贝 + appConfigLock.RLock() + if appConfig == nil { + appConfigLock.RUnlock() + defaultCfg := DefaultConfig() + cachedConfig = defaultCfg + configCacheTime = time.Now() + return defaultCfg + } + + // 生成新的配置深拷贝 configCopy := *appConfig configCopy.Security.WhiteList = append([]string(nil), appConfig.Security.WhiteList...) configCopy.Security.BlackList = append([]string(nil), appConfig.Security.BlackList...) configCopy.Proxy.WhiteList = append([]string(nil), appConfig.Proxy.WhiteList...) configCopy.Proxy.BlackList = append([]string(nil), appConfig.Proxy.BlackList...) + appConfigLock.RUnlock() - return &configCopy + cachedConfig = &configCopy + configCacheTime = time.Now() + + return cachedConfig } // setConfig 安全地设置配置 @@ -163,6 +193,11 @@ func setConfig(cfg *AppConfig) { appConfigLock.Lock() defer appConfigLock.Unlock() appConfig = cfg + + // ✅ 配置更新时清除缓存 + configCacheMutex.Lock() + cachedConfig = nil + configCacheMutex.Unlock() } // LoadConfig 加载配置文件 @@ -190,9 +225,7 @@ func LoadConfig() error { go enableViperHotReload() } - fmt.Printf("配置加载成功: 监听 %s:%d, 文件大小限制 %d MB, 限流 %d请求/%g小时, 离线镜像并发数 %d\n", - cfg.Server.Host, cfg.Server.Port, cfg.Server.FileSize/(1024*1024), - cfg.RateLimit.RequestLimit, cfg.RateLimit.PeriodHours, cfg.Download.MaxImages) + // 配置加载成功,详细信息在启动时统一显示 return nil } @@ -218,7 +251,7 @@ func enableViperHotReload() { } isViperEnabled = true - fmt.Println("热重载已启用") + // 热重载已启用,不显示额外信息 // 🚀 启用文件监听 viperInstance.WatchConfig() diff --git a/src/docker.go b/src/docker.go index e28e913..fd3c740 100644 --- a/src/docker.go +++ b/src/docker.go @@ -79,7 +79,7 @@ func initDockerProxy() { options: options, } - fmt.Printf("Docker代理已初始化\n") + // Docker代理初始化完成 } // ProxyDockerRegistryGin 标准Docker Registry API v2代理 diff --git a/src/imagetar.go b/src/imagetar.go index c668323..f6d9702 100644 --- a/src/imagetar.go +++ b/src/imagetar.go @@ -10,6 +10,7 @@ import ( "log" "net/http" "strings" + "time" "github.com/gin-gonic/gin" "github.com/google/go-containerregistry/pkg/authn" @@ -525,8 +526,7 @@ var globalImageStreamer *ImageStreamer // initImageStreamer 初始化镜像下载器 func initImageStreamer() { globalImageStreamer = NewImageStreamer(nil) - log.Printf("镜像下载器初始化完成,并发数: %d,缓存: %v", - globalImageStreamer.concurrency, isCacheEnabled()) + // 镜像下载器初始化完成 } // formatPlatformText 格式化平台文本 @@ -724,7 +724,11 @@ func (is *ImageStreamer) StreamMultipleImages(ctx context.Context, imageRefs []s log.Printf("处理镜像 %d/%d: %s", i+1, len(imageRefs), imageRef) - manifest, repositories, err := is.streamSingleImageForBatch(ctx, tarWriter, imageRef, options) + // ✅ 添加超时保护,防止单个镜像处理时间过长 + timeoutCtx, cancel := context.WithTimeout(ctx, 15*time.Minute) + manifest, repositories, err := is.streamSingleImageForBatch(timeoutCtx, tarWriter, imageRef, options) + cancel() + if err != nil { log.Printf("下载镜像 %s 失败: %v", imageRef, err) return fmt.Errorf("下载镜像 %s 失败: %w", imageRef, err) diff --git a/src/main.go b/src/main.go index b5f48bc..e7fcc66 100644 --- a/src/main.go +++ b/src/main.go @@ -3,12 +3,14 @@ package main import ( "embed" "fmt" - "github.com/gin-gonic/gin" "io" + "log" "net/http" "regexp" "strconv" "strings" + + "github.com/gin-gonic/gin" ) //go:embed public/* @@ -66,6 +68,15 @@ func main() { gin.SetMode(gin.ReleaseMode) router := gin.Default() + // ✅ 添加全局Panic恢复保护 + router.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { + log.Printf("🚨 Panic recovered: %v", recovered) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Internal server error", + "code": "INTERNAL_ERROR", + }) + })) + // 初始化镜像tar下载路由 initImageTarRoutes(router) @@ -103,7 +114,10 @@ func main() { router.NoRoute(RateLimitMiddleware(globalLimiter), handler) cfg := GetConfig() - fmt.Printf("启动成功,项目地址:https://github.com/sky22333/hubproxy \n") + fmt.Printf("🚀 HubProxy 启动成功\n") + fmt.Printf("📡 监听地址: %s:%d\n", cfg.Server.Host, cfg.Server.Port) + fmt.Printf("⚡ 限流配置: %d请求/%g小时\n", cfg.RateLimit.RequestLimit, cfg.RateLimit.PeriodHours) + fmt.Printf("🔗 项目地址: https://github.com/sky22333/hubproxy\n") err := router.Run(fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)) if err != nil { diff --git a/src/ratelimiter.go b/src/ratelimiter.go index 91db2a2..c2a4fb9 100644 --- a/src/ratelimiter.go +++ b/src/ratelimiter.go @@ -92,8 +92,7 @@ func initGlobalLimiter() *IPRateLimiter { // 启动定期清理goroutine go limiter.cleanupRoutine() - fmt.Printf("限流器初始化: %d请求/%g小时, 白名单 %d个, 黑名单 %d个\n", - cfg.RateLimit.RequestLimit, cfg.RateLimit.PeriodHours, len(whitelist), len(blacklist)) + // 限流器初始化完成,详细信息在启动时统一显示 return limiter } @@ -189,29 +188,40 @@ func (i *IPRateLimiter) GetLimiter(ip string) (*rate.Limiter, bool) { return rate.NewLimiter(rate.Inf, i.b), true // 白名单中的IP不受限制 } - // 使用纯IP作为缓存键 + now := time.Now() + + // ✅ 双重检查锁定,解决竞态条件 i.mu.RLock() entry, exists := i.ips[cleanIP] i.mu.RUnlock() - now := time.Now() - - if !exists { - // 创建新的限流器 + if exists { + // 安全更新访问时间 i.mu.Lock() - entry = &rateLimiterEntry{ - limiter: rate.NewLimiter(i.r, i.b), - lastAccess: now, + if entry, stillExists := i.ips[cleanIP]; stillExists { + entry.lastAccess = now + i.mu.Unlock() + return entry.limiter, true } - i.ips[cleanIP] = entry - i.mu.Unlock() - } else { - // 更新最后访问时间 - i.mu.Lock() - entry.lastAccess = now i.mu.Unlock() } + // 创建新条目时的双重检查 + i.mu.Lock() + if entry, exists := i.ips[cleanIP]; exists { + entry.lastAccess = now + i.mu.Unlock() + return entry.limiter, true + } + + // 创建新条目 + entry = &rateLimiterEntry{ + limiter: rate.NewLimiter(i.r, i.b), + lastAccess: now, + } + i.ips[cleanIP] = entry + i.mu.Unlock() + return entry.limiter, true } diff --git a/src/search.go b/src/search.go index 4d7651d..ec622ab 100644 --- a/src/search.go +++ b/src/search.go @@ -114,22 +114,29 @@ func (c *Cache) Set(key string, data interface{}) { c.mu.Lock() defer c.mu.Unlock() - // 如果缓存已满,删除最旧的条目 - if len(c.data) >= c.maxSize { - oldest := time.Now() - var oldestKey string - for k, v := range c.data { - if v.timestamp.Before(oldest) { - oldest = v.timestamp - oldestKey = k - } + // ✅ 先清理过期项,防止内存泄漏 + now := time.Now() + for k, v := range c.data { + if now.Sub(v.timestamp) > cacheTTL { + delete(c.data, k) + } + } + + // 如果清理后仍然超限,批量删除最旧的条目 + if len(c.data) >= c.maxSize { + toDelete := len(c.data) / 4 // 删除25%最旧的 + for k := range c.data { + if toDelete <= 0 { + break + } + delete(c.data, k) + toDelete-- } - delete(c.data, oldestKey) } c.data[key] = cacheEntry{ data: data, - timestamp: time.Now(), + timestamp: now, } }