package service import ( "ai_xhs/database" "ai_xhs/utils" "context" "fmt" "log" "time" ) // CacheService 缓存管理服务 - 统一管理缓存键和清除策略 type CacheService struct{} func NewCacheService() *CacheService { return &CacheService{} } // 缓存键前缀常量 const ( CacheKeyPrefixUser = "user:profile:" CacheKeyPrefixAuthor = "author:user:" CacheKeyPrefixXHSStatus = "xhs:status:" CacheKeyPrefixProducts = "products:enterprise:" CacheKeyPrefixRateLimit = "rate:sms:" CacheKeyPrefixLock = "lock:" ) // GetUserCacheKey 获取用户缓存键 func (s *CacheService) GetUserCacheKey(userID int) string { return fmt.Sprintf("%s%d", CacheKeyPrefixUser, userID) } // GetAuthorCacheKey 获取作者缓存键 func (s *CacheService) GetAuthorCacheKey(userID int) string { return fmt.Sprintf("%s%d", CacheKeyPrefixAuthor, userID) } // GetXHSStatusCacheKey 获取小红书状态缓存键 func (s *CacheService) GetXHSStatusCacheKey(userID int) string { return fmt.Sprintf("%s%d", CacheKeyPrefixXHSStatus, userID) } // GetProductsCacheKey 获取产品列表缓存键 func (s *CacheService) GetProductsCacheKey(enterpriseID, page, pageSize int) string { return fmt.Sprintf("%spage:%d:size:%d", CacheKeyPrefixProducts+fmt.Sprintf("%d:", enterpriseID), page, pageSize) } // GetRateLimitKey 获取限流键 func (s *CacheService) GetRateLimitKey(phone string) string { return fmt.Sprintf("%s%s", CacheKeyPrefixRateLimit, phone) } // GetLockKey 获取分布式锁键 func (s *CacheService) GetLockKey(resource string) string { return fmt.Sprintf("%s%s", CacheKeyPrefixLock, resource) } // ClearUserRelatedCache 清除用户相关的所有缓存 func (s *CacheService) ClearUserRelatedCache(ctx context.Context, userID int) error { keys := []string{ s.GetUserCacheKey(userID), s.GetAuthorCacheKey(userID), s.GetXHSStatusCacheKey(userID), } if err := utils.DelCache(ctx, keys...); err != nil { log.Printf("清除用户缓存失败 (userID=%d): %v", userID, err) return err } log.Printf("已清除用户相关缓存: userID=%d", userID) return nil } // ClearProductsCache 清除企业的产品列表缓存 func (s *CacheService) ClearProductsCache(ctx context.Context, enterpriseID int) error { // 使用模糊匹配删除所有分页缓存 pattern := fmt.Sprintf("%s%d:*", CacheKeyPrefixProducts, enterpriseID) // 注意: 这需要扫描所有键,生产环境建议记录所有已创建的缓存键 // 这里简化处理,实际应该维护一个产品缓存键集合 log.Printf("需要清除产品缓存: enterpriseID=%d, pattern=%s", enterpriseID, pattern) log.Printf("建议: 在产品更新时调用此方法") // 简化版: 只清除前几页的缓存 for page := 1; page <= 10; page++ { for _, pageSize := range []int{10, 20, 50} { key := s.GetProductsCacheKey(enterpriseID, page, pageSize) utils.DelCache(ctx, key) } } return nil } // AcquireLock 获取分布式锁 func (s *CacheService) AcquireLock(ctx context.Context, resource string, ttl time.Duration) (bool, error) { lockKey := s.GetLockKey(resource) return utils.SetCacheNX(ctx, lockKey, "locked", ttl) } // ReleaseLock 释放分布式锁 func (s *CacheService) ReleaseLock(ctx context.Context, resource string) error { lockKey := s.GetLockKey(resource) return utils.DelCache(ctx, lockKey) } // WithLock 使用分布式锁执行函数 func (s *CacheService) WithLock(ctx context.Context, resource string, ttl time.Duration, fn func() error) error { // 尝试获取锁 log.Printf("[分布式锁] 尝试获取锁: %s (TTL: %v)", resource, ttl) acquired, err := s.AcquireLock(ctx, resource, ttl) if err != nil { log.Printf("[分布式锁] 获取锁失败: %s, 错误: %v", resource, err) return fmt.Errorf("获取锁失败: %w", err) } if !acquired { log.Printf("[分布式锁] 锁已被占用: %s", resource) // 检查锁的剩余时间 lockKey := s.GetLockKey(resource) ttl, _ := database.RDB.TTL(ctx, lockKey).Result() return fmt.Errorf("资源被锁定,请稍后重试(剩余时间: %v)", ttl) } log.Printf("[分布式锁] 成功获取锁: %s", resource) // 确保释放锁 defer func() { if err := s.ReleaseLock(ctx, resource); err != nil { log.Printf("[分布式锁] 释放锁失败 (resource=%s): %v", resource, err) } else { log.Printf("[分布式锁] 成功释放锁: %s", resource) } }() // 执行函数 log.Printf("[分布式锁] 开始执行受保护的函数: %s", resource) return fn() } // SetCacheWithNullProtection 设置缓存(带空值保护,防止缓存穿透) func (s *CacheService) SetCacheWithNullProtection(ctx context.Context, key string, value interface{}, ttl time.Duration) error { if value == nil { // 缓存空值,但使用较短的过期时间(1分钟) return utils.SetCache(ctx, key, "NULL", 1*time.Minute) } return utils.SetCache(ctx, key, value, ttl) } // GetCacheWithNullCheck 获取缓存(检查空值标记) func (s *CacheService) GetCacheWithNullCheck(ctx context.Context, key string, dest interface{}) (bool, error) { var tempValue interface{} err := utils.GetCache(ctx, key, &tempValue) if err != nil { // 缓存不存在 return false, err } // 检查是否是空值标记 if strValue, ok := tempValue.(string); ok && strValue == "NULL" { return true, fmt.Errorf("cached null value") } // 正常获取缓存 return true, utils.GetCache(ctx, key, dest) }