refactor: 重构项目模块结构与代码细节优化

1.  调整后端模块路由与插件文件结构,迁移部分模块代码至api/v1目录
2.  优化代码注释格式与文档字符串,简化冗余代码
3.  添加监控仪表盘、工单评论等新模块功能
4.  修复前端部分组件交互逻辑与类型定义
5.  清理冗余的初始化数据与废弃配置文件
6.  新增租户注册表单字段与国际化支持
7.  优化HTTP请求拦截器与快捷键功能
This commit is contained in:
zhangtao
2026-07-10 00:18:11 +08:00
parent 349ab7b525
commit 5ff72b086f
323 changed files with 7633 additions and 15306 deletions
+101
View File
@@ -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