285 lines
6.1 KiB
Go
285 lines
6.1 KiB
Go
package cache
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/redis/go-redis/v9"
|
||
"go.uber.org/zap"
|
||
|
||
"tyapi-server/internal/shared/interfaces"
|
||
)
|
||
|
||
// RedisCache Redis缓存实现
|
||
type RedisCache struct {
|
||
client *redis.Client
|
||
logger *zap.Logger
|
||
prefix string
|
||
|
||
// 统计信息
|
||
hits int64
|
||
misses int64
|
||
}
|
||
|
||
// NewRedisCache 创建Redis缓存实例
|
||
func NewRedisCache(client *redis.Client, logger *zap.Logger, prefix string) *RedisCache {
|
||
return &RedisCache{
|
||
client: client,
|
||
logger: logger,
|
||
prefix: prefix,
|
||
}
|
||
}
|
||
|
||
// Name 返回服务名称
|
||
func (r *RedisCache) Name() string {
|
||
return "redis-cache"
|
||
}
|
||
|
||
// Initialize 初始化服务
|
||
func (r *RedisCache) Initialize(ctx context.Context) error {
|
||
// 测试连接
|
||
_, err := r.client.Ping(ctx).Result()
|
||
if err != nil {
|
||
r.logger.Error("Failed to connect to Redis", zap.Error(err))
|
||
return fmt.Errorf("redis connection failed: %w", err)
|
||
}
|
||
|
||
r.logger.Info("Redis cache service initialized")
|
||
return nil
|
||
}
|
||
|
||
// HealthCheck 健康检查
|
||
func (r *RedisCache) HealthCheck(ctx context.Context) error {
|
||
_, err := r.client.Ping(ctx).Result()
|
||
return err
|
||
}
|
||
|
||
// Shutdown 关闭服务
|
||
func (r *RedisCache) Shutdown(ctx context.Context) error {
|
||
return r.client.Close()
|
||
}
|
||
|
||
// Get 获取缓存值
|
||
func (r *RedisCache) Get(ctx context.Context, key string, dest interface{}) error {
|
||
fullKey := r.getFullKey(key)
|
||
|
||
val, err := r.client.Get(ctx, fullKey).Result()
|
||
if err != nil {
|
||
if err == redis.Nil {
|
||
r.misses++
|
||
return fmt.Errorf("cache miss: key %s not found", key)
|
||
}
|
||
r.logger.Error("Failed to get cache", zap.String("key", key), zap.Error(err))
|
||
return err
|
||
}
|
||
|
||
r.hits++
|
||
return json.Unmarshal([]byte(val), dest)
|
||
}
|
||
|
||
// Set 设置缓存值
|
||
func (r *RedisCache) Set(ctx context.Context, key string, value interface{}, ttl ...interface{}) error {
|
||
fullKey := r.getFullKey(key)
|
||
|
||
data, err := json.Marshal(value)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to marshal value: %w", err)
|
||
}
|
||
|
||
var expiration time.Duration
|
||
if len(ttl) > 0 {
|
||
switch v := ttl[0].(type) {
|
||
case time.Duration:
|
||
expiration = v
|
||
case int:
|
||
expiration = time.Duration(v) * time.Second
|
||
case string:
|
||
expiration, _ = time.ParseDuration(v)
|
||
default:
|
||
expiration = 24 * time.Hour // 默认24小时
|
||
}
|
||
} else {
|
||
expiration = 24 * time.Hour // 默认24小时
|
||
}
|
||
|
||
err = r.client.Set(ctx, fullKey, data, expiration).Err()
|
||
if err != nil {
|
||
r.logger.Error("Failed to set cache", zap.String("key", key), zap.Error(err))
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Delete 删除缓存
|
||
func (r *RedisCache) Delete(ctx context.Context, keys ...string) error {
|
||
if len(keys) == 0 {
|
||
return nil
|
||
}
|
||
|
||
fullKeys := make([]string, len(keys))
|
||
for i, key := range keys {
|
||
fullKeys[i] = r.getFullKey(key)
|
||
}
|
||
|
||
err := r.client.Del(ctx, fullKeys...).Err()
|
||
if err != nil {
|
||
r.logger.Error("Failed to delete cache", zap.Strings("keys", keys), zap.Error(err))
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Exists 检查键是否存在
|
||
func (r *RedisCache) Exists(ctx context.Context, key string) (bool, error) {
|
||
fullKey := r.getFullKey(key)
|
||
|
||
count, err := r.client.Exists(ctx, fullKey).Result()
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
return count > 0, nil
|
||
}
|
||
|
||
// GetMultiple 批量获取
|
||
func (r *RedisCache) GetMultiple(ctx context.Context, keys []string) (map[string]interface{}, error) {
|
||
if len(keys) == 0 {
|
||
return make(map[string]interface{}), nil
|
||
}
|
||
|
||
fullKeys := make([]string, len(keys))
|
||
for i, key := range keys {
|
||
fullKeys[i] = r.getFullKey(key)
|
||
}
|
||
|
||
values, err := r.client.MGet(ctx, fullKeys...).Result()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
result := make(map[string]interface{})
|
||
for i, val := range values {
|
||
if val != nil {
|
||
var data interface{}
|
||
if err := json.Unmarshal([]byte(val.(string)), &data); err == nil {
|
||
result[keys[i]] = data
|
||
}
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// SetMultiple 批量设置
|
||
func (r *RedisCache) SetMultiple(ctx context.Context, data map[string]interface{}, ttl ...interface{}) error {
|
||
if len(data) == 0 {
|
||
return nil
|
||
}
|
||
|
||
var expiration time.Duration
|
||
if len(ttl) > 0 {
|
||
switch v := ttl[0].(type) {
|
||
case time.Duration:
|
||
expiration = v
|
||
case int:
|
||
expiration = time.Duration(v) * time.Second
|
||
default:
|
||
expiration = 24 * time.Hour
|
||
}
|
||
} else {
|
||
expiration = 24 * time.Hour
|
||
}
|
||
|
||
pipe := r.client.Pipeline()
|
||
for key, value := range data {
|
||
fullKey := r.getFullKey(key)
|
||
jsonData, err := json.Marshal(value)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
pipe.Set(ctx, fullKey, jsonData, expiration)
|
||
}
|
||
|
||
_, err := pipe.Exec(ctx)
|
||
return err
|
||
}
|
||
|
||
// DeletePattern 按模式删除
|
||
func (r *RedisCache) DeletePattern(ctx context.Context, pattern string) error {
|
||
fullPattern := r.getFullKey(pattern)
|
||
|
||
keys, err := r.client.Keys(ctx, fullPattern).Result()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if len(keys) > 0 {
|
||
return r.client.Del(ctx, keys...).Err()
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Keys 获取匹配的键
|
||
func (r *RedisCache) Keys(ctx context.Context, pattern string) ([]string, error) {
|
||
fullPattern := r.getFullKey(pattern)
|
||
|
||
keys, err := r.client.Keys(ctx, fullPattern).Result()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 移除前缀
|
||
result := make([]string, len(keys))
|
||
prefixLen := len(r.prefix) + 1 // +1 for ":"
|
||
for i, key := range keys {
|
||
if len(key) > prefixLen {
|
||
result[i] = key[prefixLen:]
|
||
} else {
|
||
result[i] = key
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// Stats 获取缓存统计
|
||
func (r *RedisCache) Stats(ctx context.Context) (interfaces.CacheStats, error) {
|
||
dbSize, _ := r.client.DBSize(ctx).Result()
|
||
|
||
return interfaces.CacheStats{
|
||
Hits: r.hits,
|
||
Misses: r.misses,
|
||
Keys: dbSize,
|
||
Memory: 0, // 暂时设为0,后续可解析Redis info
|
||
Connections: 0, // 暂时设为0,后续可解析Redis info
|
||
}, nil
|
||
}
|
||
|
||
// getFullKey 获取完整键名
|
||
func (r *RedisCache) getFullKey(key string) string {
|
||
if r.prefix == "" {
|
||
return key
|
||
}
|
||
return fmt.Sprintf("%s:%s", r.prefix, key)
|
||
}
|
||
|
||
// Flush 清空所有缓存
|
||
func (r *RedisCache) Flush(ctx context.Context) error {
|
||
if r.prefix == "" {
|
||
return r.client.FlushDB(ctx).Err()
|
||
}
|
||
|
||
// 只删除带前缀的键
|
||
return r.DeletePattern(ctx, "*")
|
||
}
|
||
|
||
// GetClient 获取原始Redis客户端
|
||
func (r *RedisCache) GetClient() *redis.Client {
|
||
return r.client
|
||
}
|