From 463d7374afc6e826ec2e6d531fb0f82cff1569a4 Mon Sep 17 00:00:00 2001 From: hy001 Date: Fri, 15 May 2026 23:44:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=81=AB=E7=AE=AD=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/router/voice_room_rocket_routes.go | 14 ++- .../service/voiceroomrocket/event_test.go | 104 ++++++++++++++++++ internal/service/voiceroomrocket/mapper.go | 24 ++++ internal/service/voiceroomrocket/records.go | 80 ++++++++++++-- internal/service/voiceroomrocket/types.go | 3 + 5 files changed, 212 insertions(+), 13 deletions(-) diff --git a/internal/router/voice_room_rocket_routes.go b/internal/router/voice_room_rocket_routes.go index 32c54ad..5be30eb 100644 --- a/internal/router/voice_room_rocket_routes.go +++ b/internal/router/voice_room_rocket_routes.go @@ -83,7 +83,19 @@ func registerVoiceRoomRocketRoutes( appGroup.GET("/reward-records", func(c *gin.Context) { cursor, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("cursor", "1"))) limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "20"))) - resp, err := service.PageRewardRecords(c.Request.Context(), mustAuthUser(c), parseInt64(c.Query("roomId")), cursor, limit) + user := mustAuthUser(c) + roomID := parseInt64(c.Query("roomId")) + launchNo := strings.TrimSpace(c.Query("launchNo")) + scope := strings.ToLower(strings.TrimSpace(c.Query("scope"))) + var ( + resp *voiceroomrocket.RewardRecordPageResponse + err error + ) + if launchNo != "" || scope == "room" { + resp, err = service.PageRoomRewardRecords(c.Request.Context(), user, roomID, launchNo, cursor, limit) + } else { + resp, err = service.PageRewardRecords(c.Request.Context(), user, roomID, cursor, limit) + } if err != nil { writeError(c, err) return diff --git a/internal/service/voiceroomrocket/event_test.go b/internal/service/voiceroomrocket/event_test.go index 65a8981..f0a18dc 100644 --- a/internal/service/voiceroomrocket/event_test.go +++ b/internal/service/voiceroomrocket/event_test.go @@ -109,6 +109,103 @@ func TestProcessGiftPayloadDropsOverflowAndLaunchesOnce(t *testing.T) { } } +func TestPageRoomRewardRecordsReturnsAllLaunchWinnersWithProfiles(t *testing.T) { + service, db := newTestService(t) + ctx := context.Background() + gateway := newFakeRocketGateway() + gateway.profiles = map[int64]integration.UserProfile{ + 1001: {ID: integration.Int64Value(1001), Account: "1001026", UserNickname: "room1001026", UserAvatar: "https://avatar.example/1001.png"}, + 1002: {ID: integration.Int64Value(1002), Account: "1001027", UserNickname: "room1001027", UserAvatar: "https://avatar.example/1002.png"}, + } + service.userProfiles = gateway + seedRocketConfig(t, db) + + now := time.Now() + launch := model.VoiceRoomRocketLaunch{ + ID: 1, + LaunchNo: "VRR-test-launch-1", + SysOrigin: "LIKEI", + RoomID: 9001, + DayKey: "2026-05-15", + RoundNo: 1, + Level: 3, + LaunchTime: now, + CreateTime: now, + UpdateTime: now, + } + if err := db.Create(&launch).Error; err != nil { + t.Fatalf("create launch: %v", err) + } + rewardItemID := int64(2001) + expireDays := 15 + rows := []model.VoiceRoomRocketRewardRecord{ + { + ID: 11, + LaunchID: launch.ID, + LaunchNo: launch.LaunchNo, + SysOrigin: "LIKEI", + RoomID: launch.RoomID, + DayKey: launch.DayKey, + RoundNo: launch.RoundNo, + Level: launch.Level, + UserID: 1001, + RewardScene: rewardSceneTop1, + RewardType: "GOLD", + RewardName: "Yumi", + RewardAmount: 5000, + GrantEventID: "grant-event-11", + GrantStatus: rewardStatusSuccess, + PopupStatus: popupStatusUnread, + CreateTime: now, + UpdateTime: now, + }, + { + ID: 12, + LaunchID: launch.ID, + LaunchNo: launch.LaunchNo, + SysOrigin: "LIKEI", + RoomID: launch.RoomID, + DayKey: launch.DayKey, + RoundNo: launch.RoundNo, + Level: launch.Level, + UserID: 1002, + RewardScene: rewardSceneInRoom, + RewardType: "AVATAR_FRAME", + RewardItemID: &rewardItemID, + RewardName: "Frame", + RewardCover: "https://reward.example/frame.png", + RewardAmount: 1, + ExpireDays: &expireDays, + GrantEventID: "grant-event-12", + GrantStatus: rewardStatusSuccess, + PopupStatus: popupStatusUnread, + CreateTime: now.Add(time.Second), + UpdateTime: now.Add(time.Second), + }, + } + if err := db.Create(&rows).Error; err != nil { + t.Fatalf("create reward records: %v", err) + } + + resp, err := service.PageRoomRewardRecords(ctx, AuthUser{UserID: 9999, SysOrigin: "LIKEI"}, launch.RoomID, launch.LaunchNo, 1, 20) + if err != nil { + t.Fatalf("PageRoomRewardRecords() error = %v", err) + } + if len(resp.Records) != 2 { + t.Fatalf("records len = %d, want 2: %+v", len(resp.Records), resp.Records) + } + users := map[int64]RewardRecordView{} + for _, record := range resp.Records { + users[record.UserID] = record + } + if got := users[1001]; got.Account != "1001026" || got.UserNickname != "room1001026" || got.UserAvatar == "" { + t.Fatalf("user 1001 profile = %+v", got) + } + if got := users[1002]; got.Account != "1001027" || got.UserNickname != "room1001027" || got.UserAvatar == "" { + t.Fatalf("user 1002 profile = %+v", got) + } +} + func TestDecodeGiftEventPayloadEnvelope(t *testing.T) { payload := `{"tag":"give_gift_v3","body":"{\"trackId\":\"t1\",\"sendUserId\":\"10\",\"roomId\":\"20\",\"giftValue\":{\"actualAmount\":\"30\"}}"}` event, err := decodeGiftEventPayload(payload, "give_gift_v3") @@ -745,6 +842,7 @@ type fakeRocketGateway struct { goldEvents map[string]integration.GoldReceiptCommand props []integration.GivePropsBackpackRequest badges []fakeBadgeGrant + profiles map[int64]integration.UserProfile } type fakeBadgeGrant struct { @@ -790,6 +888,12 @@ func (g *fakeRocketGateway) MapRoomProfiles(ctx context.Context, roomIDs []int64 } func (g *fakeRocketGateway) GetUserProfile(ctx context.Context, userID int64) (integration.UserProfile, error) { + if profile, ok := g.profiles[userID]; ok { + if int64(profile.ID) == 0 { + profile.ID = integration.Int64Value(userID) + } + return profile, nil + } return integration.UserProfile{ID: integration.Int64Value(userID), Account: strconv.FormatInt(userID, 10), UserNickname: "user", CountryCode: "SA"}, nil } diff --git a/internal/service/voiceroomrocket/mapper.go b/internal/service/voiceroomrocket/mapper.go index 28ff7ee..e09973a 100644 --- a/internal/service/voiceroomrocket/mapper.go +++ b/internal/service/voiceroomrocket/mapper.go @@ -333,6 +333,30 @@ func toRewardRecordView(row model.VoiceRoomRocketRewardRecord) RewardRecordView } } +func toRewardRecordViews(rows []model.VoiceRoomRocketRewardRecord, profiles map[int64]integration.UserProfile) []RewardRecordView { + records := make([]RewardRecordView, 0, len(rows)) + for _, row := range rows { + view := toRewardRecordView(row) + if profile, ok := profiles[row.UserID]; ok { + applyRewardRecordProfile(&view, profile) + } + records = append(records, view) + } + return records +} + +func applyRewardRecordProfile(view *RewardRecordView, profile integration.UserProfile) { + if strings.TrimSpace(profile.UserAvatar) != "" { + view.UserAvatar = profile.UserAvatar + } + if strings.TrimSpace(profile.UserNickname) != "" { + view.UserNickname = profile.UserNickname + } + if strings.TrimSpace(profile.Account) != "" { + view.Account = profile.Account + } +} + func toLaunchRecordView(row model.VoiceRoomRocketLaunch) LaunchRecordView { return LaunchRecordView{ ID: row.ID, diff --git a/internal/service/voiceroomrocket/records.go b/internal/service/voiceroomrocket/records.go index e903308..3648d7d 100644 --- a/internal/service/voiceroomrocket/records.go +++ b/internal/service/voiceroomrocket/records.go @@ -3,6 +3,7 @@ package voiceroomrocket import ( "context" "net/http" + "strings" "time" "chatapp3-golang/internal/model" @@ -22,10 +23,7 @@ func (s *Service) ListRewardPopups(ctx context.Context, user AuthUser, roomID in if err := query.Find(&rows).Error; err != nil { return nil, err } - records := make([]RewardRecordView, 0, len(rows)) - for _, row := range rows { - records = append(records, toRewardRecordView(row)) - } + records := toRewardRecordViews(rows, s.mapUserProfiles(ctx, rewardRecordUserIDs(rows))) return &RewardPopupResponse{Records: records}, nil } @@ -76,10 +74,7 @@ func (s *Service) PageRewardRecords(ctx context.Context, user AuthUser, roomID i if err := query.Find(&rows).Error; err != nil { return nil, err } - records := make([]RewardRecordView, 0, len(rows)) - for _, row := range rows { - records = append(records, toRewardRecordView(row)) - } + records := toRewardRecordViews(rows, s.mapUserProfiles(ctx, rewardRecordUserIDs(rows))) resp := &RewardRecordPageResponse{ Records: records, Cursor: cursor, @@ -91,6 +86,54 @@ func (s *Service) PageRewardRecords(ctx context.Context, user AuthUser, roomID i return resp, nil } +// PageRoomRewardRecords 返回房间内某次火箭发射的全部中奖记录。 +func (s *Service) PageRoomRewardRecords(ctx context.Context, user AuthUser, roomID int64, launchNo string, cursor int, limit int) (*RewardRecordPageResponse, error) { + cursor, limit = normalizePage(cursor, limit) + snapshot, err := s.loadConfig(ctx, user.SysOrigin) + if err != nil { + return nil, err + } + keepDays := snapshot.RewardRecordKeepDays + if keepDays <= 0 { + keepDays = defaultRewardRecordKeepDays + } + startAt := time.Now().AddDate(0, 0, -keepDays) + launchNo = strings.TrimSpace(launchNo) + if launchNo == "" { + query := s.repo.DB.WithContext(ctx). + Model(&model.VoiceRoomRocketRewardRecord{}). + Where("sys_origin = ? AND create_time >= ? AND launch_no <> ?", snapshot.SysOrigin, startAt, ""). + Order("create_time DESC, id DESC"). + Limit(1) + if roomID > 0 { + query = query.Where("room_id = ?", roomID) + } + var launchNos []string + if err := query.Pluck("launch_no", &launchNos).Error; err != nil { + return nil, err + } + if len(launchNos) == 0 { + return &RewardRecordPageResponse{Records: []RewardRecordView{}, Cursor: cursor, Limit: limit}, nil + } + launchNo = launchNos[0] + } + + query := s.repo.DB.WithContext(ctx). + Where("sys_origin = ? AND launch_no = ? AND create_time >= ?", snapshot.SysOrigin, launchNo, startAt). + Order("create_time DESC, id DESC"). + Offset((cursor - 1) * limit). + Limit(limit) + if roomID > 0 { + query = query.Where("room_id = ?", roomID) + } + var rows []model.VoiceRoomRocketRewardRecord + if err := query.Find(&rows).Error; err != nil { + return nil, err + } + records := toRewardRecordViews(rows, s.mapUserProfiles(ctx, rewardRecordUserIDs(rows))) + return &RewardRecordPageResponse{Records: records, Cursor: cursor, Limit: limit}, nil +} + // PageAdminLaunchRecords 返回后台发射记录。 func (s *Service) PageAdminLaunchRecords(ctx context.Context, sysOrigin string, roomID int64, cursor int, limit int) (*LaunchRecordPageResponse, error) { cursor, limit = normalizePage(cursor, limit) @@ -134,9 +177,22 @@ func (s *Service) PageAdminRewardRecords(ctx context.Context, sysOrigin string, if err := query.Find(&rows).Error; err != nil { return nil, err } - records := make([]RewardRecordView, 0, len(rows)) - for _, row := range rows { - records = append(records, toRewardRecordView(row)) - } + records := toRewardRecordViews(rows, s.mapUserProfiles(ctx, rewardRecordUserIDs(rows))) return &RewardRecordPageResponse{Records: records, Cursor: cursor, Limit: limit}, nil } + +func rewardRecordUserIDs(rows []model.VoiceRoomRocketRewardRecord) []int64 { + seen := make(map[int64]struct{}, len(rows)) + userIDs := make([]int64, 0, len(rows)) + for _, row := range rows { + if row.UserID <= 0 { + continue + } + if _, ok := seen[row.UserID]; ok { + continue + } + seen[row.UserID] = struct{}{} + userIDs = append(userIDs, row.UserID) + } + return userIDs +} diff --git a/internal/service/voiceroomrocket/types.go b/internal/service/voiceroomrocket/types.go index 65515a1..1ff622d 100644 --- a/internal/service/voiceroomrocket/types.go +++ b/internal/service/voiceroomrocket/types.go @@ -457,6 +457,9 @@ type RewardRecordView struct { RoundNo int `json:"roundNo"` Level int `json:"level"` UserID int64 `json:"userId,string"` + UserAvatar string `json:"userAvatar,omitempty"` + UserNickname string `json:"userNickname,omitempty"` + Account string `json:"account,omitempty"` RewardScene string `json:"rewardScene"` RewardType string `json:"rewardType"` RewardItemID *int64 `json:"rewardItemId,string,omitempty"`