package systemconfig import ( "context" stderrors "errors" "sort" "github.com/redis/go-redis/v9" "go.uber.org/zap" "gorm.io/gorm" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/pkg/constants" ) // AlertSink 接收配置读取和缓存故障的中文安全告警。 type AlertSink interface { Warn(ctx context.Context, code, component, safeID, summary string) } // LogAlertSink 使用应用日志承接安全告警。 type LogAlertSink struct { logger *zap.Logger } // NewLogAlertSink 创建系统配置日志告警 Adapter。 func NewLogAlertSink(logger *zap.Logger) *LogAlertSink { if logger == nil { logger = zap.NewNop() } return &LogAlertSink{logger: logger} } // Warn 记录不包含配置值的中文安全告警。 func (s *LogAlertSink) Warn(_ context.Context, code, component, safeID, summary string) { s.logger.Warn(summary, zap.String("error_code", code), zap.String("component", component), zap.String("safe_id", safeID)) } // Reader 提供以 PostgreSQL 为唯一事实来源的缓存读取能力。 type Reader struct { db *gorm.DB registry *Registry cache Cache alerts AlertSink } func (r *Reader) warn(ctx context.Context, code, key, summary string) { if r.alerts != nil { r.alerts.Warn(ctx, code, "system_config", key, summary) } } // NewReader 创建系统配置读取器。 func NewReader(db *gorm.DB, registry *Registry, cache Cache, alerts AlertSink) *Reader { return &Reader{db: db, registry: registry, cache: cache, alerts: alerts} } // Get 读取单个已注册配置;Redis 故障时回退 PostgreSQL。 func (r *Reader) Get(ctx context.Context, key string) (string, error) { definition, registered := r.registry.Get(key) if !registered { return "", stderrors.New("系统配置 Key 未注册") } cacheKey := constants.RedisSystemConfigKey(key) if r.cache != nil { if value, err := r.cache.Get(ctx, cacheKey); err == nil { if ValidateValue(definition, value) == nil { r.registry.Remember(key, value) return value, nil } r.warn(ctx, "SYSTEM_CONFIG_CACHE_INVALID", key, "系统配置缓存值非法,已回退 PostgreSQL") } else if err != redis.Nil { r.warn(ctx, "SYSTEM_CONFIG_CACHE_READ_FAILED", key, "系统配置缓存读取失败,已回退 PostgreSQL") } } var record model.SystemConfig err := r.db.WithContext(ctx).Where("config_key = ?", key).First(&record).Error if err == gorm.ErrRecordNotFound { return definition.DefaultValue, nil } if err != nil { return "", err } value := record.ConfigValue if ValidateValue(definition, value) != nil { value = r.registry.LastValidatedOrDefault(definition) r.warn(ctx, "SYSTEM_CONFIG_DATABASE_VALUE_INVALID", key, "数据库配置值非法,已使用最近验证值或安全默认值") } else { r.registry.Remember(key, value) } if r.cache != nil { if err := r.cache.Set(ctx, cacheKey, value, constants.SystemConfigCacheTTL); err != nil { r.warn(ctx, "SYSTEM_CONFIG_CACHE_WRITE_FAILED", key, "系统配置缓存回填失败") } } return value, nil } // ListItem 是查询层组装 DTO 所需的稳定投影。 type ListItem struct { Definition Definition Record *model.SystemConfig Value string Registered bool } // List 合并代码注册表和数据库遗留记录;未注册记录强制只读。 func (r *Reader) List(ctx context.Context, module string) ([]ListItem, error) { var records []model.SystemConfig query := r.db.WithContext(ctx).Order("config_key ASC") if module != "" { query = query.Where("module = ?", module) } if err := query.Find(&records).Error; err != nil { return nil, err } byKey := make(map[string]*model.SystemConfig, len(records)) for index := range records { byKey[records[index].ConfigKey] = &records[index] } items := make([]ListItem, 0, len(records)+len(r.registry.List())) for _, definition := range r.registry.List() { if module != "" && definition.Module != module { continue } record := byKey[definition.Key] value := definition.DefaultValue if record != nil { value = record.ConfigValue if ValidateValue(definition, value) != nil { value = r.registry.LastValidatedOrDefault(definition) r.warn(ctx, "SYSTEM_CONFIG_DATABASE_VALUE_INVALID", definition.Key, "数据库配置值非法,列表已使用安全值") } else { r.registry.Remember(definition.Key, value) } delete(byKey, definition.Key) } items = append(items, ListItem{Definition: definition, Record: record, Value: value, Registered: true}) } for _, record := range byKey { definition := Definition{ Key: record.ConfigKey, Module: record.Module, ValueType: record.ValueType, Description: record.Description, Readonly: true, Sensitive: record.IsSensitive, Control: "readonly", } items = append(items, ListItem{Definition: definition, Record: record, Value: record.ConfigValue, Registered: false}) } sort.Slice(items, func(i, j int) bool { return items[i].Definition.Key < items[j].Definition.Key }) return items, nil }