Files
2026-07-12 22:23:41 +08:00

298 lines
8.2 KiB
Go
Raw Permalink 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 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))
}