refactor(channel shared): 新增多账号基础抽象模块
创建channel_shared下的多账号基础组件,包括账号配置基类、多账号注册中心抽象、账号生命周期混入类以及连接池混入类,统一多账号相关的基础能力实现
This commit is contained in:
parent
655eab7230
commit
b2ea3426fd
17
backend/package/yuxi/channels/channel_shared/__init__.py
Normal file
17
backend/package/yuxi/channels/channel_shared/__init__.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .multi_account_base import (
|
||||||
|
AccountConfigBase,
|
||||||
|
AccountLifecycleMixin,
|
||||||
|
ConnectionPoolMixin,
|
||||||
|
MultiAccountBase,
|
||||||
|
MultiAccountRegistryBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"AccountConfigBase",
|
||||||
|
"AccountLifecycleMixin",
|
||||||
|
"ConnectionPoolMixin",
|
||||||
|
"MultiAccountBase",
|
||||||
|
"MultiAccountRegistryBase",
|
||||||
|
]
|
||||||
@ -0,0 +1,146 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AccountConfigBase:
|
||||||
|
account_id: str
|
||||||
|
label: str = ""
|
||||||
|
enabled: bool = True
|
||||||
|
metadata: dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def configured(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class MultiAccountRegistryBase[T](ABC):
|
||||||
|
accounts: dict[str, T]
|
||||||
|
default_account_id: str
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def resolve_account(self, account_id: str | None = None) -> T | None: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def list_account_ids(self) -> list[str]: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def list_enabled(self) -> list[T]: ...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_multi(self) -> bool:
|
||||||
|
return len(self.list_enabled()) > 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def account_count(self) -> int:
|
||||||
|
return len(self.accounts)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountLifecycleMixin:
|
||||||
|
def __init_subclass__(cls, **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
if not hasattr(cls, "_lifecycle_hooks"):
|
||||||
|
cls._lifecycle_hooks: dict[str, list] = {
|
||||||
|
"on_account_added": [],
|
||||||
|
"on_account_removed": [],
|
||||||
|
"on_account_enabled": [],
|
||||||
|
"on_account_disabled": [],
|
||||||
|
"on_account_config_changed": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
def register_hook(self, event: str, handler) -> None:
|
||||||
|
hooks = getattr(self, "_lifecycle_hooks", {})
|
||||||
|
if event in hooks:
|
||||||
|
hooks[event].append(handler)
|
||||||
|
|
||||||
|
async def _fire_hook(self, event: str, account_id: str, data: dict[str, Any]) -> None:
|
||||||
|
hooks = getattr(self, "_lifecycle_hooks", {})
|
||||||
|
for handler in hooks.get(event, []):
|
||||||
|
try:
|
||||||
|
result = handler(account_id, data)
|
||||||
|
if asyncio.iscoroutine(result):
|
||||||
|
await result
|
||||||
|
except Exception:
|
||||||
|
logger.debug("Lifecycle hook error for event '%s'", event, exc_info=True)
|
||||||
|
|
||||||
|
async def add_account(self, account_id: str, config: dict[str, Any]) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def remove_account(self, account_id: str) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def delete_account(self, account_id: str) -> bool:
|
||||||
|
return await self.remove_account(account_id)
|
||||||
|
|
||||||
|
async def update_account(self, account_id: str, config: dict[str, Any]) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionPoolMixin:
|
||||||
|
def __init_subclass__(cls, **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
if not hasattr(cls, "_connections"):
|
||||||
|
cls._connections: dict[str, Any] = {}
|
||||||
|
if not hasattr(cls, "_connect_tasks"):
|
||||||
|
cls._connect_tasks: dict[str, asyncio.Task] = {}
|
||||||
|
if not hasattr(cls, "_ready_events"):
|
||||||
|
cls._ready_events: dict[str, asyncio.Event] = {}
|
||||||
|
|
||||||
|
def get_connection(self, account_id: str) -> Any | None:
|
||||||
|
return self._connections.get(account_id)
|
||||||
|
|
||||||
|
def is_connected(self, account_id: str) -> bool:
|
||||||
|
ready_evt = self._ready_events.get(account_id)
|
||||||
|
return ready_evt.is_set() if ready_evt else False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def active_ids(self) -> list[str]:
|
||||||
|
return [aid for aid, evt in self._ready_events.items() if evt.is_set()]
|
||||||
|
|
||||||
|
async def disconnect_account(self, account_id: str) -> bool:
|
||||||
|
conn = self._connections.pop(account_id, None)
|
||||||
|
task = self._connect_tasks.pop(account_id, None)
|
||||||
|
ready_evt = self._ready_events.pop(account_id, None)
|
||||||
|
disconnected = False
|
||||||
|
|
||||||
|
if task and not task.done():
|
||||||
|
task.cancel()
|
||||||
|
try:
|
||||||
|
await task
|
||||||
|
except (asyncio.CancelledError, Exception):
|
||||||
|
pass
|
||||||
|
disconnected = True
|
||||||
|
|
||||||
|
if conn is not None:
|
||||||
|
try:
|
||||||
|
close_fn = getattr(conn, "close", None) or getattr(conn, "disconnect", None)
|
||||||
|
if close_fn:
|
||||||
|
result = close_fn()
|
||||||
|
if asyncio.iscoroutine(result):
|
||||||
|
await result
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
disconnected = True
|
||||||
|
|
||||||
|
if ready_evt:
|
||||||
|
ready_evt.clear()
|
||||||
|
|
||||||
|
return disconnected
|
||||||
|
|
||||||
|
async def disconnect_all(self) -> None:
|
||||||
|
for account_id in list(self._connections.keys()):
|
||||||
|
await self.disconnect_account(account_id)
|
||||||
|
self._connections.clear()
|
||||||
|
self._connect_tasks.clear()
|
||||||
|
self._ready_events.clear()
|
||||||
|
|
||||||
|
|
||||||
|
class MultiAccountBase(AccountLifecycleMixin, ConnectionPoolMixin):
|
||||||
|
pass
|
||||||
Loading…
Reference in New Issue
Block a user