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()