Files
junhong_cmp_fiber/internal/infrastructure/systemconfig/reader.go
break 17782d5f8e
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
实现七月迭代公共技术基础
2026-07-23 17:52:48 +09:00

150 lines
4.9 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 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
}