- git 初始化 + .gitignore 补全(根 .env / logs / instance / __pycache__ 等) - 修复 utils/.gitignore 误将整个 utils/ 源码层忽略的问题,补入全部工具源码(含 ip2region.xdb) - 含后端全部代码:application / extensions / utils / config / migrations - 含前端 ui/(后续整体迁走) - 不含根 .env(含密钥,本地保留) Co-Authored-By: Claude <noreply@anthropic.com>
312 lines
10 KiB
Python
312 lines
10 KiB
Python
"""
|
|
Redis客户端封装模块
|
|
=============================================================
|
|
|
|
提供Redis操作的统一封装,支持字符串、哈希、列表、集合、有序集合等数据类型。
|
|
自动处理序列化/反序列化,业务代码无需关心底层存储格式。
|
|
|
|
使用方式:
|
|
from utils.redis_client import redis
|
|
|
|
redis.set('key', 'value', expire=3600)
|
|
value = redis.get('key')
|
|
"""
|
|
|
|
import json
|
|
import pickle
|
|
from typing import Any, Union, List, Dict, Tuple
|
|
|
|
from extensions.redis import redis_client
|
|
|
|
|
|
class RedisClient:
|
|
"""
|
|
Redis客户端封装类
|
|
|
|
提供丰富的Redis操作方法,支持字符串、哈希、列表、集合、有序集合等数据类型
|
|
"""
|
|
|
|
def __init__(self, client=None):
|
|
"""
|
|
初始化Redis客户端
|
|
|
|
Args:
|
|
client: FlaskRedis实例,如果为None则使用全局redis_client
|
|
"""
|
|
self.client = client or redis_client
|
|
|
|
# ==================== 基础方法 ====================
|
|
|
|
def set(self, key: str, value: Any, expire: int = None, nx: bool = False, xx: bool = False) -> bool:
|
|
"""设置键值对"""
|
|
serialized_value = self._serialize(value)
|
|
|
|
if nx and xx:
|
|
raise ValueError("nx和xx不能同时为True")
|
|
|
|
if nx:
|
|
result = self.client.setnx(key, serialized_value)
|
|
if expire and result:
|
|
self.client.expire(key, expire)
|
|
return result
|
|
elif xx:
|
|
result = self.client.set(key, serialized_value, xx=True)
|
|
if expire and result:
|
|
self.client.expire(key, expire)
|
|
return result
|
|
else:
|
|
if expire:
|
|
return self.client.setex(key, expire, serialized_value)
|
|
else:
|
|
return self.client.set(key, serialized_value)
|
|
|
|
def get(self, key: str, default: Any = None, deserialize: bool = True) -> Any:
|
|
"""获取键值"""
|
|
data = self.client.get(key)
|
|
if data is None:
|
|
return default
|
|
|
|
if deserialize:
|
|
return self._deserialize(data)
|
|
return data
|
|
|
|
def delete(self, *keys: str) -> int:
|
|
"""删除一个或多个键"""
|
|
if not keys:
|
|
return 0
|
|
return self.client.delete(*keys)
|
|
|
|
def exists(self, *keys: str) -> int:
|
|
"""检查键是否存在"""
|
|
if not keys:
|
|
return 0
|
|
return self.client.exists(*keys)
|
|
|
|
def expire(self, key: str, seconds: int) -> bool:
|
|
"""设置键的过期时间"""
|
|
return self.client.expire(key, seconds)
|
|
|
|
def expireat(self, key: str, timestamp: int) -> bool:
|
|
"""设置键在指定时间戳过期"""
|
|
return self.client.expireat(key, timestamp)
|
|
|
|
def ttl(self, key: str) -> int:
|
|
"""获取键的剩余过期时间"""
|
|
return self.client.ttl(key)
|
|
|
|
def keys(self, pattern: str = '*') -> List[str]:
|
|
"""获取匹配模式的所有键(使用SCAN,避免KEYS阻塞)"""
|
|
result = []
|
|
cursor = 0
|
|
while True:
|
|
cursor, batch = self.client.scan(cursor=cursor, match=pattern, count=100)
|
|
result.extend(batch)
|
|
if cursor == 0:
|
|
break
|
|
return result
|
|
|
|
# ==================== 哈希表操作 ====================
|
|
|
|
def hset(self, name: str, key: str, value: Any) -> int:
|
|
"""设置哈希表中的字段值"""
|
|
serialized_value = self._serialize(value)
|
|
return self.client.hset(name, key, serialized_value)
|
|
|
|
def hget(self, name: str, key: str, deserialize: bool = True) -> Any:
|
|
"""获取哈希表中的字段值"""
|
|
data = self.client.hget(name, key)
|
|
if data is None:
|
|
return None
|
|
|
|
if deserialize:
|
|
return self._deserialize(data)
|
|
return data
|
|
|
|
def hgetall(self, name: str, deserialize: bool = True) -> Dict[str, Any]:
|
|
"""获取哈希表中所有字段和值"""
|
|
data = self.client.hgetall(name)
|
|
if not data:
|
|
return {}
|
|
|
|
if deserialize:
|
|
return {k.decode(): self._deserialize(v) for k, v in data.items()}
|
|
return {k.decode(): v for k, v in data.items()}
|
|
|
|
def hdel(self, name: str, *keys: str) -> int:
|
|
"""删除哈希表中的字段"""
|
|
if not keys:
|
|
return 0
|
|
return self.client.hdel(name, *keys)
|
|
|
|
def hincrby(self, name: str, key: str, amount: int = 1) -> int:
|
|
"""为哈希表中的字段值增加指定数值"""
|
|
return self.client.hincrby(name, key, amount)
|
|
|
|
# ==================== 列表操作 ====================
|
|
|
|
def lpush(self, name: str, *values: Any) -> int:
|
|
"""从列表左侧插入元素"""
|
|
serialized_values = [self._serialize(v) for v in values]
|
|
return self.client.lpush(name, *serialized_values)
|
|
|
|
def rpush(self, name: str, *values: Any) -> int:
|
|
"""从列表右侧插入元素"""
|
|
serialized_values = [self._serialize(v) for v in values]
|
|
return self.client.rpush(name, *serialized_values)
|
|
|
|
def lpop(self, name: str, deserialize: bool = True) -> Any:
|
|
"""从列表左侧弹出元素"""
|
|
data = self.client.lpop(name)
|
|
if data is None:
|
|
return None
|
|
|
|
if deserialize:
|
|
return self._deserialize(data)
|
|
return data
|
|
|
|
def rpop(self, name: str, deserialize: bool = True) -> Any:
|
|
"""从列表右侧弹出元素"""
|
|
data = self.client.rpop(name)
|
|
if data is None:
|
|
return None
|
|
|
|
if deserialize:
|
|
return self._deserialize(data)
|
|
return data
|
|
|
|
def lrange(self, name: str, start: int, end: int, deserialize: bool = True) -> List[Any]:
|
|
"""获取列表指定范围的元素"""
|
|
data = self.client.lrange(name, start, end)
|
|
if not data:
|
|
return []
|
|
|
|
if deserialize:
|
|
return [self._deserialize(item) for item in data]
|
|
return [item for item in data]
|
|
|
|
def llen(self, name: str) -> int:
|
|
"""获取列表长度"""
|
|
return self.client.llen(name)
|
|
|
|
# ==================== 集合操作 ====================
|
|
|
|
def sadd(self, name: str, *values: Any) -> int:
|
|
"""向集合中添加元素"""
|
|
serialized_values = [self._serialize(v) for v in values]
|
|
return self.client.sadd(name, *serialized_values)
|
|
|
|
def srem(self, name: str, *values: Any) -> int:
|
|
"""从集合中移除元素"""
|
|
serialized_values = [self._serialize(v) for v in values]
|
|
return self.client.srem(name, *serialized_values)
|
|
|
|
def smembers(self, name: str, deserialize: bool = True) -> set:
|
|
"""获取集合中所有元素"""
|
|
data = self.client.smembers(name)
|
|
if not data:
|
|
return set()
|
|
|
|
if deserialize:
|
|
return {self._deserialize(item) for item in data}
|
|
return {item for item in data}
|
|
|
|
def sismember(self, name: str, value: Any) -> bool:
|
|
"""判断元素是否在集合中"""
|
|
serialized_value = self._serialize(value)
|
|
return self.client.sismember(name, serialized_value)
|
|
|
|
# ==================== 有序集合操作 ====================
|
|
|
|
def zadd(self, name: str, mapping: Dict[Any, float]) -> int:
|
|
"""向有序集合中添加元素"""
|
|
serialized_mapping = {self._serialize(k): v for k, v in mapping.items()}
|
|
return self.client.zadd(name, serialized_mapping)
|
|
|
|
def zrange(self, name: str, start: int, end: int, withscores: bool = False,
|
|
deserialize: bool = True) -> Union[List[Any], List[Tuple[Any, float]]]:
|
|
"""获取有序集合指定范围的元素(按分数升序)"""
|
|
data = self.client.zrange(name, start, end, withscores=withscores)
|
|
if not data:
|
|
return []
|
|
|
|
if withscores:
|
|
if deserialize:
|
|
return [(self._deserialize(item[0]), item[1]) for item in data]
|
|
return [(item[0], item[1]) for item in data]
|
|
else:
|
|
if deserialize:
|
|
return [self._deserialize(item) for item in data]
|
|
return list(data)
|
|
|
|
def zrevrange(self, name: str, start: int, end: int, withscores: bool = False,
|
|
deserialize: bool = True) -> Union[List[Any], List[Tuple[Any, float]]]:
|
|
"""获取有序集合指定范围的元素(按分数降序)"""
|
|
data = self.client.zrevrange(name, start, end, withscores=withscores)
|
|
if not data:
|
|
return []
|
|
|
|
if withscores:
|
|
if deserialize:
|
|
return [(self._deserialize(item[0]), item[1]) for item in data]
|
|
return [(item[0], item[1]) for item in data]
|
|
else:
|
|
if deserialize:
|
|
return [self._deserialize(item) for item in data]
|
|
return list(data)
|
|
|
|
# ==================== 高级操作 ====================
|
|
|
|
def pipeline(self):
|
|
"""获取管道对象,用于批量执行命令"""
|
|
return self.client.pipeline()
|
|
|
|
def incr(self, key: str, amount: int = 1) -> int:
|
|
"""增加键的值(用于计数器)"""
|
|
return self.client.incrby(key, amount)
|
|
|
|
def decr(self, key: str, amount: int = 1) -> int:
|
|
"""减少键的值"""
|
|
return self.client.decrby(key, amount)
|
|
|
|
def lock(self, key: str, timeout: int = 10, blocking: bool = True,
|
|
blocking_timeout: int = None) -> Any:
|
|
"""获取分布式锁"""
|
|
return self.client.lock(key, timeout=timeout, blocking=blocking,
|
|
blocking_timeout=blocking_timeout)
|
|
|
|
# ==================== 私有方法 ====================
|
|
|
|
def _serialize(self, value: Any) -> Union[str, bytes]:
|
|
"""序列化值"""
|
|
if isinstance(value, str):
|
|
return value
|
|
elif isinstance(value, (int, float, bool)):
|
|
return str(value)
|
|
elif isinstance(value, (dict, list, tuple)):
|
|
return json.dumps(value, ensure_ascii=False)
|
|
else:
|
|
return pickle.dumps(value)
|
|
|
|
def _deserialize(self, value: Union[str, bytes]) -> Any:
|
|
"""反序列化值"""
|
|
if not value:
|
|
return None
|
|
|
|
if isinstance(value, bytes):
|
|
try:
|
|
value = value.decode('utf-8')
|
|
except UnicodeDecodeError:
|
|
try:
|
|
return pickle.loads(value)
|
|
except Exception:
|
|
return value
|
|
|
|
try:
|
|
return json.loads(value)
|
|
except (json.JSONDecodeError, TypeError):
|
|
return value
|
|
|
|
|
|
# 创建全局Redis客户端实例
|
|
redis = RedisClient()
|