242 lines
5.8 KiB
Go
242 lines
5.8 KiB
Go
package dbclient
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"time"
|
||
|
||
"u-desk/internal/common"
|
||
|
||
"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: common.TimeoutConnect,
|
||
ReadTimeout: 3 * time.Second,
|
||
WriteTimeout: 3 * time.Second,
|
||
})
|
||
|
||
// 测试连接
|
||
ctx, cancel := context.WithTimeout(context.Background(), common.TimeoutConnect)
|
||
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
|
||
}
|