From fc214f99914c53e79a5f949fbd9fdbdede32a005 Mon Sep 17 00:00:00 2001 From: hy001 Date: Fri, 15 May 2026 15:57:10 +0800 Subject: [PATCH] feat: add daily task claim record filters --- internal/router/task_center_routes.go | 2 + internal/service/taskcenter/admin.go | 124 +++++++++++++++++- .../service/taskcenter/taskcenter_test.go | 101 ++++++++++++++ internal/service/taskcenter/types.go | 14 ++ ...task_center_claim_record_admin_indexes.sql | 46 +++++++ 5 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 migrations/041_task_center_claim_record_admin_indexes.sql diff --git a/internal/router/task_center_routes.go b/internal/router/task_center_routes.go index b5e7c3f..4b7605c 100644 --- a/internal/router/task_center_routes.go +++ b/internal/router/task_center_routes.go @@ -121,6 +121,8 @@ func registerTaskCenterRoutes(engine *gin.Engine, cfg config.Config, javaClient taskcenter.ParseInt64(c.Query("userId")), c.Query("taskCode"), c.Query("status"), + c.Query("startTime"), + c.Query("endTime"), int(taskcenter.ParseInt64(c.Query("cursor"))), int(taskcenter.ParseInt64(c.Query("limit"))), ) diff --git a/internal/service/taskcenter/admin.go b/internal/service/taskcenter/admin.go index 703c288..cb00f30 100644 --- a/internal/service/taskcenter/admin.go +++ b/internal/service/taskcenter/admin.go @@ -5,6 +5,7 @@ import ( "errors" "net/http" "sort" + "strconv" "strings" "time" @@ -195,25 +196,36 @@ func (s *Service) SaveConfig(ctx context.Context, req SaveConfigRequest) (*Confi } // PageClaimRecords 分页查询领取记录。 -func (s *Service) PageClaimRecords(ctx context.Context, sysOrigin string, taskCategory string, userID int64, taskCode string, status string, cursor int, limit int) (*PageResponse[ClaimRecordView], error) { +func (s *Service) PageClaimRecords(ctx context.Context, sysOrigin string, taskCategory string, userID int64, taskCode string, status string, startTime string, endTime string, cursor int, limit int) (*ClaimRecordPageResponse, error) { sysOrigin = s.normalizeSysOrigin(sysOrigin) category := normalizeCategory(taskCategory) if err := validateCategory(category); err != nil { return nil, err } cursor, limit = NormalizePage(cursor, limit) + startAt, endAt, err := parseClaimRecordTimeRange(startTime, endTime) + if err != nil { + return nil, err + } + code := strings.ToUpper(strings.TrimSpace(taskCode)) query := s.db.WithContext(ctx).Model(&model.TaskCenterClaimRecord{}). Where("sys_origin = ? AND task_category = ?", sysOrigin, category) if userID > 0 { query = query.Where("user_id = ?", userID) } - if code := strings.ToUpper(strings.TrimSpace(taskCode)); code != "" { + if code != "" { query = query.Where("task_code = ?", code) } if normalizedStatus := strings.ToUpper(strings.TrimSpace(status)); normalizedStatus != "" { query = query.Where("status = ?", normalizedStatus) } + if !startAt.IsZero() { + query = query.Where("create_time >= ?", startAt) + } + if !endAt.IsZero() { + query = query.Where("create_time <= ?", endAt) + } var total int64 if err := query.Count(&total).Error; err != nil { @@ -230,7 +242,52 @@ func (s *Service) PageClaimRecords(ctx context.Context, sysOrigin string, taskCa for _, row := range rows { records = append(records, claimRecordView(row)) } - return &PageResponse[ClaimRecordView]{Records: records, Total: total, Current: cursor, Size: limit}, nil + summary, err := s.claimRecordRewardSummary(ctx, sysOrigin, category, userID, code, startAt, endAt) + if err != nil { + return nil, err + } + return &ClaimRecordPageResponse{Records: records, Total: total, Current: cursor, Size: limit, Summary: summary}, nil +} + +func (s *Service) claimRecordRewardSummary(ctx context.Context, sysOrigin string, taskCategory string, userID int64, taskCode string, startAt time.Time, endAt time.Time) (ClaimRecordSummary, error) { + total, err := s.claimRecordRewardGoldSum(ctx, sysOrigin, taskCategory, userID, "", startAt, endAt) + if err != nil { + return ClaimRecordSummary{}, err + } + selected := int64(0) + if taskCode != "" { + selected, err = s.claimRecordRewardGoldSum(ctx, sysOrigin, taskCategory, userID, taskCode, startAt, endAt) + if err != nil { + return ClaimRecordSummary{}, err + } + } + return ClaimRecordSummary{ + TotalRewardGold: total, + SelectedTaskRewardGold: selected, + TaskCode: taskCode, + }, nil +} + +func (s *Service) claimRecordRewardGoldSum(ctx context.Context, sysOrigin string, taskCategory string, userID int64, taskCode string, startAt time.Time, endAt time.Time) (int64, error) { + query := s.db.WithContext(ctx).Model(&model.TaskCenterClaimRecord{}). + Where("sys_origin = ? AND task_category = ? AND status = ?", sysOrigin, taskCategory, ClaimStatusSuccess) + if userID > 0 { + query = query.Where("user_id = ?", userID) + } + if strings.TrimSpace(taskCode) != "" { + query = query.Where("task_code = ?", strings.ToUpper(strings.TrimSpace(taskCode))) + } + if !startAt.IsZero() { + query = query.Where("create_time >= ?", startAt) + } + if !endAt.IsZero() { + query = query.Where("create_time <= ?", endAt) + } + var total int64 + if err := query.Select("COALESCE(SUM(reward_gold), 0)").Scan(&total).Error; err != nil { + return 0, err + } + return total, nil } func (s *Service) normalizeTaskInputs(inputs []SaveTaskConfigInput, category string) ([]model.TaskCenterTaskConfig, error) { @@ -579,6 +636,67 @@ func normalizeConditionJumpPage(jumpType string, jumpPage string) string { return strings.TrimSpace(jumpPage) } +func parseClaimRecordTimeRange(startRaw string, endRaw string) (time.Time, time.Time, error) { + var start time.Time + var end time.Time + var err error + if strings.TrimSpace(startRaw) != "" { + start, err = parseClaimRecordTime(startRaw, false) + if err != nil { + return time.Time{}, time.Time{}, NewAppError(http.StatusBadRequest, "bad_start_time", "startTime is invalid") + } + } + if strings.TrimSpace(endRaw) != "" { + end, err = parseClaimRecordTime(endRaw, true) + if err != nil { + return time.Time{}, time.Time{}, NewAppError(http.StatusBadRequest, "bad_end_time", "endTime is invalid") + } + } + if !start.IsZero() && !end.IsZero() && 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 parseClaimRecordTime(raw string, endOfDay bool) (time.Time, error) { + value := strings.TrimSpace(raw) + if value == "" { + return time.Time{}, nil + } + if millis, err := strconv.ParseInt(value, 10, 64); err == nil { + if millis > 1_000_000_000_000 { + return time.UnixMilli(millis), nil + } + return time.Unix(millis, 0), nil + } + layouts := []string{ + time.RFC3339Nano, + time.RFC3339, + "2006-01-02 15:04:05", + "2006-01-02 15:04", + "2006-01-02", + } + var lastErr error + for _, layout := range layouts { + var parsed time.Time + var err error + if layout == time.RFC3339Nano || layout == time.RFC3339 { + parsed, err = time.Parse(layout, value) + } else { + parsed, err = time.ParseInLocation(layout, value, time.Local) + } + if err != nil { + lastErr = err + continue + } + if layout == "2006-01-02" && endOfDay { + parsed = parsed.Add(24*time.Hour - time.Nanosecond) + } + return parsed, nil + } + return time.Time{}, lastErr +} + func claimRecordView(row model.TaskCenterClaimRecord) ClaimRecordView { return ClaimRecordView{ ID: row.ID, diff --git a/internal/service/taskcenter/taskcenter_test.go b/internal/service/taskcenter/taskcenter_test.go index cf1d5ed..de438c5 100644 --- a/internal/service/taskcenter/taskcenter_test.go +++ b/internal/service/taskcenter/taskcenter_test.go @@ -410,6 +410,107 @@ func TestConditionJumpConfigAppliesToTasks(t *testing.T) { } } +func TestPageClaimRecordsFiltersTimeAndSummarizesGold(t *testing.T) { + service, db := newTestService(t, nil) + ctx := context.Background() + base := time.Date(2026, 5, 15, 10, 0, 0, 0, time.Local) + claimedAt := base + rows := []model.TaskCenterClaimRecord{ + { + ID: 1, + SysOrigin: "LIKEI", + UserID: 1001, + TaskID: 11, + TaskCode: "DAILY_MIC", + TaskName: "Mic", + TaskCategory: TaskCategoryDaily, + CycleKey: "20260515", + RewardGold: 100, + WalletEventID: "wallet-1", + Status: ClaimStatusSuccess, + ClaimedAt: &claimedAt, + CreateTime: base, + UpdateTime: base, + }, + { + ID: 2, + SysOrigin: "LIKEI", + UserID: 1002, + TaskID: 12, + TaskCode: "DAILY_GIFT", + TaskName: "Gift", + TaskCategory: TaskCategoryDaily, + CycleKey: "20260515", + RewardGold: 200, + WalletEventID: "wallet-2", + Status: ClaimStatusSuccess, + ClaimedAt: &claimedAt, + CreateTime: base.Add(time.Hour), + UpdateTime: base.Add(time.Hour), + }, + { + ID: 3, + SysOrigin: "LIKEI", + UserID: 1003, + TaskID: 11, + TaskCode: "DAILY_MIC", + TaskName: "Mic", + TaskCategory: TaskCategoryDaily, + CycleKey: "20260515", + RewardGold: 999, + WalletEventID: "wallet-3", + Status: ClaimStatusFailed, + FailureReason: "wallet failed", + CreateTime: base.Add(2 * time.Hour), + UpdateTime: base.Add(2 * time.Hour), + }, + { + ID: 4, + SysOrigin: "LIKEI", + UserID: 1004, + TaskID: 13, + TaskCode: "DAILY_OLD", + TaskName: "Old", + TaskCategory: TaskCategoryDaily, + CycleKey: "20260514", + RewardGold: 700, + WalletEventID: "wallet-4", + Status: ClaimStatusSuccess, + ClaimedAt: &claimedAt, + CreateTime: base.AddDate(0, 0, -1), + UpdateTime: base.AddDate(0, 0, -1), + }, + } + if err := db.Create(&rows).Error; err != nil { + t.Fatalf("create claim records: %v", err) + } + + resp, err := service.PageClaimRecords( + ctx, + "LIKEI", + TaskCategoryDaily, + 0, + "daily_mic", + "", + base.Format("2006-01-02 15:04:05"), + base.Add(3*time.Hour).Format("2006-01-02 15:04:05"), + 1, + 20, + ) + if err != nil { + t.Fatalf("PageClaimRecords() error = %v", err) + } + if resp.Total != 2 || len(resp.Records) != 2 { + t.Fatalf("records total=%d len=%d, want 2", resp.Total, len(resp.Records)) + } + if resp.Summary.TotalRewardGold != 300 { + t.Fatalf("total reward gold = %d, want 300", resp.Summary.TotalRewardGold) + } + if resp.Summary.SelectedTaskRewardGold != 100 { + t.Fatalf("selected task reward gold = %d, want 100", resp.Summary.SelectedTaskRewardGold) + } +} + func TestSaveConfigDerivesConditionJumpsWhenPayloadOmitted(t *testing.T) { service, _ := newTestService(t, nil) ctx := context.Background() diff --git a/internal/service/taskcenter/types.go b/internal/service/taskcenter/types.go index 8ce658b..573cf25 100644 --- a/internal/service/taskcenter/types.go +++ b/internal/service/taskcenter/types.go @@ -216,6 +216,20 @@ type PageResponse[T any] struct { Size int `json:"size"` } +type ClaimRecordSummary struct { + TotalRewardGold int64 `json:"totalRewardGold"` + SelectedTaskRewardGold int64 `json:"selectedTaskRewardGold"` + TaskCode string `json:"taskCode,omitempty"` +} + +type ClaimRecordPageResponse struct { + Records []ClaimRecordView `json:"records"` + Total int64 `json:"total"` + Current int `json:"current"` + Size int `json:"size"` + Summary ClaimRecordSummary `json:"summary"` +} + type ClaimRecordView struct { ID int64 `json:"id,string"` SysOrigin string `json:"sysOrigin"` diff --git a/migrations/041_task_center_claim_record_admin_indexes.sql b/migrations/041_task_center_claim_record_admin_indexes.sql new file mode 100644 index 0000000..5e264eb --- /dev/null +++ b/migrations/041_task_center_claim_record_admin_indexes.sql @@ -0,0 +1,46 @@ +SET @current_schema := DATABASE(); + +SET @add_task_center_claim_admin_time_sql := IF( + NOT EXISTS ( + SELECT 1 + FROM information_schema.statistics + WHERE table_schema = @current_schema + AND table_name = 'task_center_claim_record' + AND index_name = 'idx_task_center_claim_admin_time' + ), + 'ALTER TABLE `task_center_claim_record` ADD INDEX `idx_task_center_claim_admin_time` (`sys_origin`, `task_category`, `create_time`, `id`)', + 'SELECT 1' +); +PREPARE add_task_center_claim_admin_time_stmt FROM @add_task_center_claim_admin_time_sql; +EXECUTE add_task_center_claim_admin_time_stmt; +DEALLOCATE PREPARE add_task_center_claim_admin_time_stmt; + +SET @add_task_center_claim_admin_task_time_sql := IF( + NOT EXISTS ( + SELECT 1 + FROM information_schema.statistics + WHERE table_schema = @current_schema + AND table_name = 'task_center_claim_record' + AND index_name = 'idx_task_center_claim_admin_task_time' + ), + 'ALTER TABLE `task_center_claim_record` ADD INDEX `idx_task_center_claim_admin_task_time` (`sys_origin`, `task_category`, `task_code`, `create_time`, `id`)', + 'SELECT 1' +); +PREPARE add_task_center_claim_admin_task_time_stmt FROM @add_task_center_claim_admin_task_time_sql; +EXECUTE add_task_center_claim_admin_task_time_stmt; +DEALLOCATE PREPARE add_task_center_claim_admin_task_time_stmt; + +SET @add_task_center_claim_admin_status_time_sql := IF( + NOT EXISTS ( + SELECT 1 + FROM information_schema.statistics + WHERE table_schema = @current_schema + AND table_name = 'task_center_claim_record' + AND index_name = 'idx_task_center_claim_admin_status_time' + ), + 'ALTER TABLE `task_center_claim_record` ADD INDEX `idx_task_center_claim_admin_status_time` (`sys_origin`, `task_category`, `status`, `create_time`)', + 'SELECT 1' +); +PREPARE add_task_center_claim_admin_status_time_stmt FROM @add_task_center_claim_admin_status_time_sql; +EXECUTE add_task_center_claim_admin_status_time_stmt; +DEALLOCATE PREPARE add_task_center_claim_admin_status_time_stmt;