mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-17 08:27:34 +08:00
* 🐛 fix(data-viewer): 修复ClickHouse尾部分页异常并增强DuckDB复杂类型兼容 - DataViewer 新增 ClickHouse 反向分页策略,修复最后页与倒数页查询失败 - DuckDB 查询失败时按列类型生成安全 SELECT,复杂类型转 VARCHAR 重试 - 分页状态统一使用 currentPage 回填,避免页码与总数推导不一致 - 增强查询异常日志与重试路径,降低大表场景卡顿与误报 * ✨ feat(frontend-driver): 驱动管理支持快速搜索并优化信息展示 - 新增搜索框,支持按 DuckDB/ClickHouse 等关键字快速定位驱动 - 显示“匹配 x / y”统计与无结果提示 - 优化头部区域排版,提升透明/暗色场景下的视觉对齐 * 🔧 fix(connection-modal): 修复多数据源URI导入解析并校正Oracle服务名校验 - 新增单主机URI解析映射,兼容 postgres/postgresql、sqlserver、redis、tdengine、dameng(dm)、kingbase、highgo、vastbase、clickhouse、oracle - 抽取 parseSingleHostUri 复用逻辑,统一 host/port/user/password/database 回填行为 - Oracle 连接新增服务名必填校验,移除“服务名为空回退用户名”的隐式逻辑 - 连接弹窗补充 Oracle 服务名输入项与 URI 示例 * 🐛 fix(query-export): 修复查询结果导出卡住并统一按数据源能力控制导出路径 - 查询结果页导出增加稳定兜底,异常时确保 loading 关闭避免持续转圈 - DataGrid 导出逻辑按数据源能力分流,优先走后端 ExportQuery 并保留结果集导出降级 - QueryEditor 传递结果导出 SQL,保证查询结果导出范围与当前结果一致 - 后端补充 ExportData/ExportQuery 关键日志,提升导出链路可观测性 * 🐛 fix(precision): 修复查询链路与分页统计的大整数精度丢失 - 代理响应数据解码改为 UseNumber,避免默认 float64 吞精度 - 统一归一化 json.Number 与超界整数,超出 JS 安全范围转字符串 - 修复 DataViewer 总数解析,超大值不再误转 Number 参与分页 - refs #142 * 🐛 fix(driver-manager): 修复驱动管理网络告警重复并强化代理引导 - 新增下载链路域名探测,区分“GitHub可达但驱动下载链路不可达” - 网络不可达场景仅保留红色强提醒,移除重复二级告警 - 强提醒增加“打开全局代理设置”入口,优先引导使用 GoNavi 全局代理 - 统一网络检测与目录说明提示图标尺寸,修复加载期视觉不一致 - refs #141 * ♻️ refactor(frontend-interaction): 统一标签拖拽与暗色主题交互实现 - 重构Tab拖拽排序实现,统一为可配置拖拽引擎 - 规范拖拽与点击事件边界,提升交互一致性 - 统一多组件暗色透明样式策略,减少硬编码色值 - 提升Redis/表格/连接面板在透明模式下的观感一致性 - refs #144 * ♻️ refactor(update-state): 重构在线更新状态流并按版本统一进度展示 - 重构更新检查与下载状态同步流程,减少前后端状态分叉 - 进度展示严格绑定 latestVersion,避免跨版本状态串用 - 优化 about 打开场景的静默检查状态回填逻辑 - 统一下载弹窗关闭/后台隐藏行为 - 保持现有安装流程并补齐目录打开能力 * 🎨 style(sidebar-log): 将SQL执行日志入口调整为悬浮胶囊样式 - 移除侧栏底部整条日志入口容器 - 新增悬浮按钮阴影/边框/透明背景并适配明暗主题 - 为树区域预留底部空间避免入口遮挡内容 * ✨ feat(redis-cluster): 支持集群模式逻辑多库隔离与 0-15 库切换 - 前端恢复 Redis 集群场景下 db0-db15 的数据库选择与展示 - 后端新增集群逻辑库命名空间前缀映射,统一 key/pattern 读写隔离 - 覆盖扫描、读取、写入、删除、重命名等核心操作的键映射规则 - 集群命令通道支持 SELECT 逻辑切库与 FLUSHDB 逻辑库清空 - refs #145 * ✨ feat(DataGrid): 大数据表虚拟滚动性能优化及UI一致性修复 - 启用动态虚拟滚动(数据量≥500行自动切换),解决万行数据表卡顿问题 - 虚拟模式下EditableCell改用div渲染,CSS选择器从元素级改为类级适配虚拟DOM - 修复虚拟模式双水平滚动条:样式化rc-virtual-list内置滚动条为胶囊外观,禁用自定义外部滚动条 - 为rc-virtual-list水平滚动条添加鼠标滚轮支持(MutationObserver + marginLeft驱动) - 修复白色主题透明模式下列名悬浮Tooltip对比度不足的问题 - 新增白色主题全局滚动条样式适配透明模式(App.css) - App.tsx主题token与组件样式优化 - refs #147 * 🔧 chore(app): 清理 App.tsx 类型告警并收敛前端壳层实现 - 清除未使用代码和冗余状态 - 替换弃用 API 以消除 IDE 提示 - 显式处理浮动 Promise 避免告警 - 保持现有更新检查和代理设置行为不变 --------- Co-authored-by: Syngnat <yangguofeng919@gmail.com>
604 lines
19 KiB
Go
604 lines
19 KiB
Go
package app
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"fmt"
|
||
"math"
|
||
"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
|
||
)
|
||
|
||
// getRedisClient gets or creates a Redis client from cache
|
||
func (a *App) getRedisClient(config connection.ConnectionConfig) (redis.RedisClient, error) {
|
||
key := getRedisClientCacheKey(config)
|
||
shortKey := key
|
||
if len(shortKey) > 12 {
|
||
shortKey = shortKey[:12]
|
||
}
|
||
logger.Infof("获取 Redis 连接:%s 缓存Key=%s", formatRedisConnSummary(config), 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 := redis.NewRedisClient()
|
||
if err := client.Connect(config); err != nil {
|
||
logger.Error(err, "Redis 连接失败:%s 缓存Key=%s", formatRedisConnSummary(config), shortKey)
|
||
return nil, err
|
||
}
|
||
|
||
redisCache[key] = client
|
||
logger.Infof("Redis 连接成功并写入缓存:%s 缓存Key=%s", formatRedisConnSummary(config), shortKey)
|
||
return client, nil
|
||
}
|
||
|
||
func getRedisClientCacheKey(config connection.ConnectionConfig) string {
|
||
if !config.UseSSH {
|
||
config.SSH = connection.SSHConfig{}
|
||
}
|
||
b, _ := json.Marshal(config)
|
||
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)
|
||
}
|
||
|
||
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: "重命名成功"}
|
||
}
|
||
|
||
// RedisDeleteHashField deletes fields from a hash
|
||
func (a *App) RedisDeleteHashField(config connection.ConnectionConfig, key string, fields []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.DeleteHashField(key, fields...); err != nil {
|
||
logger.Error(err, "RedisDeleteHashField 删除失败:key=%s fields=%v", key, fields)
|
||
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)
|
||
}
|