package cardobservation import ( "context" stderrors "errors" "math" "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" ) // trafficQueueSettingsTTL 是队列配置在进程内的缓存时长;后台修改数秒内生效。 const trafficQueueSettingsTTL = 5 * time.Second // TrafficQueueSettings 是卡流量采集队列的运行参数。 type TrafficQueueSettings struct { Size int ClearOnNewDay bool MaxPointAgeHours int DropToleranceMB float64 } // DefaultTrafficQueueSettings 返回代码默认值。 func DefaultTrafficQueueSettings() TrafficQueueSettings { return TrafficQueueSettings{ Size: constants.TrafficQueueDefaultSize, ClearOnNewDay: true, MaxPointAgeHours: constants.TrafficQueueDefaultMaxPointAgeHours, DropToleranceMB: constants.TrafficQueueDefaultDropToleranceMB, } } // TrafficIntervalChecker 校验队列配置变更后启用中轮询配置的流量采集间隔是否仍在上限内。 type TrafficIntervalChecker func(settings TrafficQueueSettings) error // RegisterTrafficQueueConfigDefinitions 注册采集队列系统配置。checker 非空时,队列长度、跨天清空、最长点龄 // 修改前用它校验启用中的轮询配置(读取其余三项的当前值组合成修改后的配置)。 func RegisterTrafficQueueConfigDefinitions(registry *systemconfig.Registry, logger *zap.Logger, current func() TrafficQueueSettings, checker TrafficIntervalChecker) { if registry == nil { return } minSize, maxSize := int64(constants.TrafficQueueMinSize), int64(constants.TrafficQueueMaxSize) minAge, maxAge := int64(0), int64(24*30) withCheck := func(apply func(*TrafficQueueSettings, string) error) func(string) error { return func(value string) error { next := DefaultTrafficQueueSettings() if current != nil { next = current() } if err := apply(&next, value); err != nil { return err } if checker == nil { return nil } return checker(next) } } definitions := []systemconfig.Definition{ {Key: constants.SystemConfigTrafficQueueSize, Module: constants.SystemConfigModuleTraffic, ValueType: constants.SystemConfigTypeInt, DefaultValue: strconv.Itoa(constants.TrafficQueueDefaultSize), Min: &minSize, Max: &maxSize, Description: "卡流量采集队列长度 N:攒满 N 个不下降的读数才写最后一个点", Validator: withCheck(func(s *TrafficQueueSettings, v string) error { n, err := strconv.Atoi(v) s.Size = n return err })}, {Key: constants.SystemConfigTrafficQueueClearOnNewDay, Module: constants.SystemConfigModuleTraffic, ValueType: constants.SystemConfigTypeBool, DefaultValue: "true", Description: "卡流量采集队列跨天清空:新点与队列中的点不在同一上海日期时先清空", Validator: withCheck(func(s *TrafficQueueSettings, v string) error { s.ClearOnNewDay = v == "true" return nil })}, {Key: constants.SystemConfigTrafficQueueMaxPointAgeHours, Module: constants.SystemConfigModuleTraffic, ValueType: constants.SystemConfigTypeInt, DefaultValue: strconv.Itoa(constants.TrafficQueueDefaultMaxPointAgeHours), Min: &minAge, Max: &maxAge, Description: "卡流量采集队列最长点龄(小时):入队时丢弃与新点相差超过该时长的点,0 表示不限", Validator: withCheck(func(s *TrafficQueueSettings, v string) error { n, err := strconv.Atoi(v) s.MaxPointAgeHours = n return err })}, {Key: constants.SystemConfigTrafficQueueDropToleranceMB, Module: constants.SystemConfigModuleTraffic, ValueType: constants.SystemConfigTypeString, DefaultValue: "1", Description: "卡流量采集队列回退容差(MB):相邻两点回退不超过该值算持平,0 表示不允许回退", Validator: func(v string) error { _, err := parseDropTolerance(v) return err }}, } 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)) } } } func parseDropTolerance(value string) (float64, error) { tolerance, err := strconv.ParseFloat(value, 64) if err != nil || math.IsNaN(tolerance) || math.IsInf(tolerance, 0) || tolerance < 0 || tolerance > 1024 { return 0, stderrors.New("回退容差必须是 0~1024 之间的数字(MB)") } return tolerance, nil } // trafficConfigReader 读取系统配置的最小接缝。 type trafficConfigReader interface { Get(ctx context.Context, key string) (string, error) } // TrafficQueueSettingsProvider 读取采集队列配置,带短缓存;读取失败时使用代码默认值。 type TrafficQueueSettingsProvider struct { reader trafficConfigReader logger *zap.Logger mu sync.Mutex cached TrafficQueueSettings expiresAt time.Time } // NewTrafficQueueSettingsProvider 创建采集队列配置读取器。 func NewTrafficQueueSettingsProvider(reader trafficConfigReader, logger *zap.Logger) *TrafficQueueSettingsProvider { if logger == nil { logger = zap.NewNop() } return &TrafficQueueSettingsProvider{reader: reader, logger: logger} } // NewDefaultTrafficQueueSettingsProvider 用独立注册表与读取器创建采集队列配置读取器(API 与 Worker 共用同一份数据)。 func NewDefaultTrafficQueueSettingsProvider(db *gorm.DB, redisClient *redis.Client, logger *zap.Logger) *TrafficQueueSettingsProvider { registry := systemconfig.NewRegistry() RegisterTrafficQueueConfigDefinitions(registry, logger, nil, nil) var cache systemconfig.Cache if redisClient != nil { cache = systemconfig.NewRedisCache(redisClient) } reader := systemconfig.NewReader(db, registry, cache, systemconfig.NewLogAlertSink(logger)) return NewTrafficQueueSettingsProvider(reader, logger) } // Current 返回当前采集队列配置。 func (p *TrafficQueueSettingsProvider) Current(ctx context.Context) TrafficQueueSettings { if p == nil || p.reader == nil { return DefaultTrafficQueueSettings() } p.mu.Lock() defer p.mu.Unlock() now := time.Now() if now.Before(p.expiresAt) { return p.cached } settings, err := p.load(ctx) if err != nil { p.logger.Warn("读取卡流量采集队列配置失败,使用代码默认值", zap.Error(err)) settings = DefaultTrafficQueueSettings() } p.cached, p.expiresAt = settings, now.Add(trafficQueueSettingsTTL) return settings } func (p *TrafficQueueSettingsProvider) load(ctx context.Context) (TrafficQueueSettings, error) { settings := DefaultTrafficQueueSettings() raw, err := p.reader.Get(ctx, constants.SystemConfigTrafficQueueSize) if err != nil { return settings, err } if settings.Size, err = strconv.Atoi(raw); err != nil || settings.Size < constants.TrafficQueueMinSize { return DefaultTrafficQueueSettings(), stderrors.New("采集队列长度配置非法") } if raw, err = p.reader.Get(ctx, constants.SystemConfigTrafficQueueClearOnNewDay); err != nil { return DefaultTrafficQueueSettings(), err } settings.ClearOnNewDay = raw == "true" if raw, err = p.reader.Get(ctx, constants.SystemConfigTrafficQueueMaxPointAgeHours); err != nil { return DefaultTrafficQueueSettings(), err } if settings.MaxPointAgeHours, err = strconv.Atoi(raw); err != nil || settings.MaxPointAgeHours < 0 { return DefaultTrafficQueueSettings(), stderrors.New("采集队列最长点龄配置非法") } if raw, err = p.reader.Get(ctx, constants.SystemConfigTrafficQueueDropToleranceMB); err != nil { return DefaultTrafficQueueSettings(), err } if settings.DropToleranceMB, err = parseDropTolerance(raw); err != nil { return DefaultTrafficQueueSettings(), err } return settings, nil }