From 3ed3f8583787aa58d24aae458b59e0192ed8e01b Mon Sep 17 00:00:00 2001 From: zhx Date: Mon, 8 Jun 2026 19:22:58 +0800 Subject: [PATCH] fix: enrich weekly star reward groups --- .../http/activityapi/weekly_star_handler.go | 49 +++++- .../internal/transport/http/response_test.go | 163 ++++++++++++++++++ 2 files changed, 205 insertions(+), 7 deletions(-) diff --git a/services/gateway-service/internal/transport/http/activityapi/weekly_star_handler.go b/services/gateway-service/internal/transport/http/activityapi/weekly_star_handler.go index febb39a2..46ba97d1 100644 --- a/services/gateway-service/internal/transport/http/activityapi/weekly_star_handler.go +++ b/services/gateway-service/internal/transport/http/activityapi/weekly_star_handler.go @@ -32,8 +32,9 @@ type weeklyStarGiftData struct { } type weeklyStarRewardData struct { - RankNo int32 `json:"rank_no"` - ResourceGroupID int64 `json:"resource_group_id"` + RankNo int32 `json:"rank_no"` + ResourceGroupID int64 `json:"resource_group_id"` + ResourceGroup *achievementResourceGroupData `json:"resource_group,omitempty"` } type weeklyStarEntryData struct { @@ -98,8 +99,12 @@ func (h *Handler) getWeeklyStarCurrent(writer http.ResponseWriter, request *http return } users := h.resolveWeeklyStarUsers(request, weeklyStarUserIDs(resp.GetTopEntries(), resp.GetMyEntry())) + resourceGroups, ok := h.resolveWeeklyStarResourceGroups(writer, request, resp.GetCycle()) + if !ok { + return + } data := weeklyStarCurrentData{ - Cycle: weeklyStarCycleFromProto(resp.GetCycle()), + Cycle: weeklyStarCycleFromProto(resp.GetCycle(), resourceGroups), TopEntries: weeklyStarEntriesFromProto(resp.GetTopEntries(), users), ServerTimeMS: resp.GetServerTimeMs(), } @@ -133,8 +138,12 @@ func (h *Handler) listWeeklyStarLeaderboard(writer http.ResponseWriter, request return } users := h.resolveWeeklyStarUsers(request, weeklyStarUserIDs(resp.GetEntries(), nil)) + resourceGroups, ok := h.resolveWeeklyStarResourceGroups(writer, request, resp.GetCycle()) + if !ok { + return + } httpkit.WriteOK(writer, request, weeklyStarLeaderboardData{ - Cycle: weeklyStarCycleFromProto(resp.GetCycle()), + Cycle: weeklyStarCycleFromProto(resp.GetCycle(), resourceGroups), Entries: weeklyStarEntriesFromProto(resp.GetEntries(), users), NextPageToken: resp.GetNextPageToken(), Total: resp.GetTotal(), @@ -162,14 +171,20 @@ func (h *Handler) listWeeklyStarHistory(writer http.ResponseWriter, request *htt return } ids := make([]int64, 0) + cycleItems := make([]*activityv1.WeeklyStarCycle, 0, len(resp.GetCycles())) for _, cycle := range resp.GetCycles() { + cycleItems = append(cycleItems, cycle.GetCycle()) ids = append(ids, weeklyStarUserIDs(cycle.GetTopEntries(), nil)...) } users := h.resolveWeeklyStarUsers(request, ids) + resourceGroups, ok := h.resolveWeeklyStarResourceGroups(writer, request, cycleItems...) + if !ok { + return + } cycles := make([]weeklyStarHistoryCycleData, 0, len(resp.GetCycles())) for _, item := range resp.GetCycles() { cycles = append(cycles, weeklyStarHistoryCycleData{ - Cycle: weeklyStarCycleFromProto(item.GetCycle()), + Cycle: weeklyStarCycleFromProto(item.GetCycle(), resourceGroups), TopEntries: weeklyStarEntriesFromProto(item.GetTopEntries(), users), }) } @@ -213,7 +228,13 @@ func (h *Handler) resolveWeeklyStarUsers(request *http.Request, ids []int64) map return users } -func weeklyStarCycleFromProto(item *activityv1.WeeklyStarCycle) *weeklyStarCycleData { +func (h *Handler) resolveWeeklyStarResourceGroups(writer http.ResponseWriter, request *http.Request, cycles ...*activityv1.WeeklyStarCycle) (map[int64]achievementResourceGroupData, bool) { + // 周星周期只存奖励资源组 ID,资源名称、类型、素材和数量仍由 wallet-service 资源库统一维护。 + // gateway 在 App 出口补齐这些展示字段,避免 activity-service 复制资源库快照,也让后台改资源组后 App 读取保持一致。 + return h.resolveAchievementResourceGroups(writer, request, weeklyStarResourceGroupIDs(cycles...)) +} + +func weeklyStarCycleFromProto(item *activityv1.WeeklyStarCycle, resourceGroups map[int64]achievementResourceGroupData) *weeklyStarCycleData { if item == nil { return nil } @@ -235,11 +256,25 @@ func weeklyStarCycleFromProto(item *activityv1.WeeklyStarCycle) *weeklyStarCycle cycle.Gifts = append(cycle.Gifts, weeklyStarGiftData{GiftID: gift.GetGiftId(), SortOrder: gift.GetSortOrder()}) } for _, reward := range item.GetRewards() { - cycle.Rewards = append(cycle.Rewards, weeklyStarRewardData{RankNo: reward.GetRankNo(), ResourceGroupID: reward.GetResourceGroupId()}) + rewardData := weeklyStarRewardData{RankNo: reward.GetRankNo(), ResourceGroupID: reward.GetResourceGroupId()} + if group, ok := resourceGroups[reward.GetResourceGroupId()]; ok { + rewardData.ResourceGroup = &group + } + cycle.Rewards = append(cycle.Rewards, rewardData) } return cycle } +func weeklyStarResourceGroupIDs(cycles ...*activityv1.WeeklyStarCycle) []int64 { + ids := make([]int64, 0) + for _, cycle := range cycles { + for _, reward := range cycle.GetRewards() { + ids = append(ids, reward.GetResourceGroupId()) + } + } + return ids +} + func weeklyStarEntriesFromProto(items []*activityv1.WeeklyStarLeaderboardEntry, users map[int64]weeklyStarUserData) []weeklyStarEntryData { entries := make([]weeklyStarEntryData, 0, len(items)) for _, item := range items { diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index 21fa9b93..1b6a580c 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -552,6 +552,16 @@ type fakeAchievementClient struct { err error } +type fakeWeeklyStarClient struct { + lastCurrent *activityv1.GetWeeklyStarCurrentRequest + currentResp *activityv1.GetWeeklyStarCurrentResponse + lastLeaderboard *activityv1.ListWeeklyStarLeaderboardRequest + leaderboardResp *activityv1.ListWeeklyStarLeaderboardResponse + lastHistory *activityv1.ListWeeklyStarHistoryRequest + historyResp *activityv1.ListWeeklyStarHistoryResponse + err error +} + type fakeBroadcastClient struct { lastRemove *activityv1.RemoveRegionBroadcastMemberRequest err error @@ -1084,6 +1094,39 @@ func (f *fakeAchievementClient) SetBadgeDisplay(_ context.Context, req *activity return &activityv1.SetBadgeDisplayResponse{}, nil } +func (f *fakeWeeklyStarClient) GetWeeklyStarCurrent(_ context.Context, req *activityv1.GetWeeklyStarCurrentRequest) (*activityv1.GetWeeklyStarCurrentResponse, error) { + f.lastCurrent = req + if f.err != nil { + return nil, f.err + } + if f.currentResp != nil { + return f.currentResp, nil + } + return &activityv1.GetWeeklyStarCurrentResponse{}, nil +} + +func (f *fakeWeeklyStarClient) ListWeeklyStarLeaderboard(_ context.Context, req *activityv1.ListWeeklyStarLeaderboardRequest) (*activityv1.ListWeeklyStarLeaderboardResponse, error) { + f.lastLeaderboard = req + if f.err != nil { + return nil, f.err + } + if f.leaderboardResp != nil { + return f.leaderboardResp, nil + } + return &activityv1.ListWeeklyStarLeaderboardResponse{}, nil +} + +func (f *fakeWeeklyStarClient) ListWeeklyStarHistory(_ context.Context, req *activityv1.ListWeeklyStarHistoryRequest) (*activityv1.ListWeeklyStarHistoryResponse, error) { + f.lastHistory = req + if f.err != nil { + return nil, f.err + } + if f.historyResp != nil { + return f.historyResp, nil + } + return &activityv1.ListWeeklyStarHistoryResponse{}, nil +} + func (f *fakeBroadcastClient) RemoveRegionBroadcastMember(_ context.Context, req *activityv1.RemoveRegionBroadcastMemberRequest) (*activityv1.RemoveRegionBroadcastMemberResponse, error) { f.lastRemove = req if f.err != nil { @@ -4669,6 +4712,126 @@ func TestAchievementAndBadgeRoutesEmbedResourceMaterials(t *testing.T) { } } +func TestWeeklyStarCurrentEmbedsRewardResourceGroups(t *testing.T) { + rewardResource := &walletv1.Resource{ + ResourceId: 8101, + ResourceCode: "weekly_star_frame", + ResourceType: "avatar_frame", + Name: "Weekly Star Frame", + Status: "active", + Grantable: true, + GrantStrategy: "new_entitlement", + UsageScopes: []string{"profile"}, + AssetUrl: "https://cdn.example/weekly-star-frame.png", + SortOrder: 10, + } + weeklyStarClient := &fakeWeeklyStarClient{ + currentResp: &activityv1.GetWeeklyStarCurrentResponse{ + Cycle: &activityv1.WeeklyStarCycle{ + CycleId: "wstar-test", + ActivityCode: "weekly_star", + RegionId: 686, + Title: "Weekly Star", + Status: "active", + StartMs: 1778256000000, + EndMs: 1778860800000, + Gifts: []*activityv1.WeeklyStarGift{{GiftId: "308", SortOrder: 1}}, + Rewards: []*activityv1.WeeklyStarReward{{RankNo: 1, ResourceGroupId: 23}}, + }, + TopEntries: []*activityv1.WeeklyStarLeaderboardEntry{{RankNo: 1, UserId: 88, Score: 120000, FirstScoredAtMs: 1778256001000, LastScoredAtMs: 1778256002000}}, + ServerTimeMs: 1778256003000, + }, + } + walletClient := &fakeWalletClient{ + resourceGroupsByID: map[int64]*walletv1.ResourceGroup{23: { + GroupId: 23, + GroupCode: "weekly_star_top1", + Name: "Top 1 Resource Group", + Status: "active", + Items: []*walletv1.ResourceGroupItem{{ + GroupItemId: 101, + ItemType: "resource", + ResourceId: rewardResource.GetResourceId(), + Resource: rewardResource, + Quantity: 1, + DurationMs: 7 * 24 * int64(time.Hour/time.Millisecond), + SortOrder: 10, + }}, + }}, + } + userClient := &fakeUserProfileClient{ + usersByID: map[int64]*userv1.User{ + 42: {UserId: 42, DisplayUserId: "163000", Username: "viewer", RegionId: 686}, + 88: {UserId: 88, DisplayUserId: "163088", Username: "weekly_ranker", Avatar: "https://cdn.example/ranker.png", RegionId: 686}, + }, + } + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, userClient) + handler.SetWeeklyStarClient(weeklyStarClient) + handler.SetWalletClient(walletClient) + router := handler.Routes(auth.NewVerifier("secret")) + + request := httptest.NewRequest(http.MethodGet, "/api/v1/activities/weekly-star/current", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Fatalf("weekly star status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + var envelope struct { + Code string `json:"code"` + Data struct { + Cycle struct { + Rewards []struct { + ResourceGroupID int64 `json:"resource_group_id"` + ResourceGroup struct { + GroupID int64 `json:"group_id"` + Name string `json:"name"` + Items []struct { + ResourceID int64 `json:"resource_id"` + Resource struct { + ResourceID int64 `json:"resource_id"` + ResourceType string `json:"resource_type"` + Name string `json:"name"` + AssetURL string `json:"asset_url"` + } `json:"resource"` + Quantity int64 `json:"quantity"` + DurationMS int64 `json:"duration_ms"` + } `json:"items"` + } `json:"resource_group"` + } `json:"rewards"` + } `json:"cycle"` + TopEntries []struct { + User struct { + Username string `json:"username"` + } `json:"user"` + } `json:"top_entries"` + } `json:"data"` + } + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode weekly star response failed: %v", err) + } + if weeklyStarClient.lastCurrent == nil || weeklyStarClient.lastCurrent.GetUserId() != 42 || weeklyStarClient.lastCurrent.GetRegionId() != 686 { + t.Fatalf("weekly star request mismatch: %+v", weeklyStarClient.lastCurrent) + } + if envelope.Code != httpkit.CodeOK || len(envelope.Data.Cycle.Rewards) != 1 { + t.Fatalf("weekly star response missing rewards: %+v", envelope) + } + firstReward := envelope.Data.Cycle.Rewards[0] + if firstReward.ResourceGroupID != 23 || firstReward.ResourceGroup.GroupID != 23 || firstReward.ResourceGroup.Name != "Top 1 Resource Group" { + t.Fatalf("weekly star reward group mismatch: %+v", firstReward) + } + if len(firstReward.ResourceGroup.Items) != 1 { + t.Fatalf("weekly star reward group items mismatch: %+v", firstReward.ResourceGroup) + } + firstItem := firstReward.ResourceGroup.Items[0] + if firstItem.ResourceID != 8101 || firstItem.Quantity != 1 || firstItem.DurationMS == 0 || firstItem.Resource.ResourceID != 8101 || firstItem.Resource.ResourceType != "avatar_frame" || firstItem.Resource.Name != "Weekly Star Frame" || firstItem.Resource.AssetURL == "" { + t.Fatalf("weekly star reward group item must embed resource material: %+v", firstItem) + } + if len(envelope.Data.TopEntries) != 1 || envelope.Data.TopEntries[0].User.Username != "weekly_ranker" { + t.Fatalf("weekly star top entry user mismatch: %+v", envelope.Data.TopEntries) + } +} + func TestAppearanceAggregatesEquippedResourcesAndBadges(t *testing.T) { frameResource := &walletv1.Resource{ ResourceId: 8101,