Files
bl/common/data/share/task.go
昔念 0016be7ad0 feat(common): 重构 share 包并添加缓存扫描功能
- 移除了 sessionManager 结构体和相关方法
- 新增 cacheStore 结构体的 Scan 方法,用于扫描匹配模式的键
- 新增 cacheStore 结构体的 MGet 方法,用于批量获取多个键的值
- 优化了代码结构,提高了缓存操作的灵活性和效率
2025-08-09 22:29:41 +08:00

211 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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