package cache import ( "context" "crypto/md5" "encoding/json/v2" "errors" "fmt" "time" "github.com/redis/go-redis/v9" ) type Entry[T any] struct { Key string Value T TTL time.Duration } type Cache struct { client *redis.Client operationTimeout time.Duration } func NewCache(client *redis.Client, timeout time.Duration) *Cache { return &Cache{ client: client, operationTimeout: timeout, } } func (c *Cache) Get[T any](ctx context.Context, key string) (T, bool, error) { var zero T operationCtx, cancel := context.WithTimeout(ctx, c.operationTimeout) defer cancel() payload, err := c.client.Get(operationCtx, key).Result() cancel() if errors.Is(err, redis.Nil) { return zero, false, nil } if err != nil { return zero, false, fmt.Errorf("读取缓存失败:%w", err) } var value T if err := json.Unmarshal([]byte(payload), &value); err != nil { return zero, false, fmt.Errorf("缓存解码失败:%w", err) } return value, true, nil } func (c *Cache) Set[T any](ctx context.Context, entry Entry[T]) error { if entry.TTL < 0 { return fmt.Errorf("缓存过期时间必须大于等于零: %v", entry.TTL) } if err := ctx.Err(); err != nil { return fmt.Errorf("写入缓存失败:%w", err) } payload, err := json.Marshal(entry.Value) if err != nil { return fmt.Errorf("缓存编码失败:%w", err) } operationCtx, cancel := context.WithTimeout(ctx, c.operationTimeout) defer cancel() if err := c.client.Set(operationCtx, entry.Key, payload, entry.TTL).Err(); err != nil { return fmt.Errorf("写入缓存失败:%w", err) } return nil } func GenCacheKey(table, sql string, args ...any) (key string, err error) { prefix := fmt.Sprintf("lyra:cache:%s:", table) h := md5.New() // 写入查询语句 h.Write([]byte(sql)) // 序列化并写入每个参数 for _, arg := range args { jsonData, err := json.Marshal(arg) if err != nil { return "", fmt.Errorf("序列化参数失败:%w", err) } h.Write(jsonData) } return prefix + fmt.Sprintf("%x", h.Sum(nil)), nil }