155 lines
5.8 KiB
Go
155 lines
5.8 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||
"hyapp/pkg/appcode"
|
||
"hyapp/pkg/xerr"
|
||
"hyapp/services/room-service/internal/room/command"
|
||
)
|
||
|
||
type giftTargetBilling struct {
|
||
TargetUserID int64
|
||
CommandID string
|
||
Billing *walletv1.DebitGiftResponse
|
||
}
|
||
|
||
func (s *Service) debitGiftTargets(ctx context.Context, cmd command.SendGift, roomMeta RoomMeta) (*walletv1.DebitGiftResponse, []giftTargetBilling, error) {
|
||
if cmd.RobotWalletGift {
|
||
if len(cmd.TargetUserIDs) != 1 {
|
||
return nil, nil, xerr.New(xerr.InvalidArgument, "robot gift only supports one target")
|
||
}
|
||
billing, err := s.wallet.DebitRobotGift(ctx, &walletv1.DebitRobotGiftRequest{
|
||
CommandId: cmd.ID(),
|
||
RoomId: cmd.RoomID(),
|
||
SenderUserId: cmd.ActorUserID(),
|
||
TargetUserId: cmd.TargetUserID,
|
||
GiftId: cmd.GiftID,
|
||
GiftCount: cmd.GiftCount,
|
||
AppCode: appcode.FromContext(ctx),
|
||
RegionId: roomMeta.VisibleRegionID,
|
||
SenderRegionId: cmd.SenderRegionID,
|
||
})
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
return billing, []giftTargetBilling{{TargetUserID: cmd.TargetUserID, CommandID: cmd.ID(), Billing: billing}}, nil
|
||
}
|
||
if len(cmd.TargetUserIDs) == 1 {
|
||
targetScope := giftTargetHostScopeFor(cmd, cmd.TargetUserID)
|
||
// 单目标保留原 wallet command_id,避免改变已有幂等键、账务回执和排障路径。
|
||
billing, err := s.wallet.DebitGift(ctx, &walletv1.DebitGiftRequest{
|
||
CommandId: cmd.ID(),
|
||
RoomId: cmd.RoomID(),
|
||
SenderUserId: cmd.ActorUserID(),
|
||
TargetUserId: cmd.TargetUserID,
|
||
GiftId: cmd.GiftID,
|
||
GiftCount: cmd.GiftCount,
|
||
AppCode: appcode.FromContext(ctx),
|
||
RegionId: roomMeta.VisibleRegionID,
|
||
SenderRegionId: cmd.SenderRegionID,
|
||
TargetIsHost: targetScope.TargetIsHost,
|
||
TargetHostRegionId: targetScope.TargetHostRegionID,
|
||
TargetAgencyOwnerUserId: targetScope.TargetAgencyOwnerUserID,
|
||
EntitlementId: cmd.EntitlementID,
|
||
ChargeSource: cmd.Source,
|
||
})
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
return billing, []giftTargetBilling{{TargetUserID: cmd.TargetUserID, CommandID: cmd.ID(), Billing: billing}}, nil
|
||
}
|
||
|
||
targets := make([]*walletv1.DebitGiftTarget, 0, len(cmd.TargetUserIDs))
|
||
for _, targetUserID := range cmd.TargetUserIDs {
|
||
targetScope := giftTargetHostScopeFor(cmd, targetUserID)
|
||
target := &walletv1.DebitGiftTarget{
|
||
CommandId: giftTargetCommandID(cmd.ID(), targetUserID, len(cmd.TargetUserIDs)),
|
||
TargetUserId: targetUserID,
|
||
}
|
||
if targetScope.TargetIsHost {
|
||
// 每个目标只使用 gateway 为该 target 固化的主播快照,避免批量送礼时第一个接收方以外的主播丢工资钻石。
|
||
target.TargetIsHost = true
|
||
target.TargetHostRegionId = targetScope.TargetHostRegionID
|
||
target.TargetAgencyOwnerUserId = targetScope.TargetAgencyOwnerUserID
|
||
}
|
||
targets = append(targets, target)
|
||
}
|
||
resp, err := s.wallet.BatchDebitGift(ctx, &walletv1.BatchDebitGiftRequest{
|
||
CommandId: cmd.ID(),
|
||
RoomId: cmd.RoomID(),
|
||
SenderUserId: cmd.ActorUserID(),
|
||
GiftId: cmd.GiftID,
|
||
GiftCount: cmd.GiftCount,
|
||
AppCode: appcode.FromContext(ctx),
|
||
RegionId: roomMeta.VisibleRegionID,
|
||
SenderRegionId: cmd.SenderRegionID,
|
||
Targets: targets,
|
||
EntitlementId: cmd.EntitlementID,
|
||
ChargeSource: cmd.Source,
|
||
})
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
targetBillings, err := giftTargetBillingsFromBatch(resp, cmd.TargetUserIDs)
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
return resp.GetAggregate(), targetBillings, nil
|
||
}
|
||
|
||
func giftTargetHostScopeFor(cmd command.SendGift, targetUserID int64) command.GiftTargetHostScope {
|
||
for _, scope := range cmd.TargetHostScopes {
|
||
if scope.TargetUserID == targetUserID {
|
||
return scope
|
||
}
|
||
}
|
||
if targetUserID == cmd.TargetUserID && cmd.TargetIsHost {
|
||
// 老 gateway 只会传单目标字段;保留这个兜底,避免滚动发布期间新 room-service 丢旧请求的主播入账。
|
||
return command.GiftTargetHostScope{
|
||
TargetUserID: targetUserID,
|
||
TargetIsHost: true,
|
||
TargetHostRegionID: cmd.TargetHostRegionID,
|
||
TargetAgencyOwnerUserID: cmd.TargetAgencyOwnerUserID,
|
||
}
|
||
}
|
||
return command.GiftTargetHostScope{TargetUserID: targetUserID}
|
||
}
|
||
|
||
func giftTargetCommandID(commandID string, targetUserID int64, targetCount int) string {
|
||
if targetCount <= 1 {
|
||
return commandID
|
||
}
|
||
// 钱包以 command_id 做幂等主键;多目标必须按接收方派生稳定子命令,才能在同一批内保存独立交易。
|
||
return fmt.Sprintf("%s:target:%d", commandID, targetUserID)
|
||
}
|
||
|
||
func giftTargetBillingsFromBatch(resp *walletv1.BatchDebitGiftResponse, targetUserIDs []int64) ([]giftTargetBilling, error) {
|
||
if resp == nil || resp.GetAggregate() == nil {
|
||
return nil, xerr.New(xerr.Unavailable, "wallet batch debit response is empty")
|
||
}
|
||
receipts := make(map[int64]*walletv1.BatchDebitGiftReceipt, len(resp.GetReceipts()))
|
||
for _, receipt := range resp.GetReceipts() {
|
||
if receipt.GetTargetUserId() <= 0 || receipt.GetBilling() == nil {
|
||
return nil, xerr.New(xerr.Unavailable, "wallet batch debit receipt is incomplete")
|
||
}
|
||
receipts[receipt.GetTargetUserId()] = receipt
|
||
}
|
||
targetBillings := make([]giftTargetBilling, 0, len(targetUserIDs))
|
||
for _, targetUserID := range targetUserIDs {
|
||
receipt := receipts[targetUserID]
|
||
if receipt == nil {
|
||
// 批量钱包响应必须覆盖每个房间目标;少一条就不能提交房间表现和 outbox。
|
||
return nil, xerr.New(xerr.Unavailable, "wallet batch debit receipt is missing")
|
||
}
|
||
targetBillings = append(targetBillings, giftTargetBilling{
|
||
TargetUserID: targetUserID,
|
||
CommandID: receipt.GetCommandId(),
|
||
Billing: receipt.GetBilling(),
|
||
})
|
||
}
|
||
return targetBillings, nil
|
||
}
|