""" SessionID池服务 (T-021) 从内部API获取Cookie列表,随机选取sessionid用于巨量云图API调用。 """ import asyncio import random import logging from typing import List, Optional import httpx from app.config import settings logger = logging.getLogger(__name__) class SessionPool: """SessionID池管理器""" def __init__(self): self._sessions: List[str] = [] self._lock = asyncio.Lock() async def refresh(self) -> bool: """ 从内部API刷新SessionID列表。 Returns: bool: 刷新是否成功 """ async with self._lock: try: headers = {} if settings.YUNTU_API_TOKEN: headers["Authorization"] = f"Bearer {settings.YUNTU_API_TOKEN}" async with httpx.AsyncClient( timeout=settings.YUNTU_API_TIMEOUT ) as client: response = await client.get( f"{settings.BRAND_API_BASE_URL}/v1/yuntu/get_cookie", params={"page": 1, "page_size": 100}, headers=headers, ) if response.status_code == 200: data = response.json() # 响应格式: {"data": [{"sessionid": "xxx", ...}, ...]} if isinstance(data, dict): cookie_list = data.get("data", []) if isinstance(cookie_list, list): self._sessions = [ item.get("sessionid") for item in cookie_list if isinstance(item, dict) and item.get("sessionid") ] logger.info( f"SessionPool refreshed: {len(self._sessions)} sessions" ) return len(self._sessions) > 0 logger.warning( f"Failed to refresh session pool: status={response.status_code}" ) return False except httpx.TimeoutException: logger.error("SessionPool refresh timeout") return False except httpx.RequestError as e: logger.error(f"SessionPool refresh request error: {e}") return False except Exception as e: logger.error(f"SessionPool refresh unexpected error: {e}") return False def get_random(self) -> Optional[str]: """ 随机获取一个SessionID。 Returns: Optional[str]: SessionID,池为空时返回None """ if not self._sessions: return None return random.choice(self._sessions) def remove(self, session_id: str) -> None: """ 从池中移除失效的SessionID。 Args: session_id: 要移除的SessionID """ try: self._sessions.remove(session_id) logger.info(f"Removed invalid session: {session_id[:8]}...") except ValueError: pass # 已经被移除 @property def size(self) -> int: """返回池中SessionID数量""" return len(self._sessions) @property def is_empty(self) -> bool: """检查池是否为空""" return len(self._sessions) == 0 # 全局单例 session_pool = SessionPool() async def get_session_with_retry(max_retries: int = 3) -> Optional[str]: """ 获取SessionID,必要时刷新池 (T-022 支持)。 Args: max_retries: 最大重试次数 Returns: Optional[str]: SessionID,获取失败返回None """ for attempt in range(max_retries): # 如果池为空,尝试刷新 if session_pool.is_empty: success = await session_pool.refresh() if not success: logger.warning(f"Session pool refresh failed, attempt {attempt + 1}") continue session_id = session_pool.get_random() if session_id: return session_id logger.error("Failed to get session after all retries") return None