From 18ae6df115fa89bb40f121ae83cd76ec69cb0cdb Mon Sep 17 00:00:00 2001 From: ShukeBta <272197458+ShukeBta@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:44:11 +0800 Subject: [PATCH] Deduplicate missing cloud config warnings --- internal/service/boot_cloud_health.go | 62 +++++++++++++++++++--- internal/service/boot_cloud_health_test.go | 60 +++++++++++++++++++++ 2 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 internal/service/boot_cloud_health_test.go diff --git a/internal/service/boot_cloud_health.go b/internal/service/boot_cloud_health.go index e50f5ef..221db7c 100644 --- a/internal/service/boot_cloud_health.go +++ b/internal/service/boot_cloud_health.go @@ -2,49 +2,58 @@ package service import ( "context" + "strings" "time" "go.uber.org/zap" ) +const cloudStorageMissingConfigWarnPrefix = "cloud.storage.missing_config_warned." + // BootCloudStorageHealthCheck验证所有已配置的云盘存储在启动时是否可用 func (c *Container) BootCloudStorageHealthCheck(ctx context.Context) { if c == nil || c.StorageCfg == nil { return } - + configs, err := c.StorageCfg.List(ctx) if err != nil { c.Log.Warn("boot: cloud storage health check failed to list configs", zap.Error(err)) return } - + cloudConfigs := make([]StorageView, 0) for _, cfg := range configs { if cfg.Enabled && (cfg.Type == "quark" || cfg.Type == "cloud115" || cfg.Type == "clouddrive2" || cfg.Type == "openlist") { cloudConfigs = append(cloudConfigs, cfg) } } - + if len(cloudConfigs) == 0 { c.Log.Info("boot: no enabled cloud storage configured") return } - + c.Log.Info("boot: checking cloud storage health", zap.Int("count", len(cloudConfigs))) - + for _, cfg := range cloudConfigs { go func(typ string) { checkCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - + provider, err := c.StorageCfg.CloudProvider(checkCtx, typ) if err != nil { + if c.warnMissingCloudStorageConfigOnce(checkCtx, typ, err) { + return + } c.Log.Warn("boot: cloud storage unavailable", zap.String("type", typ), zap.Error(err)) return } - + if err := provider.Ping(checkCtx); err != nil { + if c.warnMissingCloudStorageConfigOnce(checkCtx, typ, err) { + return + } c.Log.Warn("boot: cloud storage ping failed", zap.String("type", typ), zap.Error(err)) } else { c.Log.Info("boot: cloud storage healthy", zap.String("type", typ)) @@ -52,3 +61,42 @@ func (c *Container) BootCloudStorageHealthCheck(ctx context.Context) { }(cfg.Type) } } + +func (c *Container) warnMissingCloudStorageConfigOnce(ctx context.Context, typ string, err error) bool { + reason := cloudStorageMissingConfigReason(err) + if reason == "" { + return false + } + if c == nil || c.Repo == nil || c.Repo.Setting == nil { + if c != nil && c.Log != nil { + c.Log.Warn("boot: cloud storage config incomplete; skipping health check", zap.String("type", typ), zap.String("reason", reason), zap.Error(err)) + } + return true + } + key := cloudStorageMissingConfigWarnPrefix + strings.TrimSpace(typ) + "." + reason + if value, getErr := c.Repo.Setting.Get(ctx, key); getErr == nil && strings.EqualFold(strings.TrimSpace(value), "true") { + return true + } + if c.Log != nil { + c.Log.Warn("boot: cloud storage config incomplete; skipping health check", zap.String("type", typ), zap.String("reason", reason), zap.Error(err)) + } + if setErr := c.Repo.Setting.Set(ctx, key, "true"); setErr != nil && c.Log != nil { + c.Log.Debug("remember cloud storage config warning failed", zap.String("type", typ), zap.Error(setErr)) + } + return true +} + +func cloudStorageMissingConfigReason(err error) string { + if err == nil { + return "" + } + msg := strings.ToLower(strings.TrimSpace(err.Error())) + switch { + case strings.Contains(msg, "missing cookie"): + return "missing_cookie" + case strings.Contains(msg, "missing webdav url"): + return "missing_webdav_url" + default: + return "" + } +} diff --git a/internal/service/boot_cloud_health_test.go b/internal/service/boot_cloud_health_test.go new file mode 100644 index 0000000..eb7f806 --- /dev/null +++ b/internal/service/boot_cloud_health_test.go @@ -0,0 +1,60 @@ +package service + +import ( + "context" + "errors" + "testing" + + "github.com/glebarez/sqlite" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" + "gorm.io/gorm" + + "github.com/ShukeBta/MediaStationGo/internal/model" + "github.com/ShukeBta/MediaStationGo/internal/repository" +) + +func TestCloudStorageMissingConfigReason(t *testing.T) { + cases := []struct { + err error + want string + }{ + {errors.New("115: missing cookie"), "missing_cookie"}, + {errors.New("clouddrive2: missing WebDAV URL"), "missing_webdav_url"}, + {errors.New("quark: token expired"), ""}, + } + for _, tc := range cases { + if got := cloudStorageMissingConfigReason(tc.err); got != tc.want { + t.Fatalf("reason(%q) = %q, want %q", tc.err, got, tc.want) + } + } +} + +func TestWarnMissingCloudStorageConfigOncePersistsMarker(t *testing.T) { + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + if err != nil { + t.Fatal(err) + } + if err := db.AutoMigrate(&model.Setting{}); err != nil { + t.Fatal(err) + } + core, observed := observer.New(zap.WarnLevel) + c := &Container{ + Log: zap.New(core), + Repo: repository.New(db), + } + err = errors.New("115: missing cookie") + + if !c.warnMissingCloudStorageConfigOnce(context.Background(), "cloud115", err) { + t.Fatal("missing config should be handled") + } + if !c.warnMissingCloudStorageConfigOnce(context.Background(), "cloud115", err) { + t.Fatal("missing config should still be classified on second call") + } + if observed.FilterMessage("boot: cloud storage config incomplete; skipping health check").Len() != 1 { + t.Fatalf("warn count = %d, want 1", observed.Len()) + } + if c.warnMissingCloudStorageConfigOnce(context.Background(), "cloud115", errors.New("network timeout")) { + t.Fatal("non-missing config error should not be swallowed") + } +}