diff --git a/.vscode/launch.json b/.vscode/launch.json index 108c54803..956fa82b9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,6 +14,7 @@ "cwd": "${workspaceFolder}", "program": "${workspaceFolder}/login", "console": "integratedTerminal" + }, { "name": "Launch logic1", diff --git a/common/data/share/share.go b/common/data/share/share.go index 73f7eea25..dc2fb10d8 100644 --- a/common/data/share/share.go +++ b/common/data/share/share.go @@ -1,18 +1,18 @@ package share import ( - "blazing/cool" "context" "fmt" + "regexp" "strings" "time" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gcache" - "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/util/gconv" ) -var ShareManager=newSessionManager() + +var ShareManager = newSessionManager() var ( // ErrCacheMiss 表示缓存未命中 ErrCacheMiss = gerror.New("缓存未找到") @@ -26,22 +26,6 @@ type cacheStore[T any] struct { prefix string // 缓存键前缀 } -// newSessionStore 创建会话缓存实例 -func newSessionStore() *cacheStore[uint32] { - return &cacheStore[uint32]{ - manager: cool.CacheManager, - prefix: "blazing:session:", - } -} - -// newUserOnlineStore 创建用户在线状态缓存实例 -func newUserOnlineStore() *cacheStore[uint16] { - return &cacheStore[uint16]{ - manager: cool.CacheManager, - prefix: "blazing:useronline:", - } -} - // 生成带前缀的缓存键 func (s *cacheStore[T]) formatKey(key string) string { return s.prefix + strings.TrimSpace(key) @@ -126,56 +110,48 @@ func (s *cacheStore[T]) GetOrSet(ctx context.Context, key string, defaultValue T return converted, nil } -// sessionManager 会话管理器 -type sessionManager struct { - sessionStore *cacheStore[uint32] // 会话缓存 - userOnlineStore *cacheStore[uint16] // 用户在线状态缓存 -} - -// newSessionManager 创建会话管理器 -func newSessionManager() *sessionManager { - return &sessionManager{ - sessionStore: newSessionStore(), - userOnlineStore: newUserOnlineStore(), +// Scan 扫描匹配模式的键(先获取所有键,再内存筛选) +func (s *cacheStore[T]) Scan(ctx context.Context, pattern string) ([]string, error) { + // 1. 获取所有键 + allKeys, err := s.manager.Keys(ctx) + if err != nil { + return nil, gerror.Wrapf(err, "获取所有键失败") } + + // 2. 筛选符合模式的键(使用字符串匹配,支持简单通配符*) + var matchedKeys []string + for _, key := range allKeys { + // 简单模式匹配:支持*匹配任意字符(可根据需求扩展为glob匹配) + if matchSimplePattern(key.(string), pattern) { + matchedKeys = append(matchedKeys, key.(string)) + } + } + return matchedKeys, nil } -// GetSession 通过会话ID获取用户ID -func (m *sessionManager) GetSession(sessionID string) (uint32, error) { - return m.sessionStore.Get(context.Background(), sessionID) +// 简单模式匹配(支持*通配符) +func matchSimplePattern(str, pattern string) bool { + // 替换pattern中的*为正则匹配符,转义其他特殊字符 + regexPattern := strings.ReplaceAll(regexp.QuoteMeta(pattern), "\\*", ".*") + regexPattern = "^" + regexPattern + "$" // 全匹配 + matched, _ := regexp.MatchString(regexPattern, str) + return matched } -// SaveSession 保存会话信息 -func (m *sessionManager) SaveSession(sessionID string, userID uint32) error { - return m.sessionStore.Set(gctx.New(), sessionID, userID, time.Hour*24) -} - -// DeleteSession 删除会话 -func (m *sessionManager) DeleteSession(sessionID string) error { - return m.sessionStore.Del(gctx.New(), sessionID) -} - -// SessionExists 检查会话是否存在 -func (m *sessionManager) SessionExists(sessionID string) (bool, error) { - return m.sessionStore.Contains(context.Background(), sessionID) -} - -// SetUserOnline 设置用户在线状态 -func (m *sessionManager) SetUserOnline(userID uint32, serverID uint16) error { - return m.userOnlineStore.Set(gctx.New(), gconv.String(userID), serverID, 0) -} - -// GetUserOnline 获取用户在线状态 -func (m *sessionManager) GetUserOnline(userID uint32) (uint16, error) { - return m.userOnlineStore.Get(context.Background(), gconv.String(userID)) -} - -// DeleteUserOnline 删除用户在线状态 -func (m *sessionManager) DeleteUserOnline(userID uint32) error { - return m.userOnlineStore.Del(gctx.New(), gconv.String(userID)) -} - -// UserOnlineExists 检查用户在线状态是否存在 -func (m *sessionManager) UserOnlineExists(userID uint32) (bool, error) { - return m.userOnlineStore.Contains(context.Background(), gconv.String(userID)) +// MGet 批量获取多个键的值(循环调用Get实现) +func (s *cacheStore[T]) MGet(ctx context.Context, keys []string) ([]interface{}, error) { + values := make([]interface{}, len(keys)) + for i, key := range keys { + formattedKey := s.formatKey(key) // 格式化键(添加前缀) + result, err := s.manager.Get(ctx, formattedKey) + if err != nil { + return nil, gerror.Wrapf(err, "批量获取缓存失败,键: %s", key) + } + if result.IsEmpty() { + values[i] = nil // 未命中时存nil + } else { + values[i] = result.Val() + } + } + return values, nil } diff --git a/common/data/share/task.go b/common/data/share/task.go new file mode 100644 index 000000000..d1ac9b8cd --- /dev/null +++ b/common/data/share/task.go @@ -0,0 +1,210 @@ +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() diff --git a/common/data/share/user.go b/common/data/share/user.go new file mode 100644 index 000000000..8d1ba74ba --- /dev/null +++ b/common/data/share/user.go @@ -0,0 +1,80 @@ +package share + +import ( + "blazing/cool" + "context" + "time" + + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gconv" +) + +// newSessionStore 创建会话缓存实例 +func newSessionStore() *cacheStore[uint32] { + return &cacheStore[uint32]{ + manager: cool.CacheManager, + prefix: "blazing:session:", + } +} + +// newUserOnlineStore 创建用户在线状态缓存实例 +func newUserOnlineStore() *cacheStore[uint16] { + return &cacheStore[uint16]{ + manager: cool.CacheManager, + prefix: "blazing:useronline:", + } +} + +// sessionManager 会话管理器 +type sessionManager struct { + sessionStore *cacheStore[uint32] // 会话缓存 + userOnlineStore *cacheStore[uint16] // 用户在线状态缓存 +} + +// newSessionManager 创建会话管理器 +func newSessionManager() *sessionManager { + return &sessionManager{ + sessionStore: newSessionStore(), + userOnlineStore: newUserOnlineStore(), + } +} + +// GetSession 通过会话ID获取用户ID +func (m *sessionManager) GetSession(sessionID string) (uint32, error) { + return m.sessionStore.Get(context.Background(), sessionID) +} + +// SaveSession 保存会话信息 +func (m *sessionManager) SaveSession(sessionID string, userID uint32) error { + return m.sessionStore.Set(gctx.New(), sessionID, userID, time.Hour*24) +} + +// DeleteSession 删除会话 +func (m *sessionManager) DeleteSession(sessionID string) error { + return m.sessionStore.Del(gctx.New(), sessionID) +} + +// SessionExists 检查会话是否存在 +func (m *sessionManager) SessionExists(sessionID string) (bool, error) { + return m.sessionStore.Contains(context.Background(), sessionID) +} + +// SetUserOnline 设置用户在线状态 +func (m *sessionManager) SetUserOnline(userID uint32, serverID uint16) error { + return m.userOnlineStore.Set(gctx.New(), gconv.String(userID), serverID, 0) +} + +// GetUserOnline 获取用户在线状态 +func (m *sessionManager) GetUserOnline(userID uint32) (uint16, error) { + return m.userOnlineStore.Get(context.Background(), gconv.String(userID)) +} + +// DeleteUserOnline 删除用户在线状态 +func (m *sessionManager) DeleteUserOnline(userID uint32) error { + return m.userOnlineStore.Del(gctx.New(), gconv.String(userID)) +} + +// UserOnlineExists 检查用户在线状态是否存在 +func (m *sessionManager) UserOnlineExists(userID uint32) (bool, error) { + return m.userOnlineStore.Contains(context.Background(), gconv.String(userID)) +}