- Removed Gorse-related configurations, handlers, and dependencies from the codebase. - Introduced HotRank feature with configuration options for ranking posts based on recent activity. - Updated application structure to support HotRank processing, including new caching mechanisms and database interactions. - Cleaned up related DTOs and repository methods to reflect the removal of Gorse and the addition of HotRank functionality.
272 lines
6.9 KiB
Go
272 lines
6.9 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
redislib "github.com/redis/go-redis/v9"
|
|
"go.uber.org/zap"
|
|
|
|
redisPkg "carrot_bbs/internal/pkg/redis"
|
|
)
|
|
|
|
// RedisCache Redis缓存实现
|
|
type RedisCache struct {
|
|
client *redisPkg.Client
|
|
ctx context.Context
|
|
}
|
|
|
|
// NewRedisCache 创建Redis缓存
|
|
func NewRedisCache(client *redisPkg.Client) *RedisCache {
|
|
return &RedisCache{
|
|
client: client,
|
|
ctx: context.Background(),
|
|
}
|
|
}
|
|
|
|
// Set 设置缓存值
|
|
func (c *RedisCache) Set(key string, value interface{}, ttl time.Duration) {
|
|
key = normalizeKey(key)
|
|
// 将值序列化为JSON
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
recordSetError()
|
|
zap.L().Error("Failed to marshal value for key",
|
|
zap.String("key", key),
|
|
zap.Error(err),
|
|
)
|
|
return
|
|
}
|
|
|
|
if err := c.client.Set(c.ctx, key, data, ttl); err != nil {
|
|
recordSetError()
|
|
zap.L().Error("Failed to set key",
|
|
zap.String("key", key),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Get 获取缓存值
|
|
func (c *RedisCache) Get(key string) (interface{}, bool) {
|
|
key = normalizeKey(key)
|
|
data, err := c.client.Get(c.ctx, key)
|
|
if err != nil {
|
|
if err == redislib.Nil {
|
|
return nil, false
|
|
}
|
|
zap.L().Error("Failed to get key",
|
|
zap.String("key", key),
|
|
zap.Error(err),
|
|
)
|
|
return nil, false
|
|
}
|
|
|
|
// 返回原始字符串,由调用侧决定如何解码为目标类型
|
|
return data, true
|
|
}
|
|
|
|
// Delete 删除缓存
|
|
func (c *RedisCache) Delete(key string) {
|
|
key = normalizeKey(key)
|
|
recordInvalidate()
|
|
if err := c.client.Del(c.ctx, key); err != nil {
|
|
zap.L().Error("Failed to delete key",
|
|
zap.String("key", key),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
}
|
|
|
|
// DeleteByPrefix 根据前缀删除缓存
|
|
func (c *RedisCache) DeleteByPrefix(prefix string) {
|
|
prefix = normalizePrefix(prefix)
|
|
// 使用原生客户端执行SCAN命令
|
|
rdb := c.client.GetClient()
|
|
var cursor uint64
|
|
for {
|
|
keys, nextCursor, err := rdb.Scan(c.ctx, cursor, prefix+"*", 100).Result()
|
|
if err != nil {
|
|
zap.L().Error("Failed to scan keys with prefix",
|
|
zap.String("prefix", prefix),
|
|
zap.Error(err),
|
|
)
|
|
return
|
|
}
|
|
|
|
if len(keys) > 0 {
|
|
recordInvalidateMultiple(int64(len(keys)))
|
|
if err := c.client.Del(c.ctx, keys...); err != nil {
|
|
zap.L().Error("Failed to delete keys with prefix",
|
|
zap.String("prefix", prefix),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
}
|
|
|
|
cursor = nextCursor
|
|
if cursor == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear 清空所有缓存
|
|
func (c *RedisCache) Clear() {
|
|
if settings.DisableFlushDB {
|
|
zap.L().Debug("Skip FlushDB because cache.disable_flushdb=true")
|
|
return
|
|
}
|
|
recordInvalidate()
|
|
rdb := c.client.GetClient()
|
|
if err := rdb.FlushDB(c.ctx).Err(); err != nil {
|
|
zap.L().Error("Failed to clear cache",
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Exists 检查键是否存在
|
|
func (c *RedisCache) Exists(key string) bool {
|
|
key = normalizeKey(key)
|
|
n, err := c.client.Exists(c.ctx, key)
|
|
if err != nil {
|
|
zap.L().Error("Failed to check existence of key",
|
|
zap.String("key", key),
|
|
zap.Error(err),
|
|
)
|
|
return false
|
|
}
|
|
return n > 0
|
|
}
|
|
|
|
// Increment 增加计数器的值
|
|
func (c *RedisCache) Increment(key string) int64 {
|
|
return c.IncrementBy(key, 1)
|
|
}
|
|
|
|
// IncrementBy 增加指定值
|
|
func (c *RedisCache) IncrementBy(key string, value int64) int64 {
|
|
key = normalizeKey(key)
|
|
rdb := c.client.GetClient()
|
|
result, err := rdb.IncrBy(c.ctx, key, value).Result()
|
|
if err != nil {
|
|
zap.L().Error("Failed to increment key",
|
|
zap.String("key", key),
|
|
zap.Error(err),
|
|
)
|
|
return 0
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ==================== RedisCache Hash 操作 ====================
|
|
|
|
// HSet 设置 Hash 字段
|
|
func (c *RedisCache) HSet(ctx context.Context, key string, field string, value interface{}) error {
|
|
key = normalizeKey(key)
|
|
return c.client.HSet(ctx, key, field, value)
|
|
}
|
|
|
|
// HMSet 批量设置 Hash 字段
|
|
func (c *RedisCache) HMSet(ctx context.Context, key string, values map[string]interface{}) error {
|
|
key = normalizeKey(key)
|
|
return c.client.HMSet(ctx, key, values)
|
|
}
|
|
|
|
// HGet 获取 Hash 字段值
|
|
func (c *RedisCache) HGet(ctx context.Context, key string, field string) (string, error) {
|
|
key = normalizeKey(key)
|
|
return c.client.HGet(ctx, key, field)
|
|
}
|
|
|
|
// HMGet 批量获取 Hash 字段值
|
|
func (c *RedisCache) HMGet(ctx context.Context, key string, fields ...string) ([]interface{}, error) {
|
|
key = normalizeKey(key)
|
|
return c.client.HMGet(ctx, key, fields...)
|
|
}
|
|
|
|
// HGetAll 获取 Hash 所有字段
|
|
func (c *RedisCache) HGetAll(ctx context.Context, key string) (map[string]string, error) {
|
|
key = normalizeKey(key)
|
|
return c.client.HGetAll(ctx, key)
|
|
}
|
|
|
|
// HDel 删除 Hash 字段
|
|
func (c *RedisCache) HDel(ctx context.Context, key string, fields ...string) error {
|
|
key = normalizeKey(key)
|
|
return c.client.HDel(ctx, key, fields...)
|
|
}
|
|
|
|
// ==================== RedisCache Sorted Set 操作 ====================
|
|
|
|
// ZAdd 添加 Sorted Set 成员
|
|
func (c *RedisCache) ZAdd(ctx context.Context, key string, score float64, member string) error {
|
|
key = normalizeKey(key)
|
|
return c.client.ZAdd(ctx, key, score, member)
|
|
}
|
|
|
|
// ZRangeByScore 按分数范围获取成员(升序)
|
|
func (c *RedisCache) ZRangeByScore(ctx context.Context, key string, min, max string, offset, count int64) ([]string, error) {
|
|
key = normalizeKey(key)
|
|
return c.client.ZRangeByScore(ctx, key, min, max, offset, count)
|
|
}
|
|
|
|
// ZRevRangeByScore 按分数范围获取成员(降序)
|
|
func (c *RedisCache) ZRevRangeByScore(ctx context.Context, key string, max, min string, offset, count int64) ([]string, error) {
|
|
key = normalizeKey(key)
|
|
return c.client.ZRevRangeByScore(ctx, key, max, min, offset, count)
|
|
}
|
|
|
|
// ZRevRange 按排名倒序获取成员
|
|
func (c *RedisCache) ZRevRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
|
|
key = normalizeKey(key)
|
|
return c.client.ZRevRange(ctx, key, start, stop)
|
|
}
|
|
|
|
// ZReplaceSortedSet 删除后整表重写 ZSET
|
|
func (c *RedisCache) ZReplaceSortedSet(ctx context.Context, key string, members []redislib.Z) error {
|
|
key = normalizeKey(key)
|
|
rdb := c.client.GetClient()
|
|
if err := rdb.Del(ctx, key).Err(); err != nil {
|
|
return err
|
|
}
|
|
if len(members) == 0 {
|
|
return nil
|
|
}
|
|
return c.client.ZAddArgs(ctx, key, members...)
|
|
}
|
|
|
|
// ZRem 删除 Sorted Set 成员
|
|
func (c *RedisCache) ZRem(ctx context.Context, key string, members ...interface{}) error {
|
|
key = normalizeKey(key)
|
|
return c.client.ZRem(ctx, key, members...)
|
|
}
|
|
|
|
// ZCard 获取 Sorted Set 成员数量
|
|
func (c *RedisCache) ZCard(ctx context.Context, key string) (int64, error) {
|
|
key = normalizeKey(key)
|
|
return c.client.ZCard(ctx, key)
|
|
}
|
|
|
|
// ==================== RedisCache 计数器操作 ====================
|
|
|
|
// Incr 原子递增(返回新值)
|
|
func (c *RedisCache) Incr(ctx context.Context, key string) (int64, error) {
|
|
key = normalizeKey(key)
|
|
return c.client.Incr(ctx, key)
|
|
}
|
|
|
|
// Expire 设置过期时间
|
|
func (c *RedisCache) Expire(ctx context.Context, key string, ttl time.Duration) error {
|
|
key = normalizeKey(key)
|
|
_, err := c.client.Expire(ctx, key, ttl)
|
|
return err
|
|
}
|
|
|
|
// GetRedisClient 获取Redis客户端
|
|
func (c *RedisCache) GetRedisClient() *redisPkg.Client {
|
|
return c.client
|
|
}
|