@@ -12,61 +12,190 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// newDailyCounterStore 创建每日计数器缓存实例
func newDailyCounterStore ( ) * cacheStore [ int64 ] {
// 周期类型定义
type PeriodType int8
const (
PeriodDaily PeriodType = 1 // 每日
PeriodWeekly PeriodType = 2 // 每周
PeriodMonthly PeriodType = 3 // 每月
PeriodQuarter PeriodType = 4 // 每季
)
// 周期函数类型定义
type (
// FormatFunc 生成周期格式化字符串的函数
FormatFunc func ( t time . Time ) string
// ExpireFunc 计算周期过期时间的函数
ExpireFunc func ( t time . Time ) time . Duration
)
// Period 周期结构体,聚合周期相关函数和元信息
type Period struct {
Type PeriodType // 周期类型
Format FormatFunc // 周期格式化函数
Expire ExpireFunc // 过期时间计算函数
Prefix string // 缓存键前缀
}
// 周期实例 - 每日
var DailyPeriod = Period {
Type : PeriodDaily ,
Format : func ( t time . Time ) string {
return t . Format ( "20060102" ) // 格式: YYYYMMDD
} ,
Expire : func ( t time . Time ) time . Duration {
tomorrow := t . AddDate ( 0 , 0 , 1 )
resetTime := time . Date ( tomorrow . Year ( ) , tomorrow . Month ( ) , tomorrow . Day ( ) , 0 , 0 , 0 , 0 , time . Local )
return resetTime . Sub ( t )
} ,
Prefix : "daily:" ,
}
// 周期实例 - 每周
var WeeklyPeriod = Period {
Type : PeriodWeekly ,
Format : func ( t time . Time ) string {
// 计算当年第几周 (ISO周编号)
year , week := t . ISOWeek ( )
return fmt . Sprintf ( "%dW%02d" , year , week ) // 格式: YYYYWww
} ,
Expire : func ( t time . Time ) time . Duration {
// 计算下周一0点
weekday := t . Weekday ( )
daysToNextMonday := ( 8 - int ( weekday ) ) % 7
nextMonday := t . AddDate ( 0 , 0 , daysToNextMonday )
resetTime := time . Date ( nextMonday . Year ( ) , nextMonday . Month ( ) , nextMonday . Day ( ) , 0 , 0 , 0 , 0 , time . Local )
return resetTime . Sub ( t )
} ,
Prefix : "weekly:" ,
}
// 周期实例 - 每月
var MonthlyPeriod = Period {
Type : PeriodMonthly ,
Format : func ( t time . Time ) string {
return t . Format ( "200601" ) // 格式: YYYYMM
} ,
Expire : func ( t time . Time ) time . Duration {
// 计算下月1日0点
nextMonth := t . AddDate ( 0 , 1 , 0 )
resetTime := time . Date ( nextMonth . Year ( ) , nextMonth . Month ( ) , 1 , 0 , 0 , 0 , 0 , time . Local )
return resetTime . Sub ( t )
} ,
Prefix : "monthly:" ,
}
// 周期实例 - 每季
var QuarterlyPeriod = Period {
Type : PeriodQuarter ,
Format : func ( t time . Time ) string {
quarter := ( int ( t . Month ( ) ) - 1 ) / 3 + 1
return fmt . Sprintf ( "%dQ%d" , t . Year ( ) , quarter ) // 格式: YYYYQq
} ,
Expire : func ( t time . Time ) time . Duration {
// 计算下季首月1日0点
quarter := ( int ( t . Month ( ) ) - 1 ) / 3 + 1
nextQuarterFirstMonth := quarter * 3 + 1
year := t . Year ( )
if nextQuarterFirstMonth > 12 {
nextQuarterFirstMonth = 1
year ++
}
resetTime := time . Date ( year , time . Month ( nextQuarterFirstMonth ) , 1 , 0 , 0 , 0 , 0 , time . Local )
return resetTime . Sub ( t )
} ,
Prefix : "quarterly:" ,
}
// 周期映射表,用于快速查找周期实例
var periodMap = map [ PeriodType ] * Period {
PeriodDaily : & DailyPeriod ,
PeriodWeekly : & WeeklyPeriod ,
PeriodMonthly : & MonthlyPeriod ,
PeriodQuarter : & QuarterlyPeriod ,
}
// GetPeriodByType 根据周期类型获取周期实例
func GetPeriodByType ( periodType PeriodType ) ( * Period , error ) {
period , ok := periodMap [ periodType ]
if ! ok {
return nil , fmt . Errorf ( "不支持的周期类型: %d" , periodType )
}
return period , nil
}
// newCounterStore 创建计数器缓存实例
func newCounterStore ( period * Period ) * cacheStore [ int64 ] {
return & cacheStore [ int64 ] {
manager : cool . CacheManager ,
prefix : "blazing:daily counter:" ,
prefix : fmt . Sprintf ( "blazing:counter:%s " , period . Prefix ) ,
}
}
// newCounter LimitStore 创建计数器 限制配置缓存实例
func newCounter LimitStore ( ) * cacheStore [ int64 ] {
// newLimitStore 创建限制配置缓存实例
func newLimitStore ( ) * cacheStore [ int64 ] {
return & cacheStore [ int64 ] {
manager : cool . CacheManager ,
prefix : "blazing:counterlimit:" ,
}
}
// c ounterManager 计数器管理器(每日计数+限制配置+批量查询)
type c ounterManager struct {
dailyCounter Store * cacheStore [ int64 ] // 每日计数器 缓存
counterLimitStore * cacheStore [ int64 ] // 计数器限制配置 缓存
// C ounterManager 计数器管理器
type C ounterManager struct {
limit Store * cacheStore [ int64 ] // 限制配置 缓存
periods map [ PeriodType ] * cacheStore [ int64 ] // 各周期 计数器缓存
}
// n ewCounterManager 创建计数器管理器实例
func n ewCounterManager( ) * c ounterManager {
return & counterManager {
dailyCounterStore : newDailyCounterStore ( ) ,
counterLimitStore : newCounterLimitStore ( ) ,
// N ewCounterManager 创建计数器管理器实例
func N ewCounterManager( ) * C ounterManager {
// 初始化所有周期的缓存存储
periods := make ( map [ PeriodType ] * cacheStore [ int64 ] )
for _ , period := range periodMap {
periods [ period . Type ] = newCounterStore ( period )
}
return & CounterManager {
limitStore : newLimitStore ( ) ,
periods : periods ,
}
}
// genDaily Key 生成单个奖品类型的每日 计数键
// 格式:{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 )
// genKey 生成计数器 键
func ( m * CounterManager ) genKey ( period * Period , userID uint32 , rewardType uint32) string {
periodStr := period . Format ( time . Now ( ) )
return fmt . Sprintf ( "%d:%d:%s" , userID , rewardType , periodStr )
}
// genUserDaily Prefix 生成用户当日 所有计数的键前缀(用于扫描)
func ( m * c ounterManager) genUserDaily Prefix ( userID uint32 ) string {
date := time . Now ( ) . Format ( "20060102" )
return fmt . Sprintf ( "%d:*:%s" , userID , date ) // 格式:{userID}:*:{date}
// genUserPrefix 生成用户某周期 所有计数的键前缀
func ( m * C ounterManager) genUserPrefix ( period * Period , userID uint32 ) string {
periodStr := period . Format ( time . Now ( ) )
return fmt . Sprintf ( "%d:*:%s" , userID , periodStr )
}
// GetDaily Count 获取用户当日 某奖品的计数
func ( m * c ounterManager) GetDaily Count ( userID uint32 , rewardType uint32 ) ( int64 , error ) {
key := m . genDailyKey ( userID , reward Type)
return m . dailyCounterStore . Get ( context . Background ( ) , key )
// GetCount 获取用户某周期 某奖品的计数
func ( m * C ounterManager) GetCount ( period * Period , userID uint32 , rewardType uint32 ) ( int64 , error ) {
store , ok := m . periods [ period . Type]
if ! ok {
return 0 , fmt . Errorf ( "不支持的周期类型: %d" , period . Type )
}
key := m . genKey ( period , userID , rewardType )
return store . Get ( context . Background ( ) , key )
}
// IncrDaily Count 增加用户当日 某奖品的计数(自动设置次日0点 过期)
func ( m * c ounterManager) IncrDaily Count ( userID uint32 , rewardType uint32 ) ( int64 , error ) {
key := m . genDailyKey ( userID , reward Type)
// IncrCount 增加用户某周期 某奖品的计数(自动设置周期结束 过期)
func ( m * C ounterManager) IncrCount ( period * Period , userID uint32 , rewardType uint32 ) ( int64 , error ) {
store , ok := m . periods [ period . Type]
if ! ok {
return 0 , fmt . Errorf ( "不支持的周期类型: %d" , period . Type )
}
key := m . genKey ( period , userID , rewardType )
// 获取当前计数( 不存在则视为0)
current , err := m . dailyCounterS tore. Get ( context . Background ( ) , key )
current , err := s tore. Get ( context . Background ( ) , key )
if err != nil && err != ErrCacheMiss {
return 0 , fmt . Errorf ( "获取当前计数失败: %w" , err )
}
@@ -77,40 +206,43 @@ func (m *counterManager) IncrDailyCount(userID uint32, rewardType uint32) (int64
// 计算新计数
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 ( ) )
// 计算过期时间
ttl := period . Expire ( time. Now ( ) )
// 保存新计数并设置过期时间
if err := m . dailyCounterS tore. Set ( gctx . New ( ) , key , newValue , ttl ) ; err != nil {
if err := s tore. Set ( gctx . New ( ) , key , newValue , ttl ) ; err != nil {
return 0 , fmt . Errorf ( "保存计数失败: %w" , err )
}
return newValue , nil
}
// ResetDaily Count 重置用户当日 某奖品的计数
func ( m * c ounterManager) ResetDaily Count ( userID uint32 , rewardType uint32 ) error {
key := m . genDailyKey ( userID , reward Type)
return m . dailyCounterStore . Del ( gctx . New ( ) , key )
// ResetCount 重置用户某周期 某奖品的计数
func ( m * C ounterManager) ResetCount ( period * Period , userID uint32 , rewardType uint32 ) error {
store , ok := m . periods [ period . Type]
if ! ok {
return fmt . Errorf ( "不支持的周期类型: %d" , period . Type )
}
key := m . genKey ( period , userID , rewardType )
return store . Del ( gctx . New ( ) , key )
}
// SetCounter Limit 设置某奖品类型的每日 限制
func ( m * c ounterManager) SetCounter Limit ( rewardType uint32 , limit int64 ) error {
key := fmt . Sprintf ( "%d" , rewardType ) // 限制键直接使用奖品类型( uint32)
return m . counterL imitStore. Set ( gctx . New ( ) , key , limit , time . Hour * 24 * 7 ) // 限制配置保留7天
// SetLimit 设置某周期某 奖品类型的限制
func ( m * C ounterManager) SetLimit ( period * Period , rewardType uint32 , limit int64 ) error {
key := fmt . Sprintf ( "%d:%d " , period . Type , rewardType)
return m . l imitStore. Set ( gctx . New ( ) , key , limit , time . Hour * 24 * 7 ) // 限制配置保留7天
}
// GetCounter Limit 获取某奖品类型的每日 限制
func ( m * c ounterManager) GetCounter Limit ( rewardType uint32 ) ( int64 , error ) {
key := fmt . Sprintf ( "%d" , rewardType )
return m . counterL imitStore. Get ( context . Background ( ) , key )
// GetLimit 获取某周期某 奖品类型的限制
func ( m * C ounterManager) GetLimit ( period * Period , rewardType uint32 ) ( int64 , error ) {
key := fmt . Sprintf ( "%d:%d" , period . Type , rewardType )
return m . l imitStore. Get ( context . Background ( ) , key )
}
// IsExceedLimit 检查用户当日 某奖品的计数是否超过限制
func ( m * c ounterManager) IsExceedLimit ( userID uint32 , rewardType uint32 ) ( bool , error ) {
count , err := m . GetDaily Count ( userID , rewardType )
// IsExceedLimit 检查用户某周期 某奖品的计数是否超过限制
func ( m * C ounterManager) IsExceedLimit ( period * Period , userID uint32 , rewardType uint32 ) ( bool , error ) {
count , err := m . GetCount ( period , userID , rewardType )
if err != nil && err != ErrCacheMiss {
return false , fmt . Errorf ( "获取计数失败: %w" , err )
}
@@ -119,7 +251,7 @@ func (m *counterManager) IsExceedLimit(userID uint32, rewardType uint32) (bool,
count = 0
}
limit , err := m . GetCounter Limit ( rewardType )
limit , err := m . GetLimit ( period , rewardType )
if err != nil {
return false , fmt . Errorf ( "获取限制失败: %w" , err )
}
@@ -127,20 +259,24 @@ func (m *counterManager) IsExceedLimit(userID uint32, rewardType uint32) (bool,
return count >= limit , nil
}
// GetUserAllDaily Counts 获取用户当日 所有奖品类型的计数
func ( m * c ounterManager) GetUserAllDaily Counts ( userID uint32 ) ( map [ uint32 ] int64 , error ) {
// 1. 生成扫描模式(包含缓存前缀,使用*通配符)
// 格式: blazing:dailycounter:{userID}:*:{今日日期}
today := time . Now ( ) . Format ( "20060102" )
// GetUserAllCounts 获取用户某周期 所有奖品类型的计数
func ( m * C ounterManager) GetUserAllCounts ( period * Period , userID uint32 ) ( map [ uint32 ] int64 , error ) {
store , ok := m . periods [ period . Type ]
if ! ok {
return nil , fmt . Errorf ( "不支持的周期类型: %d" , period . Type )
}
// 1. 生成扫描模式
periodStr := period . Format ( time . Now ( ) )
matchPattern := fmt . Sprintf (
"%s%d:*:%s" ,
m . dailyCounterS tore. prefix ,
s tore. prefix ,
userID ,
today ,
periodStr ,
)
// 2. 扫描所有匹配的键( 通过Scan方法筛选)
keys , err := m . dailyCounterS tore. Scan ( context . Background ( ) , matchPattern )
// 2. 扫描所有匹配的键
keys , err := s tore. Scan ( context . Background ( ) , matchPattern )
if err != nil {
return nil , fmt . Errorf ( "扫描键失败: %w" , err )
}
@@ -150,16 +286,16 @@ func (m *counterManager) GetUserAllDailyCounts(userID uint32) (map[uint32]int64,
// 3. 提取核心键(去除缓存前缀)
coreKeys := make ( [ ] string , 0 , len ( keys ) )
prefixLen := len ( m . dailyCounterS tore. prefix )
prefixLen := len ( s tore. prefix )
for _ , fullKey := range keys {
if strings . HasPrefix ( fullKey , m . dailyCounterS tore. prefix ) {
coreKey := fullKey [ prefixLen : ] // 核心键格式: userID:rewardType:date
if strings . HasPrefix ( fullKey , s tore. prefix ) {
coreKey := fullKey [ prefixLen : ] // 核心键格式: userID:rewardType:periodStr
coreKeys = append ( coreKeys , coreKey )
}
}
// 4. 批量获取计数
values , err := m . dailyCounterS tore. MGet ( context . Background ( ) , coreKeys )
values , err := s tore. MGet ( context . Background ( ) , coreKeys )
if err != nil {
return nil , fmt . Errorf ( "批量获取计数失败: %w" , err )
}
@@ -173,16 +309,16 @@ func (m *counterManager) GetUserAllDailyCounts(userID uint32) (map[uint32]int64,
continue
}
// 验证用户ID(防止异常键干扰)
// 验证用户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 )
// 验证周期
if parts [ 2 ] != periodStr {
fmt . Printf ( "[WARN] 忽略过期键: %s( 周 期不匹配)\n" , coreKey )
continue
}
@@ -206,5 +342,5 @@ func (m *counterManager) GetUserAllDailyCounts(userID uint32) (map[uint32]int64,
return result , nil
}
// 全局计数器管理器实例(根据实际需求初始化)
var CounterManager = n ewCounterManager( )
// 全局计数器管理器实例
var Global CounterManager = N ewCounterManager( )