Files
ai_wht_wechat/go_backend/service/cache_service.go

170 lines
5.3 KiB
Go
Raw Normal View History

2026-01-06 19:36:42 +08:00
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)
}