package dbclient import ( "context" "fmt" "time" "github.com/redis/go-redis/v9" ) // RedisClient Redis 客户端 type RedisClient struct { client *redis.Client config *RedisConfig } // RedisConfig Redis 配置 type RedisConfig struct { Host string Port int Password string DB int // 数据库编号,默认 0 } // NewRedisClient 创建 Redis 客户端 func NewRedisClient(config *RedisConfig) (*RedisClient, error) { addr := fmt.Sprintf("%s:%d", config.Host, config.Port) rdb := redis.NewClient(&redis.Options{ Addr: addr, Password: config.Password, DB: config.DB, DialTimeout: 5 * time.Second, ReadTimeout: 3 * time.Second, WriteTimeout: 3 * time.Second, }) // 测试连接 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := rdb.Ping(ctx).Err(); err != nil { return nil, fmt.Errorf("Redis 连接测试失败: %v", err) } return &RedisClient{ client: rdb, config: config, }, nil } // TestRedisConnection 测试连接 func TestRedisConnection(host string, port int, password string) error { config := &RedisConfig{ Host: host, Port: port, Password: password, DB: 0, } client, err := NewRedisClient(config) if err != nil { return err } defer client.Close() return nil } // NewRedisClientByDB 根据参数创建指定 DB 的 Redis 客户端(用于多 DB 场景) func NewRedisClientByDB(host string, port int, password string, dbNum int) (*RedisClient, error) { config := &RedisConfig{ Host: host, Port: port, Password: password, DB: dbNum, } return NewRedisClient(config) } // Close 关闭连接 func (c *RedisClient) Close() error { if c.client != nil { return c.client.Close() } return nil } // ExecuteCommand 执行 Redis 命令 func (c *RedisClient) ExecuteCommand(ctx context.Context, cmd string, args ...interface{}) (interface{}, error) { return c.client.Do(ctx, append([]interface{}{cmd}, args...)...).Result() } // GetKeys 获取 Key 列表(支持 pattern,使用 SCAN 代替 KEYS 以提高性能) func (c *RedisClient) GetKeys(ctx context.Context, pattern string) ([]string, error) { if pattern == "" { pattern = "*" } var keys []string var cursor uint64 const count = 100 // 每次扫描的数量 for { var err error var batch []string batch, cursor, err = c.client.Scan(ctx, cursor, pattern, count).Result() if err != nil { return nil, err } keys = append(keys, batch...) if cursor == 0 { break } } return keys, nil } // GetKeyType 获取 Key 类型 func (c *RedisClient) GetKeyType(ctx context.Context, key string) (string, error) { return c.client.Type(ctx, key).Result() } // GetKeyValue 获取 Key 值 func (c *RedisClient) GetKeyValue(ctx context.Context, key string) (interface{}, error) { keyType, err := c.GetKeyType(ctx, key) if err != nil { return nil, err } switch keyType { case "string": return c.client.Get(ctx, key).Result() case "list": return c.client.LRange(ctx, key, 0, -1).Result() case "set": return c.client.SMembers(ctx, key).Result() case "zset": // 对于有序集合,返回带分数的结果 zMembers, err := c.client.ZRangeWithScores(ctx, key, 0, -1).Result() if err != nil { return nil, err } // 转换为 map 格式,便于展示 result := make([]map[string]interface{}, len(zMembers)) for i, member := range zMembers { result[i] = map[string]interface{}{ "member": member.Member, "score": member.Score, } } return result, nil case "hash": return c.client.HGetAll(ctx, key).Result() default: return nil, fmt.Errorf("不支持的类型: %s", keyType) } } // GetTTL 获取 Key 的 TTL func (c *RedisClient) GetTTL(ctx context.Context, key string) (time.Duration, error) { return c.client.TTL(ctx, key).Result() } // GetKeyInfo 获取 Key 详细信息 func (c *RedisClient) GetKeyInfo(ctx context.Context, key string) (map[string]interface{}, error) { info := map[string]interface{}{ "key": key, "type": "", "value": nil, "ttl": 0, } // 获取 Key 类型 keyType, err := c.GetKeyType(ctx, key) if err != nil { return nil, fmt.Errorf("获取 Key 类型失败: %v", err) } info["type"] = keyType // 获取 TTL ttl, err := c.GetTTL(ctx, key) if err != nil { return nil, fmt.Errorf("获取 TTL 失败: %v", err) } info["ttl"] = ttl.Seconds() // 获取 Key 值(限制大小,避免过大) value, err := c.GetKeyValue(ctx, key) if err != nil { return nil, fmt.Errorf("获取 Key 值失败: %v", err) } info["value"] = formatValuePreview(value) // 获取 Key 长度(使用 STRLEN、HLEN、SCARD、ZCARD) var keyLength int64 switch keyType { case "string": keyLength, err = c.client.StrLen(ctx, key).Result() case "list": keyLength, err = c.client.LLen(ctx, key).Result() case "set": keyLength, err = c.client.SCard(ctx, key).Result() case "zset": keyLength, err = c.client.ZCard(ctx, key).Result() case "hash": keyLength, err = c.client.HLen(ctx, key).Result() } if err == nil { info["length"] = keyLength } return info, nil } // formatValuePreview 格式化值预览(限制长度) func formatValuePreview(value interface{}) string { if value == nil { return "" } const maxPreviewLength = 200 valueStr := fmt.Sprintf("%v", value) if len(valueStr) > maxPreviewLength { valueStr = valueStr[:maxPreviewLength] + "..." } return valueStr } // ListDatabases 获取数据库列表(Redis 使用 DB number) // Redis 没有传统数据库概念,这里返回空数组 func (c *RedisClient) ListDatabases(ctx context.Context) ([]string, error) { // Redis 可以使用 DB number 来隔离数据 // 这里可以返回当前配置的 DB 或者所有可用的 DB // 为简单起见,返回空数组,让用户直接操作 Key return []string{}, nil }