349 lines
11 KiB
Python
349 lines
11 KiB
Python
|
||
from python_core.config import settings
|
||
import httpx
|
||
from python_core.utils.logger import setup_logger
|
||
from typing import Dict
|
||
|
||
logger = setup_logger(__name__)
|
||
|
||
class Kv:
|
||
kv: Dict[str, str]
|
||
def __init__(self):
|
||
self.cf_account_id = settings.cloudflare_account_id
|
||
self.cf_kv_api_token = settings.cloudflare_api_key
|
||
self.cf_kv_id = settings.cloudflare_kv_id
|
||
self.base_url = f"https://api.cloudflare.com/client/v4/accounts/{self.cf_account_id}/storage/kv/namespaces/{self.cf_kv_id}"
|
||
self.headers = {"Authorization": f"Bearer {self.cf_kv_api_token}"}
|
||
|
||
def get(self, key: str, default=None):
|
||
"""
|
||
获取单个键的值
|
||
|
||
Args:
|
||
key: 要获取的键
|
||
default: 如果键不存在时返回的默认值
|
||
|
||
Returns:
|
||
键对应的值,如果不存在则返回default
|
||
"""
|
||
try:
|
||
with httpx.Client() as client:
|
||
response = client.get(
|
||
f"{self.base_url}/values/{key}",
|
||
headers=self.headers
|
||
)
|
||
if response.status_code == 404:
|
||
return default
|
||
response.raise_for_status()
|
||
return response.text
|
||
except httpx.RequestError as e:
|
||
logger.error(f"An error occurred while getting key '{key}' from cloudflare: {e}")
|
||
raise e
|
||
except httpx.HTTPStatusError as e:
|
||
if e.response.status_code == 404:
|
||
return default
|
||
logger.error(f"HTTP error occurred while getting key '{key}' from cloudflare: {e}")
|
||
raise e
|
||
except Exception as e:
|
||
logger.error(f"An unexpected error occurred while getting key '{key}': {e}")
|
||
raise e
|
||
|
||
def set(self, key: str, value: str):
|
||
"""
|
||
设置单个键值对
|
||
|
||
Args:
|
||
key: 键名
|
||
value: 值
|
||
expiration_ttl: TTL过期时间(秒)
|
||
expiration: 绝对过期时间(Unix时间戳)
|
||
|
||
Returns:
|
||
操作结果
|
||
"""
|
||
try:
|
||
with httpx.Client() as client:
|
||
params = {}
|
||
|
||
response = client.put(
|
||
f"{self.base_url}/values/{key}",
|
||
headers=self.headers,
|
||
params=params,
|
||
content=value
|
||
)
|
||
response.raise_for_status()
|
||
return response.json() if response.text else {"success": True}
|
||
except httpx.RequestError as e:
|
||
logger.error(f"An error occurred while setting key '{key}' to cloudflare: {e}")
|
||
raise e
|
||
except httpx.HTTPStatusError as e:
|
||
logger.error(f"HTTP error occurred while setting key '{key}' to cloudflare: {e}")
|
||
raise e
|
||
except Exception as e:
|
||
logger.error(f"An unexpected error occurred while setting key '{key}': {e}")
|
||
raise e
|
||
|
||
def remove(self, key: str):
|
||
"""
|
||
删除单个键
|
||
|
||
Args:
|
||
key: 要删除的键名
|
||
|
||
Returns:
|
||
操作结果
|
||
"""
|
||
try:
|
||
with httpx.Client() as client:
|
||
response = client.delete(
|
||
f"{self.base_url}/values/{key}",
|
||
headers=self.headers
|
||
)
|
||
if response.status_code == 404:
|
||
return {"success": True, "message": "Key not found"}
|
||
response.raise_for_status()
|
||
return response.json() if response.text else {"success": True}
|
||
except httpx.RequestError as e:
|
||
logger.error(f"An error occurred while removing key '{key}' from cloudflare: {e}")
|
||
raise e
|
||
except httpx.HTTPStatusError as e:
|
||
if e.response.status_code == 404:
|
||
return {"success": True, "message": "Key not found"}
|
||
logger.error(f"HTTP error occurred while removing key '{key}' from cloudflare: {e}")
|
||
raise e
|
||
except Exception as e:
|
||
logger.error(f"An unexpected error occurred while removing key '{key}': {e}")
|
||
raise e
|
||
|
||
def sets(self, caches: Dict[str, str]):
|
||
"""
|
||
批量设置多个键值对
|
||
|
||
Args:
|
||
caches: 包含键值对的字典
|
||
expiration_ttl: TTL过期时间(秒)
|
||
expiration: 绝对过期时间(Unix时间戳)
|
||
|
||
Returns:
|
||
操作结果
|
||
"""
|
||
try:
|
||
with httpx.Client() as client:
|
||
bulk_data = []
|
||
for key, value in caches.items():
|
||
item = {
|
||
"base64": False,
|
||
"key": key,
|
||
"value": value,
|
||
}
|
||
bulk_data.append(item)
|
||
|
||
response = client.put(
|
||
f"{self.base_url}/bulk",
|
||
headers=self.headers,
|
||
json=bulk_data
|
||
)
|
||
response.raise_for_status()
|
||
return response.json()
|
||
except httpx.RequestError as e:
|
||
logger.error(f"An error occurred while bulk setting keys to cloudflare: {e}")
|
||
raise e
|
||
except httpx.HTTPStatusError as e:
|
||
logger.error(f"HTTP error occurred while bulk setting keys to cloudflare: {e}")
|
||
raise e
|
||
except Exception as e:
|
||
logger.error(f"An unexpected error occurred while bulk setting keys: {e}")
|
||
raise e
|
||
|
||
def gets(self, keys: list[str]):
|
||
"""
|
||
批量获取多个键的值
|
||
|
||
Args:
|
||
keys: 要获取的键列表
|
||
|
||
Returns:
|
||
包含键值对的字典
|
||
"""
|
||
try:
|
||
# 由于Cloudflare KV的批量获取API有问题,我们使用单个获取的方式
|
||
result = {}
|
||
for key in keys:
|
||
value = self.get(key)
|
||
if value is not None:
|
||
result[key] = value
|
||
return result
|
||
|
||
except Exception as e:
|
||
logger.error(f"An error occurred while bulk getting keys from cloudflare: {e}")
|
||
raise e
|
||
|
||
def removes(self, keys: list[str]):
|
||
"""
|
||
批量删除多个键
|
||
|
||
Args:
|
||
keys: 要删除的键列表
|
||
|
||
Returns:
|
||
操作结果
|
||
"""
|
||
try:
|
||
with httpx.Client() as client:
|
||
response = client.post(
|
||
f"{self.base_url}/bulk/delete",
|
||
headers=self.headers,
|
||
json=keys
|
||
)
|
||
response.raise_for_status()
|
||
return response.json()
|
||
except httpx.RequestError as e:
|
||
logger.error(f"An error occurred while bulk removing keys from cloudflare: {e}")
|
||
raise e
|
||
except httpx.HTTPStatusError as e:
|
||
logger.error(f"HTTP error occurred while bulk removing keys from cloudflare: {e}")
|
||
raise e
|
||
except Exception as e:
|
||
logger.error(f"An unexpected error occurred while bulk removing keys: {e}")
|
||
raise e
|
||
|
||
def exists(self, key: str) -> bool:
|
||
"""
|
||
检查键是否存在
|
||
|
||
Args:
|
||
key: 要检查的键名
|
||
|
||
Returns:
|
||
如果键存在返回True,否则返回False
|
||
"""
|
||
try:
|
||
with httpx.Client() as client:
|
||
response = client.get(
|
||
f"{self.base_url}/values/{key}",
|
||
headers=self.headers
|
||
)
|
||
return response.status_code == 200
|
||
except httpx.HTTPStatusError as e:
|
||
if e.response.status_code == 404:
|
||
return False
|
||
raise e
|
||
except Exception:
|
||
return False
|
||
|
||
def list_keys(self, prefix: str = None, limit: int = 1000, cursor: str = None):
|
||
"""
|
||
列出KV存储中的键
|
||
|
||
Args:
|
||
prefix: 键名前缀过滤
|
||
limit: 返回的最大键数量(默认1000)
|
||
cursor: 分页游标
|
||
|
||
Returns:
|
||
包含键列表和分页信息的字典
|
||
"""
|
||
try:
|
||
with httpx.Client() as client:
|
||
params = {"limit": limit}
|
||
if prefix:
|
||
params["prefix"] = prefix
|
||
if cursor:
|
||
params["cursor"] = cursor
|
||
|
||
response = client.get(
|
||
f"{self.base_url}/keys",
|
||
headers=self.headers,
|
||
params=params
|
||
)
|
||
response.raise_for_status()
|
||
return response.json()
|
||
except httpx.RequestError as e:
|
||
logger.error(f"An error occurred while listing keys from cloudflare: {e}")
|
||
raise e
|
||
except httpx.HTTPStatusError as e:
|
||
logger.error(f"HTTP error occurred while listing keys from cloudflare: {e}")
|
||
raise e
|
||
except Exception as e:
|
||
logger.error(f"An unexpected error occurred while listing keys: {e}")
|
||
raise e
|
||
|
||
def get_metadata(self, key: str):
|
||
"""
|
||
获取键的元数据
|
||
|
||
Args:
|
||
key: 键名
|
||
|
||
Returns:
|
||
键的元数据信息
|
||
"""
|
||
try:
|
||
with httpx.Client() as client:
|
||
response = client.get(
|
||
f"{self.base_url}/metadata/{key}",
|
||
headers=self.headers
|
||
)
|
||
if response.status_code == 404:
|
||
return None
|
||
response.raise_for_status()
|
||
return response.json()
|
||
except httpx.RequestError as e:
|
||
logger.error(f"An error occurred while getting metadata for key '{key}' from cloudflare: {e}")
|
||
raise e
|
||
except httpx.HTTPStatusError as e:
|
||
if e.response.status_code == 404:
|
||
return None
|
||
logger.error(f"HTTP error occurred while getting metadata for key '{key}' from cloudflare: {e}")
|
||
raise e
|
||
except Exception as e:
|
||
logger.error(f"An unexpected error occurred while getting metadata for key '{key}': {e}")
|
||
raise e
|
||
|
||
def clear_all(self, prefix: str = None):
|
||
"""
|
||
清空所有键(危险操作!)
|
||
|
||
Args:
|
||
prefix: 如果提供,只删除指定前缀的键
|
||
|
||
Returns:
|
||
操作结果
|
||
"""
|
||
try:
|
||
# 首先获取所有键
|
||
all_keys = []
|
||
cursor = None
|
||
|
||
while True:
|
||
result = self.list_keys(prefix=prefix, limit=1000, cursor=cursor)
|
||
keys_data = result.get("result", [])
|
||
|
||
if not keys_data:
|
||
break
|
||
|
||
# 提取键名
|
||
for key_info in keys_data:
|
||
if isinstance(key_info, dict):
|
||
all_keys.append(key_info.get("name"))
|
||
else:
|
||
all_keys.append(str(key_info))
|
||
|
||
# 检查是否有更多数据
|
||
result_info = result.get("result_info", {})
|
||
cursor = result_info.get("cursor")
|
||
if not cursor:
|
||
break
|
||
|
||
# 批量删除
|
||
if all_keys:
|
||
return self.removes(all_keys)
|
||
else:
|
||
return {"success": True, "message": "No keys to delete"}
|
||
|
||
except Exception as e:
|
||
logger.error(f"An error occurred while clearing keys: {e}")
|
||
raise e
|
||
|
||
|
||
kv = Kv() |