# -*- 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}")