Repository: zeromicro/go-zero (go)

Issue:
Why redis.Exists() accept only one key in go-zero?
func (s *Redis) ExistsCtx(ctx context.Context, key string) (val bool, err error) {
	err = s.brk.DoWithAcceptable(func() error {
		conn, err := getRedis(s)
		if err != nil {
			return err
		}

		v, err := conn.Exists(ctx, key).Result() // 这里go-redis是可以传入批量key的
		if err != nil {
			return err
		}

		val = v == 1
		return nil
	}, acceptable)

	return
}
这里可以加入多个传入吗，为什么只能传入单个啊

Pull request description:
feat:add redis ExistsMany method
  The current `Exists` function in go-zero's Redis only supports a single key, whereas go-redis's `Exists` can query multiple keys. 
  However, directly altering go-zero's Redis implementation would lead to incompatibility between different versions of the method and would affect the implementation of the `Store` interface in `core/stores/kv/store.go`.
  To ensure compatibility between versions, a new method named `ExistsMany` should be added as a transitional solution. A unified upgrade can be planned for a later major version release.
  close #3765
```
// go-zero
// core/stores/redis.go:458
v, err := conn.Exists(ctx, key).Result()

// go-redis
Exists(ctx context.Context, keys ...string) *IntCmd
```

Patch:
diff --git a/core/stores/redis/redis.go b/core/stores/redis/redis.go
index d8301a8c..bf42bcde 100644
--- a/core/stores/redis/redis.go
+++ b/core/stores/redis/redis.go
@@ -467,6 +467,33 @@ func (s *Redis) ExistsCtx(ctx context.Context, key string) (val bool, err error)
 	return
 }
 
+// ExistsMany is the implementation of redis exists command.
+// checks the existence of multiple keys in Redis using the EXISTS command.
+func (s *Redis) ExistsMany(keys ...string) (int64, error) {
+	return s.ExistsManyCtx(context.Background(), keys...)
+}
+
+// ExistsManyCtx is the implementation of redis exists command.
+// checks the existence of multiple keys in Redis using the EXISTS command.
+func (s *Redis) ExistsManyCtx(ctx context.Context, keys ...string) (val int64, err error) {
+	err = s.brk.DoWithAcceptable(func() error {
+		conn, err := getRedis(s)
+		if err != nil {
+			return err
+		}
+
+		v, err := conn.Exists(ctx, keys...).Result()
+		if err != nil {
+			return err
+		}
+
+		val = v
+		return nil
+	}, acceptable)
+
+	return
+}
+
 // Expire is the implementation of redis expire command.
 func (s *Redis) Expire(key string, seconds int) error {
 	return s.ExpireCtx(context.Background(), key, seconds)


Test patch:
diff --git a/core/stores/redis/redis_test.go b/core/stores/redis/redis_test.go
index 8053a661..626c89f6 100644
--- a/core/stores/redis/redis_test.go
+++ b/core/stores/redis/redis_test.go
@@ -224,6 +224,36 @@ func TestRedisTLS_Exists(t *testing.T) {
 	})
 }
 
+func TestRedis_ExistsMany(t *testing.T) {
+	runOnRedis(t, func(client *Redis) {
+		// Attempt to create a new Redis instance with an incorrect type and call ExistsMany
+		_, err := New(client.Addr, badType()).ExistsMany("key1", "key2")
+		assert.NotNil(t, err)
+
+		// Check if key1 and key2 exist, expecting that they do not
+		val, err := client.ExistsMany("key1", "key2")
+		assert.Nil(t, err)
+		assert.Equal(t, int64(0), val)
+
+		// Set the value for key1 and check if key1 exists
+		assert.Nil(t, client.Set("key1", "value1"))
+		val, err = client.ExistsMany("key1")
+		assert.Nil(t, err)
+		assert.Equal(t, int64(1), val)
+
+		// Set the value for key2 and check if key1 and key2 exist
+		assert.Nil(t, client.Set("key2", "value2"))
+		val, err = client.ExistsMany("key1", "key2")
+		assert.Nil(t, err)
+		assert.Equal(t, int64(2), val)
+
+		// Check if key1, key2, and a non-existent key3 exist, expecting that key1 and key2 do
+		val, err = client.ExistsMany("key1", "key2", "key3")
+		assert.Nil(t, err)
+		assert.Equal(t, int64(2), val)
+	})
+}
+
 func TestRedis_Eval(t *testing.T) {
 	runOnRedis(t, func(client *Redis) {
 		_, err := New(client.Addr, badType()).Eval(`redis.call("EXISTS", KEYS[1])`, []string{"notexist"})