火箭份数配置

This commit is contained in:
hy001 2026-05-15 12:19:42 +08:00
parent 41c1d326ee
commit 019ffa9a4a
8 changed files with 141 additions and 35 deletions

View File

@ -52,6 +52,7 @@ type VoiceRoomRocketRewardConfig struct {
RewardCover string `gorm:"column:reward_cover;size:512"`
RewardAmount int64 `gorm:"column:reward_amount"`
ExpireDays *int `gorm:"column:expire_days"`
RewardCopies int `gorm:"column:reward_copies;default:1"`
Weight int `gorm:"column:weight"`
Sort int `gorm:"column:sort"`
Enabled bool `gorm:"column:enabled;index:idx_voice_room_rocket_reward_scene,priority:4"`

View File

@ -265,6 +265,12 @@ func (s *Service) SaveRewardConfigs(ctx context.Context, req SaveRewardConfigsRe
} else if amount <= 0 {
amount = 1
}
rewardCopies := item.RewardCopies
if scene != rewardSceneInRoom {
rewardCopies = 1
} else if rewardCopies <= 0 {
rewardCopies = 1
}
var row model.VoiceRoomRocketRewardConfig
if item.ID > 0 {
if err := tx.Where("id = ? AND sys_origin = ?", item.ID, sysOrigin).First(&row).Error; err != nil {
@ -289,6 +295,7 @@ func (s *Service) SaveRewardConfigs(ctx context.Context, req SaveRewardConfigsRe
row.RewardCover = strings.TrimSpace(item.RewardCover)
row.RewardAmount = amount
row.ExpireDays = item.ExpireDays
row.RewardCopies = rewardCopies
row.Weight = item.Weight
row.Sort = item.Sort
row.Enabled = item.Enabled

View File

@ -176,7 +176,7 @@ func TestInRoomRewardWorkerSnapshotsSelectsAndGrants(t *testing.T) {
seedLevelConfigs(t, db, []int64{100, 200, 300, 400, 500, 600})
seedRewardConfig(t, db, 1, rewardSceneTop1, rewardTypeGold, 10)
seedRewardConfig(t, db, 1, rewardSceneIgnite, rewardTypeGold, 5)
seedRewardConfig(t, db, 1, rewardSceneInRoom, rewardTypeGold, 1)
seedRewardConfigWithCopies(t, db, 1, rewardSceneInRoom, rewardTypeGold, 1, 3)
roomID := int64(9001)
for _, userID := range []int64{1001, 1002, 1003, 1004} {
@ -336,6 +336,38 @@ func TestPickInRoomRewardCanBeEmpty(t *testing.T) {
}
}
func TestInRoomRewardCopiesLimitSelection(t *testing.T) {
service, _ := newTestService(t)
service.cfg.VoiceRoomRocket.InRoomRewardUserLimit = 10
launch := model.VoiceRoomRocketLaunch{LaunchNo: "VRR-copies"}
audience := []roomAudienceUser{
{UserID: 1001},
{UserID: 1002},
{UserID: 1003},
{UserID: 1004},
}
rewards := []model.VoiceRoomRocketRewardConfig{
{
ID: 1,
RewardScene: rewardSceneInRoom,
RewardType: rewardTypeGold,
RewardName: "金币",
RewardAmount: 100,
RewardCopies: 2,
Weight: 1,
},
}
selected := service.selectInRoomRewardUsers(launch, audience, rewards)
if len(selected) != 2 {
t.Fatalf("selected users = %+v, want 2 users", selected)
}
picked := service.pickInRoomRewardsForUsers(launch, rewards, selected)
if len(picked) != 2 {
t.Fatalf("picked rewards = %+v, want 2 users with rewards", picked)
}
}
func TestNotifyLaunchPublishesIMAndRedisStream(t *testing.T) {
gateway := newFakeRocketGateway()
service, db, rdb, mr := newTestServiceWithRedis(t, gateway)
@ -457,10 +489,11 @@ func TestSaveRewardConfigsReplacesAndClearsSelectedLevel(t *testing.T) {
Rewards: []RewardConfigPayload{
{
Level: 1,
RewardScene: rewardSceneTop1,
RewardScene: rewardSceneInRoom,
RewardType: rewardTypeGold,
RewardName: "new gold",
RewardAmount: 99,
RewardCopies: 10,
Enabled: true,
Sort: 1,
},
@ -469,8 +502,8 @@ func TestSaveRewardConfigsReplacesAndClearsSelectedLevel(t *testing.T) {
if err != nil {
t.Fatalf("SaveRewardConfigs(replace) error = %v", err)
}
if len(saved) != 1 || saved[0].RewardAmount != 99 {
t.Fatalf("saved rewards = %+v, want one amount 99", saved)
if len(saved) != 1 || saved[0].RewardAmount != 99 || saved[0].RewardCopies != 10 {
t.Fatalf("saved rewards = %+v, want one amount 99 and 10 copies", saved)
}
var level1Count int64
@ -711,6 +744,10 @@ func seedLevelConfigs(t *testing.T, db *gorm.DB, energy []int64) {
}
func seedRewardConfig(t *testing.T, db *gorm.DB, level int, scene string, rewardType string, amount int64) {
seedRewardConfigWithCopies(t, db, level, scene, rewardType, amount, 1)
}
func seedRewardConfigWithCopies(t *testing.T, db *gorm.DB, level int, scene string, rewardType string, amount int64, copies int) {
t.Helper()
now := time.Now()
row := model.VoiceRoomRocketRewardConfig{
@ -721,6 +758,7 @@ func seedRewardConfig(t *testing.T, db *gorm.DB, level int, scene string, reward
RewardType: rewardType,
RewardName: scene + " reward",
RewardAmount: amount,
RewardCopies: copies,
Enabled: true,
CreateTime: now,
UpdateTime: now,

View File

@ -88,7 +88,11 @@ func (s *Service) processInRoomRewardLaunch(ctx context.Context, launchNo string
if err != nil {
return err
}
selected := s.selectInRoomRewardUsers(launch, audience)
rewards, err := s.loadInRoomRewardConfigs(ctx, launch.SysOrigin, launch.Level)
if err != nil {
return err
}
selected := s.selectInRoomRewardUsers(launch, audience, rewards)
if err := s.saveAudienceSnapshot(ctx, tx, launch, audience, selected, now); err != nil {
return err
}
@ -100,12 +104,9 @@ func (s *Service) processInRoomRewardLaunch(ctx context.Context, launchNo string
return nil
}
rewards, err := s.loadInRoomRewardConfigs(ctx, launch.SysOrigin, launch.Level)
if err != nil {
return err
}
pickedRewards := s.pickInRoomRewardsForUsers(launch, rewards, selected)
for _, userID := range selected {
for _, reward := range s.pickInRoomRewardsForUser(launch, rewards, userID) {
for _, reward := range pickedRewards[userID] {
record, err := s.createRewardRecord(ctx, tx, launch, reward, userID, now)
if err != nil {
return err
@ -201,11 +202,8 @@ func (s *Service) loadInRoomRewardConfigs(ctx context.Context, sysOrigin string,
return result, nil
}
func (s *Service) selectInRoomRewardUsers(launch model.VoiceRoomRocketLaunch, audience []roomAudienceUser) []int64 {
limit := s.cfg.VoiceRoomRocket.InRoomRewardUserLimit
if limit <= 0 {
limit = defaultInRoomRewardUserLimit
}
func (s *Service) selectInRoomRewardUsers(launch model.VoiceRoomRocketLaunch, audience []roomAudienceUser, rewards []model.VoiceRoomRocketRewardConfig) []int64 {
limit := s.inRoomRewardSelectionLimit(rewards)
if len(audience) == 0 {
return nil
}
@ -228,37 +226,80 @@ func (s *Service) selectInRoomRewardUsers(launch model.VoiceRoomRocketLaunch, au
return selected
}
func (s *Service) pickInRoomRewardsForUser(launch model.VoiceRoomRocketLaunch, rewards []model.VoiceRoomRocketRewardConfig, userID int64) []model.VoiceRoomRocketRewardConfig {
if len(rewards) == 0 {
return nil
func (s *Service) inRoomRewardSelectionLimit(rewards []model.VoiceRoomRocketRewardConfig) int {
totalCopies := 0
for _, reward := range rewards {
if !strings.EqualFold(strings.TrimSpace(reward.RewardScene), rewardSceneInRoom) {
continue
}
totalCopies += positiveOrDefault(reward.RewardCopies, 1)
}
weighted := make([]model.VoiceRoomRocketRewardConfig, 0, len(rewards))
totalWeight := 0
if totalCopies > 0 {
return totalCopies
}
limit := s.cfg.VoiceRoomRocket.InRoomRewardUserLimit
if limit <= 0 {
limit = defaultInRoomRewardUserLimit
}
return limit
}
func (s *Service) pickInRoomRewardsForUser(launch model.VoiceRoomRocketLaunch, rewards []model.VoiceRoomRocketRewardConfig, userID int64) []model.VoiceRoomRocketRewardConfig {
picked := s.pickInRoomRewardsForUsers(launch, rewards, []int64{userID})
return picked[userID]
}
func (s *Service) pickInRoomRewardsForUsers(launch model.VoiceRoomRocketLaunch, rewards []model.VoiceRoomRocketRewardConfig, userIDs []int64) map[int64][]model.VoiceRoomRocketRewardConfig {
result := make(map[int64][]model.VoiceRoomRocketRewardConfig, len(userIDs))
if len(rewards) == 0 {
return result
}
type rewardCandidate struct {
reward model.VoiceRoomRocketRewardConfig
remaining int
}
weighted := make([]rewardCandidate, 0, len(rewards))
for _, reward := range rewards {
weight := reward.Weight
if weight <= 0 {
weight = 1
}
reward.Weight = weight
weighted = append(weighted, reward)
totalWeight += weight
weighted = append(weighted, rewardCandidate{
reward: reward,
remaining: positiveOrDefault(reward.RewardCopies, 1),
})
}
if totalWeight <= 0 {
return nil
}
rng := rand.New(rand.NewSource(seedFromStrings(launch.LaunchNo, strconv.FormatInt(userID, 10), "reward")))
point := rng.Intn(totalWeight) + 1
acc := 0
for _, reward := range weighted {
acc += reward.Weight
if point <= acc {
if strings.EqualFold(strings.TrimSpace(reward.RewardType), rewardTypeNone) {
return nil
for _, userID := range userIDs {
totalWeight := 0
for _, candidate := range weighted {
if candidate.remaining > 0 {
totalWeight += candidate.reward.Weight
}
}
if totalWeight <= 0 {
continue
}
rng := rand.New(rand.NewSource(seedFromStrings(launch.LaunchNo, strconv.FormatInt(userID, 10), "reward")))
point := rng.Intn(totalWeight) + 1
acc := 0
for idx := range weighted {
if weighted[idx].remaining <= 0 {
continue
}
acc += weighted[idx].reward.Weight
if point <= acc {
weighted[idx].remaining--
if !strings.EqualFold(strings.TrimSpace(weighted[idx].reward.RewardType), rewardTypeNone) {
result[userID] = []model.VoiceRoomRocketRewardConfig{weighted[idx].reward}
}
break
}
return []model.VoiceRoomRocketRewardConfig{reward}
}
}
return nil
return result
}
func seedFromStrings(parts ...string) int64 {

View File

@ -160,6 +160,7 @@ func rewardPayload(row model.VoiceRoomRocketRewardConfig) RewardConfigPayload {
RewardCover: row.RewardCover,
RewardAmount: row.RewardAmount,
ExpireDays: row.ExpireDays,
RewardCopies: positiveOrDefault(row.RewardCopies, 1),
Weight: row.Weight,
Sort: row.Sort,
Enabled: row.Enabled,

View File

@ -333,6 +333,7 @@ type RewardConfigPayload struct {
RewardCover string `json:"rewardCover,omitempty"`
RewardAmount int64 `json:"rewardAmount"`
ExpireDays *int `json:"expireDays,omitempty"`
RewardCopies int `json:"rewardCopies"`
Weight int `json:"weight"`
Sort int `json:"sort"`
Enabled bool `json:"enabled"`

View File

@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS voice_room_rocket_reward_config (
reward_cover VARCHAR(512) DEFAULT NULL,
reward_amount BIGINT NOT NULL DEFAULT 1 COMMENT '金币数量或道具数量/天数',
expire_days INT DEFAULT NULL COMMENT '时效类奖励有效天数',
reward_copies INT NOT NULL DEFAULT 1 COMMENT 'IN_ROOM 本奖励每次发射可发放份数',
weight INT NOT NULL DEFAULT 0 COMMENT 'IN_ROOM 随机权重0 表示固定发放',
sort INT NOT NULL DEFAULT 0,
enabled TINYINT(1) NOT NULL DEFAULT 1,

View File

@ -0,0 +1,16 @@
SET @current_schema := DATABASE();
SET @add_voice_room_rocket_reward_copies_sql := IF(
NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = @current_schema
AND table_name = 'voice_room_rocket_reward_config'
AND column_name = 'reward_copies'
),
'ALTER TABLE `voice_room_rocket_reward_config` ADD COLUMN `reward_copies` int NOT NULL DEFAULT 1 COMMENT ''IN_ROOM 本奖励每次发射可发放份数'' AFTER `expire_days`',
'SELECT 1'
);
PREPARE add_voice_room_rocket_reward_copies_stmt FROM @add_voice_room_rocket_reward_copies_sql;
EXECUTE add_voice_room_rocket_reward_copies_stmt;
DEALLOCATE PREPARE add_voice_room_rocket_reward_copies_stmt;