init
This commit is contained in:
105
utils/service_base.py
Normal file
105
utils/service_base.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
服务基类 - 供 VectorizerService 等异步服务使用
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, TypeVar, Optional
|
||||
import aiohttp
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass
|
||||
class RetryConfig:
|
||||
max_retries: int = 3
|
||||
base_delay: float = 2.0
|
||||
max_delay: float = 30.0
|
||||
|
||||
|
||||
@dataclass
|
||||
class TimeoutConfig:
|
||||
connection_timeout: float = 60.0
|
||||
read_timeout: float = 240.0
|
||||
total_timeout: float = 1200.0
|
||||
|
||||
|
||||
class ServiceError(Exception):
|
||||
"""服务异常基类"""
|
||||
pass
|
||||
|
||||
|
||||
class ServiceTimeoutError(ServiceError):
|
||||
"""超时异常"""
|
||||
pass
|
||||
|
||||
|
||||
class ServiceNetworkError(ServiceError):
|
||||
"""网络异常"""
|
||||
pass
|
||||
|
||||
|
||||
class PollingMixin:
|
||||
"""轮询混入 - 子类需实现 _is_task_complete, _is_task_failed, _get_error_message"""
|
||||
def _is_task_complete(self, result: Any) -> bool:
|
||||
raise NotImplementedError
|
||||
def _is_task_failed(self, result: Any) -> bool:
|
||||
raise NotImplementedError
|
||||
def _get_error_message(self, result: Any) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class BaseService:
|
||||
"""异步服务基类"""
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "BaseService",
|
||||
base_url: str = "",
|
||||
retry_config: Optional[RetryConfig] = None,
|
||||
timeout_config: Optional[TimeoutConfig] = None,
|
||||
):
|
||||
self.name = name
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.retry_config = retry_config or RetryConfig()
|
||||
self.timeout_config = timeout_config or TimeoutConfig()
|
||||
self.logger = logging.getLogger(name)
|
||||
self._session: Optional[aiohttp.ClientSession] = None
|
||||
|
||||
async def create_http_session(self) -> aiohttp.ClientSession:
|
||||
"""创建 aiohttp 会话(不验证 SSL)"""
|
||||
connector = aiohttp.TCPConnector(ssl=False)
|
||||
timeout = aiohttp.ClientTimeout(
|
||||
connect=self.timeout_config.connection_timeout,
|
||||
total=self.timeout_config.total_timeout,
|
||||
)
|
||||
return aiohttp.ClientSession(connector=connector, timeout=timeout)
|
||||
|
||||
async def execute_with_retry(
|
||||
self,
|
||||
func: Callable[..., Any],
|
||||
*args,
|
||||
error_context: str = "",
|
||||
**kwargs,
|
||||
) -> Any:
|
||||
"""带重试的执行"""
|
||||
last_error = None
|
||||
for attempt in range(self.retry_config.max_retries + 1):
|
||||
try:
|
||||
return await func(*args, **kwargs)
|
||||
except (ServiceTimeoutError, ServiceNetworkError):
|
||||
raise
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
self.logger.warning(f"第{attempt + 1}次尝试失败{error_context}: {e}")
|
||||
if attempt < self.retry_config.max_retries:
|
||||
delay = min(
|
||||
self.retry_config.base_delay * (2 ** attempt),
|
||||
self.retry_config.max_delay,
|
||||
)
|
||||
await asyncio.sleep(delay)
|
||||
raise last_error or ServiceError("未知错误")
|
||||
|
||||
async def cleanup_failed_task(self, task_id: str) -> None:
|
||||
"""清理失败任务(子类可覆盖)"""
|
||||
self.logger.debug(f"清理任务: {task_id}")
|
||||
Reference in New Issue
Block a user