This commit is contained in:
Yvan
2026-09-23 17:42:55 +08:00
parent 53e8599bdd
commit 0a8ecbe09b
75 changed files with 2299 additions and 276 deletions
+40 -3
View File
@@ -2,10 +2,14 @@ package gift
import (
"context"
kratoserrors "github.com/go-kratos/kratos/v3/errors"
"fmt"
"sort"
"strings"
"time"
kratoserrors "github.com/go-kratos/kratos/v3/errors"
bizlive "kra/internal/biz/live"
)
var (
@@ -17,6 +21,24 @@ var (
ErrSendNotAllowed = kratoserrors.BadRequest("GIFT_SEND_NOT_ALLOWED", "gift cannot be sent")
)
// InsufficientBalanceError preserves the wallet snapshot needed by clients
// to decide whether to open the recharge flow. It unwraps to the stable
// ErrInsufficientBalance reason so existing errors.Is checks keep working.
type InsufficientBalanceError struct {
Required int64
Balance int64
Missing int64
}
func (e *InsufficientBalanceError) Error() string {
if e == nil {
return ErrInsufficientBalance.Error()
}
return fmt.Sprintf("insufficient diamond balance: need %d, have %d, missing %d", e.Required, e.Balance, e.Missing)
}
func (e *InsufficientBalanceError) Unwrap() error { return ErrInsufficientBalance }
type Gift struct {
ID uint
Name, Icon, Description string
@@ -72,13 +94,21 @@ type Repo interface {
type Notifier interface {
GiftSent(context.Context, *Send)
}
type roomAvailability interface {
IsLive(context.Context, uint) (bool, error)
}
type Usecase struct {
repo Repo
notifier Notifier
rooms roomAvailability
}
func NewUsecase(repo Repo, notifier Notifier) *Usecase {
return &Usecase{repo: repo, notifier: notifier}
func NewUsecase(repo Repo, notifier Notifier, rooms *bizlive.RoomUsecase) *Usecase {
return newUsecase(repo, notifier, rooms)
}
func newUsecase(repo Repo, notifier Notifier, rooms roomAvailability) *Usecase {
return &Usecase{repo: repo, notifier: notifier, rooms: rooms}
}
func (uc *Usecase) List(ctx context.Context, f Filter, o ListOptions) ([]*Gift, int64, error) {
f.Name = strings.TrimSpace(f.Name)
@@ -115,6 +145,13 @@ func (uc *Usecase) Send(ctx context.Context, userID, roomID, giftID uint, quanti
if userID == 0 || roomID == 0 || giftID == 0 || quantity < 1 || quantity > 9999 {
return nil, ErrSendNotAllowed
}
if uc.rooms == nil {
return nil, ErrSendNotAllowed
}
live, err := uc.rooms.IsLive(ctx, roomID)
if err != nil || !live {
return nil, ErrSendNotAllowed
}
result, err := uc.repo.Send(ctx, userID, roomID, giftID, quantity)
if err != nil {
return nil, err
+49
View File
@@ -0,0 +1,49 @@
package gift
import (
"context"
"errors"
"testing"
)
type sendRepoStub struct {
Repo
called bool
}
func (r *sendRepoStub) Send(_ context.Context, userID, roomID, giftID uint, quantity int) (*Send, error) {
r.called = true
return &Send{UserID: userID, RoomID: &roomID, GiftID: &giftID, Quantity: quantity}, nil
}
type availabilityStub struct {
live bool
err error
}
func (s availabilityStub) IsLive(context.Context, uint) (bool, error) { return s.live, s.err }
func TestSendRejectsOfflineRoomBeforeWalletMutation(t *testing.T) {
repo := &sendRepoStub{}
uc := newUsecase(repo, nil, availabilityStub{live: false})
if _, err := uc.Send(context.Background(), 1, 7, 3, 2); !errors.Is(err, ErrSendNotAllowed) {
t.Fatalf("offline send error = %v", err)
}
if repo.called {
t.Fatal("offline room reached gift repository")
}
}
func TestSendAcceptsCustomQuantityForLiveRoom(t *testing.T) {
repo := &sendRepoStub{}
uc := newUsecase(repo, nil, availabilityStub{live: true})
item, err := uc.Send(context.Background(), 1, 7, 3, 37)
if err != nil {
t.Fatal(err)
}
if !repo.called || item.Quantity != 37 {
t.Fatalf("custom quantity result = %+v called=%v", item, repo.called)
}
}