From acacac19c7ef83b06db4132e8e3bd96f6df50979 Mon Sep 17 00:00:00 2001 From: zerosaturation Date: Thu, 14 May 2026 17:05:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Redis=20=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E5=92=8C=20Token=20=E9=BB=91=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- backend/pkg/database/redis.go | 310 +++++++++++++++++----------------- 1 file changed, 155 insertions(+), 155 deletions(-) diff --git a/backend/pkg/database/redis.go b/backend/pkg/database/redis.go index 4d3c841..11b8a2c 100644 --- a/backend/pkg/database/redis.go +++ b/backend/pkg/database/redis.go @@ -1,18 +1,18 @@ package database import ( - "context" - "crypto/sha256" - "encoding/json" - "fmt" - "time" + "context" + "crypto/sha256" + "encoding/json" + "fmt" + "time" - "github.com/redis/go-redis/v9" + "github.com/redis/go-redis/v9" ) const ( - BlacklistKeyPrefix = "blacklist:token:" - InspirationFlowKeyPrefix = "inspiration_flow:" + BlacklistKeyPrefix = "blacklist:token:" + InspirationFlowKeyPrefix = "inspiration_flow:" ) // RedisClient Redis 客户端单例 @@ -20,232 +20,232 @@ var RedisClient *redis.Client // Config Redis 配置 type RedisConfig struct { - Host string - Port int - Password string - DB int + Host string + Port int + Password string + DB int } // InitRedis 初始化 Redis 连接 func InitRedis(cfg RedisConfig) error { - RedisClient = redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), - Password: cfg.Password, - DB: cfg.DB, - }) + RedisClient = redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), + Password: cfg.Password, + DB: cfg.DB, + }) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() - if err := RedisClient.Ping(ctx).Err(); err != nil { - return fmt.Errorf("failed to connect to redis: %w", err) - } + if err := RedisClient.Ping(ctx).Err(); err != nil { + return fmt.Errorf("failed to connect to redis: %w", err) + } - return nil + return nil } // CloseRedis 关闭 Redis 连接 func CloseRedis() error { - if RedisClient != nil { - return RedisClient.Close() - } - return nil + if RedisClient != nil { + return RedisClient.Close() + } + return nil } // GetRedis 获取 Redis 客户端实例 func GetRedis() *redis.Client { - return RedisClient + return RedisClient } // RedisHealthCheck 健康检查 func RedisHealthCheck() error { - if RedisClient == nil { - return fmt.Errorf("redis client is not initialized") - } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - return RedisClient.Ping(ctx).Err() + if RedisClient == nil { + return fmt.Errorf("redis client is not initialized") + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + return RedisClient.Ping(ctx).Err() } // BlacklistEntry 黑名单条目 type BlacklistEntry struct { - UserID int64 `json:"user_id"` - Reason string `json:"reason"` + UserID int64 `json:"user_id"` + Reason string `json:"reason"` } // tokenToHash 将 Token 转换为 SHA256 哈希作为 Key func tokenToHash(token string) string { - hash := sha256.Sum256([]byte(token)) - return fmt.Sprintf("%x", hash) + hash := sha256.Sum256([]byte(token)) + return fmt.Sprintf("%x", hash) } // AddToBlacklist 添加 Token 到黑名单 func AddToBlacklist(ctx context.Context, token string, userID int64, banReason string, ttl time.Duration) error { - if token == "" { - return fmt.Errorf("token is empty") - } - if RedisClient == nil { - return fmt.Errorf("redis client is not initialized") - } + if token == "" { + return fmt.Errorf("token is empty") + } + if RedisClient == nil { + return fmt.Errorf("redis client is not initialized") + } - key := BlacklistKeyPrefix + tokenToHash(token) - entry := BlacklistEntry{UserID: userID, Reason: banReason} - value, err := json.Marshal(entry) - if err != nil { - return fmt.Errorf("failed to marshal blacklist entry: %w", err) - } + key := BlacklistKeyPrefix + tokenToHash(token) + entry := BlacklistEntry{UserID: userID, Reason: banReason} + value, err := json.Marshal(entry) + if err != nil { + return fmt.Errorf("failed to marshal blacklist entry: %w", err) + } - return RedisClient.Set(ctx, key, value, ttl).Err() + return RedisClient.Set(ctx, key, value, ttl).Err() } // IsBlacklisted 检查 Token 是否在黑名单 func IsBlacklisted(ctx context.Context, token string) (bool, int64, string, error) { - if token == "" { - return false, 0, "", nil - } - if RedisClient == nil { - return false, 0, "", fmt.Errorf("redis client is not initialized") - } + if token == "" { + return false, 0, "", nil + } + if RedisClient == nil { + return false, 0, "", fmt.Errorf("redis client is not initialized") + } - key := BlacklistKeyPrefix + tokenToHash(token) - value, err := RedisClient.Get(ctx, key).Result() - if err == redis.Nil { - return false, 0, "", nil - } - if err != nil { - return false, 0, "", err - } + key := BlacklistKeyPrefix + tokenToHash(token) + value, err := RedisClient.Get(ctx, key).Result() + if err == redis.Nil { + return false, 0, "", nil + } + if err != nil { + return false, 0, "", err + } - var entry BlacklistEntry - if err := json.Unmarshal([]byte(value), &entry); err != nil { - return false, 0, "", fmt.Errorf("failed to unmarshal blacklist entry: %w", err) - } + var entry BlacklistEntry + if err := json.Unmarshal([]byte(value), &entry); err != nil { + return false, 0, "", fmt.Errorf("failed to unmarshal blacklist entry: %w", err) + } - return true, entry.UserID, entry.Reason, nil + return true, entry.UserID, entry.Reason, nil } // RemoveFromBlacklist 从黑名单移除 Token(用于解封) func RemoveFromBlacklist(ctx context.Context, token string) error { - if token == "" { - return nil - } - if RedisClient == nil { - return fmt.Errorf("redis client is not initialized") - } + if token == "" { + return nil + } + if RedisClient == nil { + return fmt.Errorf("redis client is not initialized") + } - key := BlacklistKeyPrefix + tokenToHash(token) - return RedisClient.Del(ctx, key).Err() + key := BlacklistKeyPrefix + tokenToHash(token) + return RedisClient.Del(ctx, key).Err() } // InspirationFlowCacheEntry 单个展品缓存数据 type InspirationFlowCacheEntry struct { - AssetID int64 `json:"asset_id"` - Name string `json:"name"` - CoverURL string `json:"cover_url"` - LikeCount int32 `json:"like_count"` - OwnerNickname string `json:"owner_nickname"` - Span int32 `json:"span"` - MaterialType string `json:"material_type"` + AssetID int64 `json:"asset_id"` + Name string `json:"name"` + CoverURL string `json:"cover_url"` + LikeCount int32 `json:"like_count"` + OwnerNickname string `json:"owner_nickname"` + Span int32 `json:"span"` + MaterialType string `json:"material_type"` } // InspirationFlowCache 会话缓存结构 type InspirationFlowCache struct { - DisplayedIDs []int64 `json:"displayed_ids"` // 已展示ID列表 - History map[int64]InspirationFlowCacheEntry `json:"history"` // 历史数据详情 + DisplayedIDs []int64 `json:"displayed_ids"` // 已展示ID列表 + History map[int64]InspirationFlowCacheEntry `json:"history"` // 历史数据详情 } // InspirationFlowKey 生成灵感瀑布流缓存 Key func InspirationFlowKey(starID int64, sessionID string) string { - return fmt.Sprintf("%s%d:%s", InspirationFlowKeyPrefix, starID, sessionID) + return fmt.Sprintf("%s%d:%s", InspirationFlowKeyPrefix, starID, sessionID) } // GetInspirationFlowCache 获取灵感瀑布流会话缓存 func GetInspirationFlowCache(ctx context.Context, starID int64, sessionID string) (*InspirationFlowCache, error) { - if RedisClient == nil { - return nil, fmt.Errorf("redis client is not initialized") - } + if RedisClient == nil { + return nil, fmt.Errorf("redis client is not initialized") + } - key := InspirationFlowKey(starID, sessionID) - data, err := RedisClient.Get(ctx, key).Result() - if err == redis.Nil { - return &InspirationFlowCache{ - DisplayedIDs: []int64{}, - History: make(map[int64]InspirationFlowCacheEntry), - }, nil - } - if err != nil { - return nil, err - } + key := InspirationFlowKey(starID, sessionID) + data, err := RedisClient.Get(ctx, key).Result() + if err == redis.Nil { + return &InspirationFlowCache{ + DisplayedIDs: []int64{}, + History: make(map[int64]InspirationFlowCacheEntry), + }, nil + } + if err != nil { + return nil, err + } - var cache InspirationFlowCache - if err := json.Unmarshal([]byte(data), &cache); err != nil { - return nil, err - } - return &cache, nil + var cache InspirationFlowCache + if err := json.Unmarshal([]byte(data), &cache); err != nil { + return nil, err + } + return &cache, nil } // SaveInspirationFlowCache 保存灵感瀑布流会话缓存 func SaveInspirationFlowCache(ctx context.Context, starID int64, sessionID string, cache *InspirationFlowCache, ttl time.Duration) error { - if RedisClient == nil { - return fmt.Errorf("redis client is not initialized") - } + if RedisClient == nil { + return fmt.Errorf("redis client is not initialized") + } - key := InspirationFlowKey(starID, sessionID) - data, err := json.Marshal(cache) - if err != nil { - return err - } + key := InspirationFlowKey(starID, sessionID) + data, err := json.Marshal(cache) + if err != nil { + return err + } - return RedisClient.Set(ctx, key, data, ttl).Err() + return RedisClient.Set(ctx, key, data, ttl).Err() } // AddToInspirationFlowCache 添加展品到会话缓存 func AddToInspirationFlowCache(ctx context.Context, starID int64, sessionID string, entry InspirationFlowCacheEntry, ttl time.Duration) error { - cache, err := GetInspirationFlowCache(ctx, starID, sessionID) - if err != nil { - return err - } + cache, err := GetInspirationFlowCache(ctx, starID, sessionID) + if err != nil { + return err + } - // 检查是否已存在 - for _, id := range cache.DisplayedIDs { - if id == entry.AssetID { - return nil // 已存在,跳过 - } - } + // 检查是否已存在 + for _, id := range cache.DisplayedIDs { + if id == entry.AssetID { + return nil // 已存在,跳过 + } + } - // 添加到已展示列表 - cache.DisplayedIDs = append(cache.DisplayedIDs, entry.AssetID) - // 添加到历史详情 - if cache.History == nil { - cache.History = make(map[int64]InspirationFlowCacheEntry) - } - cache.History[entry.AssetID] = entry + // 添加到已展示列表 + cache.DisplayedIDs = append(cache.DisplayedIDs, entry.AssetID) + // 添加到历史详情 + if cache.History == nil { + cache.History = make(map[int64]InspirationFlowCacheEntry) + } + cache.History[entry.AssetID] = entry - return SaveInspirationFlowCache(ctx, starID, sessionID, cache, ttl) + return SaveInspirationFlowCache(ctx, starID, sessionID, cache, ttl) } // GetHistoryPage 获取历史数据的某一页 func GetHistoryPage(cache *InspirationFlowCache, offset, limit int) []InspirationFlowCacheEntry { - if cache == nil || cache.History == nil { - return []InspirationFlowCacheEntry{} - } + if cache == nil || cache.History == nil { + return []InspirationFlowCacheEntry{} + } - // 按展示顺序反向遍历(最新展示的在前面) - items := make([]InspirationFlowCacheEntry, 0) - for i := len(cache.DisplayedIDs) - 1; i >= 0; i-- { - if entry, ok := cache.History[cache.DisplayedIDs[i]]; ok { - items = append(items, entry) - } - } + // 按展示顺序反向遍历(最新展示的在前面) + items := make([]InspirationFlowCacheEntry, 0) + for i := len(cache.DisplayedIDs) - 1; i >= 0; i-- { + if entry, ok := cache.History[cache.DisplayedIDs[i]]; ok { + items = append(items, entry) + } + } - // 分页 - start := offset - end := offset + limit - if start >= len(items) { - return []InspirationFlowCacheEntry{} - } - if end > len(items) { - end = len(items) - } - return items[start:end] -} \ No newline at end of file + // 分页 + start := offset + end := offset + limit + if start >= len(items) { + return []InspirationFlowCacheEntry{} + } + if end > len(items) { + end = len(items) + } + return items[start:end] +}