From c4d4f03ddfe2b49961d7847f7360cb96f53f375f Mon Sep 17 00:00:00 2001 From: hy001 Date: Wed, 20 May 2026 13:53:08 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BD=AC=E7=9B=98=E6=8A=BD=E5=A5=96=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/router/wheel_routes.go | 36 ++-- internal/router/wheel_routes_test.go | 118 +++++++++++ internal/service/wheel/draw_test.go | 2 +- internal/service/wheel/records.go | 283 ++++++++++++++++++++++++- internal/service/wheel/records_test.go | 185 ++++++++++++++++ internal/service/wheel/types.go | 68 +++--- 6 files changed, 648 insertions(+), 44 deletions(-) create mode 100644 internal/router/wheel_routes_test.go create mode 100644 internal/service/wheel/records_test.go diff --git a/internal/router/wheel_routes.go b/internal/router/wheel_routes.go index 652e3cd..b1e45eb 100644 --- a/internal/router/wheel_routes.go +++ b/internal/router/wheel_routes.go @@ -101,7 +101,7 @@ func registerWheelRoutes(engine *gin.Engine, cfg config.Config, javaClient authG }) residentGroup.GET("/record/page", func(c *gin.Context) { cursor, limit := parseCursorLimit(c) - resp, err := wheelService.PageRecords( + resp, err := wheelService.PageAdminRecords( c.Request.Context(), c.Query("sysOrigin"), c.Query("category"), @@ -109,6 +109,8 @@ func registerWheelRoutes(engine *gin.Engine, cfg config.Config, javaClient authG strings.TrimSpace(c.Query("status")), cursor, limit, + c.Query("startTime"), + c.Query("endTime"), ) if err != nil { writeError(c, err) @@ -217,11 +219,15 @@ func legacyRewardsHandler(wheelService *wheel.Service, category string) gin.Hand items := make([]gin.H, 0, len(pool.Rewards)) for _, reward := range pool.Rewards { items = append(items, gin.H{ - "id": reward.ID, - "value": legacyRewardValue(reward), + "id": reward.ID, + "animationUrl": reward.ResourceURL, + "resourceUrl": reward.ResourceURL, + "value": legacyRewardValue(reward), "merchandise": gin.H{ - "name": legacyRewardName(reward), - "coverUrl": legacyRewardCover(reward), + "animationUrl": reward.ResourceURL, + "coverUrl": legacyRewardCover(reward), + "name": legacyRewardName(reward), + "resourceUrl": reward.ResourceURL, }, }) } @@ -243,10 +249,12 @@ func legacyDrawHandler(wheelService *wheel.Service, category string) gin.Handler rewards := make([]gin.H, 0, len(resp.Rewards)) for _, reward := range resp.Rewards { rewards = append(rewards, gin.H{ - "coverUrl": legacyDrawRewardCover(reward), - "count": reward.Count, - "name": legacyDrawRewardName(reward), - "value": reward.Value, + "animationUrl": reward.ResourceURL, + "coverUrl": legacyDrawRewardCover(reward), + "count": reward.Count, + "name": legacyDrawRewardName(reward), + "resourceUrl": reward.ResourceURL, + "value": reward.Value, }) } legacyOK(c, gin.H{ @@ -276,10 +284,12 @@ func legacyHistoryHandler(wheelService *wheel.Service, category string) gin.Hand "drawTimes": record.DrawTimes, "rewardValue": legacyRecordValue(record), "rewards": []gin.H{{ - "coverUrl": legacyRecordCover(record), - "count": 1, - "name": legacyRecordName(record), - "value": legacyRecordValue(record), + "animationUrl": record.ResourceURL, + "coverUrl": legacyRecordCover(record), + "count": 1, + "name": legacyRecordName(record), + "resourceUrl": record.ResourceURL, + "value": legacyRecordValue(record), }}, }) } diff --git a/internal/router/wheel_routes_test.go b/internal/router/wheel_routes_test.go new file mode 100644 index 0000000..1923cd7 --- /dev/null +++ b/internal/router/wheel_routes_test.go @@ -0,0 +1,118 @@ +package router + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "chatapp3-golang/internal/config" + "chatapp3-golang/internal/model" + "chatapp3-golang/internal/repo" + wheelservice "chatapp3-golang/internal/service/wheel" + + "github.com/gin-gonic/gin" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestWheelLegacyRewardsExposeDynamicResourceURL(t *testing.T) { + gin.SetMode(gin.TestMode) + db := newRouterWheelDB(t) + now := time.Now() + resourceID := int64(2045029471274201089) + resourceURL := "https://cdn.example.com/reward.svga" + coverURL := "https://cdn.example.com/reward.png" + + if err := db.Create(&model.WheelConfig{ + ID: 1, + SysOrigin: "LIKEI", + Enabled: true, + Timezone: "Asia/Shanghai", + CreateTime: now, + UpdateTime: now, + }).Error; err != nil { + t.Fatalf("create wheel config: %v", err) + } + if err := db.Create(&model.WheelPoolConfig{ + ID: 10, + ConfigID: 1, + SysOrigin: "LIKEI", + Category: "CLASSIC", + Enabled: true, + PriceOneGold: 500, + Sort: 1, + CreateTime: now, + UpdateTime: now, + }).Error; err != nil { + t.Fatalf("create wheel pool: %v", err) + } + if err := db.Create(&model.WheelRewardConfig{ + ID: 100, + ConfigID: 1, + SysOrigin: "LIKEI", + Category: "CLASSIC", + RewardType: "RESOURCE", + ResourceID: &resourceID, + ResourceType: "GIFT", + ResourceName: "Dynamic Gift", + ResourceURL: resourceURL, + CoverURL: coverURL, + DurationDays: 7, + DisplayGoldAmount: 1000, + Probability: 1000000, + Sort: 1, + Enabled: true, + CreateTime: now, + UpdateTime: now, + }).Error; err != nil { + t.Fatalf("create wheel reward: %v", err) + } + + service := wheelservice.NewService(config.Config{}, db, nil) + engine := NewRouter(config.Config{}, &repo.Repository{}, appPopupAuthStub{}, Services{Wheel: service}) + + req := httptest.NewRequest(http.MethodGet, "/client/api/v1/probability/lucky-box/rewards", nil) + req.Header.Set("Authorization", "Bearer token") + rec := httptest.NewRecorder() + engine.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d body = %s", rec.Code, rec.Body.String()) + } + var payload map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { + t.Fatalf("decode response: %v", err) + } + if payload["code"] != float64(0) { + t.Fatalf("code = %#v body = %s", payload["code"], rec.Body.String()) + } + data, ok := payload["data"].([]any) + if !ok || len(data) == 0 { + t.Fatalf("data = %#v, want rewards", payload["data"]) + } + item := data[0].(map[string]any) + if item["resourceUrl"] != resourceURL || item["animationUrl"] != resourceURL { + t.Fatalf("dynamic urls = resource:%#v animation:%#v", item["resourceUrl"], item["animationUrl"]) + } + merchandise := item["merchandise"].(map[string]any) + if merchandise["resourceUrl"] != resourceURL || merchandise["animationUrl"] != resourceURL { + t.Fatalf("merchandise dynamic urls = resource:%#v animation:%#v", merchandise["resourceUrl"], merchandise["animationUrl"]) + } + if merchandise["coverUrl"] != coverURL { + t.Fatalf("coverUrl = %#v, want %q", merchandise["coverUrl"], coverURL) + } +} + +func newRouterWheelDB(t *testing.T) *gorm.DB { + t.Helper() + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + if err != nil { + t.Fatalf("open sqlite: %v", err) + } + if err := db.AutoMigrate(&model.WheelConfig{}, &model.WheelPoolConfig{}, &model.WheelRewardConfig{}); err != nil { + t.Fatalf("migrate wheel tables: %v", err) + } + return db +} diff --git a/internal/service/wheel/draw_test.go b/internal/service/wheel/draw_test.go index ed19c4d..75d093b 100644 --- a/internal/service/wheel/draw_test.go +++ b/internal/service/wheel/draw_test.go @@ -272,7 +272,7 @@ func newWheelDrawTestService(t *testing.T) (*Service, *fakeWheelGateway, *gorm.D if err != nil { t.Fatalf("open sqlite: %v", err) } - if err := db.AutoMigrate(&model.WheelConfig{}, &model.WheelPoolConfig{}, &model.WheelRewardConfig{}, &model.WheelDrawRecord{}, &model.ActivityIdempotency{}); err != nil { + if err := db.AutoMigrate(&model.WheelConfig{}, &model.WheelPoolConfig{}, &model.WheelRewardConfig{}, &model.WheelDrawRecord{}, &model.ActivityIdempotency{}, &model.UserBaseInfo{}); err != nil { t.Fatalf("migrate sqlite: %v", err) } gateway := &fakeWheelGateway{ diff --git a/internal/service/wheel/records.go b/internal/service/wheel/records.go index 863d48a..61820de 100644 --- a/internal/service/wheel/records.go +++ b/internal/service/wheel/records.go @@ -2,12 +2,17 @@ package wheel import ( "context" + "fmt" + "net/http" "strings" + "time" "chatapp3-golang/internal/model" + + "gorm.io/gorm" ) -// PageRecords returns admin draw records. +// PageRecords returns draw records without admin-only enrichment. func (s *Service) PageRecords( ctx context.Context, sysOrigin string, @@ -60,6 +65,80 @@ func (s *Service) PageRecords( }, nil } +// PageAdminRecords returns admin draw records with user info and range summary. +func (s *Service) PageAdminRecords( + ctx context.Context, + sysOrigin string, + category string, + userID int64, + status string, + cursor int, + limit int, + startTime string, + endTime string, +) (*RecordPageResponse, error) { + cursor, limit = normalizeRecordPage(cursor, limit) + filter, err := newWheelAdminRecordFilter(sysOrigin, category, userID, status, startTime, endTime) + if err != nil { + return nil, err + } + if filter.SysOrigin == "" { + return nil, NewAppError(400, "bad_request", "sysOrigin is required") + } + + query := s.adminRecordQuery(ctx, filter) + var total int64 + if err := query.Count(&total).Error; err != nil { + return nil, err + } + + var rows []model.WheelDrawRecord + if err := s.adminRecordQuery(ctx, filter). + Order("create_time desc, id desc"). + Offset((cursor - 1) * limit). + Limit(limit). + Find(&rows).Error; err != nil { + return nil, err + } + + summary, err := s.adminRecordSummary(ctx, filter) + if err != nil { + return nil, err + } + userInfos, err := s.adminRecordUserInfos(ctx, rows) + if err != nil { + return nil, err + } + userDrawTimes, err := s.adminRecordUserDrawTimes(ctx, filter, rows) + if err != nil { + return nil, err + } + + records := make([]DrawRecordPayload, 0, len(rows)) + for _, row := range rows { + payload := drawRecordPayload(row) + if user, ok := userInfos[row.UserID]; ok { + payload.UserShortID = user.Account + payload.UserAvatar = user.UserAvatar + payload.UserNickname = user.UserNickname + } + payload.UserTotalDrawTimes = userDrawTimes[row.UserID] + records = append(records, payload) + } + return &RecordPageResponse{ + Records: records, + Total: total, + Current: cursor, + Size: limit, + TotalPaidGold: summary.TotalPaidGold, + TotalRewardGold: summary.TotalRewardGold, + TotalDrawTimes: summary.TotalDrawTimes, + ParticipantCount: summary.ParticipantCount, + StartTime: formatDateTime(filter.StartTime), + EndTime: formatDateTime(filter.EndTime), + }, nil +} + // PageUserHistory returns app draw history for the authenticated user. func (s *Service) PageUserHistory(ctx context.Context, user AuthUser, cursor int, limit int) (*RecordPageResponse, error) { return s.PageRecords(ctx, user.SysOrigin, "", user.UserID, drawStatusSuccess, cursor, limit) @@ -160,3 +239,205 @@ func displayValueForRecord(row model.WheelDrawRecord) int64 { } return row.DisplayGoldAmount } + +type wheelAdminRecordFilter struct { + SysOrigin string + Category string + UserID int64 + Status string + StartTime time.Time + EndTime time.Time +} + +type wheelAdminRecordSummary struct { + TotalPaidGold int64 + TotalRewardGold int64 + TotalDrawTimes int64 + ParticipantCount int64 +} + +func newWheelAdminRecordFilter(sysOrigin string, category string, userID int64, status string, startRaw string, endRaw string) (wheelAdminRecordFilter, error) { + start, end, err := parseWheelAdminRecordTimeRange(startRaw, endRaw) + if err != nil { + return wheelAdminRecordFilter{}, err + } + return wheelAdminRecordFilter{ + SysOrigin: normalizeSysOrigin(sysOrigin), + Category: strings.ToUpper(strings.TrimSpace(category)), + UserID: userID, + Status: strings.ToUpper(strings.TrimSpace(status)), + StartTime: start, + EndTime: end, + }, nil +} + +func parseWheelAdminRecordTimeRange(startRaw string, endRaw string) (time.Time, time.Time, error) { + startRaw = strings.TrimSpace(startRaw) + endRaw = strings.TrimSpace(endRaw) + now := time.Now() + todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + if startRaw == "" && endRaw == "" { + return todayStart, todayStart.Add(24*time.Hour - time.Nanosecond), nil + } + + var start time.Time + var end time.Time + var err error + if startRaw != "" { + start, err = parseWheelAdminRecordTime(startRaw, false) + if err != nil { + return time.Time{}, time.Time{}, err + } + } + if endRaw != "" { + end, err = parseWheelAdminRecordTime(endRaw, true) + if err != nil { + return time.Time{}, time.Time{}, err + } + } + if startRaw == "" { + start = time.Date(end.Year(), end.Month(), end.Day(), 0, 0, 0, 0, end.Location()) + } + if endRaw == "" { + end = time.Date(start.Year(), start.Month(), start.Day(), 23, 59, 59, int(time.Second-time.Nanosecond), start.Location()) + } + if end.Before(start) { + return time.Time{}, time.Time{}, NewAppError(http.StatusBadRequest, "bad_time_range", "endTime must be greater than startTime") + } + return start, end, nil +} + +func parseWheelAdminRecordTime(raw string, endOfDay bool) (time.Time, error) { + for _, layout := range []string{ + "2006-01-02 15:04:05", + time.RFC3339, + "2006-01-02T15:04:05.000Z07:00", + "2006-01-02", + } { + var parsed time.Time + var err error + if strings.Contains(layout, "Z07:00") { + parsed, err = time.Parse(layout, raw) + } else { + parsed, err = time.ParseInLocation(layout, raw, time.Local) + } + if err == nil { + if layout == "2006-01-02" && endOfDay { + parsed = parsed.Add(24*time.Hour - time.Nanosecond) + } + return parsed, nil + } + } + return time.Time{}, NewAppError(http.StatusBadRequest, "bad_time", fmt.Sprintf("invalid time: %s", raw)) +} + +func (s *Service) adminRecordQuery(ctx context.Context, filter wheelAdminRecordFilter) *gorm.DB { + query := s.db.WithContext(ctx).Model(&model.WheelDrawRecord{}). + Where("sys_origin = ? AND create_time >= ? AND create_time <= ?", filter.SysOrigin, filter.StartTime, filter.EndTime) + if strings.TrimSpace(filter.Category) != "" { + query = query.Where("category = ?", normalizeCategory(filter.Category)) + } + if filter.UserID > 0 { + query = query.Where("user_id = ?", filter.UserID) + } + if filter.Status != "" { + query = query.Where("status = ?", filter.Status) + } + return query +} + +func (s *Service) adminRecordSummary(ctx context.Context, filter wheelAdminRecordFilter) (wheelAdminRecordSummary, error) { + var summary wheelAdminRecordSummary + if err := s.adminRecordQuery(ctx, filter). + Select("COALESCE(SUM(CASE WHEN status = ? THEN CASE WHEN reward_type = ? THEN gold_amount ELSE display_gold_amount END ELSE 0 END), 0)", drawStatusSuccess, rewardTypeGold). + Scan(&summary.TotalRewardGold).Error; err != nil { + return summary, err + } + if err := s.adminRecordQuery(ctx, filter). + Select("COUNT(DISTINCT user_id)"). + Scan(&summary.ParticipantCount).Error; err != nil { + return summary, err + } + + drawRows := s.adminRecordQuery(ctx, filter). + Select("draw_no, MAX(paid_gold) AS paid_gold, MAX(draw_times) AS draw_times"). + Group("draw_no") + if err := s.db.WithContext(ctx). + Table("(?) AS draw_rows", drawRows). + Select("COALESCE(SUM(paid_gold), 0), COALESCE(SUM(draw_times), 0)"). + Row(). + Scan(&summary.TotalPaidGold, &summary.TotalDrawTimes); err != nil { + return summary, err + } + return summary, nil +} + +func (s *Service) adminRecordUserInfos(ctx context.Context, rows []model.WheelDrawRecord) (map[int64]model.UserBaseInfo, error) { + ids := make([]int64, 0, len(rows)) + seen := make(map[int64]struct{}, len(rows)) + for _, row := range rows { + if row.UserID <= 0 { + continue + } + if _, ok := seen[row.UserID]; ok { + continue + } + seen[row.UserID] = struct{}{} + ids = append(ids, row.UserID) + } + if len(ids) == 0 { + return map[int64]model.UserBaseInfo{}, nil + } + var users []model.UserBaseInfo + if err := s.db.WithContext(ctx). + Where("id IN ?", ids). + Find(&users).Error; err != nil { + return nil, err + } + result := make(map[int64]model.UserBaseInfo, len(users)) + for _, user := range users { + result[user.ID] = user + } + return result, nil +} + +func (s *Service) adminRecordUserDrawTimes(ctx context.Context, filter wheelAdminRecordFilter, pageRows []model.WheelDrawRecord) (map[int64]int64, error) { + ids := make([]int64, 0, len(pageRows)) + seen := make(map[int64]struct{}, len(pageRows)) + for _, row := range pageRows { + if row.UserID <= 0 { + continue + } + if _, ok := seen[row.UserID]; ok { + continue + } + seen[row.UserID] = struct{}{} + ids = append(ids, row.UserID) + } + if len(ids) == 0 { + return map[int64]int64{}, nil + } + + drawRows := s.adminRecordQuery(ctx, filter). + Where("user_id IN ?", ids). + Select("user_id, draw_no, MAX(draw_times) AS draw_times"). + Group("user_id, draw_no") + + type userDrawTotal struct { + UserID int64 + TotalDrawTimes int64 + } + var rows []userDrawTotal + if err := s.db.WithContext(ctx). + Table("(?) AS user_draw_rows", drawRows). + Select("user_id, COALESCE(SUM(draw_times), 0) AS total_draw_times"). + Group("user_id"). + Scan(&rows).Error; err != nil { + return nil, err + } + result := make(map[int64]int64, len(rows)) + for _, row := range rows { + result[row.UserID] = row.TotalDrawTimes + } + return result, nil +} diff --git a/internal/service/wheel/records_test.go b/internal/service/wheel/records_test.go new file mode 100644 index 0000000..9ef7038 --- /dev/null +++ b/internal/service/wheel/records_test.go @@ -0,0 +1,185 @@ +package wheel + +import ( + "context" + "testing" + "time" + + "chatapp3-golang/internal/model" +) + +func TestPageAdminRecordsIncludesUserInfoAndRangeSummary(t *testing.T) { + service, _, db := newWheelDrawTestService(t) + now := time.Now() + todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + recordTime := todayStart.Add(time.Hour) + oldRecordTime := todayStart.Add(-time.Hour) + giftID := int64(9001) + + if err := db.Create([]model.UserBaseInfo{ + { + ID: 1001, + Account: "short1001", + UserAvatar: "https://example.com/a.png", + UserNickname: "Alice", + OriginSys: "LIKEI", + CreateTime: recordTime, + }, + { + ID: 1002, + Account: "short1002", + UserAvatar: "https://example.com/b.png", + UserNickname: "Bob", + OriginSys: "LIKEI", + CreateTime: recordTime, + }, + }).Error; err != nil { + t.Fatalf("seed users: %v", err) + } + + rows := []model.WheelDrawRecord{ + { + ID: 1, + DrawNo: "WHEEL-1001-1", + EventID: "event-1", + SysOrigin: "LIKEI", + Category: categoryClassic, + UserID: 1001, + DrawTimes: 10, + PaidGold: 1000, + RewardType: rewardTypeResource, + ResourceID: &giftID, + ResourceType: resourceTypeGift, + ResourceName: "Gift A", + DisplayGoldAmount: 50, + Probability: 500000, + Status: drawStatusSuccess, + CreateTime: recordTime, + UpdateTime: recordTime, + }, + { + ID: 2, + DrawNo: "WHEEL-1001-1", + EventID: "event-2", + SysOrigin: "LIKEI", + Category: categoryClassic, + UserID: 1001, + DrawTimes: 10, + PaidGold: 1000, + RewardType: rewardTypeGold, + GoldAmount: 70, + DisplayGoldAmount: 999, + Probability: 500000, + Status: drawStatusSuccess, + CreateTime: recordTime.Add(time.Minute), + UpdateTime: recordTime.Add(time.Minute), + }, + { + ID: 3, + DrawNo: "WHEEL-1002-1", + EventID: "event-3", + SysOrigin: "LIKEI", + Category: categoryClassic, + UserID: 1002, + DrawTimes: 1, + PaidGold: 100, + RewardType: rewardTypeResource, + ResourceID: &giftID, + ResourceType: resourceTypeGift, + ResourceName: "Gift Failed", + DisplayGoldAmount: 999, + Probability: 500000, + Status: drawStatusFailed, + CreateTime: recordTime.Add(2 * time.Minute), + UpdateTime: recordTime.Add(2 * time.Minute), + }, + { + ID: 4, + DrawNo: "WHEEL-1002-2", + EventID: "event-4", + SysOrigin: "LIKEI", + Category: categoryClassic, + UserID: 1002, + DrawTimes: 1, + PaidGold: 200, + RewardType: rewardTypeResource, + ResourceID: &giftID, + ResourceType: resourceTypeGift, + ResourceName: "Gift B", + DisplayGoldAmount: 30, + Probability: 500000, + Status: drawStatusSuccess, + CreateTime: recordTime.Add(3 * time.Minute), + UpdateTime: recordTime.Add(3 * time.Minute), + }, + { + ID: 5, + DrawNo: "WHEEL-OLD", + EventID: "event-old", + SysOrigin: "LIKEI", + Category: categoryClassic, + UserID: 1001, + DrawTimes: 50, + PaidGold: 5000, + RewardType: rewardTypeResource, + ResourceID: &giftID, + ResourceType: resourceTypeGift, + ResourceName: "Old Gift", + DisplayGoldAmount: 500, + Probability: 500000, + Status: drawStatusSuccess, + CreateTime: oldRecordTime, + UpdateTime: oldRecordTime, + }, + } + if err := db.Create(&rows).Error; err != nil { + t.Fatalf("seed records: %v", err) + } + + resp, err := service.PageAdminRecords(context.Background(), "LIKEI", "", 0, "", 1, 20, "", "") + if err != nil { + t.Fatalf("PageAdminRecords() error = %v", err) + } + if resp.Total != 4 { + t.Fatalf("total = %d, want 4", resp.Total) + } + if resp.TotalPaidGold != 1300 { + t.Fatalf("total paid = %d, want 1300", resp.TotalPaidGold) + } + if resp.TotalRewardGold != 150 { + t.Fatalf("total reward = %d, want 150", resp.TotalRewardGold) + } + if resp.TotalDrawTimes != 12 { + t.Fatalf("total draw times = %d, want 12", resp.TotalDrawTimes) + } + if resp.ParticipantCount != 2 { + t.Fatalf("participant count = %d, want 2", resp.ParticipantCount) + } + + var user1001 *DrawRecordPayload + var user1002 *DrawRecordPayload + for index := range resp.Records { + record := &resp.Records[index] + switch record.UserID { + case 1001: + user1001 = record + case 1002: + user1002 = record + } + } + if user1001 == nil { + t.Fatal("missing user 1001 record") + } + if user1001.UserShortID != "short1001" || user1001.UserAvatar == "" || user1001.UserNickname != "Alice" { + t.Fatalf("user 1001 info = %+v", user1001) + } + if user1001.UserTotalDrawTimes != 10 { + t.Fatalf("user 1001 total draw times = %d, want 10", user1001.UserTotalDrawTimes) + } + if user1002 == nil { + t.Fatal("missing user 1002 record") + } + if user1002.UserTotalDrawTimes != 2 { + t.Fatalf("user 1002 total draw times = %d, want 2", user1002.UserTotalDrawTimes) + } +} diff --git a/internal/service/wheel/types.go b/internal/service/wheel/types.go index 5108f92..a7b17c9 100644 --- a/internal/service/wheel/types.go +++ b/internal/service/wheel/types.go @@ -274,39 +274,49 @@ type DrawRewardPayload struct { // DrawRecordPayload is one draw record row. type DrawRecordPayload struct { - ID int64 `json:"id"` - DrawNo string `json:"drawNo"` - EventID string `json:"eventId,omitempty"` - UserID int64 `json:"userId"` - SysOrigin string `json:"sysOrigin"` - Category string `json:"category"` - DrawTimes int `json:"drawTimes"` - PaidGold int64 `json:"paidGold"` - RewardConfigID int64 `json:"rewardConfigId,omitempty"` - RewardType string `json:"rewardType"` - ResourceID ResourceID `json:"resourceId,omitempty"` - ResourceType string `json:"resourceType,omitempty"` - ResourceName string `json:"resourceName,omitempty"` - ResourceURL string `json:"resourceUrl,omitempty"` - CoverURL string `json:"coverUrl,omitempty"` - DurationDays int `json:"durationDays,omitempty"` - DisplayGoldAmount int64 `json:"displayGoldAmount,omitempty"` - GoldAmount int64 `json:"goldAmount,omitempty"` - Probability int `json:"probability"` - Status string `json:"status"` - RetryCount int `json:"retryCount,omitempty"` - ErrorMessage string `json:"errorMessage,omitempty"` - GrantTime string `json:"grantTime,omitempty"` - CreateTime string `json:"createTime,omitempty"` - UpdateTime string `json:"updateTime,omitempty"` + ID int64 `json:"id"` + DrawNo string `json:"drawNo"` + EventID string `json:"eventId,omitempty"` + UserID int64 `json:"userId"` + UserShortID string `json:"userShortId,omitempty"` + UserAvatar string `json:"userAvatar,omitempty"` + UserNickname string `json:"userNickname,omitempty"` + UserTotalDrawTimes int64 `json:"userTotalDrawTimes,omitempty"` + SysOrigin string `json:"sysOrigin"` + Category string `json:"category"` + DrawTimes int `json:"drawTimes"` + PaidGold int64 `json:"paidGold"` + RewardConfigID int64 `json:"rewardConfigId,omitempty"` + RewardType string `json:"rewardType"` + ResourceID ResourceID `json:"resourceId,omitempty"` + ResourceType string `json:"resourceType,omitempty"` + ResourceName string `json:"resourceName,omitempty"` + ResourceURL string `json:"resourceUrl,omitempty"` + CoverURL string `json:"coverUrl,omitempty"` + DurationDays int `json:"durationDays,omitempty"` + DisplayGoldAmount int64 `json:"displayGoldAmount,omitempty"` + GoldAmount int64 `json:"goldAmount,omitempty"` + Probability int `json:"probability"` + Status string `json:"status"` + RetryCount int `json:"retryCount,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` + GrantTime string `json:"grantTime,omitempty"` + CreateTime string `json:"createTime,omitempty"` + UpdateTime string `json:"updateTime,omitempty"` } // RecordPageResponse is an admin record page. type RecordPageResponse struct { - Records []DrawRecordPayload `json:"records"` - Total int64 `json:"total"` - Current int `json:"current"` - Size int `json:"size"` + Records []DrawRecordPayload `json:"records"` + Total int64 `json:"total"` + Current int `json:"current"` + Size int `json:"size"` + TotalPaidGold int64 `json:"totalPaidGold,omitempty"` + TotalRewardGold int64 `json:"totalRewardGold,omitempty"` + TotalDrawTimes int64 `json:"totalDrawTimes,omitempty"` + ParticipantCount int64 `json:"participantCount,omitempty"` + StartTime string `json:"startTime,omitempty"` + EndTime string `json:"endTime,omitempty"` } func normalizeSysOrigin(sysOrigin string) string {