package walletnotice import ( "context" "log/slog" "strings" "time" "hyapp/pkg/logx" ) // WalletNoticeWorkerOptions 是钱包余额私有通知 worker 的运行参数。 type WalletNoticeWorkerOptions struct { WorkerID string PollInterval time.Duration BatchSize int LockTTL time.Duration PublishTimeout time.Duration MaxRetryCount int InitialBackoff time.Duration MaxBackoff time.Duration } // RunWalletBalanceWorker 持续消费 wallet_outbox 的 WalletBalanceChanged 事件。 func (s *Service) RunWalletBalanceWorker(ctx context.Context, options WalletNoticeWorkerOptions) { options = normalizeWalletNoticeWorkerOptions(options, s.cfg.NodeID) ticker := time.NewTicker(options.PollInterval) defer ticker.Stop() for { processed, err := s.ProcessWalletBalanceNotices(ctx, options) if err != nil && ctx.Err() == nil { logx.Error(ctx, "notice_wallet_balance_batch_failed", err, slog.String("worker_id", options.WorkerID)) } if processed >= options.BatchSize { // 批次打满说明有积压,立即继续 drain,避免固定 tick 放大私有余额通知延迟。 continue } select { case <-ctx.Done(): return case <-ticker.C: } } } func normalizeWalletNoticeWorkerOptions(options WalletNoticeWorkerOptions, nodeID string) WalletNoticeWorkerOptions { if strings.TrimSpace(options.WorkerID) == "" { if strings.TrimSpace(nodeID) == "" { nodeID = "notice" } options.WorkerID = "wallet-notice-" + nodeID } if options.PollInterval <= 0 { options.PollInterval = time.Second } if options.BatchSize <= 0 { options.BatchSize = 100 } if options.LockTTL <= 0 { options.LockTTL = 30 * time.Second } if options.PublishTimeout <= 0 { options.PublishTimeout = 3 * time.Second } if options.MaxRetryCount <= 0 { options.MaxRetryCount = 10 } if options.InitialBackoff <= 0 { options.InitialBackoff = 5 * time.Second } if options.MaxBackoff <= 0 { options.MaxBackoff = 5 * time.Minute } return options } func walletNoticeBackoff(retryCount int, options WalletNoticeWorkerOptions) time.Duration { if retryCount <= 0 { return options.InitialBackoff } backoff := options.InitialBackoff for i := 1; i < retryCount; i++ { if backoff >= options.MaxBackoff/2 { return options.MaxBackoff } backoff *= 2 } if backoff > options.MaxBackoff { return options.MaxBackoff } return backoff }