feat: add rocket level contents api

This commit is contained in:
hy001 2026-05-15 21:51:31 +08:00
parent 39744caf2e
commit 2c5c247f12
5 changed files with 188 additions and 9 deletions

View File

@ -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 {

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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"`