package cache import ( "context" "encoding/json" "fmt" "time" "github.com/redis/go-redis/v9" ) // keyPrefix Redis 键前缀,避免与其他应用冲突。 const keyPrefix = "stock-analysis:" // taskKey 构造任务状态键。 func taskKey(id string) string { return keyPrefix + "task:" + id } // activeSetKey 构造活跃任务集合键。 const activeSetKey = keyPrefix + "tasks:active" // RedisCache 基于 go-redis/v9 的 Cache 实现。 type RedisCache struct { client *redis.Client } // NewRedisCache 创建 Redis 缓存实例。 // addr 格式 "host:port",password 为空表示无密码,db 为 Redis 数据库编号。 // 配置较短的超时和重试次数,Redis 不可用时快速降级到 NoopCache。 func NewRedisCache(addr, password string, db int) (*RedisCache, error) { client := redis.NewClient(&redis.Options{ Addr: addr, Password: password, DB: db, DialTimeout: 2 * time.Second, ReadTimeout: 2 * time.Second, WriteTimeout: 2 * time.Second, MaxRetries: 1, PoolSize: 10, }) return &RedisCache{client: client}, nil } // GetTask 从 Redis 读取任务状态(JSON 反序列化)。 func (c *RedisCache) GetTask(ctx context.Context, id string) (*Task, error) { data, err := c.client.Get(ctx, taskKey(id)).Bytes() if err != nil { if err == redis.Nil { return nil, nil // 不存在 } return nil, fmt.Errorf("redis get task %s: %w", id, err) } var t Task if err := json.Unmarshal(data, &t); err != nil { return nil, fmt.Errorf("unmarshal task %s: %w", id, err) } return &t, nil } // SetTask 写入任务状态(JSON 序列化),设置 TTL 防止过期数据残留。 func (c *RedisCache) SetTask(ctx context.Context, t *Task, ttl time.Duration) error { data, err := json.Marshal(t) if err != nil { return fmt.Errorf("marshal task %s: %w", t.ID, err) } if err := c.client.Set(ctx, taskKey(t.ID), data, ttl).Err(); err != nil { return fmt.Errorf("redis set task %s: %w", t.ID, err) } return nil } // DeleteTask 删除任务状态。 func (c *RedisCache) DeleteTask(ctx context.Context, id string) error { if err := c.client.Del(ctx, taskKey(id)).Err(); err != nil { return fmt.Errorf("redis del task %s: %w", id, err) } return nil } // ListActiveTaskIDs 返回活跃任务集合中的所有 ID。 func (c *RedisCache) ListActiveTaskIDs(ctx context.Context) ([]string, error) { ids, err := c.client.SMembers(ctx, activeSetKey).Result() if err != nil { return nil, fmt.Errorf("redis smembers: %w", err) } return ids, nil } // AddActiveTask 将任务 ID 加入活跃集合。 func (c *RedisCache) AddActiveTask(ctx context.Context, id string) error { if err := c.client.SAdd(ctx, activeSetKey, id).Err(); err != nil { return fmt.Errorf("redis sadd %s: %w", id, err) } return nil } // RemoveActiveTask 从活跃集合移除任务 ID。 func (c *RedisCache) RemoveActiveTask(ctx context.Context, id string) error { if err := c.client.SRem(ctx, activeSetKey, id).Err(); err != nil { return fmt.Errorf("redis srem %s: %w", id, err) } return nil } // Ping 探测 Redis 连通性。 func (c *RedisCache) Ping(ctx context.Context) error { return c.client.Ping(ctx).Err() }