300 lines
9.0 KiB
Go
300 lines
9.0 KiB
Go
package smashegg
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// PageUserRecords returns a user's own smash egg draw records.
|
|
func (s *Service) PageUserRecords(ctx context.Context, user AuthUser, cursor int, limit int) (*RecordPageResponse, error) {
|
|
cursor, limit = normalizeRecordPage(cursor, limit)
|
|
var total int64
|
|
query := s.db.WithContext(ctx).Model(&model.SmashEggDrawRecord{}).
|
|
Where("sys_origin = ? AND user_id = ?", normalizeSysOrigin(user.SysOrigin), user.UserID)
|
|
if err := query.Count(&total).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
var rows []model.SmashEggDrawRecord
|
|
if err := query.Order("create_time desc, id desc").
|
|
Offset((cursor - 1) * limit).
|
|
Limit(limit).
|
|
Find(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &RecordPageResponse{
|
|
Records: drawRecordPayloads(rows),
|
|
Total: total,
|
|
Current: cursor,
|
|
Size: limit,
|
|
}, nil
|
|
}
|
|
|
|
// PageAdminRecords returns admin smash egg draw records.
|
|
func (s *Service) PageAdminRecords(ctx context.Context, sysOrigin string, userID int64, status string, cursor int, limit int, startTime string, endTime string) (*RecordPageResponse, error) {
|
|
cursor, limit = normalizeRecordPage(cursor, limit)
|
|
filter, err := newAdminRecordFilter(sysOrigin, userID, status, startTime, endTime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
query := s.adminRecordQuery(ctx, filter)
|
|
var total int64
|
|
if err := query.Count(&total).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
var rows []model.SmashEggDrawRecord
|
|
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
|
|
}
|
|
return &RecordPageResponse{
|
|
Records: drawRecordPayloads(rows),
|
|
Total: total,
|
|
Current: cursor,
|
|
Size: limit,
|
|
TotalPaidGold: summary.TotalPaidGold,
|
|
TotalRewardGold: summary.TotalRewardGold,
|
|
ReturnRatioBasisPoints: summary.ReturnRatioBasisPoints,
|
|
ReturnRatioPercent: basisPointsPercent(summary.ReturnRatioBasisPoints),
|
|
StartTime: formatDateTime(filter.StartTime),
|
|
EndTime: formatDateTime(filter.EndTime),
|
|
}, nil
|
|
}
|
|
|
|
type adminRecordFilter struct {
|
|
SysOrigin string
|
|
UserID int64
|
|
Status string
|
|
StartTime time.Time
|
|
EndTime time.Time
|
|
}
|
|
|
|
type adminRecordSummary struct {
|
|
TotalPaidGold int64
|
|
TotalRewardGold int64
|
|
ReturnRatioBasisPoints int
|
|
}
|
|
|
|
func newAdminRecordFilter(sysOrigin string, userID int64, status string, startRaw string, endRaw string) (adminRecordFilter, error) {
|
|
start, end, err := parseAdminRecordTimeRange(startRaw, endRaw)
|
|
if err != nil {
|
|
return adminRecordFilter{}, err
|
|
}
|
|
return adminRecordFilter{
|
|
SysOrigin: normalizeSysOrigin(sysOrigin),
|
|
UserID: userID,
|
|
Status: strings.ToUpper(strings.TrimSpace(status)),
|
|
StartTime: start,
|
|
EndTime: end,
|
|
}, nil
|
|
}
|
|
|
|
func parseAdminRecordTimeRange(startRaw string, endRaw string) (time.Time, time.Time, error) {
|
|
startRaw = strings.TrimSpace(startRaw)
|
|
endRaw = strings.TrimSpace(endRaw)
|
|
now := time.Now()
|
|
if startRaw == "" && endRaw == "" {
|
|
return now.AddDate(0, 0, -7), now, nil
|
|
}
|
|
|
|
var start time.Time
|
|
var end time.Time
|
|
var err error
|
|
if startRaw != "" {
|
|
start, err = parseAdminRecordTime(startRaw, false)
|
|
if err != nil {
|
|
return time.Time{}, time.Time{}, err
|
|
}
|
|
}
|
|
if endRaw != "" {
|
|
end, err = parseAdminRecordTime(endRaw, true)
|
|
if err != nil {
|
|
return time.Time{}, time.Time{}, err
|
|
}
|
|
}
|
|
if startRaw == "" {
|
|
start = end.AddDate(0, 0, -7)
|
|
}
|
|
if endRaw == "" {
|
|
end = start.AddDate(0, 0, 7)
|
|
}
|
|
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 parseAdminRecordTime(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 adminRecordFilter) *gorm.DB {
|
|
query := s.db.WithContext(ctx).Model(&model.SmashEggDrawRecord{}).
|
|
Where("create_time >= ? AND create_time <= ?", filter.StartTime, filter.EndTime)
|
|
if filter.SysOrigin != "" {
|
|
query = query.Where("sys_origin = ?", filter.SysOrigin)
|
|
}
|
|
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 adminRecordFilter) (adminRecordSummary, error) {
|
|
var summary adminRecordSummary
|
|
if err := s.adminRecordQuery(ctx, filter).
|
|
Select("COALESCE(SUM(reward_value_gold), 0)").
|
|
Scan(&summary.TotalRewardGold).Error; err != nil {
|
|
return summary, err
|
|
}
|
|
|
|
drawPaid := s.adminRecordQuery(ctx, filter).
|
|
Select("draw_no, MAX(paid_gold) AS paid_gold").
|
|
Group("draw_no")
|
|
if err := s.db.WithContext(ctx).
|
|
Table("(?) AS draw_paid", drawPaid).
|
|
Select("COALESCE(SUM(paid_gold), 0)").
|
|
Scan(&summary.TotalPaidGold).Error; err != nil {
|
|
return summary, err
|
|
}
|
|
if summary.TotalPaidGold > 0 {
|
|
summary.ReturnRatioBasisPoints = int((summary.TotalRewardGold*10000 + summary.TotalPaidGold/2) / summary.TotalPaidGold)
|
|
}
|
|
return summary, nil
|
|
}
|
|
|
|
// ListRecentNotices returns recent successful prize notices.
|
|
func (s *Service) ListRecentNotices(ctx context.Context, sysOrigin string, limit int) ([]DrawRecordPayload, error) {
|
|
if limit <= 0 {
|
|
limit = 20
|
|
}
|
|
if limit > 100 {
|
|
limit = 100
|
|
}
|
|
var rows []model.SmashEggDrawRecord
|
|
query := s.db.WithContext(ctx).Where("status = ?", drawStatusSuccess)
|
|
if normalized := normalizeSysOrigin(sysOrigin); normalized != "" {
|
|
query = query.Where("sys_origin = ?", normalized)
|
|
}
|
|
if err := query.Order("create_time desc, id desc").Limit(limit).Find(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return drawRecordPayloads(rows), nil
|
|
}
|
|
|
|
// PageDayRank returns today's top users by reward value.
|
|
func (s *Service) PageDayRank(ctx context.Context, sysOrigin string, cursor int, limit int) ([]DayRankPayload, error) {
|
|
cursor, limit = normalizeRecordPage(cursor, limit)
|
|
normalized := normalizeSysOrigin(sysOrigin)
|
|
now := time.Now()
|
|
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|
end := start.Add(24 * time.Hour)
|
|
type row struct {
|
|
UserID int64
|
|
Score int64
|
|
}
|
|
var rows []row
|
|
query := s.db.WithContext(ctx).Model(&model.SmashEggDrawRecord{}).
|
|
Select("user_id, COALESCE(SUM(reward_value_gold), 0) AS score").
|
|
Where("status = ? AND create_time >= ? AND create_time < ?", drawStatusSuccess, start, end)
|
|
if normalized != "" {
|
|
query = query.Where("sys_origin = ?", normalized)
|
|
}
|
|
if err := query.Group("user_id").
|
|
Order("score desc, user_id asc").
|
|
Offset((cursor - 1) * limit).
|
|
Limit(limit).
|
|
Scan(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
result := make([]DayRankPayload, 0, len(rows))
|
|
for _, item := range rows {
|
|
result = append(result, DayRankPayload{
|
|
UserID: item.UserID,
|
|
Score: item.Score,
|
|
})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
type DayRankPayload struct {
|
|
UserID int64 `json:"userId"`
|
|
Score int64 `json:"score"`
|
|
}
|
|
|
|
func drawRecordPayloads(rows []model.SmashEggDrawRecord) []DrawRecordPayload {
|
|
records := make([]DrawRecordPayload, 0, len(rows))
|
|
for _, row := range rows {
|
|
records = append(records, drawRecordPayload(row))
|
|
}
|
|
return records
|
|
}
|
|
|
|
func drawRecordPayload(row model.SmashEggDrawRecord) DrawRecordPayload {
|
|
return DrawRecordPayload{
|
|
ID: row.ID,
|
|
DrawNo: row.DrawNo,
|
|
EventID: row.EventID,
|
|
UserID: row.UserID,
|
|
SysOrigin: row.SysOrigin,
|
|
PoolType: normalizePoolType(row.PoolType),
|
|
DrawOptionID: row.DrawOptionID,
|
|
DrawTimes: row.DrawTimes,
|
|
PaidGold: row.PaidGold,
|
|
RewardType: row.RewardType,
|
|
ResourceID: ResourceID(int64PtrValue(row.RewardGroupID)),
|
|
ResourceType: row.ResourceType,
|
|
ResourceName: row.RewardGroupName,
|
|
ResourceURL: row.ResourceURL,
|
|
RewardGroupID: row.RewardGroupID,
|
|
RewardGroupName: row.RewardGroupName,
|
|
CoverURL: row.CoverURL,
|
|
AnimationURL: row.AnimationURL,
|
|
DurationDays: row.DurationDays,
|
|
DisplayGoldAmount: row.DisplayGoldAmount,
|
|
GoldAmount: row.GoldAmount,
|
|
RewardValueGold: row.RewardValueGold,
|
|
Probability: row.Probability,
|
|
Status: row.Status,
|
|
ErrorMessage: row.ErrorMessage,
|
|
CreateTime: formatDateTime(row.CreateTime),
|
|
UpdateTime: formatDateTime(row.UpdateTime),
|
|
}
|
|
}
|