mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-08 20:51:55 +08:00
🐛 fix(redis): 修正 hash 字段删除参数序列化错误
- 前端统一按数组传递 hash 字段删除参数 - 后端兼容单字符串与数组两种删除入参 - 补充 Redis hash 字段删除回归测试 Fixes #343
This commit is contained in:
@@ -18,8 +18,8 @@ import (
|
||||
|
||||
// Redis client cache
|
||||
var (
|
||||
redisCache = make(map[string]redis.RedisClient)
|
||||
redisCacheMu sync.Mutex
|
||||
redisCache = make(map[string]redis.RedisClient)
|
||||
redisCacheMu sync.Mutex
|
||||
newRedisClientFunc = redis.NewRedisClient
|
||||
)
|
||||
|
||||
@@ -539,16 +539,62 @@ func (a *App) RedisKeyExists(config connection.ConnectionConfig, key string) con
|
||||
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 == "<nil>" {
|
||||
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 []string) connection.QueryResult {
|
||||
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()}
|
||||
}
|
||||
|
||||
if err := client.DeleteHashField(key, fields...); err != nil {
|
||||
logger.Error(err, "RedisDeleteHashField 删除失败:key=%s fields=%v", key, fields)
|
||||
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()}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
)
|
||||
|
||||
type capturingRedisClient struct {
|
||||
connectConfig connection.ConnectionConfig
|
||||
connectConfig connection.ConnectionConfig
|
||||
deletedHashKey string
|
||||
deletedHashFields []string
|
||||
}
|
||||
|
||||
func (c *capturingRedisClient) Connect(config connection.ConnectionConfig) error {
|
||||
@@ -45,13 +47,21 @@ func (c *capturingRedisClient) GetString(key string) (string, error) { return ""
|
||||
|
||||
func (c *capturingRedisClient) SetString(key, value string, ttl int64) error { return nil }
|
||||
|
||||
func (c *capturingRedisClient) GetHash(key string) (map[string]string, error) { return map[string]string{}, nil }
|
||||
func (c *capturingRedisClient) GetHash(key string) (map[string]string, error) {
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
|
||||
func (c *capturingRedisClient) SetHashField(key, field, value string) error { return nil }
|
||||
|
||||
func (c *capturingRedisClient) DeleteHashField(key string, fields ...string) error { return nil }
|
||||
func (c *capturingRedisClient) DeleteHashField(key string, fields ...string) error {
|
||||
c.deletedHashKey = key
|
||||
c.deletedHashFields = append([]string(nil), fields...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *capturingRedisClient) GetList(key string, start, stop int64) ([]string, error) { return nil, nil }
|
||||
func (c *capturingRedisClient) GetList(key string, start, stop int64) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *capturingRedisClient) ListPush(key string, values ...string) error { return nil }
|
||||
|
||||
@@ -83,7 +93,9 @@ func (c *capturingRedisClient) StreamDelete(key string, ids ...string) (int64, e
|
||||
|
||||
func (c *capturingRedisClient) ExecuteCommand(args []string) (interface{}, error) { return nil, nil }
|
||||
|
||||
func (c *capturingRedisClient) GetServerInfo() (map[string]string, error) { return map[string]string{}, nil }
|
||||
func (c *capturingRedisClient) GetServerInfo() (map[string]string, error) {
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
|
||||
func (c *capturingRedisClient) GetDatabases() ([]redislib.RedisDBInfo, error) { return nil, nil }
|
||||
|
||||
@@ -109,7 +121,7 @@ func (c *scriptedRedisClient) Connect(config connection.ConnectionConfig) error
|
||||
|
||||
func TestRedisConnectResolvesSavedSecretsByConnectionID(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
name string
|
||||
savedConfig connection.ConnectionConfig
|
||||
runtimeConfig connection.ConnectionConfig
|
||||
assertResolved func(t *testing.T, got connection.ConnectionConfig)
|
||||
@@ -426,3 +438,75 @@ func TestRedisConnectRetriesLegacyDefaultRootUserWithoutUsernameAfterAuthFailure
|
||||
t.Fatalf("expected fallback Redis connect attempt to clear legacy root user, got %q", connectCalls[1].User)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedisDeleteHashFieldAcceptsSingleStringField(t *testing.T) {
|
||||
app := NewAppWithSecretStore(newFakeAppSecretStore())
|
||||
app.configDir = t.TempDir()
|
||||
|
||||
CloseAllRedisClients()
|
||||
client := &capturingRedisClient{}
|
||||
originalNewRedisClientFunc := newRedisClientFunc
|
||||
originalResolveDialConfigWithProxyFunc := resolveDialConfigWithProxyFunc
|
||||
defer func() {
|
||||
newRedisClientFunc = originalNewRedisClientFunc
|
||||
resolveDialConfigWithProxyFunc = originalResolveDialConfigWithProxyFunc
|
||||
CloseAllRedisClients()
|
||||
}()
|
||||
newRedisClientFunc = func() redislib.RedisClient {
|
||||
return client
|
||||
}
|
||||
resolveDialConfigWithProxyFunc = func(raw connection.ConnectionConfig) (connection.ConnectionConfig, error) {
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
result := app.RedisDeleteHashField(connection.ConnectionConfig{
|
||||
Type: "redis",
|
||||
Host: "redis.local",
|
||||
Port: 6379,
|
||||
}, "profile", "nickname")
|
||||
if !result.Success {
|
||||
t.Fatalf("RedisDeleteHashField returned failure: %+v", result)
|
||||
}
|
||||
if client.deletedHashKey != "profile" {
|
||||
t.Fatalf("expected hash key profile, got %q", client.deletedHashKey)
|
||||
}
|
||||
if len(client.deletedHashFields) != 1 || client.deletedHashFields[0] != "nickname" {
|
||||
t.Fatalf("expected one deleted hash field nickname, got %v", client.deletedHashFields)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedisDeleteHashFieldAcceptsStringSlice(t *testing.T) {
|
||||
app := NewAppWithSecretStore(newFakeAppSecretStore())
|
||||
app.configDir = t.TempDir()
|
||||
|
||||
CloseAllRedisClients()
|
||||
client := &capturingRedisClient{}
|
||||
originalNewRedisClientFunc := newRedisClientFunc
|
||||
originalResolveDialConfigWithProxyFunc := resolveDialConfigWithProxyFunc
|
||||
defer func() {
|
||||
newRedisClientFunc = originalNewRedisClientFunc
|
||||
resolveDialConfigWithProxyFunc = originalResolveDialConfigWithProxyFunc
|
||||
CloseAllRedisClients()
|
||||
}()
|
||||
newRedisClientFunc = func() redislib.RedisClient {
|
||||
return client
|
||||
}
|
||||
resolveDialConfigWithProxyFunc = func(raw connection.ConnectionConfig) (connection.ConnectionConfig, error) {
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
result := app.RedisDeleteHashField(connection.ConnectionConfig{
|
||||
Type: "redis",
|
||||
Host: "redis.local",
|
||||
Port: 6379,
|
||||
}, "profile", []string{"nickname", "avatar"})
|
||||
if !result.Success {
|
||||
t.Fatalf("RedisDeleteHashField returned failure: %+v", result)
|
||||
}
|
||||
if client.deletedHashKey != "profile" {
|
||||
t.Fatalf("expected hash key profile, got %q", client.deletedHashKey)
|
||||
}
|
||||
if len(client.deletedHashFields) != 2 || client.deletedHashFields[0] != "nickname" || client.deletedHashFields[1] != "avatar" {
|
||||
t.Fatalf("unexpected deleted hash fields: %v", client.deletedHashFields)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user