mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 12:52:26 +00:00
refactor: 重构项目模块结构与代码细节优化
1. 调整后端模块路由与插件文件结构,迁移部分模块代码至api/v1目录 2. 优化代码注释格式与文档字符串,简化冗余代码 3. 添加监控仪表盘、工单评论等新模块功能 4. 修复前端部分组件交互逻辑与类型定义 5. 清理冗余的初始化数据与废弃配置文件 6. 新增租户注册表单字段与国际化支持 7. 优化HTTP请求拦截器与快捷键功能
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""异步事件总线 — SSE 通知推送的核心组件
|
||||
|
||||
职责:
|
||||
- 维护每个用户的 asyncio.Queue(用户退出后自动清理)
|
||||
- 提供 publish / publish_tenant / subscribe / unsubscribe 接口
|
||||
|
||||
使用方:
|
||||
- SSE 端点 → subscribe / unsubscribe
|
||||
- 各业务服务 → publish / publish_tenant
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from app.core.logger import logger
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Subscriber:
|
||||
"""订阅者信息"""
|
||||
|
||||
user_id: int
|
||||
tenant_id: int
|
||||
queue: asyncio.Queue[str] = field(default_factory=lambda: asyncio.Queue(maxsize=256))
|
||||
|
||||
|
||||
class EventBus:
|
||||
"""异步事件总线(全局单例)"""
|
||||
|
||||
_subscribers: dict[int, _Subscriber] = {}
|
||||
|
||||
@classmethod
|
||||
def subscribe(cls, user_id: int, tenant_id: int) -> asyncio.Queue[str]:
|
||||
"""为用户创建一个事件队列(已存在则返回现有队列)"""
|
||||
sub = cls._subscribers.get(user_id)
|
||||
if sub:
|
||||
return sub.queue
|
||||
sub = _Subscriber(user_id=user_id, tenant_id=tenant_id)
|
||||
cls._subscribers[user_id] = sub
|
||||
logger.debug(f"SSE 订阅: user_id={user_id} tenant_id={tenant_id}")
|
||||
return sub.queue
|
||||
|
||||
@classmethod
|
||||
def unsubscribe(cls, user_id: int) -> None:
|
||||
"""移除用户的事件队列"""
|
||||
cls._subscribers.pop(user_id, None)
|
||||
logger.debug(f"SSE 取消订阅: user_id={user_id}")
|
||||
|
||||
@classmethod
|
||||
async def publish(cls, user_id: int, event: dict[str, Any]) -> None:
|
||||
"""向指定用户推送事件(用户不在线则静默丢弃)"""
|
||||
sub = cls._subscribers.get(user_id)
|
||||
if sub is None:
|
||||
return
|
||||
payload = _build_sse_payload(event)
|
||||
try:
|
||||
await asyncio.wait_for(sub.queue.put(payload), timeout=2)
|
||||
except (TimeoutError, asyncio.QueueFull):
|
||||
logger.warning(f"SSE 推送超时或队列满: user_id={user_id}, event={event.get('type')}")
|
||||
|
||||
@classmethod
|
||||
async def publish_tenant(cls, tenant_id: int, event: dict[str, Any]) -> None:
|
||||
"""向租户下所有在线用户推送事件"""
|
||||
payload = _build_sse_payload(event)
|
||||
tasks = []
|
||||
for sub in cls._subscribers.values():
|
||||
if sub.tenant_id == tenant_id:
|
||||
tasks.append(_put(sub.queue, payload))
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
@classmethod
|
||||
async def publish_all(cls, event: dict[str, Any]) -> None:
|
||||
"""向所有在线用户广播事件"""
|
||||
payload = _build_sse_payload(event)
|
||||
tasks = [_put(sub.queue, payload) for sub in cls._subscribers.values()]
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
@classmethod
|
||||
def online_count(cls) -> int:
|
||||
"""当前在线 SSE 连接数"""
|
||||
return len(cls._subscribers)
|
||||
|
||||
|
||||
def _build_sse_payload(event: dict[str, Any]) -> str:
|
||||
"""将事件字典序列化为 SSE data 行"""
|
||||
event.setdefault("timestamp", datetime.now().isoformat())
|
||||
return json.dumps(event, ensure_ascii=False)
|
||||
|
||||
|
||||
async def _put(queue: asyncio.Queue[str], payload: str) -> None:
|
||||
try:
|
||||
await asyncio.wait_for(queue.put(payload), timeout=1)
|
||||
except (TimeoutError, asyncio.QueueFull):
|
||||
pass
|
||||
Reference in New Issue
Block a user