火箭份数配置
This commit is contained in:
parent
41c1d326ee
commit
019ffa9a4a
@ -52,6 +52,7 @@ type VoiceRoomRocketRewardConfig struct {
|
|||||||
RewardCover string `gorm:"column:reward_cover;size:512"`
|
RewardCover string `gorm:"column:reward_cover;size:512"`
|
||||||
RewardAmount int64 `gorm:"column:reward_amount"`
|
RewardAmount int64 `gorm:"column:reward_amount"`
|
||||||
ExpireDays *int `gorm:"column:expire_days"`
|
ExpireDays *int `gorm:"column:expire_days"`
|
||||||
|
RewardCopies int `gorm:"column:reward_copies;default:1"`
|
||||||
Weight int `gorm:"column:weight"`
|
Weight int `gorm:"column:weight"`
|
||||||
Sort int `gorm:"column:sort"`
|
Sort int `gorm:"column:sort"`
|
||||||
Enabled bool `gorm:"column:enabled;index:idx_voice_room_rocket_reward_scene,priority:4"`
|
Enabled bool `gorm:"column:enabled;index:idx_voice_room_rocket_reward_scene,priority:4"`
|
||||||
|
|||||||
@ -265,6 +265,12 @@ func (s *Service) SaveRewardConfigs(ctx context.Context, req SaveRewardConfigsRe
|
|||||||
} else if amount <= 0 {
|
} else if amount <= 0 {
|
||||||
amount = 1
|
amount = 1
|
||||||
}
|
}
|
||||||
|
rewardCopies := item.RewardCopies
|
||||||
|
if scene != rewardSceneInRoom {
|
||||||
|
rewardCopies = 1
|
||||||
|
} else if rewardCopies <= 0 {
|
||||||
|
rewardCopies = 1
|
||||||
|
}
|
||||||
var row model.VoiceRoomRocketRewardConfig
|
var row model.VoiceRoomRocketRewardConfig
|
||||||
if item.ID > 0 {
|
if item.ID > 0 {
|
||||||
if err := tx.Where("id = ? AND sys_origin = ?", item.ID, sysOrigin).First(&row).Error; err != nil {
|
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.RewardCover = strings.TrimSpace(item.RewardCover)
|
||||||
row.RewardAmount = amount
|
row.RewardAmount = amount
|
||||||
row.ExpireDays = item.ExpireDays
|
row.ExpireDays = item.ExpireDays
|
||||||
|
row.RewardCopies = rewardCopies
|
||||||
row.Weight = item.Weight
|
row.Weight = item.Weight
|
||||||
row.Sort = item.Sort
|
row.Sort = item.Sort
|
||||||
row.Enabled = item.Enabled
|
row.Enabled = item.Enabled
|
||||||
|
|||||||
@ -176,7 +176,7 @@ func TestInRoomRewardWorkerSnapshotsSelectsAndGrants(t *testing.T) {
|
|||||||
seedLevelConfigs(t, db, []int64{100, 200, 300, 400, 500, 600})
|
seedLevelConfigs(t, db, []int64{100, 200, 300, 400, 500, 600})
|
||||||
seedRewardConfig(t, db, 1, rewardSceneTop1, rewardTypeGold, 10)
|
seedRewardConfig(t, db, 1, rewardSceneTop1, rewardTypeGold, 10)
|
||||||
seedRewardConfig(t, db, 1, rewardSceneIgnite, rewardTypeGold, 5)
|
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)
|
roomID := int64(9001)
|
||||||
for _, userID := range []int64{1001, 1002, 1003, 1004} {
|
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) {
|
func TestNotifyLaunchPublishesIMAndRedisStream(t *testing.T) {
|
||||||
gateway := newFakeRocketGateway()
|
gateway := newFakeRocketGateway()
|
||||||
service, db, rdb, mr := newTestServiceWithRedis(t, gateway)
|
service, db, rdb, mr := newTestServiceWithRedis(t, gateway)
|
||||||
@ -457,10 +489,11 @@ func TestSaveRewardConfigsReplacesAndClearsSelectedLevel(t *testing.T) {
|
|||||||
Rewards: []RewardConfigPayload{
|
Rewards: []RewardConfigPayload{
|
||||||
{
|
{
|
||||||
Level: 1,
|
Level: 1,
|
||||||
RewardScene: rewardSceneTop1,
|
RewardScene: rewardSceneInRoom,
|
||||||
RewardType: rewardTypeGold,
|
RewardType: rewardTypeGold,
|
||||||
RewardName: "new gold",
|
RewardName: "new gold",
|
||||||
RewardAmount: 99,
|
RewardAmount: 99,
|
||||||
|
RewardCopies: 10,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Sort: 1,
|
Sort: 1,
|
||||||
},
|
},
|
||||||
@ -469,8 +502,8 @@ func TestSaveRewardConfigsReplacesAndClearsSelectedLevel(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("SaveRewardConfigs(replace) error = %v", err)
|
t.Fatalf("SaveRewardConfigs(replace) error = %v", err)
|
||||||
}
|
}
|
||||||
if len(saved) != 1 || saved[0].RewardAmount != 99 {
|
if len(saved) != 1 || saved[0].RewardAmount != 99 || saved[0].RewardCopies != 10 {
|
||||||
t.Fatalf("saved rewards = %+v, want one amount 99", saved)
|
t.Fatalf("saved rewards = %+v, want one amount 99 and 10 copies", saved)
|
||||||
}
|
}
|
||||||
|
|
||||||
var level1Count int64
|
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) {
|
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()
|
t.Helper()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
row := model.VoiceRoomRocketRewardConfig{
|
row := model.VoiceRoomRocketRewardConfig{
|
||||||
@ -721,6 +758,7 @@ func seedRewardConfig(t *testing.T, db *gorm.DB, level int, scene string, reward
|
|||||||
RewardType: rewardType,
|
RewardType: rewardType,
|
||||||
RewardName: scene + " reward",
|
RewardName: scene + " reward",
|
||||||
RewardAmount: amount,
|
RewardAmount: amount,
|
||||||
|
RewardCopies: copies,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
CreateTime: now,
|
CreateTime: now,
|
||||||
UpdateTime: now,
|
UpdateTime: now,
|
||||||
|
|||||||
@ -88,7 +88,11 @@ func (s *Service) processInRoomRewardLaunch(ctx context.Context, launchNo string
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err := s.saveAudienceSnapshot(ctx, tx, launch, audience, selected, now); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -100,12 +104,9 @@ func (s *Service) processInRoomRewardLaunch(ctx context.Context, launchNo string
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rewards, err := s.loadInRoomRewardConfigs(ctx, launch.SysOrigin, launch.Level)
|
pickedRewards := s.pickInRoomRewardsForUsers(launch, rewards, selected)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, userID := range 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)
|
record, err := s.createRewardRecord(ctx, tx, launch, reward, userID, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -201,11 +202,8 @@ func (s *Service) loadInRoomRewardConfigs(ctx context.Context, sysOrigin string,
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) selectInRoomRewardUsers(launch model.VoiceRoomRocketLaunch, audience []roomAudienceUser) []int64 {
|
func (s *Service) selectInRoomRewardUsers(launch model.VoiceRoomRocketLaunch, audience []roomAudienceUser, rewards []model.VoiceRoomRocketRewardConfig) []int64 {
|
||||||
limit := s.cfg.VoiceRoomRocket.InRoomRewardUserLimit
|
limit := s.inRoomRewardSelectionLimit(rewards)
|
||||||
if limit <= 0 {
|
|
||||||
limit = defaultInRoomRewardUserLimit
|
|
||||||
}
|
|
||||||
if len(audience) == 0 {
|
if len(audience) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -228,37 +226,80 @@ func (s *Service) selectInRoomRewardUsers(launch model.VoiceRoomRocketLaunch, au
|
|||||||
return selected
|
return selected
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) pickInRoomRewardsForUser(launch model.VoiceRoomRocketLaunch, rewards []model.VoiceRoomRocketRewardConfig, userID int64) []model.VoiceRoomRocketRewardConfig {
|
func (s *Service) inRoomRewardSelectionLimit(rewards []model.VoiceRoomRocketRewardConfig) int {
|
||||||
if len(rewards) == 0 {
|
totalCopies := 0
|
||||||
return nil
|
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))
|
if totalCopies > 0 {
|
||||||
totalWeight := 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 {
|
for _, reward := range rewards {
|
||||||
weight := reward.Weight
|
weight := reward.Weight
|
||||||
if weight <= 0 {
|
if weight <= 0 {
|
||||||
weight = 1
|
weight = 1
|
||||||
}
|
}
|
||||||
reward.Weight = weight
|
reward.Weight = weight
|
||||||
weighted = append(weighted, reward)
|
weighted = append(weighted, rewardCandidate{
|
||||||
totalWeight += weight
|
reward: reward,
|
||||||
|
remaining: positiveOrDefault(reward.RewardCopies, 1),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if totalWeight <= 0 {
|
|
||||||
return nil
|
for _, userID := range userIDs {
|
||||||
}
|
totalWeight := 0
|
||||||
rng := rand.New(rand.NewSource(seedFromStrings(launch.LaunchNo, strconv.FormatInt(userID, 10), "reward")))
|
for _, candidate := range weighted {
|
||||||
point := rng.Intn(totalWeight) + 1
|
if candidate.remaining > 0 {
|
||||||
acc := 0
|
totalWeight += candidate.reward.Weight
|
||||||
for _, reward := range weighted {
|
}
|
||||||
acc += reward.Weight
|
}
|
||||||
if point <= acc {
|
if totalWeight <= 0 {
|
||||||
if strings.EqualFold(strings.TrimSpace(reward.RewardType), rewardTypeNone) {
|
continue
|
||||||
return nil
|
}
|
||||||
|
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 {
|
func seedFromStrings(parts ...string) int64 {
|
||||||
|
|||||||
@ -160,6 +160,7 @@ func rewardPayload(row model.VoiceRoomRocketRewardConfig) RewardConfigPayload {
|
|||||||
RewardCover: row.RewardCover,
|
RewardCover: row.RewardCover,
|
||||||
RewardAmount: row.RewardAmount,
|
RewardAmount: row.RewardAmount,
|
||||||
ExpireDays: row.ExpireDays,
|
ExpireDays: row.ExpireDays,
|
||||||
|
RewardCopies: positiveOrDefault(row.RewardCopies, 1),
|
||||||
Weight: row.Weight,
|
Weight: row.Weight,
|
||||||
Sort: row.Sort,
|
Sort: row.Sort,
|
||||||
Enabled: row.Enabled,
|
Enabled: row.Enabled,
|
||||||
|
|||||||
@ -333,6 +333,7 @@ type RewardConfigPayload struct {
|
|||||||
RewardCover string `json:"rewardCover,omitempty"`
|
RewardCover string `json:"rewardCover,omitempty"`
|
||||||
RewardAmount int64 `json:"rewardAmount"`
|
RewardAmount int64 `json:"rewardAmount"`
|
||||||
ExpireDays *int `json:"expireDays,omitempty"`
|
ExpireDays *int `json:"expireDays,omitempty"`
|
||||||
|
RewardCopies int `json:"rewardCopies"`
|
||||||
Weight int `json:"weight"`
|
Weight int `json:"weight"`
|
||||||
Sort int `json:"sort"`
|
Sort int `json:"sort"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
|||||||
@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS voice_room_rocket_reward_config (
|
|||||||
reward_cover VARCHAR(512) DEFAULT NULL,
|
reward_cover VARCHAR(512) DEFAULT NULL,
|
||||||
reward_amount BIGINT NOT NULL DEFAULT 1 COMMENT '金币数量或道具数量/天数',
|
reward_amount BIGINT NOT NULL DEFAULT 1 COMMENT '金币数量或道具数量/天数',
|
||||||
expire_days INT DEFAULT NULL 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 表示固定发放',
|
weight INT NOT NULL DEFAULT 0 COMMENT 'IN_ROOM 随机权重;0 表示固定发放',
|
||||||
sort INT NOT NULL DEFAULT 0,
|
sort INT NOT NULL DEFAULT 0,
|
||||||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
|||||||
16
migrations/038_voice_room_rocket_reward_copies.sql
Normal file
16
migrations/038_voice_room_rocket_reward_copies.sql
Normal 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;
|
||||||
Loading…
x
Reference in New Issue
Block a user