chore: 基线提交(重构前代码快照)
- 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>
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user