package gift import ( "context" kratoserrors "github.com/go-kratos/kratos/v3/errors" "sort" "strings" "time" ) var ( ErrNotFound = kratoserrors.NotFound("GIFT_NOT_FOUND", "gift not found") ErrNameExists = kratoserrors.BadRequest("GIFT_NAME_EXISTS", "gift name already exists") ErrInvalid = kratoserrors.BadRequest("GIFT_INVALID", "invalid gift data") ErrLastEnabled = kratoserrors.BadRequest("GIFT_LAST_ENABLED", "at least one enabled gift is required") ErrInsufficientBalance = kratoserrors.BadRequest("GIFT_INSUFFICIENT_BALANCE", "insufficient diamond balance") ErrSendNotAllowed = kratoserrors.BadRequest("GIFT_SEND_NOT_ALLOWED", "gift cannot be sent") ) type Gift struct { ID uint Name, Icon, Description string Price int64 Tiers []int Sort int Enabled bool SendCount int64 CreatedAt, UpdatedAt time.Time } type Send struct { ID uint SerialNo string UserID uint Username, Nickname string RoomID *uint RoomName string RoomDeleted bool AnchorUserID uint AnchorName string GiftID *uint GiftName, GiftIcon string GiftPrice int64 Quantity int DiamondTotal, RateCents, EquivalentCents int64 CreatedAt time.Time } type Filter struct { Name string MinPrice, MaxPrice int64 Enabled *bool } type SendFilter struct { RoomName, AnchorName, UserKeyword string GiftID uint CreatedAt []time.Time } type ListOptions struct { Offset, Limit int OrderBy string } type Summary struct{ Count, Diamonds, AmountCents int64 } type Repo interface { List(context.Context, Filter, ListOptions) ([]*Gift, int64, error) Get(context.Context, uint) (*Gift, error) Save(context.Context, *Gift) (*Gift, error) Delete(context.Context, uint) error SetEnabled(context.Context, uint, bool) error SaveSort(context.Context, map[uint]int) error ListSends(context.Context, SendFilter, ListOptions) ([]*Send, int64, *Summary, error) Send(context.Context, uint, uint, uint, int) (*Send, error) } type Notifier interface { GiftSent(context.Context, *Send) } type Usecase struct { repo Repo notifier Notifier } func NewUsecase(repo Repo, notifier Notifier) *Usecase { return &Usecase{repo: repo, notifier: notifier} } func (uc *Usecase) List(ctx context.Context, f Filter, o ListOptions) ([]*Gift, int64, error) { f.Name = strings.TrimSpace(f.Name) return uc.repo.List(ctx, f, normalize(o)) } func (uc *Usecase) Get(ctx context.Context, id uint) (*Gift, error) { if id == 0 { return nil, ErrNotFound } return uc.repo.Get(ctx, id) } func (uc *Usecase) Save(ctx context.Context, item *Gift) (*Gift, error) { if err := validate(item); err != nil { return nil, err } return uc.repo.Save(ctx, item) } func (uc *Usecase) Delete(ctx context.Context, id uint) error { return uc.repo.Delete(ctx, id) } func (uc *Usecase) SetEnabled(ctx context.Context, id uint, enabled bool) error { return uc.repo.SetEnabled(ctx, id, enabled) } func (uc *Usecase) SaveSort(ctx context.Context, values map[uint]int) error { for id, v := range values { if id == 0 || v < 0 || v > 9999 { return ErrInvalid } } return uc.repo.SaveSort(ctx, values) } func (uc *Usecase) ListSends(ctx context.Context, f SendFilter, o ListOptions) ([]*Send, int64, *Summary, error) { return uc.repo.ListSends(ctx, f, normalize(o)) } func (uc *Usecase) Send(ctx context.Context, userID, roomID, giftID uint, quantity int) (*Send, error) { if userID == 0 || roomID == 0 || giftID == 0 || quantity < 1 || quantity > 9999 { return nil, ErrSendNotAllowed } result, err := uc.repo.Send(ctx, userID, roomID, giftID, quantity) if err != nil { return nil, err } if uc.notifier != nil { uc.notifier.GiftSent(ctx, result) } return result, nil } func normalize(o ListOptions) ListOptions { if o.Offset < 0 { o.Offset = 0 } if o.Limit < 1 || o.Limit > 100 { o.Limit = 10 } return o } func validate(item *Gift) error { if item == nil { return ErrInvalid } item.Name = strings.TrimSpace(item.Name) item.Icon = strings.TrimSpace(item.Icon) item.Description = strings.TrimSpace(item.Description) if len([]rune(item.Name)) < 1 || len([]rune(item.Name)) > 12 || item.Icon == "" || item.Price < 1 || item.Price > 99999 || len([]rune(item.Description)) > 50 || item.Sort < 0 || item.Sort > 9999 || len(item.Tiers) < 1 || len(item.Tiers) > 6 { return ErrInvalid } seen := map[int]struct{}{} for _, v := range item.Tiers { if v < 1 || v > 9999 { return ErrInvalid } if _, ok := seen[v]; ok { return ErrInvalid } seen[v] = struct{}{} } sort.Ints(item.Tiers) return nil }