Files
bl/common/data/share/task.go

211 lines
6.5 KiB
Go
Raw Normal View History

package share
import (
"blazing/cool"
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/util/gconv"
)
// newDailyCounterStore 创建每日计数器缓存实例
func newDailyCounterStore() *cacheStore[int64] {
return &cacheStore[int64]{
manager: cool.CacheManager,
prefix: "blazing:dailycounter:",
}
}
// newCounterLimitStore 创建计数器限制配置缓存实例
func newCounterLimitStore() *cacheStore[int64] {
return &cacheStore[int64]{
manager: cool.CacheManager,
prefix: "blazing:counterlimit:",
}
}
// counterManager 计数器管理器(每日计数+限制配置+批量查询)
type counterManager struct {
dailyCounterStore *cacheStore[int64] // 每日计数器缓存
counterLimitStore *cacheStore[int64] // 计数器限制配置缓存
}
// newCounterManager 创建计数器管理器实例
func newCounterManager() *counterManager {
return &counterManager{
dailyCounterStore: newDailyCounterStore(),
counterLimitStore: newCounterLimitStore(),
}
}
// genDailyKey 生成单个奖品类型的每日计数键
// 格式:{userID}:{rewardType}:{date}userID和rewardType为uint32
func (m *counterManager) genDailyKey(userID uint32, rewardType uint32) string {
date := time.Now().Format("20060102") // 当日日期YYYYMMDD
return fmt.Sprintf("%d:%d:%s", userID, rewardType, date)
}
// genUserDailyPrefix 生成用户当日所有计数的键前缀(用于扫描)
func (m *counterManager) genUserDailyPrefix(userID uint32) string {
date := time.Now().Format("20060102")
return fmt.Sprintf("%d:*:%s", userID, date) // 格式:{userID}:*:{date}
}
// GetDailyCount 获取用户当日某奖品的计数
func (m *counterManager) GetDailyCount(userID uint32, rewardType uint32) (int64, error) {
key := m.genDailyKey(userID, rewardType)
return m.dailyCounterStore.Get(context.Background(), key)
}
// IncrDailyCount 增加用户当日某奖品的计数自动设置次日0点过期
func (m *counterManager) IncrDailyCount(userID uint32, rewardType uint32) (int64, error) {
key := m.genDailyKey(userID, rewardType)
// 获取当前计数不存在则视为0
current, err := m.dailyCounterStore.Get(context.Background(), key)
if err != nil && err != ErrCacheMiss {
return 0, fmt.Errorf("获取当前计数失败: %w", err)
}
if err == ErrCacheMiss {
current = 0
}
// 计算新计数
newValue := current + 1
// 计算过期时间到次日0点确保当日数据自动重置
tomorrow := time.Now().AddDate(0, 0, 1)
resetTime := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 0, 0, 0, 0, time.Local)
ttl := resetTime.Sub(time.Now())
// 保存新计数并设置过期时间
if err := m.dailyCounterStore.Set(gctx.New(), key, newValue, ttl); err != nil {
return 0, fmt.Errorf("保存计数失败: %w", err)
}
return newValue, nil
}
// ResetDailyCount 重置用户当日某奖品的计数
func (m *counterManager) ResetDailyCount(userID uint32, rewardType uint32) error {
key := m.genDailyKey(userID, rewardType)
return m.dailyCounterStore.Del(gctx.New(), key)
}
// SetCounterLimit 设置某奖品类型的每日限制
func (m *counterManager) SetCounterLimit(rewardType uint32, limit int64) error {
key := fmt.Sprintf("%d", rewardType) // 限制键直接使用奖品类型uint32
return m.counterLimitStore.Set(gctx.New(), key, limit, time.Hour*24*7) // 限制配置保留7天
}
// GetCounterLimit 获取某奖品类型的每日限制
func (m *counterManager) GetCounterLimit(rewardType uint32) (int64, error) {
key := fmt.Sprintf("%d", rewardType)
return m.counterLimitStore.Get(context.Background(), key)
}
// IsExceedLimit 检查用户当日某奖品的计数是否超过限制
func (m *counterManager) IsExceedLimit(userID uint32, rewardType uint32) (bool, error) {
count, err := m.GetDailyCount(userID, rewardType)
if err != nil && err != ErrCacheMiss {
return false, fmt.Errorf("获取计数失败: %w", err)
}
// 计数不存在时视为0
if err == ErrCacheMiss {
count = 0
}
limit, err := m.GetCounterLimit(rewardType)
if err != nil {
return false, fmt.Errorf("获取限制失败: %w", err)
}
return count >= limit, nil
}
// GetUserAllDailyCounts 获取用户当日所有奖品类型的计数
func (m *counterManager) GetUserAllDailyCounts(userID uint32) (map[uint32]int64, error) {
// 1. 生成扫描模式(包含缓存前缀,使用*通配符)
// 格式blazing:dailycounter:{userID}:*:{今日日期}
today := time.Now().Format("20060102")
matchPattern := fmt.Sprintf(
"%s%d:*:%s",
m.dailyCounterStore.prefix,
userID,
today,
)
// 2. 扫描所有匹配的键通过Scan方法筛选
keys, err := m.dailyCounterStore.Scan(context.Background(), matchPattern)
if err != nil {
return nil, fmt.Errorf("扫描键失败: %w", err)
}
if len(keys) == 0 {
return make(map[uint32]int64), nil // 无数据时返回空map
}
// 3. 提取核心键(去除缓存前缀)
coreKeys := make([]string, 0, len(keys))
prefixLen := len(m.dailyCounterStore.prefix)
for _, fullKey := range keys {
if strings.HasPrefix(fullKey, m.dailyCounterStore.prefix) {
coreKey := fullKey[prefixLen:] // 核心键格式userID:rewardType:date
coreKeys = append(coreKeys, coreKey)
}
}
// 4. 批量获取计数
values, err := m.dailyCounterStore.MGet(context.Background(), coreKeys)
if err != nil {
return nil, fmt.Errorf("批量获取计数失败: %w", err)
}
// 5. 解析结果
result := make(map[uint32]int64, len(coreKeys))
for i, coreKey := range coreKeys {
parts := strings.Split(coreKey, ":")
if len(parts) != 3 {
fmt.Printf("[WARN] 忽略格式错误的键: %s\n", coreKey)
continue
}
// 验证用户ID防止异常键干扰
parsedUserID, err := strconv.ParseUint(parts[0], 10, 32)
if err != nil || uint32(parsedUserID) != userID {
fmt.Printf("[WARN] 忽略用户不匹配的键: %s\n", coreKey)
continue
}
// 验证日期(确保是今日)
if parts[2] != today {
fmt.Printf("[WARN] 忽略过期键: %s日期不匹配\n", coreKey)
continue
}
// 解析rewardType
rewardType, err := strconv.ParseUint(parts[1], 10, 32)
if err != nil {
fmt.Printf("[WARN] 解析rewardType失败: %s\n", coreKey)
continue
}
// 解析计数
val := values[i]
if val == nil {
continue
}
count := gconv.Int64(val)
result[uint32(rewardType)] = count
}
return result, nil
}
// 全局计数器管理器实例(根据实际需求初始化)
var CounterManager = newCounterManager()