feat(cache): 添加复合键缓存操作支持 添加了基于 uint32+string 组合键的缓存操作方法,包括 GetByCompoundKey、SetByCompoundKey、DelByCompoundKey 和 ContainsByCompoundKey 方法,用于处理用户ID和会话ID的组合缓存场景 fix(vscode): 添加 cSpell 配置支持 struc 词汇 refactor(session): 移除过时的会话管理方法 移除了基于单一字符串键的会话管理方法,因为已迁移到使用 复合键的缓存操作方式 ```
225 lines
7.3 KiB
Go
225 lines
7.3 KiB
Go
package share
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gogf/gf/v2/errors/gerror"
|
||
"github.com/gogf/gf/v2/os/gcache"
|
||
"github.com/gogf/gf/v2/util/gconv"
|
||
)
|
||
|
||
var ShareManager = newSessionManager()
|
||
var (
|
||
// ErrCacheMiss 表示缓存未命中
|
||
ErrCacheMiss = gerror.New("缓存未找到")
|
||
// ErrTypeConvert 表示类型转换失败
|
||
ErrTypeConvert = gerror.New("缓存值类型转换失败")
|
||
)
|
||
|
||
// cacheStore 泛型缓存存储
|
||
type cacheStore[T any] struct {
|
||
manager *gcache.Cache // 缓存管理器
|
||
prefix string // 缓存键前缀
|
||
}
|
||
|
||
// 生成带前缀的缓存键 - 为普通字符串键使用
|
||
func (s *cacheStore[T]) formatKey(key string) string {
|
||
return s.prefix + strings.TrimSpace(key)
|
||
}
|
||
|
||
// 生成带前缀的复合缓存键 - 为 uint32+string 组合使用
|
||
func (s *cacheStore[T]) formatCompoundKey(userID uint32, sessionID string) string {
|
||
return s.prefix + strconv.FormatUint(uint64(userID), 10) + ":" + strings.TrimSpace(sessionID)
|
||
}
|
||
|
||
// Get 通过键获取缓存值 - 原始方法保持兼容性
|
||
func (s *cacheStore[T]) Get(ctx context.Context, key string) (T, error) {
|
||
var zero T
|
||
result, err := s.manager.Get(ctx, s.formatKey(key))
|
||
if err != nil {
|
||
return zero, gerror.Wrapf(err, "获取缓存失败,键: %s", key)
|
||
}
|
||
if result.IsEmpty() {
|
||
return zero, ErrCacheMiss
|
||
}
|
||
|
||
// 使用 ConvertWithRefer 进行类型转换
|
||
value := gconv.ConvertWithRefer(result.Val(), zero)
|
||
|
||
// 类型断言检查转换结果
|
||
converted, ok := value.(T)
|
||
if !ok {
|
||
return zero, gerror.Wrapf(
|
||
ErrTypeConvert,
|
||
"键: %s,缓存值实际类型: %T,期望类型: %T",
|
||
key, result.Val(), zero,
|
||
)
|
||
}
|
||
|
||
return converted, nil
|
||
}
|
||
|
||
// GetByCompoundKey 通过 uint32+string 组合键获取缓存值
|
||
func (s *cacheStore[T]) GetByCompoundKey(ctx context.Context, userID uint32, sessionID string) (T, error) {
|
||
var zero T
|
||
key := s.formatCompoundKey(userID, sessionID)
|
||
result, err := s.manager.Get(ctx, key)
|
||
if err != nil {
|
||
return zero, gerror.Wrapf(err, "获取缓存失败,键: %s", key)
|
||
}
|
||
if result.IsEmpty() {
|
||
return zero, ErrCacheMiss
|
||
}
|
||
|
||
// 使用 ConvertWithRefer 进行类型转换
|
||
value := gconv.ConvertWithRefer(result.Val(), zero)
|
||
|
||
// 类型断言检查转换结果
|
||
converted, ok := value.(T)
|
||
if !ok {
|
||
return zero, gerror.Wrapf(
|
||
ErrTypeConvert,
|
||
"键: %s,缓存值实际类型: %T,期望类型: %T",
|
||
key, result.Val(), zero,
|
||
)
|
||
}
|
||
|
||
return converted, nil
|
||
}
|
||
|
||
// Set 设置缓存值并带有效期 - 原始方法保持兼容性
|
||
func (s *cacheStore[T]) Set(ctx context.Context, key string, value T, duration time.Duration) error {
|
||
err := s.manager.Set(ctx, s.formatKey(key), value, duration)
|
||
if err != nil {
|
||
return gerror.Wrapf(err, "设置缓存失败,键: %s,值: %v", key, value)
|
||
}
|
||
fmt.Printf("[INFO] 缓存操作 [%s] 键: %s 值: %v 有效期: %v\n",
|
||
s.prefix, key, value, duration)
|
||
return nil
|
||
}
|
||
|
||
// SetByCompoundKey 通过 uint32+string 组合键设置缓存值
|
||
func (s *cacheStore[T]) SetByCompoundKey(ctx context.Context, userID uint32, sessionID string, value T, duration time.Duration) error {
|
||
key := s.formatCompoundKey(userID, sessionID)
|
||
err := s.manager.Set(ctx, key, value, duration)
|
||
if err != nil {
|
||
return gerror.Wrapf(err, "设置缓存失败,键: %s,值: %v", key, value)
|
||
}
|
||
fmt.Printf("[INFO] 缓存操作 [%s] 键: %d:%s 值: %v 有效期: %v\n",
|
||
s.prefix, userID, sessionID, value, duration)
|
||
return nil
|
||
}
|
||
|
||
// Del 删除缓存 - 原始方法保持兼容性
|
||
func (s *cacheStore[T]) Del(ctx context.Context, key string) error {
|
||
_, err := s.manager.Remove(ctx, s.formatKey(key))
|
||
if err != nil {
|
||
return gerror.Wrapf(err, "删除缓存失败,键: %s", key)
|
||
}
|
||
fmt.Printf("[INFO] 删除缓存 [%s] 键: %s 成功\n", s.prefix, key)
|
||
return nil
|
||
}
|
||
|
||
// DelByCompoundKey 通过 uint32+string 组合键删除缓存
|
||
func (s *cacheStore[T]) DelByCompoundKey(ctx context.Context, userID uint32, sessionID string) error {
|
||
key := s.formatCompoundKey(userID, sessionID)
|
||
_, err := s.manager.Remove(ctx, key)
|
||
if err != nil {
|
||
return gerror.Wrapf(err, "删除缓存失败,键: %s", key)
|
||
}
|
||
fmt.Printf("[INFO] 删除缓存 [%s] 键: %d:%s 成功\n", s.prefix, userID, sessionID)
|
||
return nil
|
||
}
|
||
|
||
// Contains 检查缓存是否存在 - 原始方法保持兼容性
|
||
func (s *cacheStore[T]) Contains(ctx context.Context, key string) (bool, error) {
|
||
exists, err := s.manager.Contains(ctx, s.formatKey(key))
|
||
if err != nil {
|
||
return false, gerror.Wrapf(err, "检查缓存是否存在失败,键: %s", key)
|
||
}
|
||
return exists, nil
|
||
}
|
||
|
||
// ContainsByCompoundKey 检查 uint32+string 组合键的缓存是否存在
|
||
func (s *cacheStore[T]) ContainsByCompoundKey(ctx context.Context, userID uint32, sessionID string) (bool, error) {
|
||
key := s.formatCompoundKey(userID, sessionID)
|
||
exists, err := s.manager.Contains(ctx, key)
|
||
if err != nil {
|
||
return false, gerror.Wrapf(err, "检查缓存是否存在失败,键: %s", key)
|
||
}
|
||
return exists, nil
|
||
}
|
||
|
||
// GetOrSet 获取缓存值,如果不存在则设置默认值
|
||
func (s *cacheStore[T]) GetOrSet(ctx context.Context, key string, defaultValue T, duration time.Duration) (T, error) {
|
||
var zero T
|
||
result, err := s.manager.GetOrSet(ctx, s.formatKey(key), defaultValue, duration)
|
||
if err != nil {
|
||
return zero, gerror.Wrapf(err, "获取或设置缓存失败,键: %s", key)
|
||
}
|
||
|
||
// 类型转换
|
||
value := gconv.ConvertWithRefer(result.Val(), zero)
|
||
converted, ok := value.(T)
|
||
if !ok {
|
||
return zero, gerror.Wrapf(
|
||
ErrTypeConvert,
|
||
"键: %s,缓存值实际类型: %T,期望类型: %T",
|
||
key, result.Val(), zero,
|
||
)
|
||
}
|
||
|
||
return converted, nil
|
||
}
|
||
|
||
// 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
|
||
}
|
||
|
||
// 简单模式匹配(支持*通配符)
|
||
func matchSimplePattern(str, pattern string) bool {
|
||
// 替换pattern中的*为正则匹配符,转义其他特殊字符
|
||
regexPattern := strings.ReplaceAll(regexp.QuoteMeta(pattern), "\\*", ".*")
|
||
regexPattern = "^" + regexPattern + "$" // 全匹配
|
||
matched, _ := regexp.MatchString(regexPattern, str)
|
||
return matched
|
||
}
|
||
|
||
// 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
|
||
}
|