feat: add redis

This commit is contained in:
2025-08-29 21:42:29 +08:00
parent af68d94efa
commit dc60a0a4bd
10 changed files with 875 additions and 52 deletions

154
backend/src/redis.rs Normal file
View File

@ -0,0 +1,154 @@
use redis::{Client, AsyncCommands};
use redis::aio::ConnectionManager;
use once_cell::sync::OnceCell;
use crate::error::AppError;
pub type RedisPool = ConnectionManager;
static REDIS_POOL: OnceCell<RedisPool> = OnceCell::new();
/// 初始化Redis连接池
pub async fn init_redis() -> Result<RedisPool, AppError> {
let redis_url = std::env::var("REDIS_URL")
.unwrap_or_else(|_| "redis://:123456@127.0.0.1:6379/9".into());
tracing::info!("Connecting to Redis at: {}", redis_url.replace(":123456", ":***"));
let client = Client::open(redis_url)
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Failed to create Redis client: {}", e)))?;
let manager = ConnectionManager::new(client).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Failed to create Redis connection manager: {}", e)))?;
tracing::info!("Redis connection established successfully");
Ok(manager)
}
/// 获取Redis连接池
pub fn get_redis() -> Result<&'static RedisPool, AppError> {
REDIS_POOL.get().ok_or_else(|| AppError::Anyhow(anyhow::anyhow!("Redis pool not initialized")))
}
/// 设置Redis连接池
pub fn set_redis_pool(pool: RedisPool) -> Result<(), AppError> {
REDIS_POOL.set(pool)
.map_err(|_| AppError::Anyhow(anyhow::anyhow!("Redis pool already initialized")))
}
/// Redis工具函数
pub struct RedisHelper;
impl RedisHelper {
/// 设置带过期时间的键值对
pub async fn set_ex(key: &str, value: &str, expire_seconds: u64) -> Result<(), AppError> {
let mut conn = get_redis()?.clone();
tracing::debug!("Redis SET_EX: key={}, value_len={}, expire_seconds={}", key, value.len(), expire_seconds);
let _: String = conn.set_ex(key, value, expire_seconds).await
.map_err(|e| {
tracing::error!("Redis set_ex failed for key {}: {}", key, e);
AppError::Anyhow(anyhow::anyhow!("Redis set_ex failed: {}", e))
})?;
tracing::debug!("Redis SET_EX successful for key: {}", key);
Ok(())
}
/// 获取键值
pub async fn get(key: &str) -> Result<Option<String>, AppError> {
let mut conn = get_redis()?.clone();
let result: Option<String> = conn.get(key).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis get failed: {}", e)))?;
Ok(result)
}
/// 删除键
pub async fn del(key: &str) -> Result<(), AppError> {
let mut conn = get_redis()?.clone();
let _: i32 = conn.del(key).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis del failed: {}", e)))?;
Ok(())
}
/// 检查键是否存在
pub async fn exists(key: &str) -> Result<bool, AppError> {
let mut conn = get_redis()?.clone();
let result: bool = conn.exists(key).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis exists failed: {}", e)))?;
Ok(result)
}
/// 设置键的过期时间
pub async fn expire(key: &str, seconds: u64) -> Result<(), AppError> {
let mut conn = get_redis()?.clone();
let _: bool = conn.expire(key, seconds as i64).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis expire failed: {}", e)))?;
Ok(())
}
/// 删除用户相关的所有token
pub async fn del_user_tokens(user_id: i64) -> Result<(), AppError> {
let pattern = format!("token:*:user:{}", user_id);
let mut conn = get_redis()?.clone();
// 获取匹配的键
let keys: Vec<String> = conn.keys(&pattern).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis keys failed: {}", e)))?;
// 删除所有匹配的键
if !keys.is_empty() {
let _: i32 = conn.del(&keys).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis del multiple failed: {}", e)))?;
}
Ok(())
}
}
/// Token相关的Redis操作
pub struct TokenRedis;
impl TokenRedis {
/// 存储访问token
pub async fn store_access_token(token: &str, user_id: i64, expire_seconds: u64) -> Result<(), AppError> {
let key = format!("token:access:user:{}", user_id);
tracing::info!("Storing access token for user {} with key: {}, expires in {} seconds", user_id, key, expire_seconds);
RedisHelper::set_ex(&key, token, expire_seconds).await
}
/// 存储刷新token
pub async fn store_refresh_token(token: &str, user_id: i64, expire_seconds: u64) -> Result<(), AppError> {
let key = format!("token:refresh:user:{}", user_id);
tracing::info!("Storing refresh token for user {} with key: {}, expires in {} seconds", user_id, key, expire_seconds);
RedisHelper::set_ex(&key, token, expire_seconds).await
}
/// 验证访问token
pub async fn validate_access_token(token: &str, user_id: i64) -> Result<bool, AppError> {
let key = format!("token:access:user:{}", user_id);
let stored_token = RedisHelper::get(&key).await?;
Ok(stored_token.as_deref() == Some(token))
}
/// 验证刷新token
pub async fn validate_refresh_token(token: &str, user_id: i64) -> Result<bool, AppError> {
let key = format!("token:refresh:user:{}", user_id);
let stored_token = RedisHelper::get(&key).await?;
Ok(stored_token.as_deref() == Some(token))
}
/// 删除用户的访问token
pub async fn revoke_access_token(user_id: i64) -> Result<(), AppError> {
let key = format!("token:access:user:{}", user_id);
RedisHelper::del(&key).await
}
/// 删除用户的刷新token
pub async fn revoke_refresh_token(user_id: i64) -> Result<(), AppError> {
let key = format!("token:refresh:user:{}", user_id);
RedisHelper::del(&key).await
}
/// 删除用户的所有token
pub async fn revoke_all_tokens(user_id: i64) -> Result<(), AppError> {
RedisHelper::del_user_tokens(user_id).await
}
}