mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-14 02:19:58 +08:00
🐛 fix(redis): 修复 Sentinel 切换数据库配置丢失
- 切换 Redis DB 时复用完整 Connect 逻辑,保留 Sentinel、TLS、SSH 等连接参数 - 补充 Sentinel 切 DB 与 Redis RPC 配置字段回归测试
This commit is contained in:
@@ -199,6 +199,30 @@ describe('buildRpcConnectionConfig', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves Redis cluster and Sentinel topology fields for RPC calls', () => {
|
||||
const result = buildRpcConnectionConfig({
|
||||
id: 'conn-redis-sentinel',
|
||||
type: 'redis',
|
||||
host: 'sentinel-a.local',
|
||||
port: '26379' as unknown as number,
|
||||
hosts: ['sentinel-b.local:26379', 'sentinel-c.local:26379'],
|
||||
topology: 'sentinel',
|
||||
user: 'default',
|
||||
password: 'redis-secret',
|
||||
redisSentinelMaster: 'mymaster',
|
||||
redisSentinelUser: 'sentinel-user',
|
||||
redisSentinelPassword: 'sentinel-secret',
|
||||
redisDB: '3' as unknown as number,
|
||||
} as any);
|
||||
|
||||
expect(result.topology).toBe('sentinel');
|
||||
expect(result.hosts).toEqual(['sentinel-b.local:26379', 'sentinel-c.local:26379']);
|
||||
expect(result.redisSentinelMaster).toBe('mymaster');
|
||||
expect(result.redisSentinelUser).toBe('sentinel-user');
|
||||
expect(result.redisSentinelPassword).toBe('sentinel-secret');
|
||||
expect(result.redisDB).toBe(3);
|
||||
});
|
||||
|
||||
it('returns a Wails connection model instance for RPC compatibility', () => {
|
||||
const result = buildRpcConnectionConfig({
|
||||
id: 'conn-model',
|
||||
|
||||
@@ -49,6 +49,10 @@ const (
|
||||
redisSearchMaxDuration = 3 * time.Second
|
||||
)
|
||||
|
||||
var redisDBSwitchConnect = func(client *RedisClientImpl, config connection.ConnectionConfig) error {
|
||||
return client.Connect(config)
|
||||
}
|
||||
|
||||
// NewRedisClient creates a new Redis client instance
|
||||
func NewRedisClient() RedisClient {
|
||||
return &RedisClientImpl{}
|
||||
@@ -1589,49 +1593,18 @@ func (r *RedisClientImpl) SelectDB(index int) error {
|
||||
return fmt.Errorf("数据库索引必须在 0-15 之间")
|
||||
}
|
||||
|
||||
// Create new client with different DB
|
||||
addr := ""
|
||||
if len(r.seedAddrs) > 0 {
|
||||
addr = r.seedAddrs[0]
|
||||
}
|
||||
if r.forwarder != nil {
|
||||
addr = r.forwarder.LocalAddr
|
||||
}
|
||||
if addr == "" {
|
||||
addr = fmt.Sprintf("%s:%d", r.config.Host, r.config.Port)
|
||||
}
|
||||
|
||||
timeout := normalizeRedisTimeout(r.config.Timeout)
|
||||
|
||||
opts := &redis.Options{
|
||||
Addr: addr,
|
||||
Username: strings.TrimSpace(r.config.User),
|
||||
Password: r.config.Password,
|
||||
DB: index,
|
||||
DialTimeout: timeout,
|
||||
ReadTimeout: timeout,
|
||||
WriteTimeout: timeout,
|
||||
}
|
||||
|
||||
newClient := redis.NewClient(opts)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
if err := newClient.Ping(ctx).Err(); err != nil {
|
||||
newClient.Close()
|
||||
nextConfig := r.config
|
||||
nextConfig.RedisDB = index
|
||||
nextClient := &RedisClientImpl{}
|
||||
if err := redisDBSwitchConnect(nextClient, nextConfig); err != nil {
|
||||
return fmt.Errorf("切换数据库失败: %w", err)
|
||||
}
|
||||
|
||||
// Close old client and replace
|
||||
if r.client != nil {
|
||||
_ = r.client.Close()
|
||||
oldClient := r.client
|
||||
*r = *nextClient
|
||||
if oldClient != nil {
|
||||
_ = oldClient.Close()
|
||||
}
|
||||
r.client = newClient
|
||||
r.singleClient = newClient
|
||||
r.clusterClient = nil
|
||||
r.currentDB = index
|
||||
r.config.RedisDB = index
|
||||
|
||||
logger.Infof("Redis 切换到数据库: db%d", index)
|
||||
return nil
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
goredis "github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// 回归保护:HGETALL 在 RESP3 下返回 map[interface{}]interface{}(go-redis v9 默认 RESP3),
|
||||
@@ -275,6 +277,70 @@ func TestRedisClusterKeepsSSHValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedisSelectDBReconnectsWithSentinelConfig(t *testing.T) {
|
||||
oldConnect := redisDBSwitchConnect
|
||||
defer func() {
|
||||
redisDBSwitchConnect = oldConnect
|
||||
}()
|
||||
|
||||
var captured connection.ConnectionConfig
|
||||
redisDBSwitchConnect = func(client *RedisClientImpl, config connection.ConnectionConfig) error {
|
||||
captured = config
|
||||
next := goredis.NewClient(&goredis.Options{Addr: "127.0.0.1:0"})
|
||||
client.client = next
|
||||
client.singleClient = next
|
||||
client.config = config
|
||||
client.currentDB = config.RedisDB
|
||||
return nil
|
||||
}
|
||||
|
||||
oldClient := goredis.NewClient(&goredis.Options{Addr: "127.0.0.1:0"})
|
||||
client := &RedisClientImpl{
|
||||
client: oldClient,
|
||||
singleClient: oldClient,
|
||||
config: connection.ConnectionConfig{
|
||||
Type: "redis",
|
||||
Host: "sentinel-a.local",
|
||||
Port: 26379,
|
||||
Hosts: []string{"sentinel-b.local:26379", "sentinel-c.local:26379"},
|
||||
Topology: "sentinel",
|
||||
User: "data-user",
|
||||
Password: "data-pass",
|
||||
RedisSentinelMaster: "mymaster",
|
||||
RedisSentinelUser: "sentinel-user",
|
||||
RedisSentinelPassword: "sentinel-pass",
|
||||
UseSSL: true,
|
||||
SSLMode: "required",
|
||||
RedisDB: 0,
|
||||
},
|
||||
currentDB: 0,
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if err := client.SelectDB(6); err != nil {
|
||||
t.Fatalf("SelectDB returned error: %v", err)
|
||||
}
|
||||
|
||||
if captured.RedisDB != 6 || client.currentDB != 6 {
|
||||
t.Fatalf("expected RedisDB/currentDB=6, captured=%d current=%d", captured.RedisDB, client.currentDB)
|
||||
}
|
||||
if captured.Topology != "sentinel" {
|
||||
t.Fatalf("expected sentinel topology to be preserved, got %q", captured.Topology)
|
||||
}
|
||||
if captured.RedisSentinelMaster != "mymaster" {
|
||||
t.Fatalf("expected Sentinel master to be preserved, got %q", captured.RedisSentinelMaster)
|
||||
}
|
||||
if captured.RedisSentinelUser != "sentinel-user" || captured.RedisSentinelPassword != "sentinel-pass" {
|
||||
t.Fatalf("expected Sentinel credentials to be preserved, got user=%q password=%q", captured.RedisSentinelUser, captured.RedisSentinelPassword)
|
||||
}
|
||||
if len(captured.Hosts) != 2 || captured.Hosts[0] != "sentinel-b.local:26379" || captured.Hosts[1] != "sentinel-c.local:26379" {
|
||||
t.Fatalf("expected Sentinel hosts to be preserved, got %#v", captured.Hosts)
|
||||
}
|
||||
if !captured.UseSSL || captured.SSLMode != "required" {
|
||||
t.Fatalf("expected TLS settings to be preserved, got useSSL=%v sslMode=%q", captured.UseSSL, captured.SSLMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRedisKeyGone(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user