Files
aiData/Util/RedisKit.py
HuangHai b66f683dfb 'commit'
2026-01-12 07:49:18 +08:00

844 lines
27 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
import logging
import uuid
import json
import redis
from Config.Config import (
REDIS_HOST, REDIS_PORT, REDIS_DB, REDIS_PASSWORD,
REDIS_DECODE_RESPONSES, REDIS_MAX_CONNECTIONS
)
# 创建logger实例
logger = logging.getLogger(__name__)
class RedisKit:
"""
异步Redis工具类,提供所有Redis操作的异步实现
单例模式确保全局只有一个Redis连接池
"""
_instance = None
_lock = asyncio.Lock()
_redis_pool = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
@classmethod
async def _ensure_pool(cls):
"""
确保Redis连接池已创建
"""
if cls._redis_pool is None:
async with cls._lock:
if cls._redis_pool is None:
try:
# 创建同步Redis连接池
sync_pool = redis.ConnectionPool(
host=REDIS_HOST,
port=REDIS_PORT,
db=REDIS_DB,
password=REDIS_PASSWORD,
decode_responses=REDIS_DECODE_RESPONSES,
max_connections=REDIS_MAX_CONNECTIONS
)
# 创建Redis连接实例
cls._redis_pool = redis.Redis(
connection_pool=sync_pool,
encoding='utf-8',
decode_responses=REDIS_DECODE_RESPONSES
)
# 测试连接
await asyncio.to_thread(cls._redis_pool.ping)
logger.info("Redis连接池创建成功")
except Exception as e:
logger.error(f"Redis连接池创建失败: {e}")
raise
@classmethod
async def get_connection(cls):
"""
获取Redis连接实例
Returns:
redis.Redis: Redis连接实例
"""
await cls._ensure_pool()
return cls._redis_pool
async def get_data(self, key):
"""
异步从Redis中获取数据
Args:
key (str): Redis键名
Returns:
any: Redis中存储的值,如果键不存在则返回None
"""
try:
await self._ensure_pool()
return await asyncio.to_thread(RedisKit._redis_pool.get, key)
except Exception as e:
logger.error(f"从Redis获取数据失败(key={key}): {e}")
return None
async def exists(self, key):
"""
异步检查Redis键是否存在
Args:
key (str): Redis键名
Returns:
bool: 键存在返回True,不存在或失败返回False
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.exists, key)
return bool(result)
except Exception as e:
logger.error(f"检查Redis键是否存在失败(key={key}): {e}")
return False
async def set_data(self, key, value, expire=None):
"""
异步向Redis中写入数据
Args:
key (str): Redis键名
value (str): 要存储的值
expire (int, optional): 过期时间(秒),默认不过期
Returns:
bool: 写入成功返回True,失败返回False
"""
try:
await self._ensure_pool()
if expire:
result = await asyncio.to_thread(RedisKit._redis_pool.setex, key, expire, value)
else:
result = await asyncio.to_thread(RedisKit._redis_pool.set, key, value)
return result
except Exception as e:
logger.error(f"向Redis写入数据失败(key={key}): {e}")
return False
async def delete_data(self, key):
"""
异步从Redis中删除数据
Args:
key (str): Redis键名
Returns:
bool: 删除成功返回True,失败返回False
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.delete, key)
return result > 0 # delete返回删除的键数量,大于0表示删除成功
except Exception as e:
logger.error(f"从Redis删除数据失败(key={key}): {e}")
return False
async def keys(self, pattern='*'):
"""
异步获取匹配模式的所有键
Args:
pattern (str): 匹配模式
Returns:
list: 匹配的键列表
"""
try:
await self._ensure_pool()
return await asyncio.to_thread(RedisKit._redis_pool.keys, pattern)
except Exception as e:
logger.error(f"获取Redis键列表失败(pattern={pattern}): {e}")
return []
async def delete(self, *names):
"""
异步删除一个或多个键
Args:
*names: 要删除的键名
Returns:
int: 删除的键数量
"""
try:
await self._ensure_pool()
if not names:
return 0
return await asyncio.to_thread(RedisKit._redis_pool.delete, *names)
except Exception as e:
logger.error(f"删除Redis键失败: {e}")
return 0
async def publish(self, channel, message):
"""
异步发布消息到Redis频道
Args:
channel (str): Redis频道名称
message (str): 要发布的消息
Returns:
int: 接收消息的订阅者数量
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.publish, channel, message)
return result
except Exception as e:
logger.error(f"Redis发布消息失败(channel={channel}): {e}")
return 0
async def subscribe(self, channel):
"""
异步订阅Redis频道
Args:
channel (str): Redis频道名称
Returns:
redis.client.PubSub: PubSub对象,用于接收消息
"""
try:
await self._ensure_pool()
pubsub = RedisKit._redis_pool.pubsub()
await asyncio.to_thread(pubsub.subscribe, channel)
return pubsub
except Exception as e:
logger.error(f"Redis订阅频道失败(channel={channel}): {e}")
return None
async def save_user_token(self, person_id, platform, token, expire=86400):
"""
异步保存用户的token到Redis
Args:
person_id (str): 用户ID
platform (str): 平台
token (str): 生成的token
expire (int): 过期时间(秒),默认24小时
Returns:
bool: 保存成功返回True,失败返回False
"""
try:
# 使用固定格式的key:user_token:{person_id}:{platform}
key = f"user_token:{person_id}:{platform}"
return await self.set_data(key, token, expire)
except Exception as e:
logger.error(f"保存用户token失败(person_id={person_id}, platform={platform}): {e}")
return False
async def get_user_token(self, person_id, platform):
"""
异步获取用户在Redis中存储的token
Args:
person_id (str): 用户ID
platform (str): 平台
Returns:
str: 用户的token,如果不存在或过期则返回None
"""
try:
key = f"user_token:{person_id}:{platform}"
return await self.get_data(key)
except Exception as e:
logger.error(f"获取用户token失败(person_id={person_id}, platform={platform}): {e}")
return None
async def validate_user_token(self, person_id, platform, token):
"""
异步验证用户的token是否与Redis中存储的一致
Args:
person_id (str): 用户ID
platform (str): 平台、终端
token (str): 待验证的token
Returns:
bool: token有效返回True,无效返回False
"""
try:
stored_token = await self.get_user_token(person_id, platform)
# 如果没有存储的token,或者存储的token与当前token不一致,则token无效
return stored_token is not None and stored_token == token
except Exception as e:
logger.error(f"验证用户token失败(person_id={person_id}, platform={platform}): {e}")
return False
async def delete_user_token(self, person_id, platform=None):
"""
异步删除用户在Redis中存储的token
Args:
person_id (str): 用户ID
platform (str, optional): 平台,如果为None则删除所有平台的token
Returns:
bool: 删除成功返回True,失败返回False
"""
try:
await self._ensure_pool()
if platform:
# 删除特定平台的token
key = f"user_token:{person_id}:{platform}"
return await self.delete_data(key)
else:
# 删除所有平台的token(使用模式匹配)
pattern = f"user_token:{person_id}:*"
cursor = b'0'
deleted_count = 0
# 使用scan命令查找所有匹配的键
while cursor:
cursor, keys = await asyncio.to_thread(
RedisKit._redis_pool.scan,
cursor=cursor,
match=pattern,
count=100
)
if keys:
deleted = await asyncio.to_thread(
RedisKit._redis_pool.delete, *keys
)
deleted_count += deleted
return deleted_count > 0
except Exception as e:
logger.error(f"删除用户token失败(person_id={person_id}): {e}")
return False
async def list_push(self, key, value, expire=None):
"""
异步向Redis List中推入数据(LPUSH)
Args:
key (str): Redis键名
value (str): 要推入的值
expire (int, optional): 过期时间(秒),如果设置则同时为key设置过期时间
Returns:
int: 推入后list的长度,失败返回-1
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.lpush, key, value)
# 如果设置了过期时间,为key设置TTL
if expire and result > 0:
await asyncio.to_thread(RedisKit._redis_pool.expire, key, expire)
return result
except Exception as e:
logger.error(f"向Redis List推入数据失败(key={key}): {e}")
return -1
async def list_pop(self, key):
"""
异步从Redis List中弹出数据(RPOP)
Args:
key (str): Redis键名
Returns:
str: 弹出的值,如果list为空或失败返回None
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.rpop, key)
return result
except Exception as e:
logger.error(f"从Redis List弹出数据失败(key={key}): {e}")
return None
async def list_length(self, key):
"""
异步获取Redis List的长度
Args:
key (str): Redis键名
Returns:
int: list的长度,如果失败返回-1
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.llen, key)
return result
except Exception as e:
logger.error(f"获取Redis List长度失败(key={key}): {e}")
return -1
async def list_get_all(self, key):
"""
异步获取Redis List中的所有数据(LRANGE 0 -1)
Args:
key (str): Redis键名
Returns:
list: list中的所有数据,如果失败返回空列表
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.lrange, key, 0, -1)
return result if result else []
except Exception as e:
logger.error(f"获取Redis List所有数据失败(key={key}): {e}")
return []
async def list_clear(self, key):
"""
异步清空Redis List(删除key)
Args:
key (str): Redis键名
Returns:
bool: 清空成功返回True,失败返回False
"""
try:
return await self.delete_data(key)
except Exception as e:
logger.error(f"清空Redis List失败(key={key}): {e}")
return False
async def set_is_member(self, key, value):
"""
异步检查值是否在Redis集合中
Args:
key (str): Redis键名
value: 要检查的值
Returns:
bool: 值在集合中返回True,不在或失败返回False
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.sismember, key, value)
return bool(result)
except Exception as e:
logger.error(f"检查值是否在Redis集合中失败(key={key}): {e}")
return False
async def set_add(self, key, value):
"""
异步向Redis集合中添加值
Args:
key (str): Redis键名
value: 要添加的值
Returns:
bool: 添加成功返回True,失败返回False
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.sadd, key, value)
return result > 0 # sadd返回添加的元素数量,大于0表示添加成功
except Exception as e:
logger.error(f"向Redis集合添加值失败(key={key}): {e}")
return False
async def set_remove(self, key, value):
"""
异步从Redis集合中移除值
Args:
key (str): Redis键名
value: 要移除的值
Returns:
bool: 移除成功返回True,失败返回False
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.srem, key, value)
return result > 0 # srem返回移除的元素数量,大于0表示移除成功
except Exception as e:
logger.error(f"从Redis集合移除值失败(key={key}): {e}")
return False
async def set_cardinality(self, key):
"""
异步获取Redis集合的成员数量
Args:
key (str): Redis键名
Returns:
int: 集合的成员数量,如果失败返回0
"""
try:
await self._ensure_pool()
result = await asyncio.to_thread(RedisKit._redis_pool.scard, key)
return result
except Exception as e:
logger.error(f"获取Redis集合成员数量失败(key={key}): {e}")
return 0
# 分布式锁相关方法
async def acquire_lock(self, lock_name, timeout=300):
"""
获取分布式锁
Args:
lock_name (str): 锁的名称
timeout (int): 锁的超时时间(秒),默认300秒
Returns:
str: 锁ID,如果获取失败返回None
"""
try:
await self._ensure_pool()
# 生成唯一锁ID
lock_id = str(uuid.uuid4())
key = f"lock:{lock_name}"
# 使用Redis原子操作设置锁
result = await asyncio.to_thread(
RedisKit._redis_pool.setnx, key, lock_id
)
if result:
# 设置锁的过期时间
await asyncio.to_thread(
RedisKit._redis_pool.expire, key, timeout
)
logger.info(f"获取分布式锁成功: {lock_name}, lock_id: {lock_id}")
return lock_id
else:
logger.info(f"获取分布式锁失败: {lock_name} (锁已存在)")
return None
except Exception as e:
logger.error(f"获取分布式锁失败: {lock_name}, 错误: {e}")
return None
async def release_lock(self, lock_name, lock_id):
"""
释放分布式锁
Args:
lock_name (str): 锁的名称
lock_id (str): 锁ID
Returns:
bool: 是否成功释放锁
"""
try:
await self._ensure_pool()
key = f"lock:{lock_name}"
# 使用Lua脚本原子性检查并释放锁
lua_script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
result = await asyncio.to_thread(
RedisKit._redis_pool.eval, lua_script, 1, key, lock_id
)
if result == 1:
logger.info(f"释放分布式锁成功: {lock_name}, lock_id: {lock_id}")
return True
else:
logger.info(f"释放分布式锁失败: {lock_name}, lock_id: {lock_id} (锁不存在或已被其他进程持有)")
return False
except Exception as e:
logger.error(f"释放分布式锁失败: {lock_name}, 错误: {e}")
return False
async def renew_lock(self, lock_name, lock_id, timeout=300):
"""
续约分布式锁
Args:
lock_name (str): 锁的名称
lock_id (str): 锁ID
timeout (int): 续约时间(秒),默认300秒
Returns:
bool: 是否成功续约
"""
try:
await self._ensure_pool()
key = f"lock:{lock_name}"
# 使用Lua脚本原子性检查并续约锁
lua_script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('expire', KEYS[1], ARGV[2])
else
return 0
end
"""
result = await asyncio.to_thread(
RedisKit._redis_pool.eval, lua_script, 1, key, lock_id, timeout
)
if result == 1:
logger.info(f"续约分布式锁成功: {lock_name}, lock_id: {lock_id}")
return True
else:
logger.info(f"续约分布式锁失败: {lock_name}, lock_id: {lock_id} (锁不存在或已被其他进程持有)")
return False
except Exception as e:
logger.error(f"续约分布式锁失败: {lock_name}, 错误: {e}")
return False
# 任务进度监控相关方法
async def set_task_progress(self, task_id, progress, status="processing", message=""):
"""
设置任务进度
Args:
task_id (str): 任务ID
progress (int): 进度百分比(0-100)
status (str): 任务状态(waiting, processing, completed, failed)
message (str): 任务消息
Returns:
bool: 设置成功返回True,失败返回False
"""
try:
await self._ensure_pool()
key = f"task:progress:{task_id}"
# 构造进度数据
progress_data = {
"task_id": task_id,
"progress": min(100, max(0, progress)), # 确保进度在0-100之间
"status": status,
"message": message,
"update_time": asyncio.get_event_loop().time()
}
# 序列化进度数据
progress_json = json.dumps(progress_data, ensure_ascii=False)
# 保存到Redis
await asyncio.to_thread(
RedisKit._redis_pool.set, key, progress_json
)
# 设置过期时间为24小时
await asyncio.to_thread(
RedisKit._redis_pool.expire, key, 86400
)
logger.info(f"设置任务进度成功: {task_id}, 进度: {progress}%, 状态: {status}")
return True
except Exception as e:
logger.error(f"设置任务进度失败: {task_id}, 错误: {e}")
return False
async def get_task_progress(self, task_id):
"""
获取任务进度
Args:
task_id (str): 任务ID
Returns:
dict: 任务进度数据,如果不存在返回None
"""
try:
await self._ensure_pool()
key = f"task:progress:{task_id}"
# 从Redis获取进度数据
progress_json = await asyncio.to_thread(
RedisKit._redis_pool.get, key
)
if progress_json:
return json.loads(progress_json)
else:
return None
except Exception as e:
logger.error(f"获取任务进度失败: {task_id}, 错误: {e}")
return None
async def complete_task(self, task_id, success=True, message=""):
"""
完成任务
Args:
task_id (str): 任务ID
success (bool): 任务是否成功完成
message (str): 完成消息
Returns:
bool: 设置成功返回True,失败返回False
"""
status = "completed" if success else "failed"
progress = 100 if success else 0
return await self.set_task_progress(task_id, progress, status, message)
async def delete_task_progress(self, task_id):
"""
删除任务进度记录
Args:
task_id (str): 任务ID
Returns:
bool: 删除成功返回True,失败返回False
"""
try:
await self._ensure_pool()
key = f"task:progress:{task_id}"
# 从Redis删除进度记录
result = await asyncio.to_thread(
RedisKit._redis_pool.delete, key
)
logger.info(f"删除任务进度记录成功: {task_id}")
return result > 0
except Exception as e:
logger.error(f"删除任务进度记录失败: {task_id}, 错误: {e}")
return False
@classmethod
async def close(cls):
"""
关闭Redis连接池
"""
if cls._redis_pool:
# 同步Redis连接池不需要等待关闭
cls._redis_pool.connection_pool.disconnect()
cls._redis_pool = None
logger.info("Redis连接池已关闭")
# 创建全局实例
redisKit = RedisKit()
async def main():
"""
测试异步Redis操作的main函数
"""
print("开始测试异步Redis工具类...")
try:
# 测试基本数据操作
print("\n=== 测试基本数据操作 ===")
# 测试设置和获取数据
test_key = "test_key"
test_value = "test_value_123"
result = await redisKit.set_data(test_key, test_value, expire=60)
print(f"设置数据结果: {result}")
get_value = await redisKit.get_data(test_key)
print(f"获取数据结果: {get_value}")
# 测试用户token操作
print("\n=== 测试用户token操作 ===")
# 保存用户token
person_id = "user123"
platform = "web"
token = "jwt_token_xyz789"
save_result = await redisKit.save_user_token(person_id, platform, token, expire=120)
print(f"保存token结果: {save_result}")
# 获取用户token
stored_token = await redisKit.get_user_token(person_id, platform)
print(f"获取token结果: {stored_token}")
# 验证token
validate_result = await redisKit.validate_user_token(person_id, platform, token)
print(f"验证token结果: {validate_result}")
# 验证错误的token
wrong_token = "wrong_token"
wrong_validate_result = await redisKit.validate_user_token(person_id, platform, wrong_token)
print(f"验证错误token结果: {wrong_validate_result}")
# 测试删除操作
print("\n=== 测试删除操作 ===")
# 删除测试数据
delete_result = await redisKit.delete_data(test_key)
print(f"删除数据结果: {delete_result}")
# 删除用户token
delete_token_result = await redisKit.delete_user_token(person_id, platform)
print(f"删除token结果: {delete_token_result}")
# 测试批量token删除功能
print("\n=== 测试批量token删除功能 ===")
# 保存多个平台的token
platforms = ["android", "ios", "web"]
for i, platform in enumerate(platforms):
await redisKit.save_user_token(f"user{i + 1}", platform, f"token{i + 1}", expire=300)
print(f"保存用户{i+1}的{platform}平台token")
# 删除特定用户的所有token
user_to_delete = "user2"
batch_delete_result = await redisKit.delete_user_token(user_to_delete)
print(f"批量删除{user_to_delete}的所有token结果: {batch_delete_result}")
# 验证删除结果
for platform in platforms:
token_check = await redisKit.get_user_token(user_to_delete, platform)
print(f"删除后{user_to_delete}-{platform}平台的token状态: {'已删除' if token_check is None else '存在'}")
print("\n=== 所有测试完成 ===")
except Exception as e:
print(f"测试过程中发生错误: {e}")
import traceback
traceback.print_exc()
finally:
# 清理测试数据并关闭连接池
print("\n清理测试数据并关闭连接池...")
# 清理测试用的数据
test_keys_to_cleanup = ["test_key"]
for key in test_keys_to_cleanup:
await redisKit.delete_data(key)
# 清理测试用户token
cleanup_users = ["user123", "user1", "user2", "user3"]
for user in cleanup_users:
try:
await redisKit.delete_user_token(user)
except:
pass # 忽略清理过程中的错误
# 关闭Redis连接池
await redisKit.close()
print("Redis连接池已关闭")
if __name__ == "__main__":
asyncio.run(main())