package app import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "math" "net/url" "strconv" "strings" "sync" "GoNavi-Wails/internal/connection" "GoNavi-Wails/internal/logger" "GoNavi-Wails/internal/redis" ) // Redis client cache var ( redisCache = make(map[string]redis.RedisClient) redisCacheMu sync.Mutex newRedisClientFunc = redis.NewRedisClient ) // getRedisClient gets or creates a Redis client from cache func (a *App) getRedisClient(config connection.ConnectionConfig) (redis.RedisClient, error) { resolvedConfig, err := a.resolveConnectionSecrets(config) if err != nil { wrapped := wrapConnectError(config, err) logger.Error(wrapped, "Redis 密文解析失败:%s", formatRedisConnSummary(config)) return nil, wrapped } effectiveConfig := applyGlobalProxyToConnection(resolvedConfig) connectConfig, proxyErr := resolveDialConfigWithProxyFunc(effectiveConfig) if proxyErr != nil { wrapped := wrapConnectError(effectiveConfig, proxyErr) logger.Error(wrapped, "Redis 代理准备失败:%s", formatRedisConnSummary(effectiveConfig)) return nil, wrapped } key := getRedisClientCacheKey(connectConfig) shortKey := key if len(shortKey) > 12 { shortKey = shortKey[:12] } logger.Infof("获取 Redis 连接:%s 缓存Key=%s", formatRedisConnSummary(effectiveConfig), shortKey) redisCacheMu.Lock() defer redisCacheMu.Unlock() if client, ok := redisCache[key]; ok { logger.Infof("命中 Redis 连接缓存,开始检测可用性:缓存Key=%s", shortKey) if err := client.Ping(); err == nil { logger.Infof("缓存 Redis 连接可用:缓存Key=%s", shortKey) return client, nil } else { logger.Error(err, "缓存 Redis 连接不可用,准备重建:缓存Key=%s", shortKey) } client.Close() delete(redisCache, key) } logger.Infof("创建 Redis 客户端实例:缓存Key=%s", shortKey) client, connectedConfig, connectErr := connectRedisClientWithLegacyRootFallback(connectConfig) if connectErr != nil { wrapped := wrapConnectError(connectedConfig, connectErr) logger.Error(wrapped, "Redis 连接失败:%s 缓存Key=%s", formatRedisConnSummary(connectedConfig), shortKey) return nil, wrapped } redisCache[key] = client logger.Infof("Redis 连接成功并写入缓存:%s 缓存Key=%s", formatRedisConnSummary(connectedConfig), shortKey) return client, nil } func connectRedisClientWithLegacyRootFallback(config connection.ConnectionConfig) (redis.RedisClient, connection.ConnectionConfig, error) { client := newRedisClientFunc() if err := client.Connect(config); err == nil { return client, config, nil } else { client.Close() if !shouldRetryRedisWithClearedLegacyRoot(config, err) { return nil, config, err } fallbackConfig := config fallbackConfig.User = "" logger.Warnf("Redis 使用用户名 root 认证失败,已按历史默认值回退为空用户名重试:%s", formatRedisConnSummary(config)) fallbackClient := newRedisClientFunc() if retryErr := fallbackClient.Connect(fallbackConfig); retryErr != nil { fallbackClient.Close() return nil, fallbackConfig, retryErr } return fallbackClient, fallbackConfig, nil } } func shouldRetryRedisWithClearedLegacyRoot(config connection.ConnectionConfig, err error) bool { if err == nil || strings.ToLower(strings.TrimSpace(config.Type)) != "redis" { return false } if strings.TrimSpace(config.User) != "root" { return false } if _, ok := extractExplicitRedisUsername(config.URI); ok { return false } lower := strings.ToLower(strings.TrimSpace(err.Error())) return strings.Contains(lower, "wrongpass") || strings.Contains(lower, "invalid username-password pair") || strings.Contains(lower, "auth failed") || strings.Contains(lower, "wrong number of arguments for 'auth' command") || strings.Contains(lower, "authentication failed") } func extractExplicitRedisUsername(rawURI string) (string, bool) { trimmed := strings.TrimSpace(rawURI) if trimmed == "" { return "", false } parsed, err := url.Parse(trimmed) if err != nil || parsed.User == nil { return "", false } username := strings.TrimSpace(parsed.User.Username()) if username == "" { return "", false } return username, true } func getRedisClientCacheKey(config connection.ConnectionConfig) string { normalized := normalizeCacheKeyConfig(config) b, _ := json.Marshal(normalized) sum := sha256.Sum256(b) return hex.EncodeToString(sum[:]) } func formatRedisConnSummary(config connection.ConnectionConfig) string { var b strings.Builder b.WriteString("类型=redis 地址=") b.WriteString(config.Host) b.WriteString(":") b.WriteString(strconv.Itoa(config.Port)) if topology := strings.TrimSpace(config.Topology); topology != "" { b.WriteString(" 模式=") b.WriteString(topology) } if len(config.Hosts) > 0 { b.WriteString(" 节点数=") b.WriteString(strconv.Itoa(len(config.Hosts))) } b.WriteString(" DB=") b.WriteString(strconv.Itoa(config.RedisDB)) if config.UseSSH { b.WriteString(" SSH=") b.WriteString(config.SSH.Host) b.WriteString(":") b.WriteString(strconv.Itoa(config.SSH.Port)) b.WriteString(" 用户=") b.WriteString(config.SSH.User) } if config.UseProxy { b.WriteString(" 代理=") b.WriteString(strings.ToLower(strings.TrimSpace(config.Proxy.Type))) b.WriteString("://") b.WriteString(config.Proxy.Host) b.WriteString(":") b.WriteString(strconv.Itoa(config.Proxy.Port)) if strings.TrimSpace(config.Proxy.User) != "" { b.WriteString(" 代理认证=已配置") } } if config.UseHTTPTunnel { b.WriteString(" HTTP隧道=") b.WriteString(strings.TrimSpace(config.HTTPTunnel.Host)) b.WriteString(":") b.WriteString(strconv.Itoa(config.HTTPTunnel.Port)) if strings.TrimSpace(config.HTTPTunnel.User) != "" { b.WriteString(" HTTP隧道认证=已配置") } } return b.String() } // RedisConnect tests a Redis connection func (a *App) RedisConnect(config connection.ConnectionConfig) connection.QueryResult { config.Type = "redis" _, err := a.getRedisClient(config) if err != nil { logger.Error(err, "RedisConnect 连接失败:%s", formatRedisConnSummary(config)) return connection.QueryResult{Success: false, Message: err.Error()} } logger.Infof("RedisConnect 连接成功:%s", formatRedisConnSummary(config)) return connection.QueryResult{Success: true, Message: "连接成功"} } // RedisTestConnection tests a Redis connection (alias for RedisConnect) func (a *App) RedisTestConnection(config connection.ConnectionConfig) connection.QueryResult { return a.RedisConnect(config) } // RedisScanKeys scans keys matching a pattern func (a *App) RedisScanKeys(config connection.ConnectionConfig, pattern string, cursor any, count int64) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } parsedCursor, err := parseRedisScanCursor(cursor) if err != nil { logger.Warnf("RedisScanKeys 游标解析失败,已回退到起始游标:cursor=%v err=%v", cursor, err) parsedCursor = 0 } result, err := client.ScanKeys(pattern, parsedCursor, count) if err != nil { logger.Error(err, "RedisScanKeys 扫描失败:pattern=%s", pattern) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Data: result} } func parseRedisScanCursor(cursor any) (uint64, error) { switch v := cursor.(type) { case nil: return 0, nil case uint64: return v, nil case uint32: return uint64(v), nil case uint16: return uint64(v), nil case uint8: return uint64(v), nil case uint: return uint64(v), nil case int64: if v < 0 { return 0, fmt.Errorf("游标不能为负数: %d", v) } return uint64(v), nil case int32: if v < 0 { return 0, fmt.Errorf("游标不能为负数: %d", v) } return uint64(v), nil case int16: if v < 0 { return 0, fmt.Errorf("游标不能为负数: %d", v) } return uint64(v), nil case int8: if v < 0 { return 0, fmt.Errorf("游标不能为负数: %d", v) } return uint64(v), nil case int: if v < 0 { return 0, fmt.Errorf("游标不能为负数: %d", v) } return uint64(v), nil case float64: return parseRedisScanCursorFromFloat(v) case float32: return parseRedisScanCursorFromFloat(float64(v)) case json.Number: return parseRedisScanCursor(strings.TrimSpace(v.String())) case string: trimmed := strings.TrimSpace(v) if trimmed == "" { return 0, nil } parsed, err := strconv.ParseUint(trimmed, 10, 64) if err != nil { return 0, fmt.Errorf("无效游标: %q", v) } return parsed, nil default: return 0, fmt.Errorf("不支持的游标类型: %T", cursor) } } func parseRedisScanCursorFromFloat(value float64) (uint64, error) { if math.IsNaN(value) || math.IsInf(value, 0) { return 0, fmt.Errorf("无效浮点游标: %v", value) } if value < 0 { return 0, fmt.Errorf("游标不能为负数: %v", value) } if math.Trunc(value) != value { return 0, fmt.Errorf("游标必须为整数: %v", value) } if value > float64(math.MaxUint64) { return 0, fmt.Errorf("游标超出范围: %v", value) } return uint64(value), nil } // RedisGetValue gets the value of a key func (a *App) RedisGetValue(config connection.ConnectionConfig, key string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } value, err := client.GetValue(key) if err != nil { logger.Error(err, "RedisGetValue 获取失败:key=%s", key) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Data: value} } // RedisSetString sets a string value func (a *App) RedisSetString(config connection.ConnectionConfig, key, value string, ttl int64) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.SetString(key, value, ttl); err != nil { logger.Error(err, "RedisSetString 设置失败:key=%s", key) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "设置成功"} } // RedisSetHashField sets a field in a hash func (a *App) RedisSetHashField(config connection.ConnectionConfig, key, field, value string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.SetHashField(key, field, value); err != nil { logger.Error(err, "RedisSetHashField 设置失败:key=%s field=%s", key, field) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "设置成功"} } // RedisDeleteKeys deletes one or more keys func (a *App) RedisDeleteKeys(config connection.ConnectionConfig, keys []string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } deleted, err := client.DeleteKeys(keys) if err != nil { logger.Error(err, "RedisDeleteKeys 删除失败:keys=%v", keys) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Data: map[string]int64{"deleted": deleted}} } // RedisSetTTL sets the TTL of a key func (a *App) RedisSetTTL(config connection.ConnectionConfig, key string, ttl int64) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.SetTTL(key, ttl); err != nil { logger.Error(err, "RedisSetTTL 设置失败:key=%s ttl=%d", key, ttl) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "设置成功"} } // RedisExecuteCommand executes a raw Redis command func (a *App) RedisExecuteCommand(config connection.ConnectionConfig, command string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } // Parse command string into args args := parseRedisCommand(command) if len(args) == 0 { return connection.QueryResult{Success: false, Message: "命令不能为空"} } result, err := client.ExecuteCommand(args) if err != nil { logger.Error(err, "RedisExecuteCommand 执行失败:command=%s", command) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Data: result} } // parseRedisCommand parses a Redis command string into arguments func parseRedisCommand(command string) []string { command = strings.TrimSpace(command) if command == "" { return nil } var args []string var current strings.Builder inQuote := false quoteChar := rune(0) for _, ch := range command { if inQuote { if ch == quoteChar { inQuote = false args = append(args, current.String()) current.Reset() } else { current.WriteRune(ch) } } else { if ch == '"' || ch == '\'' { inQuote = true quoteChar = ch } else if ch == ' ' || ch == '\t' { if current.Len() > 0 { args = append(args, current.String()) current.Reset() } } else { current.WriteRune(ch) } } } if current.Len() > 0 { args = append(args, current.String()) } return args } // RedisGetServerInfo returns server information func (a *App) RedisGetServerInfo(config connection.ConnectionConfig) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } info, err := client.GetServerInfo() if err != nil { logger.Error(err, "RedisGetServerInfo 获取失败") return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Data: info} } // RedisGetDatabases returns information about all databases func (a *App) RedisGetDatabases(config connection.ConnectionConfig) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } dbs, err := client.GetDatabases() if err != nil { logger.Error(err, "RedisGetDatabases 获取失败") return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Data: dbs} } // RedisSelectDB selects a database func (a *App) RedisSelectDB(config connection.ConnectionConfig, dbIndex int) connection.QueryResult { config.Type = "redis" config.RedisDB = dbIndex client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.SelectDB(dbIndex); err != nil { logger.Error(err, "RedisSelectDB 切换失败:db=%d", dbIndex) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "切换成功"} } // RedisRenameKey renames a key func (a *App) RedisRenameKey(config connection.ConnectionConfig, oldKey, newKey string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.RenameKey(oldKey, newKey); err != nil { logger.Error(err, "RedisRenameKey 重命名失败:%s -> %s", oldKey, newKey) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "重命名成功"} } // RedisKeyExists checks whether a key already exists func (a *App) RedisKeyExists(config connection.ConnectionConfig, key string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } exists, err := client.KeyExists(key) if err != nil { logger.Error(err, "RedisKeyExists 检查失败:key=%s", key) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Data: map[string]bool{"exists": exists}} } func normalizeRedisStringArgs(raw any, argName string) ([]string, error) { switch v := raw.(type) { case nil: return nil, fmt.Errorf("%s 不能为空", argName) case string: text := strings.TrimSpace(v) if text == "" { return nil, fmt.Errorf("%s 不能为空", argName) } return []string{text}, nil case []string: items := make([]string, 0, len(v)) for _, item := range v { text := strings.TrimSpace(item) if text == "" { continue } items = append(items, text) } if len(items) == 0 { return nil, fmt.Errorf("%s 不能为空", argName) } return items, nil case []interface{}: items := make([]string, 0, len(v)) for _, item := range v { text := strings.TrimSpace(fmt.Sprintf("%v", item)) if text == "" || text == "" { continue } items = append(items, text) } if len(items) == 0 { return nil, fmt.Errorf("%s 不能为空", argName) } return items, nil default: return nil, fmt.Errorf("%s 类型无效", argName) } } // RedisDeleteHashField deletes fields from a hash func (a *App) RedisDeleteHashField(config connection.ConnectionConfig, key string, fields any) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } normalizedFields, err := normalizeRedisStringArgs(fields, "fields") if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.DeleteHashField(key, normalizedFields...); err != nil { logger.Error(err, "RedisDeleteHashField 删除失败:key=%s fields=%v", key, normalizedFields) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "删除成功"} } // RedisListPush pushes values to a list func (a *App) RedisListPush(config connection.ConnectionConfig, key string, values []string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.ListPush(key, values...); err != nil { logger.Error(err, "RedisListPush 添加失败:key=%s", key) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "添加成功"} } // RedisListSet sets a value at an index in a list func (a *App) RedisListSet(config connection.ConnectionConfig, key string, index int64, value string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.ListSet(key, index, value); err != nil { logger.Error(err, "RedisListSet 设置失败:key=%s index=%d", key, index) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "设置成功"} } // RedisSetAdd adds members to a set func (a *App) RedisSetAdd(config connection.ConnectionConfig, key string, members []string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.SetAdd(key, members...); err != nil { logger.Error(err, "RedisSetAdd 添加失败:key=%s", key) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "添加成功"} } // RedisSetRemove removes members from a set func (a *App) RedisSetRemove(config connection.ConnectionConfig, key string, members []string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.SetRemove(key, members...); err != nil { logger.Error(err, "RedisSetRemove 删除失败:key=%s", key) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "删除成功"} } // RedisZSetAdd adds members to a sorted set func (a *App) RedisZSetAdd(config connection.ConnectionConfig, key string, members []redis.ZSetMember) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.ZSetAdd(key, members...); err != nil { logger.Error(err, "RedisZSetAdd 添加失败:key=%s", key) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "添加成功"} } // RedisZSetRemove removes members from a sorted set func (a *App) RedisZSetRemove(config connection.ConnectionConfig, key string, members []string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.ZSetRemove(key, members...); err != nil { logger.Error(err, "RedisZSetRemove 删除失败:key=%s", key) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "删除成功"} } // RedisStreamAdd adds an entry to a stream func (a *App) RedisStreamAdd(config connection.ConnectionConfig, key string, fields map[string]string, id string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } newID, err := client.StreamAdd(key, fields, id) if err != nil { logger.Error(err, "RedisStreamAdd 添加失败:key=%s id=%s", key, id) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "添加成功", Data: map[string]string{"id": newID}} } // RedisStreamDelete deletes stream entries by IDs func (a *App) RedisStreamDelete(config connection.ConnectionConfig, key string, ids []string) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } deleted, err := client.StreamDelete(key, ids...) if err != nil { logger.Error(err, "RedisStreamDelete 删除失败:key=%s ids=%v", key, ids) return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "删除成功", Data: map[string]int64{"deleted": deleted}} } // RedisFlushDB flushes the current database func (a *App) RedisFlushDB(config connection.ConnectionConfig) connection.QueryResult { config.Type = "redis" client, err := a.getRedisClient(config) if err != nil { return connection.QueryResult{Success: false, Message: err.Error()} } if err := client.FlushDB(); err != nil { logger.Error(err, "RedisFlushDB 清空失败") return connection.QueryResult{Success: false, Message: err.Error()} } return connection.QueryResult{Success: true, Message: "清空成功"} } // CloseAllRedisClients closes all cached Redis clients (called on shutdown) func CloseAllRedisClients() { redisCacheMu.Lock() defer redisCacheMu.Unlock() for key, client := range redisCache { if client != nil { client.Close() logger.Infof("已关闭 Redis 连接:%s", key[:12]) } } redisCache = make(map[string]redis.RedisClient) }