Files
junhong_cmp_fiber/internal/infrastructure/systemconfig/reader.go
break 73f5125d3d
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
2026-07-25 17:06:58 +08:00

188 lines
6.5 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
}
// GetStrict 严格读取单个已注册配置;已落库的非法值或数据库读取失败不会回退默认值。
// 该入口用于支付方式等必须失败关闭的关键配置,未落库时仍返回代码注册的首次初始化默认值。
func (r *Reader) GetStrict(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 {
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
}
if ValidateValue(definition, record.ConfigValue) != nil {
r.warn(ctx, "SYSTEM_CONFIG_DATABASE_VALUE_INVALID", key, "关键系统配置数据库值非法,已失败关闭")
return "", stderrors.New("系统配置数据库值非法")
}
if r.cache != nil {
if err := r.cache.Set(ctx, cacheKey, record.ConfigValue, constants.SystemConfigCacheTTL); err != nil {
r.warn(ctx, "SYSTEM_CONFIG_CACHE_WRITE_FAILED", key, "关键系统配置缓存回填失败")
}
}
return record.ConfigValue, 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
}