From 2c5c247f12f8f687ec727c325345b323d4ef38e4 Mon Sep 17 00:00:00 2001 From: hy001 Date: Fri, 15 May 2026 21:51:31 +0800 Subject: [PATCH] feat: add rocket level contents api --- internal/router/voice_room_rocket_routes.go | 8 ++ internal/service/voiceroomrocket/config.go | 17 ++++ .../service/voiceroomrocket/config_test.go | 92 +++++++++++++++++++ internal/service/voiceroomrocket/mapper.go | 64 +++++++++++-- internal/service/voiceroomrocket/types.go | 16 ++++ 5 files changed, 188 insertions(+), 9 deletions(-) diff --git a/internal/router/voice_room_rocket_routes.go b/internal/router/voice_room_rocket_routes.go index 97b0fd5..32c54ad 100644 --- a/internal/router/voice_room_rocket_routes.go +++ b/internal/router/voice_room_rocket_routes.go @@ -32,6 +32,14 @@ func registerVoiceRoomRocketRoutes( } writeOK(c, resp) }) + appGroup.GET("/level-contents", func(c *gin.Context) { + resp, err := service.GetLevelContents(c.Request.Context(), mustAuthUser(c)) + if err != nil { + writeError(c, err) + return + } + writeOK(c, resp) + }) appGroup.GET("/status", func(c *gin.Context) { resp, err := service.GetStatus(c.Request.Context(), mustAuthUser(c), parseInt64(c.Query("roomId"))) if err != nil { diff --git a/internal/service/voiceroomrocket/config.go b/internal/service/voiceroomrocket/config.go index a71a064..5b7e369 100644 --- a/internal/service/voiceroomrocket/config.go +++ b/internal/service/voiceroomrocket/config.go @@ -19,6 +19,23 @@ func (s *Service) GetConfig(ctx context.Context, user AuthUser) (*ConfigResponse return s.getConfigResponse(ctx, user.SysOrigin, 0) } +// GetLevelContents 返回 App 查看高等级所需的全部等级资源和每级奖励内容。 +func (s *Service) GetLevelContents(ctx context.Context, user AuthUser) (*LevelContentsResponse, error) { + snapshot, err := s.loadConfig(ctx, user.SysOrigin) + if err != nil { + return nil, err + } + levels, err := s.loadLevelConfigs(ctx, snapshot.SysOrigin, true) + if err != nil { + return nil, err + } + rewards, err := s.loadRewardConfigs(ctx, snapshot.SysOrigin, 0) + if err != nil { + return nil, err + } + return levelContentsResponse(snapshot, levels, rewards), nil +} + // GetRule 返回火箭规则说明。 func (s *Service) GetRule(ctx context.Context, user AuthUser) (json.RawMessage, error) { snapshot, err := s.loadConfig(ctx, user.SysOrigin) diff --git a/internal/service/voiceroomrocket/config_test.go b/internal/service/voiceroomrocket/config_test.go index 597fd50..abd19b1 100644 --- a/internal/service/voiceroomrocket/config_test.go +++ b/internal/service/voiceroomrocket/config_test.go @@ -83,3 +83,95 @@ func TestGetStatusReturnsRocketResourcesToApp(t *testing.T) { t.Fatalf("status levels = %+v", status.Levels) } } + +func TestGetLevelContentsReturnsEveryLevelRewards(t *testing.T) { + service, _ := newTestService(t) + ctx := context.Background() + + _, err := service.SaveLevelConfigs(ctx, SaveLevelConfigsRequest{ + SysOrigin: "LIKEI", + Levels: []LevelConfigPayload{ + { + Level: 1, + NeedEnergy: 100, + ShakeThresholdPercent: "95.0000", + PreviewRocketURL: "https://cdn.example.com/lv1.png", + Enabled: true, + Sort: 1, + }, + { + Level: 2, + NeedEnergy: 200, + ShakeThresholdPercent: "95.0000", + PreviewRocketURL: "https://cdn.example.com/lv2.png", + Enabled: true, + Sort: 2, + }, + }, + }) + if err != nil { + t.Fatalf("SaveLevelConfigs error = %v", err) + } + + _, err = service.SaveRewardConfigs(ctx, SaveRewardConfigsRequest{ + SysOrigin: "LIKEI", + Rewards: []RewardConfigPayload{ + { + Level: 1, + RewardScene: rewardSceneTop1, + RewardType: rewardTypeGold, + RewardName: "Lv1 top1", + RewardAmount: 100, + Enabled: true, + Sort: 1, + }, + { + Level: 2, + RewardScene: rewardSceneIgnite, + RewardType: rewardTypeGold, + RewardName: "Lv2 ignite", + RewardAmount: 200, + Enabled: true, + Sort: 1, + }, + { + Level: 2, + RewardScene: rewardSceneInRoom, + RewardType: rewardTypeNone, + RewardName: "轮空", + Enabled: true, + Sort: 2, + }, + }, + }) + if err != nil { + t.Fatalf("SaveRewardConfigs error = %v", err) + } + + resp, err := service.GetLevelContents(ctx, AuthUser{SysOrigin: "LIKEI"}) + if err != nil { + t.Fatalf("GetLevelContents error = %v", err) + } + if len(resp.Levels) != 2 { + t.Fatalf("level count = %d, want 2", len(resp.Levels)) + } + levels := map[int]LevelContentPayload{} + for _, item := range resp.Levels { + levels[item.Level] = item + } + if levels[1].PreviewRocketURL != "https://cdn.example.com/lv1.png" { + t.Fatalf("level 1 previewRocketUrl = %q", levels[1].PreviewRocketURL) + } + if got := levels[1].RewardPreview.Top1; len(got) != 1 || got[0].RewardName != "Lv1 top1" { + t.Fatalf("level 1 top1 preview = %+v", got) + } + if got := levels[2].RewardPreview.Ignite; len(got) != 1 || got[0].RewardName != "Lv2 ignite" { + t.Fatalf("level 2 ignite preview = %+v", got) + } + if len(levels[2].Rewards) != 2 { + t.Fatalf("level 2 rewards = %+v", levels[2].Rewards) + } + if len(levels[2].RewardPreview.InRoom) != 0 { + t.Fatalf("NONE reward should not appear in preview: %+v", levels[2].RewardPreview.InRoom) + } +} diff --git a/internal/service/voiceroomrocket/mapper.go b/internal/service/voiceroomrocket/mapper.go index aec9892..28ff7ee 100644 --- a/internal/service/voiceroomrocket/mapper.go +++ b/internal/service/voiceroomrocket/mapper.go @@ -167,16 +167,17 @@ func rewardPayload(row model.VoiceRoomRocketRewardConfig) RewardConfigPayload { } } +func rewardPayloads(rows []model.VoiceRoomRocketRewardConfig) []RewardConfigPayload { + sorted := sortedRewardRows(rows) + result := make([]RewardConfigPayload, 0, len(sorted)) + for _, row := range sorted { + result = append(result, rewardPayload(row)) + } + return result +} + func rewardPreviewPayload(rows []model.VoiceRoomRocketRewardConfig) RewardPreviewResponse { - sort.Slice(rows, func(i, j int) bool { - if rows[i].RewardScene != rows[j].RewardScene { - return rewardSceneOrder(rows[i].RewardScene) < rewardSceneOrder(rows[j].RewardScene) - } - if rows[i].Sort != rows[j].Sort { - return rows[i].Sort < rows[j].Sort - } - return rows[i].ID < rows[j].ID - }) + rows = sortedRewardRows(rows) resp := RewardPreviewResponse{} for _, row := range rows { if strings.EqualFold(strings.TrimSpace(row.RewardType), rewardTypeNone) { @@ -195,6 +196,20 @@ func rewardPreviewPayload(rows []model.VoiceRoomRocketRewardConfig) RewardPrevie return resp } +func sortedRewardRows(rows []model.VoiceRoomRocketRewardConfig) []model.VoiceRoomRocketRewardConfig { + sorted := append([]model.VoiceRoomRocketRewardConfig(nil), rows...) + sort.Slice(sorted, func(i, j int) bool { + if sorted[i].RewardScene != sorted[j].RewardScene { + return rewardSceneOrder(sorted[i].RewardScene) < rewardSceneOrder(sorted[j].RewardScene) + } + if sorted[i].Sort != sorted[j].Sort { + return sorted[i].Sort < sorted[j].Sort + } + return sorted[i].ID < sorted[j].ID + }) + return sorted +} + func rewardSceneOrder(scene string) int { switch strings.ToUpper(strings.TrimSpace(scene)) { case rewardSceneTop1: @@ -224,6 +239,37 @@ func isSupportedRewardType(rewardType string) bool { } } +func levelContentsResponse(snapshot configSnapshot, levels []model.VoiceRoomRocketLevelConfig, rewards []model.VoiceRoomRocketRewardConfig) *LevelContentsResponse { + sort.Slice(levels, func(i, j int) bool { + if levels[i].Sort != levels[j].Sort { + return levels[i].Sort < levels[j].Sort + } + return levels[i].Level < levels[j].Level + }) + rewardsByLevel := make(map[int][]model.VoiceRoomRocketRewardConfig) + for _, row := range rewards { + rewardsByLevel[row.Level] = append(rewardsByLevel[row.Level], row) + } + levelPayloads := make([]LevelContentPayload, 0, len(levels)) + for _, row := range levels { + levelRewards := rewardsByLevel[row.Level] + levelPayloads = append(levelPayloads, LevelContentPayload{ + LevelConfigPayload: levelPayload(row), + Rewards: rewardPayloads(levelRewards), + RewardPreview: rewardPreviewPayload(levelRewards), + }) + } + return &LevelContentsResponse{ + Configured: snapshot.Configured, + SysOrigin: snapshot.SysOrigin, + Enabled: snapshot.Enabled, + Timezone: snapshot.Timezone, + MaxLevel: snapshot.MaxLevel, + Levels: levelPayloads, + UpdateTime: formatDateTime(snapshot.UpdateTime), + } +} + func configResponse(snapshot configSnapshot, levels []model.VoiceRoomRocketLevelConfig, rewards []model.VoiceRoomRocketRewardConfig) *ConfigResponse { levelPayloads := make([]LevelConfigPayload, 0, len(levels)) sort.Slice(levels, func(i, j int) bool { diff --git a/internal/service/voiceroomrocket/types.go b/internal/service/voiceroomrocket/types.go index 8f28767..65515a1 100644 --- a/internal/service/voiceroomrocket/types.go +++ b/internal/service/voiceroomrocket/types.go @@ -348,6 +348,22 @@ type RewardPreviewResponse struct { InRoom []RewardConfigPayload `json:"inRoom"` } +type LevelContentPayload struct { + LevelConfigPayload + Rewards []RewardConfigPayload `json:"rewards"` + RewardPreview RewardPreviewResponse `json:"rewardPreview"` +} + +type LevelContentsResponse struct { + Configured bool `json:"configured"` + SysOrigin string `json:"sysOrigin"` + Enabled bool `json:"enabled"` + Timezone string `json:"timezone"` + MaxLevel int `json:"maxLevel"` + Levels []LevelContentPayload `json:"levels"` + UpdateTime string `json:"updateTime,omitempty"` +} + type ConfigResponse struct { Configured bool `json:"configured"` ID int64 `json:"id,string,omitempty"`