Files
ai_wht_wechat/go_backend/service/cache_service.go
2026-01-06 19:36:42 +08:00

170 lines
5.3 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 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)
}