Skip to main content

yang_db/redis/
client.rs

1use crate::{DbError, RedisConfig, RedisValue, Result};
2use deadpool_redis::{Config, Pool, Runtime};
3
4/// Redis 客户端
5///
6/// 提供 Redis 数据库操作的统一接口,支持连接池管理
7#[derive(Clone)]
8pub struct RedisClient {
9    /// 连接池
10    pool: Pool,
11    /// 配置
12    #[allow(dead_code)]
13    config: RedisConfig,
14}
15
16impl RedisClient {
17    /// 连接到 Redis 服务器
18    ///
19    /// # 参数
20    /// - `url`: Redis 连接 URL,格式为 `redis://host:port` 或 `redis://host:port/db`
21    ///
22    /// # 返回
23    /// - `Ok(RedisClient)`: 连接成功
24    /// - `Err(DbError)`: 连接失败
25    ///
26    /// # 示例
27    /// ```no_run
28    /// use yang_db::RedisClient;
29    ///
30    /// #[tokio::main]
31    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
32    ///     let client = RedisClient::connect("redis://127.0.0.1:6379").await?;
33    ///     Ok(())
34    /// }
35    /// ```
36    pub async fn connect(url: impl Into<String>) -> Result<Self> {
37        let config = RedisConfig::default();
38        Self::connect_with_config(url, config).await
39    }
40
41    /// 使用自定义配置连接到 Redis 服务器
42    ///
43    /// # 参数
44    /// - `url`: Redis 连接 URL
45    /// - `config`: Redis 配置
46    ///
47    /// # 返回
48    /// - `Ok(RedisClient)`: 连接成功
49    /// - `Err(DbError)`: 连接失败
50    ///
51    /// # 示例
52    /// ```no_run
53    /// use yang_db::{RedisClient, RedisConfig};
54    ///
55    /// #[tokio::main]
56    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
57    ///     let config = RedisConfig::new(20, 10, 15, true);
58    ///     let client = RedisClient::connect_with_config("redis://127.0.0.1:6379", config).await?;
59    ///     Ok(())
60    /// }
61    /// ```
62    pub async fn connect_with_config(url: impl Into<String>, config: RedisConfig) -> Result<Self> {
63        let url = url.into();
64
65        // 创建连接池配置
66        let pool_config = Config {
67            url: Some(url.clone()),
68            ..Default::default()
69        };
70
71        // 创建连接池
72        let pool = pool_config
73            .create_pool(Some(Runtime::Tokio1))
74            .map_err(|e| DbError::RedisConnectionError(format!("创建连接池失败: {}", e)))?;
75
76        // 测试连接
77        let mut conn = pool
78            .get()
79            .await
80            .map_err(|e| DbError::RedisConnectionError(format!("获取连接失败: {}", e)))?;
81
82        // 执行 PING 命令测试连接
83        redis::cmd("PING")
84            .query_async::<String>(&mut *conn)
85            .await
86            .map_err(|e| DbError::RedisConnectionError(format!("连接测试失败: {}", e)))?;
87
88        if config.enable_logging {
89            log::info!("Redis 连接成功: {}", url);
90        }
91
92        Ok(Self { pool, config })
93    }
94
95    /// 获取连接池引用
96    ///
97    /// # 返回
98    /// 连接池的引用
99    pub fn pool(&self) -> &Pool {
100        &self.pool
101    }
102
103    /// 执行 Redis 命令
104    ///
105    /// # 参数
106    /// - `cmd`: Redis 命令
107    ///
108    /// # 返回
109    /// - `Ok(RedisValue)`: 命令执行成功
110    /// - `Err(DbError)`: 命令执行失败
111    ///
112    /// # 示例
113    /// ```no_run
114    /// use yang_db::RedisClient;
115    /// use redis::cmd;
116    ///
117    /// #[tokio::main]
118    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
119    ///     let client = RedisClient::connect("redis://127.0.0.1:6379").await?;
120    ///     
121    ///     let mut cmd = cmd("SET");
122    ///     cmd.arg("key").arg("value");
123    ///     let result = client.execute(&cmd).await?;
124    ///     
125    ///     Ok(())
126    /// }
127    /// ```
128    pub async fn execute(&self, cmd: &redis::Cmd) -> Result<RedisValue> {
129        let mut conn = self
130            .pool
131            .get()
132            .await
133            .map_err(|e| DbError::RedisPoolError(format!("获取连接失败: {}", e)))?;
134
135        let value: redis::Value = cmd
136            .query_async(&mut *conn)
137            .await
138            .map_err(|e| DbError::RedisCommandError(format!("命令执行失败: {}", e)))?;
139
140        Ok(RedisValue::from(value))
141    }
142
143    // ==================== String 操作 ====================
144
145    /// SET - 设置键值
146    ///
147    /// # 参数
148    /// - `key`: 键
149    /// - `value`: 值
150    ///
151    /// # 返回
152    /// - `Ok(())`: 设置成功
153    /// - `Err(DbError)`: 设置失败
154    pub async fn set(&self, key: impl Into<String>, value: impl Into<String>) -> Result<()> {
155        let mut cmd = redis::cmd("SET");
156        cmd.arg(key.into()).arg(value.into());
157        self.execute(&cmd).await?;
158        Ok(())
159    }
160
161    /// GET - 获取键的值
162    ///
163    /// # 参数
164    /// - `key`: 键
165    ///
166    /// # 返回
167    /// - `Ok(Some(String))`: 键存在,返回值
168    /// - `Ok(None)`: 键不存在
169    /// - `Err(DbError)`: 获取失败
170    pub async fn get(&self, key: impl Into<String>) -> Result<Option<String>> {
171        let mut cmd = redis::cmd("GET");
172        cmd.arg(key.into());
173        let value = self.execute(&cmd).await?;
174        Ok(value.as_string())
175    }
176
177    /// SETEX - 设置键值并指定过期时间(秒)
178    ///
179    /// # 参数
180    /// - `key`: 键
181    /// - `seconds`: 过期时间(秒)
182    /// - `value`: 值
183    pub async fn setex(
184        &self,
185        key: impl Into<String>,
186        seconds: i64,
187        value: impl Into<String>,
188    ) -> Result<()> {
189        let mut cmd = redis::cmd("SETEX");
190        cmd.arg(key.into()).arg(seconds).arg(value.into());
191        self.execute(&cmd).await?;
192        Ok(())
193    }
194
195    /// SETNX - 仅当键不存在时设置值
196    ///
197    /// # 返回
198    /// - `Ok(true)`: 设置成功
199    /// - `Ok(false)`: 键已存在,未设置
200    pub async fn setnx(&self, key: impl Into<String>, value: impl Into<String>) -> Result<bool> {
201        let mut cmd = redis::cmd("SETNX");
202        cmd.arg(key.into()).arg(value.into());
203        let result = self.execute(&cmd).await?;
204        Ok(result.as_i64() == Some(1))
205    }
206
207    /// GETSET - 设置新值并返回旧值
208    ///
209    /// # 返回
210    /// - `Ok(Some(String))`: 返回旧值
211    /// - `Ok(None)`: 键不存在
212    pub async fn getset(
213        &self,
214        key: impl Into<String>,
215        value: impl Into<String>,
216    ) -> Result<Option<String>> {
217        let mut cmd = redis::cmd("GETSET");
218        cmd.arg(key.into()).arg(value.into());
219        let result = self.execute(&cmd).await?;
220        Ok(result.as_string())
221    }
222
223    /// MGET - 批量获取多个键的值
224    ///
225    /// # 返回
226    /// 返回值数组,不存在的键返回 None
227    pub async fn mget(&self, keys: &[String]) -> Result<Vec<Option<String>>> {
228        let mut cmd = redis::cmd("MGET");
229        for key in keys {
230            cmd.arg(key);
231        }
232        let result = self.execute(&cmd).await?;
233        if let Some(arr) = result.as_array() {
234            Ok(arr.iter().map(|v| v.as_string()).collect())
235        } else {
236            Ok(vec![])
237        }
238    }
239
240    /// MSET - 批量设置多个键值对
241    ///
242    /// # 参数
243    /// - `pairs`: 键值对数组
244    pub async fn mset(&self, pairs: &[(String, String)]) -> Result<()> {
245        let mut cmd = redis::cmd("MSET");
246        for (key, value) in pairs {
247            cmd.arg(key).arg(value);
248        }
249        self.execute(&cmd).await?;
250        Ok(())
251    }
252
253    /// INCR - 将键的值增加 1
254    ///
255    /// # 返回
256    /// 返回增加后的值
257    pub async fn incr(&self, key: impl Into<String>) -> Result<i64> {
258        let mut cmd = redis::cmd("INCR");
259        cmd.arg(key.into());
260        let result = self.execute(&cmd).await?;
261        result
262            .as_i64()
263            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
264    }
265
266    /// INCRBY - 将键的值增加指定数量
267    ///
268    /// # 返回
269    /// 返回增加后的值
270    pub async fn incrby(&self, key: impl Into<String>, increment: i64) -> Result<i64> {
271        let mut cmd = redis::cmd("INCRBY");
272        cmd.arg(key.into()).arg(increment);
273        let result = self.execute(&cmd).await?;
274        result
275            .as_i64()
276            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
277    }
278
279    /// DECR - 将键的值减少 1
280    ///
281    /// # 返回
282    /// 返回减少后的值
283    pub async fn decr(&self, key: impl Into<String>) -> Result<i64> {
284        let mut cmd = redis::cmd("DECR");
285        cmd.arg(key.into());
286        let result = self.execute(&cmd).await?;
287        result
288            .as_i64()
289            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
290    }
291
292    /// DECRBY - 将键的值减少指定数量
293    ///
294    /// # 返回
295    /// 返回减少后的值
296    pub async fn decrby(&self, key: impl Into<String>, decrement: i64) -> Result<i64> {
297        let mut cmd = redis::cmd("DECRBY");
298        cmd.arg(key.into()).arg(decrement);
299        let result = self.execute(&cmd).await?;
300        result
301            .as_i64()
302            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
303    }
304
305    /// APPEND - 将值追加到键的原值末尾
306    ///
307    /// # 返回
308    /// 返回追加后字符串的长度
309    pub async fn append(&self, key: impl Into<String>, value: impl Into<String>) -> Result<i64> {
310        let mut cmd = redis::cmd("APPEND");
311        cmd.arg(key.into()).arg(value.into());
312        let result = self.execute(&cmd).await?;
313        result
314            .as_i64()
315            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
316    }
317
318    /// STRLEN - 获取键存储的字符串长度
319    ///
320    /// # 返回
321    /// 返回字符串长度,键不存在返回 0
322    pub async fn strlen(&self, key: impl Into<String>) -> Result<i64> {
323        let mut cmd = redis::cmd("STRLEN");
324        cmd.arg(key.into());
325        let result = self.execute(&cmd).await?;
326        result
327            .as_i64()
328            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
329    }
330
331    // ==================== Hash 操作 ====================
332
333    /// HSET - 设置哈希表字段的值
334    ///
335    /// # 返回
336    /// - `Ok(true)`: 新字段被设置
337    /// - `Ok(false)`: 字段已存在,值被更新
338    pub async fn hset(
339        &self,
340        key: impl Into<String>,
341        field: impl Into<String>,
342        value: impl Into<String>,
343    ) -> Result<bool> {
344        let mut cmd = redis::cmd("HSET");
345        cmd.arg(key.into()).arg(field.into()).arg(value.into());
346        let result = self.execute(&cmd).await?;
347        Ok(result.as_i64() == Some(1))
348    }
349
350    /// HGET - 获取哈希表字段的值
351    ///
352    /// # 返回
353    /// - `Ok(Some(String))`: 字段存在
354    /// - `Ok(None)`: 字段不存在
355    pub async fn hget(
356        &self,
357        key: impl Into<String>,
358        field: impl Into<String>,
359    ) -> Result<Option<String>> {
360        let mut cmd = redis::cmd("HGET");
361        cmd.arg(key.into()).arg(field.into());
362        let result = self.execute(&cmd).await?;
363        Ok(result.as_string())
364    }
365
366    /// HDEL - 删除哈希表的一个或多个字段
367    ///
368    /// # 返回
369    /// 返回被删除字段的数量
370    pub async fn hdel(&self, key: impl Into<String>, fields: &[String]) -> Result<i64> {
371        let mut cmd = redis::cmd("HDEL");
372        cmd.arg(key.into());
373        for field in fields {
374            cmd.arg(field);
375        }
376        let result = self.execute(&cmd).await?;
377        result
378            .as_i64()
379            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
380    }
381
382    /// HEXISTS - 检查哈希表字段是否存在
383    ///
384    /// # 返回
385    /// - `Ok(true)`: 字段存在
386    /// - `Ok(false)`: 字段不存在
387    pub async fn hexists(&self, key: impl Into<String>, field: impl Into<String>) -> Result<bool> {
388        let mut cmd = redis::cmd("HEXISTS");
389        cmd.arg(key.into()).arg(field.into());
390        let result = self.execute(&cmd).await?;
391        Ok(result.as_i64() == Some(1))
392    }
393
394    /// HMSET - 批量设置哈希表的多个字段
395    pub async fn hmset(&self, key: impl Into<String>, fields: &[(String, String)]) -> Result<()> {
396        let mut cmd = redis::cmd("HMSET");
397        cmd.arg(key.into());
398        for (field, value) in fields {
399            cmd.arg(field).arg(value);
400        }
401        self.execute(&cmd).await?;
402        Ok(())
403    }
404
405    /// HMGET - 批量获取哈希表的多个字段值
406    ///
407    /// # 返回
408    /// 返回值数组,不存在的字段返回 None
409    pub async fn hmget(
410        &self,
411        key: impl Into<String>,
412        fields: &[String],
413    ) -> Result<Vec<Option<String>>> {
414        let mut cmd = redis::cmd("HMGET");
415        cmd.arg(key.into());
416        for field in fields {
417            cmd.arg(field);
418        }
419        let result = self.execute(&cmd).await?;
420        if let Some(arr) = result.as_array() {
421            Ok(arr.iter().map(|v| v.as_string()).collect())
422        } else {
423            Ok(vec![])
424        }
425    }
426
427    /// HGETALL - 获取哈希表的所有字段和值
428    ///
429    /// # 返回
430    /// 返回字段-值对的向量
431    pub async fn hgetall(&self, key: impl Into<String>) -> Result<Vec<(String, String)>> {
432        let mut cmd = redis::cmd("HGETALL");
433        cmd.arg(key.into());
434        let result = self.execute(&cmd).await?;
435        if let Some(arr) = result.as_array() {
436            let mut pairs = Vec::new();
437            for i in (0..arr.len()).step_by(2) {
438                if i + 1 < arr.len()
439                    && let (Some(field), Some(value)) = (arr[i].as_string(), arr[i + 1].as_string())
440                {
441                    pairs.push((field, value));
442                }
443            }
444            Ok(pairs)
445        } else {
446            Ok(vec![])
447        }
448    }
449
450    /// HLEN - 获取哈希表的字段数量
451    pub async fn hlen(&self, key: impl Into<String>) -> Result<i64> {
452        let mut cmd = redis::cmd("HLEN");
453        cmd.arg(key.into());
454        let result = self.execute(&cmd).await?;
455        result
456            .as_i64()
457            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
458    }
459
460    /// HKEYS - 获取哈希表的所有字段名
461    pub async fn hkeys(&self, key: impl Into<String>) -> Result<Vec<String>> {
462        let mut cmd = redis::cmd("HKEYS");
463        cmd.arg(key.into());
464        let result = self.execute(&cmd).await?;
465        if let Some(arr) = result.as_array() {
466            Ok(arr.iter().filter_map(|v| v.as_string()).collect())
467        } else {
468            Ok(vec![])
469        }
470    }
471
472    /// HVALS - 获取哈希表的所有值
473    pub async fn hvals(&self, key: impl Into<String>) -> Result<Vec<String>> {
474        let mut cmd = redis::cmd("HVALS");
475        cmd.arg(key.into());
476        let result = self.execute(&cmd).await?;
477        if let Some(arr) = result.as_array() {
478            Ok(arr.iter().filter_map(|v| v.as_string()).collect())
479        } else {
480            Ok(vec![])
481        }
482    }
483
484    /// HINCRBY - 将哈希表字段的整数值增加指定数量
485    pub async fn hincrby(
486        &self,
487        key: impl Into<String>,
488        field: impl Into<String>,
489        increment: i64,
490    ) -> Result<i64> {
491        let mut cmd = redis::cmd("HINCRBY");
492        cmd.arg(key.into()).arg(field.into()).arg(increment);
493        let result = self.execute(&cmd).await?;
494        result
495            .as_i64()
496            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
497    }
498
499    /// HINCRBYFLOAT - 将哈希表字段的浮点数值增加指定数量
500    pub async fn hincrbyfloat(
501        &self,
502        key: impl Into<String>,
503        field: impl Into<String>,
504        increment: f64,
505    ) -> Result<f64> {
506        let mut cmd = redis::cmd("HINCRBYFLOAT");
507        cmd.arg(key.into()).arg(field.into()).arg(increment);
508        let result = self.execute(&cmd).await?;
509        // Redis 返回字符串形式的浮点数
510        if let Some(s) = result.as_string() {
511            s.parse::<f64>()
512                .map_err(|_| DbError::RedisTypeConversionError("无法转换为浮点数".to_string()))
513        } else {
514            result
515                .as_f64()
516                .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为浮点数".to_string()))
517        }
518    }
519
520    // ==================== List 操作 ====================
521
522    /// LPUSH - 将一个或多个值插入列表头部
523    ///
524    /// # 返回
525    /// 返回插入后列表的长度
526    pub async fn lpush(&self, key: impl Into<String>, values: &[String]) -> Result<i64> {
527        let mut cmd = redis::cmd("LPUSH");
528        cmd.arg(key.into());
529        for value in values {
530            cmd.arg(value);
531        }
532        let result = self.execute(&cmd).await?;
533        result
534            .as_i64()
535            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
536    }
537
538    /// RPUSH - 将一个或多个值插入列表尾部
539    ///
540    /// # 返回
541    /// 返回插入后列表的长度
542    pub async fn rpush(&self, key: impl Into<String>, values: &[String]) -> Result<i64> {
543        let mut cmd = redis::cmd("RPUSH");
544        cmd.arg(key.into());
545        for value in values {
546            cmd.arg(value);
547        }
548        let result = self.execute(&cmd).await?;
549        result
550            .as_i64()
551            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
552    }
553
554    /// LPOP - 移除并返回列表的头元素
555    ///
556    /// # 返回
557    /// - `Ok(Some(String))`: 返回头元素
558    /// - `Ok(None)`: 列表为空
559    pub async fn lpop(&self, key: impl Into<String>) -> Result<Option<String>> {
560        let mut cmd = redis::cmd("LPOP");
561        cmd.arg(key.into());
562        let result = self.execute(&cmd).await?;
563        Ok(result.as_string())
564    }
565
566    /// RPOP - 移除并返回列表的尾元素
567    ///
568    /// # 返回
569    /// - `Ok(Some(String))`: 返回尾元素
570    /// - `Ok(None)`: 列表为空
571    pub async fn rpop(&self, key: impl Into<String>) -> Result<Option<String>> {
572        let mut cmd = redis::cmd("RPOP");
573        cmd.arg(key.into());
574        let result = self.execute(&cmd).await?;
575        Ok(result.as_string())
576    }
577
578    /// LRANGE - 获取列表指定范围内的元素
579    ///
580    /// # 参数
581    /// - `start`: 起始索引(0 表示第一个元素)
582    /// - `stop`: 结束索引(-1 表示最后一个元素)
583    pub async fn lrange(
584        &self,
585        key: impl Into<String>,
586        start: i64,
587        stop: i64,
588    ) -> Result<Vec<String>> {
589        let mut cmd = redis::cmd("LRANGE");
590        cmd.arg(key.into()).arg(start).arg(stop);
591        let result = self.execute(&cmd).await?;
592        if let Some(arr) = result.as_array() {
593            Ok(arr.iter().filter_map(|v| v.as_string()).collect())
594        } else {
595            Ok(vec![])
596        }
597    }
598
599    /// LLEN - 获取列表长度
600    pub async fn llen(&self, key: impl Into<String>) -> Result<i64> {
601        let mut cmd = redis::cmd("LLEN");
602        cmd.arg(key.into());
603        let result = self.execute(&cmd).await?;
604        result
605            .as_i64()
606            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
607    }
608
609    /// LINDEX - 获取列表指定索引的元素
610    ///
611    /// # 返回
612    /// - `Ok(Some(String))`: 返回元素
613    /// - `Ok(None)`: 索引超出范围
614    pub async fn lindex(&self, key: impl Into<String>, index: i64) -> Result<Option<String>> {
615        let mut cmd = redis::cmd("LINDEX");
616        cmd.arg(key.into()).arg(index);
617        let result = self.execute(&cmd).await?;
618        Ok(result.as_string())
619    }
620
621    /// LSET - 设置列表指定索引的元素值
622    pub async fn lset(
623        &self,
624        key: impl Into<String>,
625        index: i64,
626        value: impl Into<String>,
627    ) -> Result<()> {
628        let mut cmd = redis::cmd("LSET");
629        cmd.arg(key.into()).arg(index).arg(value.into());
630        self.execute(&cmd).await?;
631        Ok(())
632    }
633
634    /// LTRIM - 修剪列表,仅保留指定范围内的元素
635    pub async fn ltrim(&self, key: impl Into<String>, start: i64, stop: i64) -> Result<()> {
636        let mut cmd = redis::cmd("LTRIM");
637        cmd.arg(key.into()).arg(start).arg(stop);
638        self.execute(&cmd).await?;
639        Ok(())
640    }
641
642    // ==================== Set 操作 ====================
643
644    /// SADD - 向集合添加一个或多个成员
645    ///
646    /// # 返回
647    /// 返回被添加到集合中的新元素数量
648    pub async fn sadd(&self, key: impl Into<String>, members: &[String]) -> Result<i64> {
649        let mut cmd = redis::cmd("SADD");
650        cmd.arg(key.into());
651        for member in members {
652            cmd.arg(member);
653        }
654        let result = self.execute(&cmd).await?;
655        result
656            .as_i64()
657            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
658    }
659
660    /// SREM - 移除集合中的一个或多个成员
661    ///
662    /// # 返回
663    /// 返回被移除的元素数量
664    pub async fn srem(&self, key: impl Into<String>, members: &[String]) -> Result<i64> {
665        let mut cmd = redis::cmd("SREM");
666        cmd.arg(key.into());
667        for member in members {
668            cmd.arg(member);
669        }
670        let result = self.execute(&cmd).await?;
671        result
672            .as_i64()
673            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
674    }
675
676    /// SMEMBERS - 获取集合的所有成员
677    pub async fn smembers(&self, key: impl Into<String>) -> Result<Vec<String>> {
678        let mut cmd = redis::cmd("SMEMBERS");
679        cmd.arg(key.into());
680        let result = self.execute(&cmd).await?;
681        if let Some(arr) = result.as_array() {
682            Ok(arr.iter().filter_map(|v| v.as_string()).collect())
683        } else {
684            Ok(vec![])
685        }
686    }
687
688    /// SISMEMBER - 检查元素是否是集合的成员
689    ///
690    /// # 返回
691    /// - `Ok(true)`: 元素是集合成员
692    /// - `Ok(false)`: 元素不是集合成员
693    pub async fn sismember(
694        &self,
695        key: impl Into<String>,
696        member: impl Into<String>,
697    ) -> Result<bool> {
698        let mut cmd = redis::cmd("SISMEMBER");
699        cmd.arg(key.into()).arg(member.into());
700        let result = self.execute(&cmd).await?;
701        Ok(result.as_i64() == Some(1))
702    }
703
704    /// SCARD - 获取集合的成员数量
705    pub async fn scard(&self, key: impl Into<String>) -> Result<i64> {
706        let mut cmd = redis::cmd("SCARD");
707        cmd.arg(key.into());
708        let result = self.execute(&cmd).await?;
709        result
710            .as_i64()
711            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
712    }
713
714    /// SPOP - 移除并返回集合中的一个随机元素
715    ///
716    /// # 返回
717    /// - `Ok(Some(String))`: 返回被移除的元素
718    /// - `Ok(None)`: 集合为空
719    pub async fn spop(&self, key: impl Into<String>) -> Result<Option<String>> {
720        let mut cmd = redis::cmd("SPOP");
721        cmd.arg(key.into());
722        let result = self.execute(&cmd).await?;
723        Ok(result.as_string())
724    }
725
726    /// SRANDMEMBER - 返回集合中的一个随机元素(不移除)
727    ///
728    /// # 返回
729    /// - `Ok(Some(String))`: 返回随机元素
730    /// - `Ok(None)`: 集合为空
731    pub async fn srandmember(&self, key: impl Into<String>) -> Result<Option<String>> {
732        let mut cmd = redis::cmd("SRANDMEMBER");
733        cmd.arg(key.into());
734        let result = self.execute(&cmd).await?;
735        Ok(result.as_string())
736    }
737
738    // ==================== Sorted Set 操作 ====================
739
740    /// ZADD - 向有序集合添加一个或多个成员
741    ///
742    /// # 返回
743    /// 返回被添加的新成员数量
744    pub async fn zadd(&self, key: impl Into<String>, members: &[(f64, String)]) -> Result<i64> {
745        let mut cmd = redis::cmd("ZADD");
746        cmd.arg(key.into());
747        for (score, member) in members {
748            cmd.arg(score).arg(member);
749        }
750        let result = self.execute(&cmd).await?;
751        result
752            .as_i64()
753            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
754    }
755
756    /// ZREM - 移除有序集合中的一个或多个成员
757    ///
758    /// # 返回
759    /// 返回被移除的成员数量
760    pub async fn zrem(&self, key: impl Into<String>, members: &[String]) -> Result<i64> {
761        let mut cmd = redis::cmd("ZREM");
762        cmd.arg(key.into());
763        for member in members {
764            cmd.arg(member);
765        }
766        let result = self.execute(&cmd).await?;
767        result
768            .as_i64()
769            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
770    }
771
772    /// ZSCORE - 获取成员的分数
773    ///
774    /// # 返回
775    /// - `Ok(Some(f64))`: 返回分数
776    /// - `Ok(None)`: 成员不存在
777    pub async fn zscore(
778        &self,
779        key: impl Into<String>,
780        member: impl Into<String>,
781    ) -> Result<Option<f64>> {
782        let mut cmd = redis::cmd("ZSCORE");
783        cmd.arg(key.into()).arg(member.into());
784        let result = self.execute(&cmd).await?;
785        if let Some(s) = result.as_string() {
786            Ok(s.parse::<f64>().ok())
787        } else {
788            Ok(result.as_f64())
789        }
790    }
791
792    /// ZCARD - 获取有序集合的成员数量
793    pub async fn zcard(&self, key: impl Into<String>) -> Result<i64> {
794        let mut cmd = redis::cmd("ZCARD");
795        cmd.arg(key.into());
796        let result = self.execute(&cmd).await?;
797        result
798            .as_i64()
799            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
800    }
801
802    /// ZRANGE - 按索引范围获取有序集合的成员
803    ///
804    /// # 参数
805    /// - `start`: 起始索引
806    /// - `stop`: 结束索引
807    pub async fn zrange(
808        &self,
809        key: impl Into<String>,
810        start: i64,
811        stop: i64,
812    ) -> Result<Vec<String>> {
813        let mut cmd = redis::cmd("ZRANGE");
814        cmd.arg(key.into()).arg(start).arg(stop);
815        let result = self.execute(&cmd).await?;
816        if let Some(arr) = result.as_array() {
817            Ok(arr.iter().filter_map(|v| v.as_string()).collect())
818        } else {
819            Ok(vec![])
820        }
821    }
822
823    /// ZRANGEBYSCORE - 按分数范围获取有序集合的成员
824    ///
825    /// # 参数
826    /// - `min`: 最小分数
827    /// - `max`: 最大分数
828    pub async fn zrangebyscore(
829        &self,
830        key: impl Into<String>,
831        min: f64,
832        max: f64,
833    ) -> Result<Vec<String>> {
834        let mut cmd = redis::cmd("ZRANGEBYSCORE");
835        cmd.arg(key.into()).arg(min).arg(max);
836        let result = self.execute(&cmd).await?;
837        if let Some(arr) = result.as_array() {
838            Ok(arr.iter().filter_map(|v| v.as_string()).collect())
839        } else {
840            Ok(vec![])
841        }
842    }
843
844    /// ZCOUNT - 计算分数范围内的成员数量
845    pub async fn zcount(&self, key: impl Into<String>, min: f64, max: f64) -> Result<i64> {
846        let mut cmd = redis::cmd("ZCOUNT");
847        cmd.arg(key.into()).arg(min).arg(max);
848        let result = self.execute(&cmd).await?;
849        result
850            .as_i64()
851            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
852    }
853
854    /// ZINCRBY - 将成员的分数增加指定数量
855    ///
856    /// # 返回
857    /// 返回增加后的分数
858    pub async fn zincrby(
859        &self,
860        key: impl Into<String>,
861        increment: f64,
862        member: impl Into<String>,
863    ) -> Result<f64> {
864        let mut cmd = redis::cmd("ZINCRBY");
865        cmd.arg(key.into()).arg(increment).arg(member.into());
866        let result = self.execute(&cmd).await?;
867        if let Some(s) = result.as_string() {
868            s.parse::<f64>()
869                .map_err(|_| DbError::RedisTypeConversionError("无法转换为浮点数".to_string()))
870        } else {
871            result
872                .as_f64()
873                .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为浮点数".to_string()))
874        }
875    }
876
877    // ==================== 通用键操作 ====================
878
879    /// DEL - 删除一个或多个键
880    ///
881    /// # 返回
882    /// 返回被删除的键数量
883    pub async fn del(&self, keys: &[String]) -> Result<i64> {
884        let mut cmd = redis::cmd("DEL");
885        for key in keys {
886            cmd.arg(key);
887        }
888        let result = self.execute(&cmd).await?;
889        result
890            .as_i64()
891            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
892    }
893
894    /// EXISTS - 检查一个或多个键是否存在
895    ///
896    /// # 返回
897    /// 返回存在的键数量
898    pub async fn exists(&self, keys: &[String]) -> Result<i64> {
899        let mut cmd = redis::cmd("EXISTS");
900        for key in keys {
901            cmd.arg(key);
902        }
903        let result = self.execute(&cmd).await?;
904        result
905            .as_i64()
906            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
907    }
908
909    /// EXPIRE - 设置键的过期时间(秒)
910    ///
911    /// # 返回
912    /// - `Ok(true)`: 设置成功
913    /// - `Ok(false)`: 键不存在
914    pub async fn expire(&self, key: impl Into<String>, seconds: i64) -> Result<bool> {
915        let mut cmd = redis::cmd("EXPIRE");
916        cmd.arg(key.into()).arg(seconds);
917        let result = self.execute(&cmd).await?;
918        Ok(result.as_i64() == Some(1))
919    }
920
921    /// TTL - 获取键的剩余生存时间(秒)
922    ///
923    /// # 返回
924    /// - 正数: 剩余秒数
925    /// - -1: 键存在但没有过期时间
926    /// - -2: 键不存在
927    pub async fn ttl(&self, key: impl Into<String>) -> Result<i64> {
928        let mut cmd = redis::cmd("TTL");
929        cmd.arg(key.into());
930        let result = self.execute(&cmd).await?;
931        result
932            .as_i64()
933            .ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
934    }
935
936    /// PERSIST - 移除键的过期时间
937    ///
938    /// # 返回
939    /// - `Ok(true)`: 移除成功
940    /// - `Ok(false)`: 键不存在或没有过期时间
941    pub async fn persist(&self, key: impl Into<String>) -> Result<bool> {
942        let mut cmd = redis::cmd("PERSIST");
943        cmd.arg(key.into());
944        let result = self.execute(&cmd).await?;
945        Ok(result.as_i64() == Some(1))
946    }
947
948    /// KEYS - 查找所有匹配给定模式的键
949    ///
950    /// # 警告
951    /// 此命令在生产环境中可能导致性能问题,请谨慎使用
952    ///
953    /// # 参数
954    /// - `pattern`: 匹配模式(例如 "user:*")
955    pub async fn keys(&self, pattern: impl Into<String>) -> Result<Vec<String>> {
956        let mut cmd = redis::cmd("KEYS");
957        cmd.arg(pattern.into());
958        let result = self.execute(&cmd).await?;
959        if let Some(arr) = result.as_array() {
960            Ok(arr.iter().filter_map(|v| v.as_string()).collect())
961        } else {
962            Ok(vec![])
963        }
964    }
965}
966
967#[cfg(test)]
968mod tests {
969    use super::*;
970
971    #[test]
972    fn test_redis_client_clone() {
973        // 测试 Clone trait
974        let config = RedisConfig::default();
975        // 注意:这里只测试结构体的 Clone,不测试实际连接
976        let _ = config.clone();
977    }
978
979    #[test]
980    fn test_redis_config_default() {
981        let config = RedisConfig::default();
982        assert_eq!(config.max_connections, 10);
983        assert_eq!(config.connect_timeout, 5);
984        assert_eq!(config.wait_timeout, 10);
985        assert!(!config.enable_logging);
986    }
987
988    #[test]
989    fn test_redis_config_custom() {
990        let config = RedisConfig::new(20, 10, 15, true);
991        assert_eq!(config.max_connections, 20);
992        assert_eq!(config.connect_timeout, 10);
993        assert_eq!(config.wait_timeout, 15);
994        assert!(config.enable_logging);
995    }
996}