298 lines
8.2 KiB
Go
298 lines
8.2 KiB
Go
package cache
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/EthanCodeCraft/xlgo-core/config"
|
||
)
|
||
|
||
// KeyBuilder 缓存键名构建器(CK2 修复:mu 保护并发访问)。
|
||
// 使用场景: 多个小项目共用一台 Redis 服务器,每个项目设置不同前缀
|
||
type KeyBuilder struct {
|
||
mu sync.Mutex
|
||
prefix string // 项目/站点前缀,如 "site_a"
|
||
_separator string // 分隔符,默认 ":"
|
||
_cacheType string // 缓存类型标识,如 "cache"
|
||
}
|
||
|
||
// KeyBuilderOption 配置选项
|
||
type KeyBuilderOption func(*KeyBuilder)
|
||
|
||
// WithPrefix 设置前缀(项目/站点别名)
|
||
// 示例: WithPrefix("site_a") -> 所有 key 自动添加 "site_a" 前缀
|
||
func WithPrefix(prefix string) KeyBuilderOption {
|
||
return func(kb *KeyBuilder) {
|
||
if kb == nil {
|
||
return
|
||
}
|
||
kb.prefix = prefix
|
||
}
|
||
}
|
||
|
||
// WithSeparator 设置分隔符
|
||
// 示例: WithSeparator(":") -> "site_a:user:1"
|
||
func WithSeparator(separator string) KeyBuilderOption {
|
||
return func(kb *KeyBuilder) {
|
||
if kb == nil {
|
||
return
|
||
}
|
||
kb._separator = separator
|
||
}
|
||
}
|
||
|
||
// WithCacheType 设置缓存类型标识
|
||
// 示例: WithCacheType("session") -> "session:site_a:user:1"
|
||
func WithCacheType(cacheType string) KeyBuilderOption {
|
||
return func(kb *KeyBuilder) {
|
||
if kb == nil {
|
||
return
|
||
}
|
||
kb._cacheType = cacheType
|
||
}
|
||
}
|
||
|
||
// NewKeyBuilder 创建键名构建器
|
||
func NewKeyBuilder(opts ...KeyBuilderOption) *KeyBuilder {
|
||
kb := &KeyBuilder{
|
||
prefix: "",
|
||
_separator: ":",
|
||
_cacheType: "cache",
|
||
}
|
||
for _, opt := range opts {
|
||
if opt == nil {
|
||
continue
|
||
}
|
||
opt(kb)
|
||
}
|
||
return kb
|
||
}
|
||
|
||
// Build 构建完整键名(CK2 修复:mu 读保护)。
|
||
// 格式: {cacheType}{separator}{prefix}{separator}{key}
|
||
// 示例: kb.Build("user:1") -> "cache:site_a:user:1"
|
||
func (kb *KeyBuilder) Build(key string) string {
|
||
kb.mu.Lock()
|
||
defer kb.mu.Unlock()
|
||
parts := []string{}
|
||
if kb._cacheType != "" {
|
||
parts = append(parts, kb._cacheType)
|
||
}
|
||
if kb.prefix != "" {
|
||
parts = append(parts, kb.prefix)
|
||
}
|
||
parts = append(parts, key)
|
||
return strings.Join(parts, kb._separator)
|
||
}
|
||
|
||
// BuildTemp 构建临时缓存键名(CK2 修复:mu 读保护)。
|
||
// 格式: "temp{separator}{prefix}{separator}{key}"
|
||
func (kb *KeyBuilder) BuildTemp(key string) string {
|
||
kb.mu.Lock()
|
||
defer kb.mu.Unlock()
|
||
parts := []string{"temp"}
|
||
if kb.prefix != "" {
|
||
parts = append(parts, kb.prefix)
|
||
}
|
||
parts = append(parts, key)
|
||
return strings.Join(parts, kb._separator)
|
||
}
|
||
|
||
// BuildPerm 构建永久缓存键名(CK2 修复:mu 读保护)。
|
||
// 格式: "perm{separator}{prefix}{separator}{key}"
|
||
func (kb *KeyBuilder) BuildPerm(key string) string {
|
||
kb.mu.Lock()
|
||
defer kb.mu.Unlock()
|
||
parts := []string{"perm"}
|
||
if kb.prefix != "" {
|
||
parts = append(parts, kb.prefix)
|
||
}
|
||
parts = append(parts, key)
|
||
return strings.Join(parts, kb._separator)
|
||
}
|
||
|
||
// BuildLock 构建分布式锁键名(CK2 修复:mu 读保护)。
|
||
// 格式: "lock{separator}{prefix}{separator}{key}"
|
||
func (kb *KeyBuilder) BuildLock(key string) string {
|
||
kb.mu.Lock()
|
||
defer kb.mu.Unlock()
|
||
parts := []string{"lock"}
|
||
if kb.prefix != "" {
|
||
parts = append(parts, kb.prefix)
|
||
}
|
||
parts = append(parts, key)
|
||
return strings.Join(parts, kb._separator)
|
||
}
|
||
|
||
// BuildCounter 构建计数器键名(CK2 修复:mu 读保护)。
|
||
// 格式: "counter{separator}{prefix}{separator}{key}"
|
||
func (kb *KeyBuilder) BuildCounter(key string) string {
|
||
kb.mu.Lock()
|
||
defer kb.mu.Unlock()
|
||
parts := []string{"counter"}
|
||
if kb.prefix != "" {
|
||
parts = append(parts, kb.prefix)
|
||
}
|
||
parts = append(parts, key)
|
||
return strings.Join(parts, kb._separator)
|
||
}
|
||
|
||
// BuildSession 构建会话键名(CK2 修复:mu 读保护)。
|
||
// 格式: "session{separator}{prefix}{separator}{key}"
|
||
func (kb *KeyBuilder) BuildSession(key string) string {
|
||
kb.mu.Lock()
|
||
defer kb.mu.Unlock()
|
||
parts := []string{"session"}
|
||
if kb.prefix != "" {
|
||
parts = append(parts, kb.prefix)
|
||
}
|
||
parts = append(parts, key)
|
||
return strings.Join(parts, kb._separator)
|
||
}
|
||
|
||
// BuildPattern 构建匹配模式(用于 SCAN/Keys)(CK2 修复:mu 读保护)。
|
||
// 示例: kb.BuildPattern("user:*") -> "cache:site_a:user:*"
|
||
func (kb *KeyBuilder) BuildPattern(pattern string) string {
|
||
return kb.Build(pattern)
|
||
}
|
||
|
||
// GetPrefix 获取当前前缀(CK2 修复:mu 读保护)。
|
||
func (kb *KeyBuilder) GetPrefix() string {
|
||
kb.mu.Lock()
|
||
defer kb.mu.Unlock()
|
||
return kb.prefix
|
||
}
|
||
|
||
// SetPrefix 动态设置前缀(CK2 修复:mu 写保护,并发安全)。
|
||
func (kb *KeyBuilder) SetPrefix(prefix string) *KeyBuilder {
|
||
kb.mu.Lock()
|
||
defer kb.mu.Unlock()
|
||
kb.prefix = prefix
|
||
return kb
|
||
}
|
||
|
||
// ===== 全局键名构建器 =====
|
||
|
||
// globalKeyBuilder 全局构建器,受 globalKBMu 保护;globalKBOnce 保证自动初始化只执行一次(M13)。
|
||
var (
|
||
globalKeyBuilder *KeyBuilder
|
||
globalKBMu sync.RWMutex
|
||
globalKBOnce sync.Once
|
||
)
|
||
|
||
// InitKeyBuilder 初始化全局键名构建器
|
||
// 参数: prefix 站点别名,如果为空则自动从配置读取
|
||
// 示例:
|
||
//
|
||
// InitKeyBuilder("site_a") // 手动指定
|
||
// InitKeyBuilder("") // 自动从配置读取
|
||
// InitKeyBuilder("", WithSeparator(":")) // 自动读取 + 自定义分隔符
|
||
func InitKeyBuilder(prefix string, opts ...KeyBuilderOption) {
|
||
// 如果 prefix 为空,尝试从配置读取
|
||
if prefix == "" {
|
||
cfg := config.Get()
|
||
if cfg != nil {
|
||
prefix = cfg.GetSiteName()
|
||
}
|
||
}
|
||
|
||
opts = append([]KeyBuilderOption{WithPrefix(prefix)}, opts...)
|
||
kb := NewKeyBuilder(opts...)
|
||
globalKBMu.Lock()
|
||
globalKeyBuilder = kb
|
||
globalKBMu.Unlock()
|
||
}
|
||
|
||
// AutoInitKeyBuilder 自动从配置初始化键名构建器
|
||
// 配置示例:
|
||
//
|
||
// app:
|
||
// site_name: "site_a"
|
||
// env: "prod"
|
||
func AutoInitKeyBuilder(opts ...KeyBuilderOption) {
|
||
InitKeyBuilder("", opts...)
|
||
}
|
||
|
||
// GetKeyBuilder 获取全局键名构建器,未初始化时用 sync.Once 自动初始化一次(M13)。
|
||
func GetKeyBuilder() *KeyBuilder {
|
||
globalKBOnce.Do(func() {
|
||
// 仅在仍为 nil 时自动初始化(已由 InitKeyBuilder 设置则跳过)。
|
||
globalKBMu.RLock()
|
||
kb := globalKeyBuilder
|
||
globalKBMu.RUnlock()
|
||
if kb == nil {
|
||
AutoInitKeyBuilder()
|
||
}
|
||
})
|
||
globalKBMu.RLock()
|
||
defer globalKBMu.RUnlock()
|
||
return globalKeyBuilder
|
||
}
|
||
|
||
// K 快捷构建键名(使用全局构建器)
|
||
// 示例: cache.K("user:1") -> 自动添加前缀
|
||
func K(key string) string {
|
||
return GetKeyBuilder().Build(key)
|
||
}
|
||
|
||
// KTemp 快捷构建临时缓存键名
|
||
func KTemp(key string) string {
|
||
return GetKeyBuilder().BuildTemp(key)
|
||
}
|
||
|
||
// KPerm 快捷构建永久缓存键名
|
||
func KPerm(key string) string {
|
||
return GetKeyBuilder().BuildPerm(key)
|
||
}
|
||
|
||
// KLock 快捷构建锁键名
|
||
func KLock(key string) string {
|
||
return GetKeyBuilder().BuildLock(key)
|
||
}
|
||
|
||
// KCounter 快捷构建计数器键名
|
||
func KCounter(key string) string {
|
||
return GetKeyBuilder().BuildCounter(key)
|
||
}
|
||
|
||
// KSession 快捷构建会话键名
|
||
func KSession(key string) string {
|
||
return GetKeyBuilder().BuildSession(key)
|
||
}
|
||
|
||
// ===== 带键名构建器的缓存操作 =====
|
||
|
||
// SetWithPrefix 带前缀的缓存设置
|
||
func SetWithPrefix(ctx context.Context, key string, value any, ttl time.Duration, prefix string) error {
|
||
kb := NewKeyBuilder(WithPrefix(prefix))
|
||
return GetCache().Set(ctx, kb.Build(key), value, ttl)
|
||
}
|
||
|
||
// GetWithPrefix 带前缀的缓存获取。命中返回 (true, nil);未命中返回 (false, nil);
|
||
// Redis 错误或反序列化失败返回 (false, err)。
|
||
func GetWithPrefix(ctx context.Context, key string, dest any, prefix string) (bool, error) {
|
||
kb := NewKeyBuilder(WithPrefix(prefix))
|
||
return GetCache().Get(ctx, kb.Build(key), dest)
|
||
}
|
||
|
||
// DeleteWithPrefix 带前缀的缓存删除
|
||
func DeleteWithPrefix(ctx context.Context, key string, prefix string) error {
|
||
kb := NewKeyBuilder(WithPrefix(prefix))
|
||
return GetCache().Delete(ctx, kb.Build(key))
|
||
}
|
||
|
||
// LockWithPrefix 带前缀的分布式锁
|
||
func LockWithPrefix(ctx context.Context, key string, ttl time.Duration, prefix string) (bool, error) {
|
||
kb := NewKeyBuilder(WithPrefix(prefix))
|
||
return Lock(ctx, kb.BuildLock(key), ttl)
|
||
}
|
||
|
||
// UnlockWithPrefix 带前缀的锁释放
|
||
// 注意: 此函数不检查 Token,仅用于向后兼容,建议使用 NewLock/Unlock 组合
|
||
func UnlockWithPrefix(ctx context.Context, key string, prefix string) error {
|
||
kb := NewKeyBuilder(WithPrefix(prefix))
|
||
return UnlockByKey(ctx, kb.BuildLock(key))
|
||
}
|