// Package packagesilence 实现实名即生效套餐沉默期的设置、判定、扫描、总开关与延期用例(add-package-silence-period)。 package packagesilence import ( "context" "strconv" "sync" "time" "github.com/redis/go-redis/v9" "go.uber.org/zap" "gorm.io/gorm" "github.com/break/junhong_cmp_fiber/internal/infrastructure/systemconfig" "github.com/break/junhong_cmp_fiber/pkg/constants" ) // switchCacheTTL 是总开关在进程内的缓存时间;应急关闭最多延迟这么久在各进程生效。 const switchCacheTTL = 5 * time.Second // RegisterConfigDefinitions 注册沉默期相关系统配置。 // 总开关注册为只读:通用系统配置接口不能修改,只能走带打开前检查的专用接口。 func RegisterConfigDefinitions(registry *systemconfig.Registry, logger *zap.Logger) { if registry == nil { return } minDays, maxDays := int64(constants.SilencePackageDaysMin), int64(constants.SilencePackageDaysMax) minPrice, maxPrice := int64(0), int64(1000000) definitions := []systemconfig.Definition{ {Key: constants.SystemConfigSilenceEnabled, Module: constants.SystemConfigModulePackage, ValueType: constants.SystemConfigTypeBool, DefaultValue: "false", Readonly: true, Description: "沉默期总开关:关闭时只记录沉默期,不拦截激活、不扫描、不展示、延期申请拒绝;只能通过沉默期总开关接口切换"}, {Key: constants.SystemConfigSilenceDefaultDays, Module: constants.SystemConfigModulePackage, ValueType: constants.SystemConfigTypeInt, DefaultValue: strconv.Itoa(constants.SilenceDefaultPeriodDays), Description: "新建实名即生效套餐时预填的默认沉默期(天)", Min: &minDays, Max: &maxDays}, {Key: constants.SystemConfigSilenceExtensionUnitPrice, Module: constants.SystemConfigModulePackage, ValueType: constants.SystemConfigTypeInt, DefaultValue: strconv.Itoa(constants.SilenceDefaultUnitPrice), Description: "新建延期政策时预填的单价(分/月),实际计价以政策为准", Min: &minPrice, Max: &maxPrice}, } for _, definition := range definitions { if _, exists := registry.Get(definition.Key); exists { continue } if err := registry.Register(definition); err != nil && logger != nil { logger.Error("注册沉默期系统配置失败,将使用代码默认值", zap.String("config_key", definition.Key), zap.Error(err)) } } } // ConfigReader 读取系统配置的最小接缝。 type ConfigReader interface { Get(ctx context.Context, key string) (string, error) } // Switch 读取沉默期总开关 package.silence.enabled。 // 读取失败时按关闭处理(行为与现状一致),不因配置故障误拦截激活或退款。 type Switch struct { reader ConfigReader logger *zap.Logger mu sync.Mutex cached bool expiresAt time.Time } // NewSwitch 创建总开关读取器。 func NewSwitch(reader ConfigReader, logger *zap.Logger) *Switch { if logger == nil { logger = zap.NewNop() } return &Switch{reader: reader, logger: logger} } // NewDefaultSwitch 用独立注册表与读取器创建总开关(API 与 Worker 共用同一份数据库与缓存数据)。 func NewDefaultSwitch(db *gorm.DB, redisClient *redis.Client, logger *zap.Logger) *Switch { registry := systemconfig.NewRegistry() RegisterConfigDefinitions(registry, logger) var cache systemconfig.Cache if redisClient != nil { cache = systemconfig.NewRedisCache(redisClient) } reader := systemconfig.NewReader(db, registry, cache, systemconfig.NewLogAlertSink(logger)) return NewSwitch(reader, logger) } // Enabled 返回总开关是否打开。 func (s *Switch) Enabled(ctx context.Context) bool { if s == nil || s.reader == nil { return false } s.mu.Lock() defer s.mu.Unlock() now := time.Now() if now.Before(s.expiresAt) { return s.cached } value, err := s.reader.Get(ctx, constants.SystemConfigSilenceEnabled) if err != nil { s.logger.Warn("读取沉默期总开关失败,按关闭处理", zap.Error(err)) s.cached = false } else { s.cached = value == "true" } s.expiresAt = now.Add(switchCacheTTL) return s.cached } // Invalidate 清除进程内缓存(本进程切换开关后立即生效)。 func (s *Switch) Invalidate() { if s == nil { return } s.mu.Lock() s.expiresAt = time.Time{} s.mu.Unlock() }