use crate::{DbError, RedisConfig, RedisValue, Result};
use deadpool_redis::{Config, Pool, Runtime};
#[derive(Clone)]
pub struct RedisClient {
pool: Pool,
#[allow(dead_code)]
config: RedisConfig,
}
impl RedisClient {
pub async fn connect(url: impl Into<String>) -> Result<Self> {
let config = RedisConfig::default();
Self::connect_with_config(url, config).await
}
pub async fn connect_with_config(url: impl Into<String>, config: RedisConfig) -> Result<Self> {
let url = url.into();
let pool_config = Config {
url: Some(url.clone()),
..Default::default()
};
let pool = pool_config
.create_pool(Some(Runtime::Tokio1))
.map_err(|e| DbError::RedisConnectionError(format!("创建连接池失败: {}", e)))?;
let mut conn = pool
.get()
.await
.map_err(|e| DbError::RedisConnectionError(format!("获取连接失败: {}", e)))?;
redis::cmd("PING")
.query_async::<String>(&mut *conn)
.await
.map_err(|e| DbError::RedisConnectionError(format!("连接测试失败: {}", e)))?;
if config.enable_logging {
log::info!("Redis 连接成功: {}", url);
}
Ok(Self { pool, config })
}
pub fn pool(&self) -> &Pool {
&self.pool
}
pub async fn execute(&self, cmd: &redis::Cmd) -> Result<RedisValue> {
let mut conn = self
.pool
.get()
.await
.map_err(|e| DbError::RedisPoolError(format!("获取连接失败: {}", e)))?;
let value: redis::Value = cmd
.query_async(&mut *conn)
.await
.map_err(|e| DbError::RedisCommandError(format!("命令执行失败: {}", e)))?;
Ok(RedisValue::from(value))
}
pub async fn set(&self, key: impl Into<String>, value: impl Into<String>) -> Result<()> {
let mut cmd = redis::cmd("SET");
cmd.arg(key.into()).arg(value.into());
self.execute(&cmd).await?;
Ok(())
}
pub async fn get(&self, key: impl Into<String>) -> Result<Option<String>> {
let mut cmd = redis::cmd("GET");
cmd.arg(key.into());
let value = self.execute(&cmd).await?;
Ok(value.as_string())
}
pub async fn setex(
&self,
key: impl Into<String>,
seconds: i64,
value: impl Into<String>,
) -> Result<()> {
let mut cmd = redis::cmd("SETEX");
cmd.arg(key.into()).arg(seconds).arg(value.into());
self.execute(&cmd).await?;
Ok(())
}
pub async fn setnx(&self, key: impl Into<String>, value: impl Into<String>) -> Result<bool> {
let mut cmd = redis::cmd("SETNX");
cmd.arg(key.into()).arg(value.into());
let result = self.execute(&cmd).await?;
Ok(result.as_i64() == Some(1))
}
pub async fn getset(
&self,
key: impl Into<String>,
value: impl Into<String>,
) -> Result<Option<String>> {
let mut cmd = redis::cmd("GETSET");
cmd.arg(key.into()).arg(value.into());
let result = self.execute(&cmd).await?;
Ok(result.as_string())
}
pub async fn mget(&self, keys: &[String]) -> Result<Vec<Option<String>>> {
let mut cmd = redis::cmd("MGET");
for key in keys {
cmd.arg(key);
}
let result = self.execute(&cmd).await?;
if let Some(arr) = result.as_array() {
Ok(arr.iter().map(|v| v.as_string()).collect())
} else {
Ok(vec![])
}
}
pub async fn mset(&self, pairs: &[(String, String)]) -> Result<()> {
let mut cmd = redis::cmd("MSET");
for (key, value) in pairs {
cmd.arg(key).arg(value);
}
self.execute(&cmd).await?;
Ok(())
}
pub async fn incr(&self, key: impl Into<String>) -> Result<i64> {
let mut cmd = redis::cmd("INCR");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn incrby(&self, key: impl Into<String>, increment: i64) -> Result<i64> {
let mut cmd = redis::cmd("INCRBY");
cmd.arg(key.into()).arg(increment);
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn decr(&self, key: impl Into<String>) -> Result<i64> {
let mut cmd = redis::cmd("DECR");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn decrby(&self, key: impl Into<String>, decrement: i64) -> Result<i64> {
let mut cmd = redis::cmd("DECRBY");
cmd.arg(key.into()).arg(decrement);
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn append(&self, key: impl Into<String>, value: impl Into<String>) -> Result<i64> {
let mut cmd = redis::cmd("APPEND");
cmd.arg(key.into()).arg(value.into());
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn strlen(&self, key: impl Into<String>) -> Result<i64> {
let mut cmd = redis::cmd("STRLEN");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn hset(
&self,
key: impl Into<String>,
field: impl Into<String>,
value: impl Into<String>,
) -> Result<bool> {
let mut cmd = redis::cmd("HSET");
cmd.arg(key.into()).arg(field.into()).arg(value.into());
let result = self.execute(&cmd).await?;
Ok(result.as_i64() == Some(1))
}
pub async fn hget(
&self,
key: impl Into<String>,
field: impl Into<String>,
) -> Result<Option<String>> {
let mut cmd = redis::cmd("HGET");
cmd.arg(key.into()).arg(field.into());
let result = self.execute(&cmd).await?;
Ok(result.as_string())
}
pub async fn hdel(&self, key: impl Into<String>, fields: &[String]) -> Result<i64> {
let mut cmd = redis::cmd("HDEL");
cmd.arg(key.into());
for field in fields {
cmd.arg(field);
}
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn hexists(&self, key: impl Into<String>, field: impl Into<String>) -> Result<bool> {
let mut cmd = redis::cmd("HEXISTS");
cmd.arg(key.into()).arg(field.into());
let result = self.execute(&cmd).await?;
Ok(result.as_i64() == Some(1))
}
pub async fn hmset(&self, key: impl Into<String>, fields: &[(String, String)]) -> Result<()> {
let mut cmd = redis::cmd("HMSET");
cmd.arg(key.into());
for (field, value) in fields {
cmd.arg(field).arg(value);
}
self.execute(&cmd).await?;
Ok(())
}
pub async fn hmget(
&self,
key: impl Into<String>,
fields: &[String],
) -> Result<Vec<Option<String>>> {
let mut cmd = redis::cmd("HMGET");
cmd.arg(key.into());
for field in fields {
cmd.arg(field);
}
let result = self.execute(&cmd).await?;
if let Some(arr) = result.as_array() {
Ok(arr.iter().map(|v| v.as_string()).collect())
} else {
Ok(vec![])
}
}
pub async fn hgetall(&self, key: impl Into<String>) -> Result<Vec<(String, String)>> {
let mut cmd = redis::cmd("HGETALL");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
if let Some(arr) = result.as_array() {
let mut pairs = Vec::new();
for i in (0..arr.len()).step_by(2) {
if i + 1 < arr.len()
&& let (Some(field), Some(value)) = (arr[i].as_string(), arr[i + 1].as_string())
{
pairs.push((field, value));
}
}
Ok(pairs)
} else {
Ok(vec![])
}
}
pub async fn hlen(&self, key: impl Into<String>) -> Result<i64> {
let mut cmd = redis::cmd("HLEN");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn hkeys(&self, key: impl Into<String>) -> Result<Vec<String>> {
let mut cmd = redis::cmd("HKEYS");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
if let Some(arr) = result.as_array() {
Ok(arr.iter().filter_map(|v| v.as_string()).collect())
} else {
Ok(vec![])
}
}
pub async fn hvals(&self, key: impl Into<String>) -> Result<Vec<String>> {
let mut cmd = redis::cmd("HVALS");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
if let Some(arr) = result.as_array() {
Ok(arr.iter().filter_map(|v| v.as_string()).collect())
} else {
Ok(vec![])
}
}
pub async fn hincrby(
&self,
key: impl Into<String>,
field: impl Into<String>,
increment: i64,
) -> Result<i64> {
let mut cmd = redis::cmd("HINCRBY");
cmd.arg(key.into()).arg(field.into()).arg(increment);
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn hincrbyfloat(
&self,
key: impl Into<String>,
field: impl Into<String>,
increment: f64,
) -> Result<f64> {
let mut cmd = redis::cmd("HINCRBYFLOAT");
cmd.arg(key.into()).arg(field.into()).arg(increment);
let result = self.execute(&cmd).await?;
if let Some(s) = result.as_string() {
s.parse::<f64>()
.map_err(|_| DbError::RedisTypeConversionError("无法转换为浮点数".to_string()))
} else {
result
.as_f64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为浮点数".to_string()))
}
}
pub async fn lpush(&self, key: impl Into<String>, values: &[String]) -> Result<i64> {
let mut cmd = redis::cmd("LPUSH");
cmd.arg(key.into());
for value in values {
cmd.arg(value);
}
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn rpush(&self, key: impl Into<String>, values: &[String]) -> Result<i64> {
let mut cmd = redis::cmd("RPUSH");
cmd.arg(key.into());
for value in values {
cmd.arg(value);
}
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn lpop(&self, key: impl Into<String>) -> Result<Option<String>> {
let mut cmd = redis::cmd("LPOP");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
Ok(result.as_string())
}
pub async fn rpop(&self, key: impl Into<String>) -> Result<Option<String>> {
let mut cmd = redis::cmd("RPOP");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
Ok(result.as_string())
}
pub async fn lrange(
&self,
key: impl Into<String>,
start: i64,
stop: i64,
) -> Result<Vec<String>> {
let mut cmd = redis::cmd("LRANGE");
cmd.arg(key.into()).arg(start).arg(stop);
let result = self.execute(&cmd).await?;
if let Some(arr) = result.as_array() {
Ok(arr.iter().filter_map(|v| v.as_string()).collect())
} else {
Ok(vec![])
}
}
pub async fn llen(&self, key: impl Into<String>) -> Result<i64> {
let mut cmd = redis::cmd("LLEN");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn lindex(&self, key: impl Into<String>, index: i64) -> Result<Option<String>> {
let mut cmd = redis::cmd("LINDEX");
cmd.arg(key.into()).arg(index);
let result = self.execute(&cmd).await?;
Ok(result.as_string())
}
pub async fn lset(
&self,
key: impl Into<String>,
index: i64,
value: impl Into<String>,
) -> Result<()> {
let mut cmd = redis::cmd("LSET");
cmd.arg(key.into()).arg(index).arg(value.into());
self.execute(&cmd).await?;
Ok(())
}
pub async fn ltrim(&self, key: impl Into<String>, start: i64, stop: i64) -> Result<()> {
let mut cmd = redis::cmd("LTRIM");
cmd.arg(key.into()).arg(start).arg(stop);
self.execute(&cmd).await?;
Ok(())
}
pub async fn sadd(&self, key: impl Into<String>, members: &[String]) -> Result<i64> {
let mut cmd = redis::cmd("SADD");
cmd.arg(key.into());
for member in members {
cmd.arg(member);
}
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn srem(&self, key: impl Into<String>, members: &[String]) -> Result<i64> {
let mut cmd = redis::cmd("SREM");
cmd.arg(key.into());
for member in members {
cmd.arg(member);
}
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn smembers(&self, key: impl Into<String>) -> Result<Vec<String>> {
let mut cmd = redis::cmd("SMEMBERS");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
if let Some(arr) = result.as_array() {
Ok(arr.iter().filter_map(|v| v.as_string()).collect())
} else {
Ok(vec![])
}
}
pub async fn sismember(
&self,
key: impl Into<String>,
member: impl Into<String>,
) -> Result<bool> {
let mut cmd = redis::cmd("SISMEMBER");
cmd.arg(key.into()).arg(member.into());
let result = self.execute(&cmd).await?;
Ok(result.as_i64() == Some(1))
}
pub async fn scard(&self, key: impl Into<String>) -> Result<i64> {
let mut cmd = redis::cmd("SCARD");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn spop(&self, key: impl Into<String>) -> Result<Option<String>> {
let mut cmd = redis::cmd("SPOP");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
Ok(result.as_string())
}
pub async fn srandmember(&self, key: impl Into<String>) -> Result<Option<String>> {
let mut cmd = redis::cmd("SRANDMEMBER");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
Ok(result.as_string())
}
pub async fn zadd(&self, key: impl Into<String>, members: &[(f64, String)]) -> Result<i64> {
let mut cmd = redis::cmd("ZADD");
cmd.arg(key.into());
for (score, member) in members {
cmd.arg(score).arg(member);
}
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn zrem(&self, key: impl Into<String>, members: &[String]) -> Result<i64> {
let mut cmd = redis::cmd("ZREM");
cmd.arg(key.into());
for member in members {
cmd.arg(member);
}
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn zscore(
&self,
key: impl Into<String>,
member: impl Into<String>,
) -> Result<Option<f64>> {
let mut cmd = redis::cmd("ZSCORE");
cmd.arg(key.into()).arg(member.into());
let result = self.execute(&cmd).await?;
if let Some(s) = result.as_string() {
Ok(s.parse::<f64>().ok())
} else {
Ok(result.as_f64())
}
}
pub async fn zcard(&self, key: impl Into<String>) -> Result<i64> {
let mut cmd = redis::cmd("ZCARD");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn zrange(
&self,
key: impl Into<String>,
start: i64,
stop: i64,
) -> Result<Vec<String>> {
let mut cmd = redis::cmd("ZRANGE");
cmd.arg(key.into()).arg(start).arg(stop);
let result = self.execute(&cmd).await?;
if let Some(arr) = result.as_array() {
Ok(arr.iter().filter_map(|v| v.as_string()).collect())
} else {
Ok(vec![])
}
}
pub async fn zrangebyscore(
&self,
key: impl Into<String>,
min: f64,
max: f64,
) -> Result<Vec<String>> {
let mut cmd = redis::cmd("ZRANGEBYSCORE");
cmd.arg(key.into()).arg(min).arg(max);
let result = self.execute(&cmd).await?;
if let Some(arr) = result.as_array() {
Ok(arr.iter().filter_map(|v| v.as_string()).collect())
} else {
Ok(vec![])
}
}
pub async fn zcount(&self, key: impl Into<String>, min: f64, max: f64) -> Result<i64> {
let mut cmd = redis::cmd("ZCOUNT");
cmd.arg(key.into()).arg(min).arg(max);
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn zincrby(
&self,
key: impl Into<String>,
increment: f64,
member: impl Into<String>,
) -> Result<f64> {
let mut cmd = redis::cmd("ZINCRBY");
cmd.arg(key.into()).arg(increment).arg(member.into());
let result = self.execute(&cmd).await?;
if let Some(s) = result.as_string() {
s.parse::<f64>()
.map_err(|_| DbError::RedisTypeConversionError("无法转换为浮点数".to_string()))
} else {
result
.as_f64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为浮点数".to_string()))
}
}
pub async fn del(&self, keys: &[String]) -> Result<i64> {
let mut cmd = redis::cmd("DEL");
for key in keys {
cmd.arg(key);
}
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn exists(&self, keys: &[String]) -> Result<i64> {
let mut cmd = redis::cmd("EXISTS");
for key in keys {
cmd.arg(key);
}
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn expire(&self, key: impl Into<String>, seconds: i64) -> Result<bool> {
let mut cmd = redis::cmd("EXPIRE");
cmd.arg(key.into()).arg(seconds);
let result = self.execute(&cmd).await?;
Ok(result.as_i64() == Some(1))
}
pub async fn ttl(&self, key: impl Into<String>) -> Result<i64> {
let mut cmd = redis::cmd("TTL");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
result
.as_i64()
.ok_or_else(|| DbError::RedisTypeConversionError("无法转换为整数".to_string()))
}
pub async fn persist(&self, key: impl Into<String>) -> Result<bool> {
let mut cmd = redis::cmd("PERSIST");
cmd.arg(key.into());
let result = self.execute(&cmd).await?;
Ok(result.as_i64() == Some(1))
}
pub async fn keys(&self, pattern: impl Into<String>) -> Result<Vec<String>> {
let mut cmd = redis::cmd("KEYS");
cmd.arg(pattern.into());
let result = self.execute(&cmd).await?;
if let Some(arr) = result.as_array() {
Ok(arr.iter().filter_map(|v| v.as_string()).collect())
} else {
Ok(vec![])
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_redis_client_clone() {
let config = RedisConfig::default();
let _ = config.clone();
}
#[test]
fn test_redis_config_default() {
let config = RedisConfig::default();
assert_eq!(config.max_connections, 10);
assert_eq!(config.connect_timeout, 5);
assert_eq!(config.wait_timeout, 10);
assert!(!config.enable_logging);
}
#[test]
fn test_redis_config_custom() {
let config = RedisConfig::new(20, 10, 15, true);
assert_eq!(config.max_connections, 20);
assert_eq!(config.connect_timeout, 10);
assert_eq!(config.wait_timeout, 15);
assert!(config.enable_logging);
}
}