Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Redis管理模块
|
||||
"""
|
||||
@@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Redis管理API(异步版本)
|
||||
"""
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from core.redis_manager.schema import (
|
||||
RedisKeyDetailSchema,
|
||||
RedisKeyCreateSchema,
|
||||
RedisKeyUpdateSchema,
|
||||
RedisKeySearchSchema,
|
||||
RedisKeyListResponse,
|
||||
RedisDatabaseListResponse,
|
||||
RedisKeyRenameSchema,
|
||||
RedisKeyExpireSchema,
|
||||
RedisBatchDeleteSchema,
|
||||
RedisFlushDBSchema,
|
||||
RedisOperationResponse
|
||||
)
|
||||
from core.redis_manager.service import AsyncRedisManagerService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/redis_manager", tags=["Redis管理"])
|
||||
|
||||
|
||||
@router.get("/databases", response_model=RedisDatabaseListResponse, summary="获取所有Redis数据库信息")
|
||||
async def get_redis_databases():
|
||||
"""获取所有Redis数据库信息"""
|
||||
try:
|
||||
service = AsyncRedisManagerService(db_index=0)
|
||||
databases, total_keys = await service.get_all_databases()
|
||||
await service.close()
|
||||
|
||||
return {
|
||||
'databases': databases,
|
||||
'total_keys': total_keys
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get redis databases: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{db_index}/keys/search", response_model=RedisKeyListResponse, summary="搜索Redis键")
|
||||
async def search_redis_keys(db_index: int, search: RedisKeySearchSchema):
|
||||
"""搜索Redis键"""
|
||||
try:
|
||||
service = AsyncRedisManagerService(db_index=db_index)
|
||||
keys, total = await service.search_keys(
|
||||
pattern=search.pattern,
|
||||
key_type=search.key_type,
|
||||
page=search.page,
|
||||
page_size=search.page_size
|
||||
)
|
||||
await service.close()
|
||||
|
||||
return {
|
||||
'total': total,
|
||||
'keys': keys,
|
||||
'page': search.page,
|
||||
'page_size': search.page_size
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to search redis keys: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/{db_index}/keys/{key:path}", response_model=RedisKeyDetailSchema, summary="获取Redis键详情")
|
||||
async def get_redis_key_detail(db_index: int, key: str):
|
||||
"""获取Redis键详情"""
|
||||
try:
|
||||
service = AsyncRedisManagerService(db_index=db_index)
|
||||
detail = await service.get_key_detail(key)
|
||||
await service.close()
|
||||
|
||||
return detail
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get redis key detail: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{db_index}/keys", response_model=RedisOperationResponse, summary="创建Redis键")
|
||||
async def create_redis_key(db_index: int, data: RedisKeyCreateSchema):
|
||||
"""创建Redis键"""
|
||||
try:
|
||||
service = AsyncRedisManagerService(db_index=db_index)
|
||||
success = await service.create_key(
|
||||
key=data.key,
|
||||
key_type=data.type,
|
||||
value=data.value,
|
||||
ttl=data.ttl
|
||||
)
|
||||
await service.close()
|
||||
|
||||
return {
|
||||
'success': success,
|
||||
'message': f"Key '{data.key}' created successfully"
|
||||
}
|
||||
except ValueError as e:
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create redis key: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.put("/{db_index}/keys/{key:path}", response_model=RedisOperationResponse, summary="更新Redis键")
|
||||
async def update_redis_key(db_index: int, key: str, data: RedisKeyUpdateSchema):
|
||||
"""更新Redis键"""
|
||||
try:
|
||||
service = AsyncRedisManagerService(db_index=db_index)
|
||||
success = await service.update_key(
|
||||
key=key,
|
||||
value=data.value,
|
||||
ttl=data.ttl
|
||||
)
|
||||
await service.close()
|
||||
|
||||
return {
|
||||
'success': success,
|
||||
'message': f"Key '{key}' updated successfully"
|
||||
}
|
||||
except ValueError as e:
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update redis key: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.delete("/{db_index}/keys/{key:path}", response_model=RedisOperationResponse, summary="删除Redis键")
|
||||
async def delete_redis_key(db_index: int, key: str):
|
||||
"""删除Redis键"""
|
||||
try:
|
||||
service = AsyncRedisManagerService(db_index=db_index)
|
||||
success = await service.delete_key(key)
|
||||
await service.close()
|
||||
|
||||
return {
|
||||
'success': success,
|
||||
'message': f"Key '{key}' deleted successfully" if success else f"Key '{key}' not found"
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete redis key: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{db_index}/keys/batch-delete", response_model=RedisOperationResponse, summary="批量删除Redis键")
|
||||
async def batch_delete_redis_keys(db_index: int, data: RedisBatchDeleteSchema):
|
||||
"""批量删除Redis键"""
|
||||
try:
|
||||
service = AsyncRedisManagerService(db_index=db_index)
|
||||
count = await service.batch_delete_keys(data.keys)
|
||||
await service.close()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': f"Deleted {count} keys",
|
||||
'data': {'deleted_count': count}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to batch delete redis keys: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{db_index}/keys/rename", response_model=RedisOperationResponse, summary="重命名Redis键")
|
||||
async def rename_redis_key(db_index: int, data: RedisKeyRenameSchema):
|
||||
"""重命名Redis键"""
|
||||
try:
|
||||
service = AsyncRedisManagerService(db_index=db_index)
|
||||
success = await service.rename_key(data.old_key, data.new_key)
|
||||
await service.close()
|
||||
|
||||
return {
|
||||
'success': success,
|
||||
'message': f"Key renamed from '{data.old_key}' to '{data.new_key}'"
|
||||
}
|
||||
except ValueError as e:
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to rename redis key: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{db_index}/keys/expire", response_model=RedisOperationResponse, summary="设置Redis键过期时间")
|
||||
async def set_redis_key_expire(db_index: int, data: RedisKeyExpireSchema):
|
||||
"""设置Redis键过期时间"""
|
||||
try:
|
||||
service = AsyncRedisManagerService(db_index=db_index)
|
||||
success = await service.set_expire(data.key, data.ttl)
|
||||
await service.close()
|
||||
|
||||
ttl_msg = "永不过期" if data.ttl == -1 else f"{data.ttl}秒后过期"
|
||||
return {
|
||||
'success': success,
|
||||
'message': f"Key '{data.key}' set to {ttl_msg}"
|
||||
}
|
||||
except ValueError as e:
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to set redis key expire: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{db_index}/flush", response_model=RedisOperationResponse, summary="清空Redis数据库")
|
||||
async def flush_redis_database(db_index: int, data: RedisFlushDBSchema):
|
||||
"""清空Redis数据库"""
|
||||
try:
|
||||
service = AsyncRedisManagerService(db_index=db_index)
|
||||
success = await service.flush_db(confirm=data.confirm)
|
||||
await service.close()
|
||||
|
||||
return {
|
||||
'success': success,
|
||||
'message': f"Database {db_index} flushed successfully"
|
||||
}
|
||||
except ValueError as e:
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to flush redis database: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Redis管理Schema
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Any, Dict
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.base_schema import CSTDatetime
|
||||
|
||||
|
||||
class RedisKeySchema(BaseModel):
|
||||
"""Redis键Schema"""
|
||||
key: str
|
||||
type: str # string, list, set, zset, hash
|
||||
ttl: int # -1表示永不过期,-2表示已过期
|
||||
size: Optional[int] = None # 键的大小(字节)
|
||||
length: Optional[int] = None # 集合/列表的元素数量
|
||||
encoding: Optional[str] = None # 编码方式
|
||||
|
||||
|
||||
class RedisKeyDetailSchema(BaseModel):
|
||||
"""Redis键详情Schema"""
|
||||
key: str
|
||||
type: str
|
||||
ttl: int
|
||||
value: Any # 根据类型不同,值的格式也不同
|
||||
size: Optional[int] = None
|
||||
encoding: Optional[str] = None
|
||||
created_at: Optional[CSTDatetime] = None
|
||||
|
||||
|
||||
class RedisKeyCreateSchema(BaseModel):
|
||||
"""创建Redis键Schema"""
|
||||
key: str = Field(..., description="键名")
|
||||
type: str = Field(..., description="数据类型: string, list, set, zset, hash")
|
||||
value: Any = Field(..., description="值")
|
||||
ttl: Optional[int] = Field(None, description="过期时间(秒),-1表示永不过期")
|
||||
|
||||
|
||||
class RedisKeyUpdateSchema(BaseModel):
|
||||
"""更新Redis键Schema"""
|
||||
value: Any = Field(..., description="新值")
|
||||
ttl: Optional[int] = Field(None, description="过期时间(秒)")
|
||||
|
||||
|
||||
class RedisStringValueSchema(BaseModel):
|
||||
"""String类型值Schema"""
|
||||
value: str
|
||||
|
||||
|
||||
class RedisListValueSchema(BaseModel):
|
||||
"""List类型值Schema"""
|
||||
values: List[str]
|
||||
|
||||
|
||||
class RedisSetValueSchema(BaseModel):
|
||||
"""Set类型值Schema"""
|
||||
members: List[str]
|
||||
|
||||
|
||||
class RedisZSetValueSchema(BaseModel):
|
||||
"""ZSet类型值Schema"""
|
||||
members: List[Dict[str, Any]] # [{"member": "xxx", "score": 1.0}]
|
||||
|
||||
|
||||
class RedisHashValueSchema(BaseModel):
|
||||
"""Hash类型值Schema"""
|
||||
fields: Dict[str, str]
|
||||
|
||||
|
||||
class RedisKeySearchSchema(BaseModel):
|
||||
"""Redis键搜索Schema"""
|
||||
pattern: str = Field(default="*", description="搜索模式,支持通配符")
|
||||
key_type: Optional[str] = Field(None, description="键类型过滤")
|
||||
page: int = Field(default=1, ge=1, description="页码")
|
||||
page_size: int = Field(default=20, ge=1, le=100, description="每页数量")
|
||||
|
||||
|
||||
class RedisKeyListResponse(BaseModel):
|
||||
"""Redis键列表响应Schema"""
|
||||
total: int
|
||||
keys: List[RedisKeySchema]
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
|
||||
class RedisDatabaseSchema(BaseModel):
|
||||
"""Redis数据库Schema"""
|
||||
db_index: int
|
||||
keys_count: int
|
||||
expires_count: int
|
||||
avg_ttl: int
|
||||
|
||||
|
||||
class RedisDatabaseListResponse(BaseModel):
|
||||
"""Redis数据库列表响应Schema"""
|
||||
databases: List[RedisDatabaseSchema]
|
||||
total_keys: int
|
||||
|
||||
|
||||
class RedisKeyRenameSchema(BaseModel):
|
||||
"""重命名键Schema"""
|
||||
old_key: str
|
||||
new_key: str
|
||||
|
||||
|
||||
class RedisKeyExpireSchema(BaseModel):
|
||||
"""设置过期时间Schema"""
|
||||
key: str
|
||||
ttl: int # 秒
|
||||
|
||||
|
||||
class RedisBatchDeleteSchema(BaseModel):
|
||||
"""批量删除Schema"""
|
||||
keys: List[str]
|
||||
|
||||
|
||||
class RedisFlushDBSchema(BaseModel):
|
||||
"""清空数据库Schema"""
|
||||
db_index: int
|
||||
confirm: bool = Field(..., description="确认清空")
|
||||
|
||||
|
||||
class RedisOperationResponse(BaseModel):
|
||||
"""操作响应Schema"""
|
||||
success: bool
|
||||
message: str
|
||||
data: Optional[Any] = None
|
||||
@@ -0,0 +1,506 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Redis管理服务(异步版本)
|
||||
"""
|
||||
import logging
|
||||
from typing import List, Dict, Any, Optional, Tuple
|
||||
|
||||
import redis.asyncio as aioredis
|
||||
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AsyncRedisManagerService:
|
||||
"""异步Redis管理服务"""
|
||||
|
||||
def __init__(self, db_index: int = 0):
|
||||
"""
|
||||
初始化Redis连接
|
||||
|
||||
Args:
|
||||
db_index: Redis数据库索引(0-15)
|
||||
"""
|
||||
redis_host = settings.REDIS_HOST
|
||||
redis_port = settings.REDIS_PORT
|
||||
redis_password = settings.REDIS_PASSWORD
|
||||
if redis_password == '':
|
||||
redis_password = None
|
||||
|
||||
# 不自动解码,因为可能有二进制数据
|
||||
self.client = aioredis.Redis(
|
||||
host=redis_host,
|
||||
port=redis_port,
|
||||
db=db_index,
|
||||
password=redis_password,
|
||||
decode_responses=False # 不自动解码,手动处理
|
||||
)
|
||||
self.db_index = db_index
|
||||
self.host = redis_host
|
||||
self.port = redis_port
|
||||
self.password = redis_password
|
||||
|
||||
def _safe_decode(self, value: any) -> str:
|
||||
"""安全解码Redis值"""
|
||||
if value is None:
|
||||
return ''
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
return value.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
import base64
|
||||
return f"<binary data: {base64.b64encode(value).decode('ascii')}>"
|
||||
return str(value)
|
||||
|
||||
async def get_all_databases(self) -> Tuple[List[Dict[str, Any]], int]:
|
||||
"""
|
||||
获取所有Redis数据库信息
|
||||
|
||||
Returns:
|
||||
(数据库列表, 总键数)
|
||||
"""
|
||||
databases = []
|
||||
total_keys = 0
|
||||
|
||||
# Redis默认有16个数据库(0-15)
|
||||
for db_idx in range(16):
|
||||
try:
|
||||
temp_client = aioredis.Redis(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
db=db_idx,
|
||||
password=self.password,
|
||||
decode_responses=True
|
||||
)
|
||||
|
||||
# 获取数据库信息
|
||||
info = await temp_client.info('keyspace')
|
||||
db_key = f'db{db_idx}'
|
||||
|
||||
if db_key in info:
|
||||
db_info = info[db_key]
|
||||
keys_count = db_info.get('keys', 0)
|
||||
expires_count = db_info.get('expires', 0)
|
||||
avg_ttl = db_info.get('avg_ttl', 0)
|
||||
else:
|
||||
keys_count = 0
|
||||
expires_count = 0
|
||||
avg_ttl = 0
|
||||
|
||||
databases.append({
|
||||
'db_index': db_idx,
|
||||
'keys_count': keys_count,
|
||||
'expires_count': expires_count,
|
||||
'avg_ttl': avg_ttl
|
||||
})
|
||||
|
||||
total_keys += keys_count
|
||||
await temp_client.aclose()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get info for db{db_idx}: {e}")
|
||||
databases.append({
|
||||
'db_index': db_idx,
|
||||
'keys_count': 0,
|
||||
'expires_count': 0,
|
||||
'avg_ttl': 0
|
||||
})
|
||||
|
||||
return databases, total_keys
|
||||
|
||||
async def search_keys(
|
||||
self,
|
||||
pattern: str = "*",
|
||||
key_type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20
|
||||
) -> Tuple[List[Dict[str, Any]], int]:
|
||||
"""
|
||||
搜索Redis键
|
||||
|
||||
Args:
|
||||
pattern: 搜索模式
|
||||
key_type: 键类型过滤
|
||||
page: 页码
|
||||
page_size: 每页数量
|
||||
|
||||
Returns:
|
||||
(键列表, 总数)
|
||||
"""
|
||||
try:
|
||||
# 使用SCAN命令遍历所有键
|
||||
all_keys = []
|
||||
cursor = 0
|
||||
|
||||
while True:
|
||||
cursor, keys = await self.client.scan(
|
||||
cursor,
|
||||
match=pattern.encode() if isinstance(pattern, str) else pattern,
|
||||
count=100
|
||||
)
|
||||
all_keys.extend([self._safe_decode(k) for k in keys])
|
||||
if cursor == 0:
|
||||
break
|
||||
|
||||
logger.info(f"Scanned {len(all_keys)} keys with pattern '{pattern}'")
|
||||
|
||||
# 按类型过滤
|
||||
if key_type:
|
||||
filtered_keys = []
|
||||
for key in all_keys:
|
||||
ktype = await self.client.type(key)
|
||||
ktype_str = self._safe_decode(ktype)
|
||||
if ktype_str == key_type:
|
||||
filtered_keys.append(key)
|
||||
logger.info(f"Filtered to {len(filtered_keys)} keys of type '{key_type}'")
|
||||
all_keys = filtered_keys
|
||||
|
||||
total = len(all_keys)
|
||||
logger.info(f"Total keys: {total}, returning page {page} with {page_size} items per page")
|
||||
|
||||
# 分页
|
||||
start = (page - 1) * page_size
|
||||
end = start + page_size
|
||||
page_keys = all_keys[start:end]
|
||||
|
||||
# 获取键的详细信息
|
||||
keys_info = []
|
||||
for key in page_keys:
|
||||
try:
|
||||
key_info = await self._get_key_info(key)
|
||||
keys_info.append(key_info)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get info for key {key}: {e}")
|
||||
keys_info.append({
|
||||
'key': key,
|
||||
'type': 'unknown',
|
||||
'ttl': -1,
|
||||
'size': None,
|
||||
'length': None,
|
||||
'encoding': None
|
||||
})
|
||||
|
||||
return keys_info, total
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to search keys: {e}")
|
||||
raise
|
||||
|
||||
async def _get_key_info(self, key: str) -> Dict[str, Any]:
|
||||
"""获取键的基本信息"""
|
||||
key_bytes = key.encode() if isinstance(key, str) else key
|
||||
key_type = self._safe_decode(await self.client.type(key_bytes))
|
||||
ttl = await self.client.ttl(key_bytes)
|
||||
|
||||
info = {
|
||||
'key': key,
|
||||
'type': key_type,
|
||||
'ttl': ttl,
|
||||
'encoding': None
|
||||
}
|
||||
|
||||
# 获取编码信息
|
||||
try:
|
||||
if await self.client.exists(key_bytes):
|
||||
encoding = await self.client.object('encoding', key_bytes)
|
||||
info['encoding'] = self._safe_decode(encoding) if encoding else None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 获取大小和长度
|
||||
if key_type == 'string':
|
||||
value = await self.client.get(key_bytes)
|
||||
info['size'] = len(value) if value else 0
|
||||
elif key_type == 'list':
|
||||
info['length'] = await self.client.llen(key_bytes)
|
||||
elif key_type == 'set':
|
||||
info['length'] = await self.client.scard(key_bytes)
|
||||
elif key_type == 'zset':
|
||||
info['length'] = await self.client.zcard(key_bytes)
|
||||
elif key_type == 'hash':
|
||||
info['length'] = await self.client.hlen(key_bytes)
|
||||
|
||||
return info
|
||||
|
||||
async def get_key_detail(self, key: str) -> Dict[str, Any]:
|
||||
"""
|
||||
获取键的详细信息
|
||||
|
||||
Args:
|
||||
key: 键名
|
||||
|
||||
Returns:
|
||||
键的详细信息
|
||||
"""
|
||||
key_bytes = key.encode() if isinstance(key, str) else key
|
||||
|
||||
if not await self.client.exists(key_bytes):
|
||||
raise ValueError(f"Key '{key}' does not exist")
|
||||
|
||||
key_type = self._safe_decode(await self.client.type(key_bytes))
|
||||
ttl = await self.client.ttl(key_bytes)
|
||||
|
||||
detail = {
|
||||
'key': key,
|
||||
'type': key_type,
|
||||
'ttl': ttl,
|
||||
'encoding': None
|
||||
}
|
||||
|
||||
# 获取编码信息
|
||||
try:
|
||||
encoding = await self.client.object('encoding', key_bytes)
|
||||
detail['encoding'] = self._safe_decode(encoding) if encoding else None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 根据类型获取值
|
||||
if key_type == 'string':
|
||||
value = await self.client.get(key_bytes)
|
||||
detail['value'] = self._safe_decode(value)
|
||||
detail['size'] = len(value) if value else 0
|
||||
elif key_type == 'list':
|
||||
values = await self.client.lrange(key_bytes, 0, -1)
|
||||
detail['value'] = [self._safe_decode(v) for v in values]
|
||||
detail['length'] = len(values)
|
||||
elif key_type == 'set':
|
||||
members = await self.client.smembers(key_bytes)
|
||||
detail['value'] = [self._safe_decode(m) for m in members]
|
||||
detail['length'] = len(members)
|
||||
elif key_type == 'zset':
|
||||
members = await self.client.zrange(key_bytes, 0, -1, withscores=True)
|
||||
detail['value'] = [{'member': self._safe_decode(m), 'score': s} for m, s in members]
|
||||
detail['length'] = len(members)
|
||||
elif key_type == 'hash':
|
||||
hash_data = await self.client.hgetall(key_bytes)
|
||||
detail['value'] = {self._safe_decode(k): self._safe_decode(v) for k, v in hash_data.items()}
|
||||
detail['length'] = len(hash_data)
|
||||
|
||||
return detail
|
||||
|
||||
async def create_key(self, key: str, key_type: str, value: Any, ttl: Optional[int] = None) -> bool:
|
||||
"""
|
||||
创建Redis键
|
||||
|
||||
Args:
|
||||
key: 键名
|
||||
key_type: 数据类型
|
||||
value: 值
|
||||
ttl: 过期时间(秒)
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
try:
|
||||
# 检查键是否已存在
|
||||
if await self.client.exists(key):
|
||||
raise ValueError(f"Key '{key}' already exists")
|
||||
|
||||
# 根据类型设置值
|
||||
if key_type == 'string':
|
||||
await self.client.set(key, value)
|
||||
elif key_type == 'list':
|
||||
if isinstance(value, list):
|
||||
await self.client.rpush(key, *value)
|
||||
else:
|
||||
raise ValueError("List type requires a list value")
|
||||
elif key_type == 'set':
|
||||
if isinstance(value, list):
|
||||
await self.client.sadd(key, *value)
|
||||
else:
|
||||
raise ValueError("Set type requires a list value")
|
||||
elif key_type == 'zset':
|
||||
if isinstance(value, list):
|
||||
mapping = {item['member']: item['score'] for item in value}
|
||||
await self.client.zadd(key, mapping)
|
||||
else:
|
||||
raise ValueError("ZSet type requires a list of {member, score} dicts")
|
||||
elif key_type == 'hash':
|
||||
if isinstance(value, dict):
|
||||
await self.client.hset(key, mapping=value)
|
||||
else:
|
||||
raise ValueError("Hash type requires a dict value")
|
||||
else:
|
||||
raise ValueError(f"Unsupported type: {key_type}")
|
||||
|
||||
# 设置过期时间
|
||||
if ttl and ttl > 0:
|
||||
await self.client.expire(key, ttl)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create key {key}: {e}")
|
||||
raise
|
||||
|
||||
async def update_key(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
|
||||
"""
|
||||
更新Redis键
|
||||
|
||||
Args:
|
||||
key: 键名
|
||||
value: 新值
|
||||
ttl: 过期时间(秒)
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
try:
|
||||
if not await self.client.exists(key):
|
||||
raise ValueError(f"Key '{key}' does not exist")
|
||||
|
||||
key_type = self._safe_decode(await self.client.type(key))
|
||||
|
||||
# 删除旧值
|
||||
await self.client.delete(key)
|
||||
|
||||
# 设置新值
|
||||
if key_type == 'string':
|
||||
await self.client.set(key, value)
|
||||
elif key_type == 'list':
|
||||
if isinstance(value, list):
|
||||
await self.client.rpush(key, *value)
|
||||
else:
|
||||
raise ValueError("List type requires a list value")
|
||||
elif key_type == 'set':
|
||||
if isinstance(value, list):
|
||||
await self.client.sadd(key, *value)
|
||||
else:
|
||||
raise ValueError("Set type requires a list value")
|
||||
elif key_type == 'zset':
|
||||
if isinstance(value, list):
|
||||
mapping = {item['member']: item['score'] for item in value}
|
||||
await self.client.zadd(key, mapping)
|
||||
else:
|
||||
raise ValueError("ZSet type requires a list of {member, score} dicts")
|
||||
elif key_type == 'hash':
|
||||
if isinstance(value, dict):
|
||||
await self.client.hset(key, mapping=value)
|
||||
else:
|
||||
raise ValueError("Hash type requires a dict value")
|
||||
|
||||
# 设置过期时间
|
||||
if ttl is not None:
|
||||
if ttl > 0:
|
||||
await self.client.expire(key, ttl)
|
||||
elif ttl == -1:
|
||||
await self.client.persist(key)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update key {key}: {e}")
|
||||
raise
|
||||
|
||||
async def delete_key(self, key: str) -> bool:
|
||||
"""
|
||||
删除Redis键
|
||||
|
||||
Args:
|
||||
key: 键名
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
try:
|
||||
result = await self.client.delete(key)
|
||||
return result > 0
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete key {key}: {e}")
|
||||
raise
|
||||
|
||||
async def batch_delete_keys(self, keys: List[str]) -> int:
|
||||
"""
|
||||
批量删除Redis键
|
||||
|
||||
Args:
|
||||
keys: 键名列表
|
||||
|
||||
Returns:
|
||||
删除的键数量
|
||||
"""
|
||||
try:
|
||||
if not keys:
|
||||
return 0
|
||||
return await self.client.delete(*keys)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to batch delete keys: {e}")
|
||||
raise
|
||||
|
||||
async def rename_key(self, old_key: str, new_key: str) -> bool:
|
||||
"""
|
||||
重命名键
|
||||
|
||||
Args:
|
||||
old_key: 旧键名
|
||||
new_key: 新键名
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
try:
|
||||
if not await self.client.exists(old_key):
|
||||
raise ValueError(f"Key '{old_key}' does not exist")
|
||||
|
||||
if await self.client.exists(new_key):
|
||||
raise ValueError(f"Key '{new_key}' already exists")
|
||||
|
||||
await self.client.rename(old_key, new_key)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to rename key {old_key} to {new_key}: {e}")
|
||||
raise
|
||||
|
||||
async def set_expire(self, key: str, ttl: int) -> bool:
|
||||
"""
|
||||
设置键的过期时间
|
||||
|
||||
Args:
|
||||
key: 键名
|
||||
ttl: 过期时间(秒),-1表示永不过期
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
try:
|
||||
if not await self.client.exists(key):
|
||||
raise ValueError(f"Key '{key}' does not exist")
|
||||
|
||||
if ttl == -1:
|
||||
await self.client.persist(key)
|
||||
else:
|
||||
await self.client.expire(key, ttl)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to set expire for key {key}: {e}")
|
||||
raise
|
||||
|
||||
async def flush_db(self, confirm: bool = False) -> bool:
|
||||
"""
|
||||
清空当前数据库
|
||||
|
||||
Args:
|
||||
confirm: 确认清空
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
if not confirm:
|
||||
raise ValueError("Must confirm to flush database")
|
||||
|
||||
try:
|
||||
await self.client.flushdb()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to flush database: {e}")
|
||||
raise
|
||||
|
||||
async def close(self):
|
||||
"""关闭连接"""
|
||||
if self.client:
|
||||
await self.client.aclose()
|
||||
Reference in New Issue
Block a user