feat: add daily task claim record filters
This commit is contained in:
parent
31433b0a1b
commit
fc214f9991
@ -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"))),
|
||||
)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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"`
|
||||
|
||||
46
migrations/041_task_center_claim_record_admin_indexes.sql
Normal file
46
migrations/041_task_center_claim_record_admin_indexes.sql
Normal file
@ -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;
|
||||
Loading…
x
Reference in New Issue
Block a user