2025-07-20 20:53:26 +08:00
|
|
|
|
package cache
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"crypto/md5"
|
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"reflect"
|
|
|
|
|
|
"strings"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"sync"
|
2025-07-20 20:53:26 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
|
|
|
|
|
|
"tyapi-server/internal/shared/interfaces"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// GormCachePlugin GORM缓存插件
|
|
|
|
|
|
type GormCachePlugin struct {
|
|
|
|
|
|
cache interfaces.CacheService
|
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
|
config CacheConfig
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 缓存失效去重机制
|
|
|
|
|
|
invalidationQueue map[string]*time.Timer
|
|
|
|
|
|
queueMutex sync.RWMutex
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CacheConfig 缓存配置
|
|
|
|
|
|
type CacheConfig struct {
|
|
|
|
|
|
// 基础配置
|
|
|
|
|
|
DefaultTTL time.Duration `json:"default_ttl"` // 默认TTL
|
|
|
|
|
|
TablePrefix string `json:"table_prefix"` // 表前缀
|
|
|
|
|
|
EnabledTables []string `json:"enabled_tables"` // 启用缓存的表
|
|
|
|
|
|
DisabledTables []string `json:"disabled_tables"` // 禁用缓存的表
|
|
|
|
|
|
|
|
|
|
|
|
// 查询配置
|
|
|
|
|
|
MaxCacheSize int `json:"max_cache_size"` // 单次查询最大缓存记录数
|
|
|
|
|
|
CacheComplexSQL bool `json:"cache_complex_sql"` // 是否缓存复杂SQL
|
|
|
|
|
|
|
|
|
|
|
|
// 高级特性
|
|
|
|
|
|
EnableStats bool `json:"enable_stats"` // 启用统计
|
|
|
|
|
|
EnableWarmup bool `json:"enable_warmup"` // 启用预热
|
|
|
|
|
|
PenetrationGuard bool `json:"penetration_guard"` // 缓存穿透保护
|
|
|
|
|
|
BloomFilter bool `json:"bloom_filter"` // 布隆过滤器
|
|
|
|
|
|
|
|
|
|
|
|
// 失效策略
|
|
|
|
|
|
AutoInvalidate bool `json:"auto_invalidate"` // 自动失效
|
|
|
|
|
|
InvalidateDelay time.Duration `json:"invalidate_delay"` // 延迟失效时间
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DefaultCacheConfig 默认缓存配置
|
|
|
|
|
|
func DefaultCacheConfig() CacheConfig {
|
|
|
|
|
|
return CacheConfig{
|
|
|
|
|
|
DefaultTTL: 30 * time.Minute,
|
|
|
|
|
|
TablePrefix: "gorm_cache",
|
|
|
|
|
|
MaxCacheSize: 1000,
|
|
|
|
|
|
CacheComplexSQL: false,
|
|
|
|
|
|
EnableStats: true,
|
|
|
|
|
|
EnableWarmup: false,
|
|
|
|
|
|
PenetrationGuard: true,
|
|
|
|
|
|
BloomFilter: false,
|
|
|
|
|
|
AutoInvalidate: true,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 增加延迟失效时间,减少频繁的缓存失效操作
|
|
|
|
|
|
InvalidateDelay: 500 * time.Millisecond,
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewGormCachePlugin 创建GORM缓存插件
|
|
|
|
|
|
func NewGormCachePlugin(cache interfaces.CacheService, logger *zap.Logger, config ...CacheConfig) *GormCachePlugin {
|
|
|
|
|
|
cfg := DefaultCacheConfig()
|
|
|
|
|
|
if len(config) > 0 {
|
|
|
|
|
|
cfg = config[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &GormCachePlugin{
|
|
|
|
|
|
cache: cache,
|
|
|
|
|
|
logger: logger,
|
|
|
|
|
|
config: cfg,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
invalidationQueue: make(map[string]*time.Timer),
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Name 插件名称
|
|
|
|
|
|
func (p *GormCachePlugin) Name() string {
|
|
|
|
|
|
return "gorm-cache-plugin"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize 初始化插件
|
|
|
|
|
|
func (p *GormCachePlugin) Initialize(db *gorm.DB) error {
|
|
|
|
|
|
p.logger.Info("初始化GORM缓存插件",
|
|
|
|
|
|
zap.Duration("default_ttl", p.config.DefaultTTL),
|
|
|
|
|
|
zap.Bool("auto_invalidate", p.config.AutoInvalidate),
|
|
|
|
|
|
zap.Bool("penetration_guard", p.config.PenetrationGuard),
|
2025-07-28 01:46:39 +08:00
|
|
|
|
zap.Duration("invalidate_delay", p.config.InvalidateDelay),
|
2025-07-20 20:53:26 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 注册回调函数
|
|
|
|
|
|
return p.registerCallbacks(db)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// Shutdown 关闭插件,清理资源
|
|
|
|
|
|
func (p *GormCachePlugin) Shutdown() {
|
|
|
|
|
|
p.queueMutex.Lock()
|
|
|
|
|
|
defer p.queueMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// 停止所有定时器
|
|
|
|
|
|
for table, timer := range p.invalidationQueue {
|
|
|
|
|
|
timer.Stop()
|
|
|
|
|
|
p.logger.Debug("停止缓存失效定时器", zap.String("table", table))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空队列
|
|
|
|
|
|
p.invalidationQueue = make(map[string]*time.Timer)
|
|
|
|
|
|
|
|
|
|
|
|
p.logger.Info("GORM缓存插件已关闭")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// registerCallbacks 注册GORM回调
|
|
|
|
|
|
func (p *GormCachePlugin) registerCallbacks(db *gorm.DB) error {
|
|
|
|
|
|
// Query回调 - 查询时检查缓存
|
|
|
|
|
|
db.Callback().Query().Before("gorm:query").Register("cache:before_query", p.beforeQuery)
|
|
|
|
|
|
db.Callback().Query().After("gorm:query").Register("cache:after_query", p.afterQuery)
|
|
|
|
|
|
|
|
|
|
|
|
// Create回调 - 创建时失效缓存
|
|
|
|
|
|
db.Callback().Create().After("gorm:create").Register("cache:after_create", p.afterCreate)
|
|
|
|
|
|
|
|
|
|
|
|
// Update回调 - 更新时失效缓存
|
|
|
|
|
|
db.Callback().Update().After("gorm:update").Register("cache:after_update", p.afterUpdate)
|
|
|
|
|
|
|
|
|
|
|
|
// Delete回调 - 删除时失效缓存
|
|
|
|
|
|
db.Callback().Delete().After("gorm:delete").Register("cache:after_delete", p.afterDelete)
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================ 查询回调 ================
|
|
|
|
|
|
|
|
|
|
|
|
// beforeQuery 查询前回调
|
|
|
|
|
|
func (p *GormCachePlugin) beforeQuery(db *gorm.DB) {
|
|
|
|
|
|
// 检查是否启用缓存
|
|
|
|
|
|
if !p.shouldCache(db) {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
p.logger.Debug("跳过缓存", zap.String("table", db.Statement.Table))
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ctx := db.Statement.Context
|
|
|
|
|
|
if ctx == nil {
|
|
|
|
|
|
ctx = context.Background()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成缓存键
|
|
|
|
|
|
cacheKey := p.generateCacheKey(db)
|
|
|
|
|
|
|
|
|
|
|
|
// 从缓存获取结果
|
|
|
|
|
|
var cachedResult CachedResult
|
|
|
|
|
|
if err := p.cache.Get(ctx, cacheKey, &cachedResult); err == nil {
|
|
|
|
|
|
p.logger.Debug("缓存命中",
|
|
|
|
|
|
zap.String("cache_key", cacheKey),
|
|
|
|
|
|
zap.String("table", db.Statement.Table),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复查询结果
|
|
|
|
|
|
if err := p.restoreFromCache(db, &cachedResult); err == nil {
|
|
|
|
|
|
// 设置标记,跳过实际查询
|
|
|
|
|
|
db.Statement.Set("cache:hit", true)
|
|
|
|
|
|
db.Statement.Set("cache:key", cacheKey)
|
|
|
|
|
|
|
|
|
|
|
|
// 更新统计
|
|
|
|
|
|
if p.config.EnableStats {
|
|
|
|
|
|
p.updateStats("hit", db.Statement.Table)
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
2025-07-28 01:46:39 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
p.logger.Warn("缓存数据恢复失败,将执行数据库查询",
|
|
|
|
|
|
zap.String("cache_key", cacheKey),
|
|
|
|
|
|
zap.Error(err))
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
p.logger.Debug("缓存未命中",
|
|
|
|
|
|
zap.String("cache_key", cacheKey),
|
|
|
|
|
|
zap.Error(err))
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 缓存未命中,设置标记
|
|
|
|
|
|
db.Statement.Set("cache:miss", true)
|
|
|
|
|
|
db.Statement.Set("cache:key", cacheKey)
|
|
|
|
|
|
|
|
|
|
|
|
if p.config.EnableStats {
|
|
|
|
|
|
p.updateStats("miss", db.Statement.Table)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// afterQuery 查询后回调
|
|
|
|
|
|
func (p *GormCachePlugin) afterQuery(db *gorm.DB) {
|
|
|
|
|
|
// 检查是否缓存未命中
|
|
|
|
|
|
if _, ok := db.Statement.Get("cache:miss"); !ok {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查查询是否成功
|
|
|
|
|
|
if db.Error != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ctx := db.Statement.Context
|
|
|
|
|
|
if ctx == nil {
|
|
|
|
|
|
ctx = context.Background()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cacheKey, _ := db.Statement.Get("cache:key")
|
|
|
|
|
|
|
|
|
|
|
|
// 将查询结果保存到缓存
|
|
|
|
|
|
if err := p.saveToCache(ctx, cacheKey.(string), db); err != nil {
|
|
|
|
|
|
p.logger.Warn("保存查询结果到缓存失败",
|
|
|
|
|
|
zap.String("cache_key", cacheKey.(string)),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================ CUD回调 ================
|
|
|
|
|
|
|
|
|
|
|
|
// afterCreate 创建后回调
|
|
|
|
|
|
func (p *GormCachePlugin) afterCreate(db *gorm.DB) {
|
|
|
|
|
|
if !p.config.AutoInvalidate || db.Error != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 只对启用缓存的表执行失效操作
|
|
|
|
|
|
if p.shouldInvalidateTable(db.Statement.Table) {
|
|
|
|
|
|
p.invalidateTableCache(db.Statement.Context, db.Statement.Table)
|
|
|
|
|
|
}
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// afterUpdate 更新后回调
|
|
|
|
|
|
func (p *GormCachePlugin) afterUpdate(db *gorm.DB) {
|
|
|
|
|
|
if !p.config.AutoInvalidate || db.Error != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 只对启用缓存的表执行失效操作
|
|
|
|
|
|
if p.shouldInvalidateTable(db.Statement.Table) {
|
|
|
|
|
|
p.invalidateTableCache(db.Statement.Context, db.Statement.Table)
|
|
|
|
|
|
}
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// afterDelete 删除后回调
|
|
|
|
|
|
func (p *GormCachePlugin) afterDelete(db *gorm.DB) {
|
|
|
|
|
|
if !p.config.AutoInvalidate || db.Error != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 只对启用缓存的表执行失效操作
|
|
|
|
|
|
if p.shouldInvalidateTable(db.Statement.Table) {
|
|
|
|
|
|
p.invalidateTableCache(db.Statement.Context, db.Statement.Table)
|
|
|
|
|
|
}
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================ 缓存管理方法 ================
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// shouldInvalidateTable 判断是否应该对表执行缓存失效操作
|
|
|
|
|
|
func (p *GormCachePlugin) shouldInvalidateTable(table string) bool {
|
|
|
|
|
|
// 使用全局缓存配置管理器进行智能决策
|
|
|
|
|
|
if GlobalCacheConfigManager != nil {
|
|
|
|
|
|
return GlobalCacheConfigManager.IsTableCacheEnabled(table)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果全局管理器未初始化,使用本地配置
|
|
|
|
|
|
// 检查表是否在禁用列表中
|
|
|
|
|
|
for _, disabledTable := range p.config.DisabledTables {
|
|
|
|
|
|
if disabledTable == table {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果配置了启用列表,只对启用列表中的表执行失效操作
|
|
|
|
|
|
if len(p.config.EnabledTables) > 0 {
|
|
|
|
|
|
for _, enabledTable := range p.config.EnabledTables {
|
|
|
|
|
|
if enabledTable == table {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有配置启用列表,默认对所有表执行失效操作(除了禁用列表中的表)
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// shouldCache 判断是否应该缓存
|
|
|
|
|
|
func (p *GormCachePlugin) shouldCache(db *gorm.DB) bool {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 检查是否手动禁用缓存
|
2025-07-20 20:53:26 +08:00
|
|
|
|
if value, ok := db.Statement.Get("cache:disabled"); ok && value.(bool) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 检查是否手动启用缓存
|
2025-07-20 20:53:26 +08:00
|
|
|
|
if value, ok := db.Statement.Get("cache:enabled"); ok && value.(bool) {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 使用全局缓存配置管理器进行智能决策
|
|
|
|
|
|
if GlobalCacheConfigManager != nil {
|
|
|
|
|
|
return GlobalCacheConfigManager.IsTableCacheEnabled(db.Statement.Table)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果全局管理器未初始化,使用本地配置
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 检查表是否在禁用列表中
|
2025-07-28 01:46:39 +08:00
|
|
|
|
for _, disabledTable := range p.config.DisabledTables {
|
|
|
|
|
|
if disabledTable == db.Statement.Table {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查表是否在启用列表中(如果配置了启用列表)
|
|
|
|
|
|
if len(p.config.EnabledTables) > 0 {
|
|
|
|
|
|
for _, table := range p.config.EnabledTables {
|
|
|
|
|
|
if table == db.Statement.Table {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 如果没有配置启用列表,默认对所有表启用缓存(除了禁用列表中的表)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// isComplexQuery 判断是否为复杂查询
|
|
|
|
|
|
func (p *GormCachePlugin) isComplexQuery(db *gorm.DB) bool {
|
|
|
|
|
|
sql := db.Statement.SQL.String()
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否包含复杂操作
|
|
|
|
|
|
complexKeywords := []string{
|
|
|
|
|
|
"JOIN", "UNION", "SUBQUERY", "GROUP BY",
|
|
|
|
|
|
"HAVING", "WINDOW", "RECURSIVE",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
upperSQL := strings.ToUpper(sql)
|
|
|
|
|
|
for _, keyword := range complexKeywords {
|
|
|
|
|
|
if strings.Contains(upperSQL, keyword) {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// generateCacheKey 生成缓存键
|
|
|
|
|
|
func (p *GormCachePlugin) generateCacheKey(db *gorm.DB) string {
|
|
|
|
|
|
// 构建缓存键的组成部分
|
|
|
|
|
|
keyParts := []string{
|
|
|
|
|
|
p.config.TablePrefix,
|
|
|
|
|
|
db.Statement.Table,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加SQL语句hash
|
|
|
|
|
|
sqlHash := p.hashSQL(db.Statement.SQL.String(), db.Statement.Vars)
|
|
|
|
|
|
keyParts = append(keyParts, sqlHash)
|
|
|
|
|
|
|
|
|
|
|
|
return strings.Join(keyParts, ":")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// hashSQL 对SQL语句和参数进行hash
|
|
|
|
|
|
func (p *GormCachePlugin) hashSQL(sql string, vars []interface{}) string {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 修复:改进SQL hash生成,确保唯一性
|
2025-07-20 20:53:26 +08:00
|
|
|
|
combined := sql
|
|
|
|
|
|
for _, v := range vars {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 使用更精确的格式化,避免类型信息丢失
|
|
|
|
|
|
switch val := v.(type) {
|
|
|
|
|
|
case string:
|
|
|
|
|
|
combined += ":" + val
|
|
|
|
|
|
case int, int32, int64:
|
|
|
|
|
|
combined += fmt.Sprintf(":%d", val)
|
|
|
|
|
|
case float32, float64:
|
|
|
|
|
|
combined += fmt.Sprintf(":%f", val)
|
|
|
|
|
|
case bool:
|
|
|
|
|
|
combined += fmt.Sprintf(":%t", val)
|
|
|
|
|
|
default:
|
|
|
|
|
|
// 对于复杂类型,使用JSON序列化
|
|
|
|
|
|
if jsonData, err := json.Marshal(v); err == nil {
|
|
|
|
|
|
combined += ":" + string(jsonData)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
combined += fmt.Sprintf(":%v", v)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算MD5 hash
|
|
|
|
|
|
hasher := md5.New()
|
|
|
|
|
|
hasher.Write([]byte(combined))
|
|
|
|
|
|
return hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CachedResult 缓存结果结构
|
|
|
|
|
|
type CachedResult struct {
|
|
|
|
|
|
Data interface{} `json:"data"`
|
|
|
|
|
|
RowCount int64 `json:"row_count"`
|
|
|
|
|
|
Timestamp time.Time `json:"timestamp"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// saveToCache 保存结果到缓存
|
|
|
|
|
|
func (p *GormCachePlugin) saveToCache(ctx context.Context, cacheKey string, db *gorm.DB) error {
|
|
|
|
|
|
// 检查结果大小限制
|
|
|
|
|
|
if db.Statement.RowsAffected > int64(p.config.MaxCacheSize) {
|
|
|
|
|
|
p.logger.Debug("查询结果过大,跳过缓存",
|
|
|
|
|
|
zap.String("cache_key", cacheKey),
|
|
|
|
|
|
zap.Int64("rows", db.Statement.RowsAffected),
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取查询结果
|
|
|
|
|
|
dest := db.Statement.Dest
|
|
|
|
|
|
if dest == nil {
|
|
|
|
|
|
return fmt.Errorf("查询结果为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 修复:改进缓存数据保存逻辑
|
|
|
|
|
|
var dataToCache interface{}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果dest是切片,需要特殊处理
|
|
|
|
|
|
destValue := reflect.ValueOf(dest)
|
|
|
|
|
|
if destValue.Kind() == reflect.Ptr && destValue.Elem().Kind() == reflect.Slice {
|
|
|
|
|
|
// 对于切片,直接使用原始数据
|
|
|
|
|
|
dataToCache = destValue.Elem().Interface()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 对于单个对象,也直接使用原始数据
|
|
|
|
|
|
dataToCache = dest
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 构建缓存结果
|
|
|
|
|
|
result := CachedResult{
|
2025-07-28 01:46:39 +08:00
|
|
|
|
Data: dataToCache,
|
2025-07-20 20:53:26 +08:00
|
|
|
|
RowCount: db.Statement.RowsAffected,
|
|
|
|
|
|
Timestamp: time.Now(),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取TTL
|
|
|
|
|
|
ttl := p.getTTL(db)
|
|
|
|
|
|
|
|
|
|
|
|
// 保存到缓存
|
|
|
|
|
|
if err := p.cache.Set(ctx, cacheKey, result, ttl); err != nil {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
p.logger.Error("保存查询结果到缓存失败",
|
|
|
|
|
|
zap.String("cache_key", cacheKey),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return fmt.Errorf("保存到缓存失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
p.logger.Debug("查询结果已缓存",
|
|
|
|
|
|
zap.String("cache_key", cacheKey),
|
|
|
|
|
|
zap.Int64("rows", db.Statement.RowsAffected),
|
|
|
|
|
|
zap.Duration("ttl", ttl),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// restoreFromCache 从缓存恢复结果
|
|
|
|
|
|
func (p *GormCachePlugin) restoreFromCache(db *gorm.DB, cachedResult *CachedResult) error {
|
|
|
|
|
|
if cachedResult.Data == nil {
|
|
|
|
|
|
return fmt.Errorf("缓存数据为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 反序列化到目标对象
|
|
|
|
|
|
destValue := reflect.ValueOf(db.Statement.Dest)
|
|
|
|
|
|
if destValue.Kind() != reflect.Ptr || destValue.IsNil() {
|
|
|
|
|
|
return fmt.Errorf("目标对象必须是指针")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 修复:改进缓存数据恢复逻辑
|
2025-07-20 20:53:26 +08:00
|
|
|
|
cachedValue := reflect.ValueOf(cachedResult.Data)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果类型完全匹配,直接赋值
|
|
|
|
|
|
if cachedValue.Type().AssignableTo(destValue.Elem().Type()) {
|
|
|
|
|
|
destValue.Elem().Set(cachedValue)
|
|
|
|
|
|
} else {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
// 尝试JSON转换
|
|
|
|
|
|
jsonData, err := json.Marshal(cachedResult.Data)
|
|
|
|
|
|
if err != nil {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
p.logger.Error("序列化缓存数据失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("缓存数据类型转换失败: %w", err)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := json.Unmarshal(jsonData, db.Statement.Dest); err != nil {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
p.logger.Error("反序列化缓存数据失败",
|
|
|
|
|
|
zap.String("json_data", string(jsonData)),
|
|
|
|
|
|
zap.Error(err))
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return fmt.Errorf("JSON反序列化失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置影响行数
|
|
|
|
|
|
db.Statement.RowsAffected = cachedResult.RowCount
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
p.logger.Debug("从缓存恢复数据成功",
|
|
|
|
|
|
zap.Int64("rows", cachedResult.RowCount),
|
|
|
|
|
|
zap.Time("timestamp", cachedResult.Timestamp))
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// getTTL 获取TTL
|
|
|
|
|
|
func (p *GormCachePlugin) getTTL(db *gorm.DB) time.Duration {
|
|
|
|
|
|
// 检查是否设置了自定义TTL
|
|
|
|
|
|
if value, ok := db.Statement.Get("cache:ttl"); ok {
|
|
|
|
|
|
if ttl, ok := value.(time.Duration); ok {
|
|
|
|
|
|
return ttl
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return p.config.DefaultTTL
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// invalidateTableCache 失效表相关缓存
|
|
|
|
|
|
func (p *GormCachePlugin) invalidateTableCache(ctx context.Context, table string) {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 使用去重机制,避免重复的缓存失效操作
|
|
|
|
|
|
p.queueMutex.Lock()
|
|
|
|
|
|
defer p.queueMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经有相同的失效操作在队列中,取消之前的定时器
|
|
|
|
|
|
if timer, exists := p.invalidationQueue[table]; exists {
|
|
|
|
|
|
timer.Stop()
|
|
|
|
|
|
delete(p.invalidationQueue, table)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建独立的上下文,避免受到原始请求上下文的影响
|
|
|
|
|
|
// 设置合理的超时时间,避免缓存失效操作阻塞
|
|
|
|
|
|
cacheCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新的定时器
|
|
|
|
|
|
timer := time.AfterFunc(p.config.InvalidateDelay, func() {
|
|
|
|
|
|
// 执行缓存失效
|
|
|
|
|
|
p.doInvalidateTableCache(cacheCtx, table)
|
|
|
|
|
|
|
|
|
|
|
|
// 清理定时器引用
|
|
|
|
|
|
p.queueMutex.Lock()
|
|
|
|
|
|
delete(p.invalidationQueue, table)
|
|
|
|
|
|
p.queueMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// 取消上下文
|
|
|
|
|
|
cancel()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 将定时器加入队列
|
|
|
|
|
|
p.invalidationQueue[table] = timer
|
|
|
|
|
|
|
|
|
|
|
|
p.logger.Debug("缓存失效操作已加入队列",
|
|
|
|
|
|
zap.String("table", table),
|
|
|
|
|
|
zap.Duration("delay", p.config.InvalidateDelay),
|
|
|
|
|
|
)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// doInvalidateTableCache 执行缓存失效
|
|
|
|
|
|
func (p *GormCachePlugin) doInvalidateTableCache(ctx context.Context, table string) {
|
|
|
|
|
|
pattern := fmt.Sprintf("%s:%s:*", p.config.TablePrefix, table)
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 添加重试机制,提高缓存失效的可靠性
|
|
|
|
|
|
maxRetries := 3
|
|
|
|
|
|
for attempt := 1; attempt <= maxRetries; attempt++ {
|
|
|
|
|
|
if err := p.cache.DeletePattern(ctx, pattern); err != nil {
|
|
|
|
|
|
if attempt < maxRetries {
|
|
|
|
|
|
p.logger.Warn("缓存失效失败,准备重试",
|
|
|
|
|
|
zap.String("table", table),
|
|
|
|
|
|
zap.String("pattern", pattern),
|
|
|
|
|
|
zap.Int("attempt", attempt),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
// 短暂延迟后重试
|
|
|
|
|
|
time.Sleep(time.Duration(attempt) * 100 * time.Millisecond)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
p.logger.Warn("失效表缓存失败",
|
|
|
|
|
|
zap.String("table", table),
|
|
|
|
|
|
zap.String("pattern", pattern),
|
|
|
|
|
|
zap.Int("attempts", maxRetries),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 成功删除,记录日志并退出
|
|
|
|
|
|
p.logger.Debug("表缓存已失效",
|
2025-07-20 20:53:26 +08:00
|
|
|
|
zap.String("table", table),
|
|
|
|
|
|
zap.String("pattern", pattern),
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// updateStats 更新统计信息
|
|
|
|
|
|
func (p *GormCachePlugin) updateStats(operation, table string) {
|
|
|
|
|
|
// 这里可以接入Prometheus等监控系统
|
|
|
|
|
|
p.logger.Debug("缓存统计",
|
|
|
|
|
|
zap.String("operation", operation),
|
|
|
|
|
|
zap.String("table", table),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================ 高级功能 ================
|
|
|
|
|
|
|
|
|
|
|
|
// WarmupCache 预热缓存
|
|
|
|
|
|
func (p *GormCachePlugin) WarmupCache(ctx context.Context, db *gorm.DB, queries []string) error {
|
|
|
|
|
|
if !p.config.EnableWarmup {
|
|
|
|
|
|
return fmt.Errorf("缓存预热未启用")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, query := range queries {
|
|
|
|
|
|
if err := db.Raw(query).Error; err != nil {
|
|
|
|
|
|
p.logger.Warn("缓存预热失败",
|
|
|
|
|
|
zap.String("query", query),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetCacheStats 获取缓存统计
|
|
|
|
|
|
func (p *GormCachePlugin) GetCacheStats(ctx context.Context) (map[string]interface{}, error) {
|
|
|
|
|
|
stats, err := p.cache.Stats(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return map[string]interface{}{
|
|
|
|
|
|
"hits": stats.Hits,
|
|
|
|
|
|
"misses": stats.Misses,
|
|
|
|
|
|
"keys": stats.Keys,
|
|
|
|
|
|
"memory": stats.Memory,
|
|
|
|
|
|
"connections": stats.Connections,
|
|
|
|
|
|
"config": p.config,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SetCacheEnabled 设置缓存启用状态
|
|
|
|
|
|
func (p *GormCachePlugin) SetCacheEnabled(db *gorm.DB, enabled bool) *gorm.DB {
|
|
|
|
|
|
return db.Set("cache:enabled", enabled)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SetCacheDisabled 设置缓存禁用状态
|
|
|
|
|
|
func (p *GormCachePlugin) SetCacheDisabled(db *gorm.DB, disabled bool) *gorm.DB {
|
|
|
|
|
|
return db.Set("cache:disabled", disabled)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SetCacheTTL 设置缓存TTL
|
|
|
|
|
|
func (p *GormCachePlugin) SetCacheTTL(db *gorm.DB, ttl time.Duration) *gorm.DB {
|
|
|
|
|
|
return db.Set("cache:ttl", ttl)
|
|
|
|
|
|
}
|