// Package invite runs asynchronous invite validity consumers. package invite import ( "context" "log" "time" invitedomain "hyapp/services/user-service/internal/domain/invite" ) // Repository applies wallet recharge events to user-service invite progress. type Repository interface { ApplyRechargeEvent(ctx context.Context, event invitedomain.RechargeEvent) (invitedomain.RechargeApplyResult, error) } // WalletOutboxReader scans wallet recharge facts in creation order. type WalletOutboxReader interface { ListRechargeEventsAfter(ctx context.Context, cursor invitedomain.WalletRechargeCursor, batchSize int) ([]invitedomain.RechargeEvent, error) } // Service owns the invite validity event consumer. type Service struct { repository Repository reader WalletOutboxReader } // WorkerOptions controls local wallet outbox polling. type WorkerOptions struct { WorkerID string PollInterval time.Duration BatchSize int } // New creates an invite service with persistence and wallet outbox reader dependencies. func New(repository Repository, reader WalletOutboxReader) *Service { return &Service{repository: repository, reader: reader} } // RunWalletRechargeWorker consumes WalletRechargeRecorded facts and advances valid_invite_count. func (s *Service) RunWalletRechargeWorker(ctx context.Context, options WorkerOptions) { if s == nil || s.repository == nil || s.reader == nil { log.Printf("component=user_invite op=wallet_recharge_worker status=disabled reason=missing_dependency") return } if options.PollInterval <= 0 { options.PollInterval = 5 * time.Second } if options.BatchSize <= 0 { options.BatchSize = 100 } var cursor invitedomain.WalletRechargeCursor for { processed, err := s.processBatch(ctx, &cursor, options.BatchSize) if err != nil { log.Printf("component=user_invite op=wallet_recharge_worker worker_id=%s status=failed error=%q", options.WorkerID, err.Error()) } if ctx.Err() != nil { return } if processed >= options.BatchSize { // 有积压时不 sleep,直到追上 wallet_outbox 当前末尾。 continue } select { case <-ctx.Done(): return case <-time.After(options.PollInterval): } } } func (s *Service) processBatch(ctx context.Context, cursor *invitedomain.WalletRechargeCursor, batchSize int) (int, error) { events, err := s.reader.ListRechargeEventsAfter(ctx, *cursor, batchSize) if err != nil { return 0, err } for _, event := range events { if _, err := s.repository.ApplyRechargeEvent(ctx, event); err != nil { return 0, err } cursor.CreatedAtMs = event.OccurredAtMs cursor.EventID = event.EventID } return len(events), nil }