All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
150 lines
4.9 KiB
Go
150 lines
4.9 KiB
Go
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
|
||
}
|