233 lines
5.9 KiB
Go
233 lines
5.9 KiB
Go
package weekstar
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/model"
|
|
)
|
|
|
|
// buildGiftPayloads 把礼物配置表转换成对外载体。
|
|
func buildGiftPayloads(rows []model.WeekStarActivityGiftConfig) []WeekStarGiftConfigPayload {
|
|
sort.Slice(rows, func(i, j int) bool {
|
|
return rows[i].Sort < rows[j].Sort
|
|
})
|
|
result := make([]WeekStarGiftConfigPayload, 0, len(rows))
|
|
for _, row := range rows {
|
|
result = append(result, WeekStarGiftConfigPayload{
|
|
GiftID: row.GiftID,
|
|
GiftName: row.GiftName,
|
|
GiftPhoto: row.GiftPhoto,
|
|
GiftCandy: row.GiftCandy,
|
|
Sort: row.Sort,
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
// calculateGiftScore 按活动口径计算一笔送礼的金币积分。
|
|
func calculateGiftScore(event weekStarGiftEvent) int64 {
|
|
if actual := int64(event.GiftValue.ActualAmount); actual > 0 {
|
|
return actual
|
|
}
|
|
acceptUserSize := int64(countDistinctAcceptUsers(event.Accepts))
|
|
if acceptUserSize <= 0 {
|
|
acceptUserSize = 1
|
|
}
|
|
quantity := int64(event.Quantity)
|
|
if quantity <= 0 {
|
|
quantity = 1
|
|
}
|
|
giftCandy := int64(event.GiftConfig.GiftCandy)
|
|
if giftCandy <= 0 {
|
|
return 0
|
|
}
|
|
return giftCandy * quantity * acceptUserSize
|
|
}
|
|
|
|
// countDistinctAcceptUsers 统计不同收礼用户数。
|
|
func countDistinctAcceptUsers(users []weekStarGiftEventUser) int {
|
|
if len(users) == 0 {
|
|
return 0
|
|
}
|
|
set := make(map[int64]struct{}, len(users))
|
|
for _, user := range users {
|
|
if int64(user.AcceptUserID) <= 0 {
|
|
continue
|
|
}
|
|
set[int64(user.AcceptUserID)] = struct{}{}
|
|
}
|
|
return len(set)
|
|
}
|
|
|
|
// containsGiftID 判断礼物是否命中活动指定礼物列表。
|
|
func containsGiftID(rows []model.WeekStarActivityGiftConfig, giftID int64) bool {
|
|
for _, row := range rows {
|
|
if row.GiftID == giftID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// parseRedisMemberInt64 把 Redis member 统一解析为用户 ID。
|
|
func parseRedisMemberInt64(member any) (int64, bool) {
|
|
switch value := member.(type) {
|
|
case string:
|
|
parsed, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
|
|
return parsed, err == nil
|
|
case []byte:
|
|
parsed, err := strconv.ParseInt(strings.TrimSpace(string(value)), 10, 64)
|
|
return parsed, err == nil
|
|
case int64:
|
|
return value, true
|
|
case int:
|
|
return int64(value), true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
// parseDateTimeInLocation 按指定时区解析后台传入的时间字符串。
|
|
func parseDateTimeInLocation(value string, location *time.Location) (time.Time, error) {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return time.Time{}, fmt.Errorf("time is required")
|
|
}
|
|
layouts := []string{
|
|
"2006-01-02 15:04:05",
|
|
"2006-01-02T15:04:05",
|
|
time.RFC3339,
|
|
"2006-01-02",
|
|
}
|
|
for _, layout := range layouts {
|
|
if layout == time.RFC3339 {
|
|
if parsed, err := time.Parse(layout, value); err == nil {
|
|
return parsed.In(location), nil
|
|
}
|
|
continue
|
|
}
|
|
if parsed, err := time.ParseInLocation(layout, value, location); err == nil {
|
|
return parsed, nil
|
|
}
|
|
}
|
|
return time.Time{}, fmt.Errorf("unsupported time format: %s", value)
|
|
}
|
|
|
|
// resolveMillisTime 把毫秒时间戳解析为时间,失败时回退默认值。
|
|
func resolveMillisTime(raw int64, fallback time.Time) time.Time {
|
|
if raw <= 0 {
|
|
return fallback
|
|
}
|
|
if raw < 1_000_000_000_000 {
|
|
return time.Unix(raw, 0)
|
|
}
|
|
return time.UnixMilli(raw)
|
|
}
|
|
|
|
// normalizePage 规范分页参数,避免非法页码或 pageSize。
|
|
func normalizePage(cursor, limit int) (int, int) {
|
|
if cursor <= 0 {
|
|
cursor = 1
|
|
}
|
|
if limit <= 0 {
|
|
limit = 20
|
|
}
|
|
if limit > 100 {
|
|
limit = 100
|
|
}
|
|
return cursor, limit
|
|
}
|
|
|
|
// formatDateTime 把时间格式化成统一展示字符串。
|
|
func formatDateTime(t time.Time) string {
|
|
if t.IsZero() {
|
|
return ""
|
|
}
|
|
return t.Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
// formatPeriodText 把周期起止时间格式化成页面展示文案。
|
|
func formatPeriodText(start, end time.Time) string {
|
|
if start.IsZero() || end.IsZero() || !end.After(start) {
|
|
return ""
|
|
}
|
|
return start.Format("2006/01/02") + "-" + end.Format("2006/01/02")
|
|
}
|
|
|
|
// truncateString 截断字符串,避免超出字段长度限制。
|
|
func truncateString(value string, limit int) string {
|
|
if limit <= 0 || len(value) <= limit {
|
|
return value
|
|
}
|
|
return value[:limit]
|
|
}
|
|
|
|
// maxTime 返回两个时间中的较大值。
|
|
func maxTime(left, right time.Time) time.Time {
|
|
if left.After(right) {
|
|
return left
|
|
}
|
|
return right
|
|
}
|
|
|
|
// minTime 返回两个时间中的较小值。
|
|
func minTime(left, right time.Time) time.Time {
|
|
if left.Before(right) {
|
|
return left
|
|
}
|
|
return right
|
|
}
|
|
|
|
// maxInt64 返回两个整数中的较大值。
|
|
func maxInt64(left, right int64) int64 {
|
|
if left > right {
|
|
return left
|
|
}
|
|
return right
|
|
}
|
|
|
|
// isDuplicateKeyErr 判断是否是数据库唯一键冲突错误。
|
|
func isDuplicateKeyErr(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
message := strings.ToLower(err.Error())
|
|
return strings.Contains(message, "duplicate entry") || strings.Contains(message, "duplicate key")
|
|
}
|
|
|
|
// toWeekStarRewardRecordView 把发奖记录模型转换成后台展示对象。
|
|
func toWeekStarRewardRecordView(row model.WeekStarRewardRecord) WeekStarRewardRecordView {
|
|
return WeekStarRewardRecordView{
|
|
ID: row.ID,
|
|
ConfigID: row.ConfigID,
|
|
CycleKey: row.CycleKey,
|
|
UserID: row.UserID,
|
|
UserAvatar: row.UserAvatar,
|
|
UserNickname: row.UserNickname,
|
|
Account: row.Account,
|
|
CountryCode: row.CountryCode,
|
|
CountryName: row.CountryName,
|
|
Rank: row.Rank,
|
|
RewardGroupID: row.RewardGroupID,
|
|
RewardGroupName: row.RewardGroupName,
|
|
ScoreGold: row.ScoreGold,
|
|
Status: row.Status,
|
|
RetryCount: row.RetryCount,
|
|
LastError: row.LastError,
|
|
SentAt: formatDateTime(pointerTimeValue(row.SentAt)),
|
|
UpdateTime: formatDateTime(row.UpdateTime),
|
|
}
|
|
}
|
|
|
|
// pointerTimeValue 把可空时间指针转换成非空时间值。
|
|
func pointerTimeValue(value *time.Time) time.Time {
|
|
if value == nil {
|
|
return time.Time{}
|
|
}
|
|
return *value
|
|
}
|