package luckygift import ( "context" "strings" "time" "hyapp/pkg/appcode" "hyapp/pkg/xerr" domain "hyapp/services/activity-service/internal/domain/luckygift" ) // Repository 是幸运礼物唯一持久化边界;RTP、奖池、风控计数必须在同一个 MySQL 事务内修改。 type Repository interface { GetLuckyGiftConfig(ctx context.Context, poolID string) (domain.Config, bool, error) UpsertLuckyGiftConfig(ctx context.Context, config domain.Config, nowMS int64) (domain.Config, error) ListLuckyGiftConfigs(ctx context.Context) ([]domain.Config, error) CheckLuckyGift(ctx context.Context, cmd domain.CheckCommand) (domain.CheckResult, error) ExecuteLuckyGiftDraw(ctx context.Context, cmd domain.DrawCommand, nowMS int64) (domain.DrawResult, error) ListLuckyGiftDraws(ctx context.Context, query domain.DrawQuery) ([]domain.DrawResult, int64, error) GetLuckyGiftDrawSummary(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error) } // Service 承载幸运礼物查询、抽奖命令和后台规则配置。 type Service struct { repository Repository now func() time.Time } func New(repository Repository) *Service { return &Service{repository: repository, now: time.Now} } func (s *Service) SetClock(now func() time.Time) { if now != nil { s.now = now } } func (s *Service) Check(ctx context.Context, cmd domain.CheckCommand) (domain.CheckResult, error) { if err := s.requireRepository(); err != nil { return domain.CheckResult{}, err } cmd.PoolID = normalizePoolID(cmd.PoolID) cmd.GiftID = strings.TrimSpace(cmd.GiftID) cmd.RoomID = strings.TrimSpace(cmd.RoomID) if cmd.GiftID == "" || cmd.UserID <= 0 || cmd.RoomID == "" { return domain.CheckResult{}, xerr.New(xerr.InvalidArgument, "lucky gift check command is incomplete") } return s.repository.CheckLuckyGift(ctx, cmd) } func (s *Service) Draw(ctx context.Context, cmd domain.DrawCommand) (domain.DrawResult, error) { if err := s.requireRepository(); err != nil { return domain.DrawResult{}, err } cmd.CommandID = strings.TrimSpace(cmd.CommandID) cmd.PoolID = normalizePoolID(cmd.PoolID) cmd.GiftID = strings.TrimSpace(cmd.GiftID) cmd.DeviceID = strings.TrimSpace(cmd.DeviceID) cmd.RoomID = strings.TrimSpace(cmd.RoomID) cmd.AnchorID = strings.TrimSpace(cmd.AnchorID) if cmd.CommandID == "" || cmd.UserID <= 0 || cmd.DeviceID == "" || cmd.RoomID == "" || cmd.AnchorID == "" || cmd.GiftID == "" || cmd.CoinSpent <= 0 { return domain.DrawResult{}, xerr.New(xerr.InvalidArgument, "lucky gift draw command is incomplete") } if cmd.PaidAtMS <= 0 { cmd.PaidAtMS = s.now().UTC().UnixMilli() } return s.repository.ExecuteLuckyGiftDraw(ctx, cmd, s.now().UTC().UnixMilli()) } func (s *Service) GetConfig(ctx context.Context, poolID string) (domain.Config, error) { if err := s.requireRepository(); err != nil { return domain.Config{}, err } poolID = normalizePoolID(poolID) config, exists, err := s.repository.GetLuckyGiftConfig(ctx, poolID) if err != nil { return domain.Config{}, err } if !exists { return DefaultConfig(appcode.FromContext(ctx), poolID), nil } return config, nil } func (s *Service) UpsertConfig(ctx context.Context, config domain.Config) (domain.Config, error) { if err := s.requireRepository(); err != nil { return domain.Config{}, err } config.AppCode = appcode.FromContext(ctx) config.PoolID = normalizePoolID(config.PoolID) config.GiftID = config.PoolID config.Tiers, config.MultiplierPPMs = normalizeTiers(config.Tiers, config.MultiplierPPMs, config.GiftPrice, config.HighMultiplier) if err := validateConfig(config); err != nil { return domain.Config{}, err } return s.repository.UpsertLuckyGiftConfig(ctx, config, s.now().UTC().UnixMilli()) } func (s *Service) ListConfigs(ctx context.Context) ([]domain.Config, error) { if err := s.requireRepository(); err != nil { return nil, err } return s.repository.ListLuckyGiftConfigs(ctx) } func (s *Service) ListDraws(ctx context.Context, query domain.DrawQuery) ([]domain.DrawResult, int64, error) { if err := s.requireRepository(); err != nil { return nil, 0, err } query = normalizeDrawQuery(query) if query.Page <= 0 { query.Page = 1 } if query.PageSize <= 0 { query.PageSize = 20 } if query.PageSize > 100 { query.PageSize = 100 } return s.repository.ListLuckyGiftDraws(ctx, query) } func (s *Service) GetDrawSummary(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error) { if err := s.requireRepository(); err != nil { return domain.DrawSummary{}, err } return s.repository.GetLuckyGiftDrawSummary(ctx, normalizeDrawQuery(query)) } func (s *Service) requireRepository() error { if s == nil || s.repository == nil { return xerr.New(xerr.Unavailable, "lucky gift repository is not configured") } return nil } func normalizeDrawQuery(query domain.DrawQuery) domain.DrawQuery { query.PoolID = normalizePoolID(query.PoolID) query.GiftID = strings.TrimSpace(query.GiftID) query.RoomID = strings.TrimSpace(query.RoomID) query.Status = strings.TrimSpace(query.Status) return query }