Files
junhong_cmp_fiber/internal/application/systemconfig/update.go
break b3499adfca 固化七月迭代审计治理进展以隔离线上热修
Constraint: 切换 main 前必须保存当前七月分支全部项目进展,套餐生效提案仅属于 Iteration/7-11。

Rejected: 将七月套餐修复直接移植到 main | 两个分支的可靠投递架构不同。

Confidence: medium

Scope-risk: broad

Directive: 不得将本提交整体 cherry-pick 到 main;main 套餐热修必须基于其纯 Asynq 代码独立实施。

Tested: git diff --check;openspec validate fix-package-activation-starvation --strict。

Not-tested: 按用户要求未运行自动化测试;go build ./... 因当前审计改造中的 Enterprise 模型字面量和 role.recordFailure 参数类型错误未通过。
2026-08-03 09:47:22 +08:00

205 lines
7.6 KiB
Go

// Package systemconfig 提供受控系统配置的简单写事务脚本。
package systemconfig
import (
"context"
"strconv"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
configinfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/systemconfig"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/auditfailure"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
)
// ChangeAudit 是系统配置更新交给统一审计 Port 的事实。
type ChangeAudit struct {
OperatorID uint
OperationType string
Description string
ConfigKey string
Module string
BeforeData map[string]any
AfterData map[string]any
RequestID string
CorrelationID string
Result string
ErrorCode string
ErrorSummary string
}
// AuditWriter 接收系统配置事务内审计事实。
type AuditWriter interface {
WriteConfigChange(ctx context.Context, tx *gorm.DB, audit ChangeAudit) error
}
// UpdateService 执行单 Key 校验、事务更新、审计和提交后缓存失效。
type UpdateService struct {
db *gorm.DB
registry *configinfra.Registry
cache configinfra.Cache
audit AuditWriter
alerts configinfra.AlertSink
now func() time.Time
}
// NewUpdateService 创建系统配置更新事务脚本。
func NewUpdateService(
db *gorm.DB,
registry *configinfra.Registry,
cache configinfra.Cache,
audit AuditWriter,
alerts configinfra.AlertSink,
now func() time.Time,
) *UpdateService {
if now == nil {
now = time.Now
}
return &UpdateService{db: db, registry: registry, cache: cache, audit: audit, alerts: alerts, now: now}
}
// Execute 更新一个已注册且非只读的配置 Key。
func (s *UpdateService) Execute(ctx context.Context, key string, request dto.UpdateSystemConfigRequest) (*dto.SystemConfigItem, error) {
if middleware.GetUserTypeFromContext(ctx) != constants.UserTypeSuperAdmin {
return nil, errors.New(errors.CodeForbidden)
}
operatorID := middleware.GetUserIDFromContext(ctx)
if operatorID == 0 || key == "" {
return nil, errors.New(errors.CodeInvalidParam)
}
if s.audit == nil {
return nil, errors.New(errors.CodeInvalidStatus, "系统配置审计接缝未配置")
}
definition, registered := s.registry.Get(key)
if !registered {
return nil, errors.New(errors.CodeInvalidParam, "系统配置 Key 未注册")
}
if definition.Readonly {
s.recordFailure(ctx, ChangeAudit{
OperatorID: operatorID, OperationType: constants.AuditOperationSystemConfigUpdate,
Description: "拒绝更新只读系统配置", ConfigKey: key, Module: definition.Module,
Result: constants.AuditResultDenied, ErrorCode: strconv.Itoa(errors.CodeInvalidStatus),
ErrorSummary: "系统配置为只读,更新请求已拒绝",
})
return nil, errors.New(errors.CodeInvalidStatus, "系统配置为只读,不能更新")
}
if err := configinfra.ValidateValue(definition, request.Value); err != nil {
s.recordFailure(ctx, ChangeAudit{
OperatorID: operatorID, OperationType: constants.AuditOperationSystemConfigUpdate,
Description: "拒绝非法系统配置值", ConfigKey: key, Module: definition.Module,
Result: constants.AuditResultDenied, ErrorCode: strconv.Itoa(errors.CodeInvalidParam),
ErrorSummary: "系统配置值不符合注册规则",
})
return nil, errors.New(errors.CodeInvalidParam, "系统配置值不符合注册规则")
}
now := s.now().UTC()
var saved model.SystemConfig
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 同一 Key 的首次创建和后续更新都由 PostgreSQL 事务级咨询锁串行裁决。
if err := tx.Exec("SELECT pg_advisory_xact_lock(hashtext(?))", key).Error; err != nil {
return err
}
var existing model.SystemConfig
findErr := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("config_key = ?", key).First(&existing).Error
beforeValue := definition.DefaultValue
if findErr == nil {
beforeValue = existing.ConfigValue
} else if findErr != gorm.ErrRecordNotFound {
return findErr
}
if findErr == gorm.ErrRecordNotFound {
existing = model.SystemConfig{
ConfigKey: key, ConfigValue: request.Value, ValueType: definition.ValueType,
Module: definition.Module, Description: definition.Description,
IsReadonly: definition.Readonly, IsSensitive: definition.Sensitive,
Creator: operatorID, Updater: operatorID, CreatedAt: now, UpdatedAt: now,
}
if err := tx.Create(&existing).Error; err != nil {
return err
}
} else {
if err := tx.Model(&model.SystemConfig{}).Where("id = ?", existing.ID).Updates(map[string]any{
"config_value": request.Value, "value_type": definition.ValueType,
"module": definition.Module, "description": definition.Description,
"is_readonly": definition.Readonly, "is_sensitive": definition.Sensitive,
"updater": operatorID, "updated_at": now,
}).Error; err != nil {
return err
}
existing.ConfigValue = request.Value
existing.Updater = operatorID
existing.UpdatedAt = now
}
requestID := ""
if value := middleware.GetRequestIDFromContext(ctx); value != nil {
requestID = *value
}
if err := s.audit.WriteConfigChange(ctx, tx, ChangeAudit{
OperatorID: operatorID, OperationType: constants.AuditOperationSystemConfigUpdate, Description: "更新受控系统配置",
ConfigKey: key, Module: definition.Module,
BeforeData: auditData(definition, beforeValue), AfterData: auditData(definition, request.Value),
RequestID: requestID, CorrelationID: requestID,
}); err != nil {
return err
}
saved = existing
return nil
})
if err != nil {
s.recordFailure(ctx, ChangeAudit{
OperatorID: operatorID, OperationType: constants.AuditOperationSystemConfigUpdate,
Description: "系统配置更新失败", ConfigKey: key, Module: definition.Module,
Result: constants.AuditResultFailed, ErrorCode: strconv.Itoa(errors.CodeDatabaseError),
ErrorSummary: "系统配置更新事务已回滚",
})
return nil, errors.Wrap(errors.CodeDatabaseError, err, "更新系统配置失败")
}
s.registry.Remember(key, request.Value)
if s.cache != nil {
if err := s.cache.Delete(ctx, constants.RedisSystemConfigKey(key)); err != nil {
if s.alerts != nil {
s.alerts.Warn(ctx, "SYSTEM_CONFIG_CACHE_INVALIDATE_FAILED", "system_config", key, "系统配置缓存失效失败,数据库事实已提交")
}
}
}
value := saved.ConfigValue
if definition.Sensitive && value != "" {
value = "[已配置]"
}
updatedAt := saved.UpdatedAt
return &dto.SystemConfigItem{
ConfigKey: key, Value: value, ValueType: definition.ValueType, Module: definition.Module,
Description: definition.Description, Readonly: definition.Readonly, Sensitive: definition.Sensitive,
Registered: true, Control: definition.Control, EnumValues: definition.EnumValues,
Min: definition.Min, Max: definition.Max, UpdatedAt: &updatedAt,
}, nil
}
func (s *UpdateService) recordFailure(ctx context.Context, audit ChangeAudit) {
if value := middleware.GetRequestIDFromContext(ctx); value != nil {
audit.RequestID = *value
audit.CorrelationID = *value
}
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
return s.audit.WriteConfigChange(ctx, tx, audit)
})
if err != nil {
auditfailure.RecordSecondaryWriteFailure(
audit.OperationType, audit.ConfigKey, audit.RequestID, audit.CorrelationID, audit.ErrorCode, err,
)
}
}
func auditData(definition configinfra.Definition, value string) map[string]any {
if definition.Sensitive {
return map[string]any{"credentials_configured": value != ""}
}
return map[string]any{"value": value}
}