feat: 新增告警、成本、配额、微信模块及缓存服务
All checks were successful
continuous-integration/drone/push Build is passing

- 新增告警模块 (alerts): 告警规则配置与触发
- 新增成本管理模块 (cost): 成本统计与分析
- 新增配额模块 (quota): 配额管理与限制
- 新增微信模块 (wechat): 微信相关功能接口
- 新增缓存服务 (cache): Redis 缓存封装
- 新增请求日志中间件 (request_logger)
- 新增异常处理和链路追踪中间件
- 更新 dashboard 前端展示
- 更新 SDK stats_client 功能
This commit is contained in:
111
2026-01-24 16:53:47 +08:00
parent eab2533c36
commit 6c6c48cf71
29 changed files with 4607 additions and 41 deletions

View File

@@ -0,0 +1,309 @@
"""Redis缓存服务"""
import json
import logging
from typing import Optional, Any, Union
from functools import lru_cache
try:
import redis
from redis import Redis
REDIS_AVAILABLE = True
except ImportError:
REDIS_AVAILABLE = False
Redis = None
from ..config import get_settings
logger = logging.getLogger(__name__)
# 全局Redis连接池
_redis_pool: Optional[Any] = None
_redis_client: Optional[Any] = None
def get_redis_client() -> Optional[Any]:
"""获取Redis客户端单例"""
global _redis_pool, _redis_client
if not REDIS_AVAILABLE:
logger.warning("Redis module not installed, cache disabled")
return None
if _redis_client is not None:
return _redis_client
settings = get_settings()
try:
_redis_pool = redis.ConnectionPool.from_url(
settings.REDIS_URL,
max_connections=20,
decode_responses=True
)
_redis_client = Redis(connection_pool=_redis_pool)
# 测试连接
_redis_client.ping()
logger.info(f"Redis connected: {settings.REDIS_URL}")
return _redis_client
except Exception as e:
logger.warning(f"Redis connection failed: {e}, cache disabled")
_redis_client = None
return None
class CacheService:
"""缓存服务
提供统一的缓存接口支持Redis和内存回退
使用示例:
cache = CacheService()
# 设置缓存
cache.set("user:123", {"name": "test"}, ttl=3600)
# 获取缓存
user = cache.get("user:123")
# 删除缓存
cache.delete("user:123")
"""
def __init__(self, prefix: Optional[str] = None):
"""初始化缓存服务
Args:
prefix: 键前缀默认使用配置中的REDIS_PREFIX
"""
settings = get_settings()
self.prefix = prefix or settings.REDIS_PREFIX
self._client = get_redis_client()
# 内存回退缓存当Redis不可用时使用
self._memory_cache: dict = {}
@property
def is_available(self) -> bool:
"""Redis是否可用"""
return self._client is not None
def _make_key(self, key: str) -> str:
"""生成完整的缓存键"""
return f"{self.prefix}{key}"
def get(self, key: str, default: Any = None) -> Any:
"""获取缓存值
Args:
key: 缓存键
default: 默认值
Returns:
缓存值或默认值
"""
full_key = self._make_key(key)
if self._client:
try:
value = self._client.get(full_key)
if value is None:
return default
# 尝试解析JSON
try:
return json.loads(value)
except (json.JSONDecodeError, TypeError):
return value
except Exception as e:
logger.error(f"Cache get error: {e}")
return default
else:
# 内存回退
return self._memory_cache.get(full_key, default)
def set(
self,
key: str,
value: Any,
ttl: Optional[int] = None,
nx: bool = False
) -> bool:
"""设置缓存值
Args:
key: 缓存键
value: 缓存值
ttl: 过期时间(秒)
nx: 只在键不存在时设置
Returns:
是否设置成功
"""
full_key = self._make_key(key)
# 序列化值
if isinstance(value, (dict, list)):
serialized = json.dumps(value, ensure_ascii=False)
else:
serialized = str(value) if value is not None else ""
if self._client:
try:
if nx:
result = self._client.set(full_key, serialized, ex=ttl, nx=True)
else:
result = self._client.set(full_key, serialized, ex=ttl)
return bool(result)
except Exception as e:
logger.error(f"Cache set error: {e}")
return False
else:
# 内存回退不支持TTL和NX
if nx and full_key in self._memory_cache:
return False
self._memory_cache[full_key] = value
return True
def delete(self, key: str) -> bool:
"""删除缓存
Args:
key: 缓存键
Returns:
是否删除成功
"""
full_key = self._make_key(key)
if self._client:
try:
return bool(self._client.delete(full_key))
except Exception as e:
logger.error(f"Cache delete error: {e}")
return False
else:
return self._memory_cache.pop(full_key, None) is not None
def exists(self, key: str) -> bool:
"""检查键是否存在
Args:
key: 缓存键
Returns:
是否存在
"""
full_key = self._make_key(key)
if self._client:
try:
return bool(self._client.exists(full_key))
except Exception as e:
logger.error(f"Cache exists error: {e}")
return False
else:
return full_key in self._memory_cache
def ttl(self, key: str) -> int:
"""获取键的剩余过期时间
Args:
key: 缓存键
Returns:
剩余秒数,-1表示永不过期-2表示键不存在
"""
full_key = self._make_key(key)
if self._client:
try:
return self._client.ttl(full_key)
except Exception as e:
logger.error(f"Cache ttl error: {e}")
return -2
else:
return -1 if full_key in self._memory_cache else -2
def incr(self, key: str, amount: int = 1) -> int:
"""递增计数器
Args:
key: 缓存键
amount: 递增量
Returns:
递增后的值
"""
full_key = self._make_key(key)
if self._client:
try:
return self._client.incrby(full_key, amount)
except Exception as e:
logger.error(f"Cache incr error: {e}")
return 0
else:
current = self._memory_cache.get(full_key, 0)
new_value = int(current) + amount
self._memory_cache[full_key] = new_value
return new_value
def expire(self, key: str, ttl: int) -> bool:
"""设置键的过期时间
Args:
key: 缓存键
ttl: 过期时间(秒)
Returns:
是否设置成功
"""
full_key = self._make_key(key)
if self._client:
try:
return bool(self._client.expire(full_key, ttl))
except Exception as e:
logger.error(f"Cache expire error: {e}")
return False
else:
return full_key in self._memory_cache
def clear_prefix(self, prefix: str) -> int:
"""删除指定前缀的所有键
Args:
prefix: 键前缀
Returns:
删除的键数量
"""
full_prefix = self._make_key(prefix)
if self._client:
try:
keys = self._client.keys(f"{full_prefix}*")
if keys:
return self._client.delete(*keys)
return 0
except Exception as e:
logger.error(f"Cache clear_prefix error: {e}")
return 0
else:
count = 0
keys_to_delete = [k for k in self._memory_cache if k.startswith(full_prefix)]
for k in keys_to_delete:
del self._memory_cache[k]
count += 1
return count
# 全局缓存实例
_cache_instance: Optional[CacheService] = None
def get_cache() -> CacheService:
"""获取全局缓存实例"""
global _cache_instance
if _cache_instance is None:
_cache_instance = CacheService()
return _cache_instance