This commit is contained in:
zhx 2026-07-17 17:39:49 +08:00
parent 17f2788dcf
commit dc79adae36
4 changed files with 67 additions and 21 deletions

View File

@ -15,11 +15,11 @@ type DebitGiftCommand struct {
GiftCount int32
PriceVersion string
RegionID int64
// SenderRegionID 是送礼用户所属区域;用于按区域匹配礼物入主播周期钻石比例
// SenderRegionID 是送礼用户所属区域快照;保留给用户归属相关的账务审计和风控语义
SenderRegionID int64
// TargetIsHost 只能由 gateway 根据 user-service active host profile 注入,客户端输入不可信。
TargetIsHost bool
// TargetHostRegionID 是主播身份所属区域;工资政策按该区域匹配,不能用房间可见区域替代。
// TargetHostRegionID 是主播身份所属区域;主播周期钻石比例和工资政策按该区域匹配,不能用房间可见区域替代。
TargetHostRegionID int64
// TargetAgencyOwnerUserID 是送礼瞬间主播上级代理的收款用户快照;结算按快照发放,避免月底组织变化错账。
TargetAgencyOwnerUserID int64
@ -41,7 +41,7 @@ type DebitGiftTargetCommand struct {
TargetUserID int64
// TargetIsHost 只能由 gateway 注入,批量目标之间不能共享该身份快照。
TargetIsHost bool
// TargetHostRegionID 是该目标主播身份所属区域
// TargetHostRegionID 是该目标主播身份所属区域;批量结算必须按每个目标自己的区域解析周期钻石比例
TargetHostRegionID int64
// TargetAgencyOwnerUserID 是该目标主播当前代理 owner 收款快照。
TargetAgencyOwnerUserID int64

View File

@ -577,7 +577,7 @@ func TestDebitGiftAppliesGlobalDefaultGiftDiamondRatioToHeatValueByGiftType(t *t
}
}
func TestDebitGiftUsesRegionalReturnCoinRatioWithoutChangingHostDiamonds(t *testing.T) {
func TestDebitGiftUsesTargetHostRegionDiamondRatioAndRegionalReturnCoinRatio(t *testing.T) {
repository := mysqltest.NewRepository(t)
svc := walletservice.New(repository)
repository.SetBalance(14501, 500)
@ -602,8 +602,8 @@ func TestDebitGiftUsesRegionalReturnCoinRatioWithoutChangingHostDiamonds(t *test
if err != nil {
t.Fatalf("DebitGift regional return failed: %v", err)
}
if receipt.HeatValue != 100 || receipt.HostPeriodDiamondAdded != 100 {
t.Fatalf("return coin ratio must not change heat or host diamonds: %+v", receipt)
if receipt.HeatValue != 100 || receipt.HostPeriodDiamondAdded != 80 {
t.Fatalf("host region ratio must change host diamonds without changing global room contribution: %+v", receipt)
}
balances, err := svc.GetBalances(context.Background(), 14502, []string{ledger.AssetCoin})
if err != nil {
@ -698,6 +698,39 @@ func TestBatchDebitGiftAppliesGlobalDefaultGiftDiamondRatioToEachTargetHeatValue
}
}
func TestBatchDebitGiftUsesEachTargetHostRegionDiamondRatioWithGlobalFallback(t *testing.T) {
repository := mysqltest.NewRepository(t)
repository.SetBalance(15101, 1000)
repository.SetGiftPrice("batch-host-region-ratio-gift", "v1", 100, 100, 100)
repository.SetGiftType("batch-host-region-ratio-gift", resourcedomain.GiftTypeNormal)
repository.SetGiftDiamondRatio(7701, resourcedomain.GiftTypeNormal, "80.00")
svc := walletservice.New(repository)
receipt, err := svc.BatchDebitGift(context.Background(), ledger.BatchDebitGiftCommand{
CommandID: "cmd-batch-host-region-ratio",
RoomID: "room-host-region-ratio",
SenderUserID: 15101,
SenderRegionID: 1001,
RegionID: 9901,
GiftID: "batch-host-region-ratio-gift",
GiftCount: 1,
PriceVersion: "v1",
Targets: []ledger.DebitGiftTargetCommand{
{CommandID: "cmd-batch-host-region-ratio:target:15102", TargetUserID: 15102, TargetIsHost: true, TargetHostRegionID: 7701},
{CommandID: "cmd-batch-host-region-ratio:target:15103", TargetUserID: 15103, TargetIsHost: true, TargetHostRegionID: 7702},
},
})
if err != nil {
t.Fatalf("BatchDebitGift failed: %v", err)
}
if len(receipt.Targets) != 2 || receipt.Targets[0].Receipt.HostPeriodDiamondAdded != 80 || receipt.Targets[1].Receipt.HostPeriodDiamondAdded != 100 {
t.Fatalf("batch host region ratio or global fallback mismatch: %+v", receipt)
}
if receipt.Targets[0].Receipt.HeatValue != 100 || receipt.Targets[1].Receipt.HeatValue != 100 {
t.Fatalf("host region ratio must not change global room contribution: %+v", receipt)
}
}
// TestDebitGiftRejectsHostPeriodWithoutRegion 锁定工资政策必须有主播区域才能入账。
func TestDebitGiftRejectsHostPeriodWithoutRegion(t *testing.T) {
repository := mysqltest.NewRepository(t)

View File

@ -233,20 +233,28 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
events = append(events, resourceEvent)
}
anyHostTarget := false
for _, target := range command.Targets {
if target.TargetIsHost && !hostPointPolicies[target.CommandID].active {
anyHostTarget = true
break
}
type resolvedHostDiamondRatio struct {
ratio giftDiamondRatioSnapshot
effectiveRegionID int64
}
hostRatio := giftDiamondRatioSnapshot{Percent: "0.00", PPM: 0}
hostRatioRegionID := int64(0)
if anyHostTarget {
hostRatio, hostRatioRegionID, err = r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode)
// 批量送礼中的主播可能属于不同区域,必须逐区域解析周期钻石比例;同一区域只查一次,
// 解析器会在区域配置缺失时回退 region_id=0既避免 N 次重复查询,也不混用其他主播区域。
hostRatiosByRegion := make(map[int64]resolvedHostDiamondRatio)
for _, target := range command.Targets {
if !target.TargetIsHost || hostPointPolicies[target.CommandID].active {
continue
}
if _, exists := hostRatiosByRegion[target.TargetHostRegionID]; exists {
continue
}
ratio, effectiveRegionID, err := r.resolveGiftDiamondRatio(ctx, tx, command.AppCode, target.TargetHostRegionID, giftConfig.GiftTypeCode)
if err != nil {
return ledger.BatchGiftReceipt{}, err
}
hostRatiosByRegion[target.TargetHostRegionID] = resolvedHostDiamondRatio{
ratio: ratio,
effectiveRegionID: effectiveRegionID,
}
}
targetReceipts := make([]ledger.BatchGiftTargetReceipt, 0, len(command.Targets))
@ -257,13 +265,16 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
giftDiamondRatioPercent := "0.00"
giftDiamondRatioRegionID := int64(0)
if target.TargetIsHost && !hostPointPolicy.active {
hostPeriodDiamondAdded, err = giftDiamondAmount(chargeAmount, hostRatio.PPM)
resolvedRatio, exists := hostRatiosByRegion[target.TargetHostRegionID]
if !exists {
return ledger.BatchGiftReceipt{}, xerr.New(xerr.Internal, "host gift diamond ratio is missing")
}
hostPeriodDiamondAdded, err = giftDiamondAmount(chargeAmount, resolvedRatio.ratio.PPM)
if err != nil {
return ledger.BatchGiftReceipt{}, err
}
giftDiamondRatioPercent = hostRatio.Percent
giftDiamondRatioRegionID = hostRatioRegionID
giftDiamondRatioPercent = resolvedRatio.ratio.Percent
giftDiamondRatioRegionID = resolvedRatio.effectiveRegionID
hostPeriodCycleKey = hostPeriodCycleKeyFromMS(nowMs)
}

View File

@ -130,7 +130,9 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
giftDiamondRatioPercent := "0.00"
giftDiamondRatioRegionID := int64(0)
if command.TargetIsHost && !command.RobotGift && !hostPointPolicyActive {
ratio, ratioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode)
// 主播周期钻石按主播身份所属区域结算,而不是房间可见区域或送礼人区域;
// 区域未配置时由解析器回退 region_id=0保证历史全局配置继续生效。
ratio, ratioRegionID, err := r.resolveGiftDiamondRatio(ctx, tx, command.AppCode, command.TargetHostRegionID, giftConfig.GiftTypeCode)
if err != nil {
return ledger.Receipt{}, err
}