Files
FastapiAdmin/backend/app/api/v1/module_common/sse/controller.py
T
zhangtao 5ff72b086f refactor: 重构项目模块结构与代码细节优化
1.  调整后端模块路由与插件文件结构,迁移部分模块代码至api/v1目录
2.  优化代码注释格式与文档字符串,简化冗余代码
3.  添加监控仪表盘、工单评论等新模块功能
4.  修复前端部分组件交互逻辑与类型定义
5.  清理冗余的初始化数据与废弃配置文件
6.  新增租户注册表单字段与国际化支持
7.  优化HTTP请求拦截器与快捷键功能
2026-07-10 00:18:11 +08:00

68 lines
2.3 KiB
Python

"""SSE 事件推送端点"""
from __future__ import annotations
import asyncio
import json
from collections.abc import AsyncIterable
from typing import Annotated
from fastapi import APIRouter, Depends, Header, Query
from fastapi.sse import EventSourceResponse, ServerSentEvent
from app.core.base_schema import AuthSchema
from app.core.dependencies import get_current_user_ws
from app.core.event_bus import EventBus
from app.core.logger import logger
SSERouter = APIRouter(prefix="/events", tags=["SSE 事件推送"])
@SSERouter.get("", response_class=EventSourceResponse)
async def sse_event_stream(
token: Annotated[str, Query(..., description="认证 token")],
auth: Annotated[AuthSchema, Depends(get_current_user_ws)],
last_event_id: Annotated[str | None, Header()] = None,
) -> AsyncIterable[ServerSentEvent]:
"""SSE 事件流端点
客户端通过 EventSource 连接后可持续接收服务端推送的事件。
连接断开后浏览器自动重连,并通过 Last-Event-ID 恢复断连期间的事件。
## 事件类型
- `notification` — 系统通知
- `payment_success` — 支付成功
- `ticket_reply` — 工单回复
连接保活由 FastAPI 自动处理(每 15 秒发送 ping 注释)。
"""
user_id = auth.user.id
tenant_id = auth.user.tenant_id
queue = EventBus.subscribe(user_id, tenant_id)
logger.info(f"SSE 连接建立: user_id={user_id} tenant_id={tenant_id}")
# 解析断连前最后收到的事件 ID,用于恢复
event_id = int(last_event_id) if last_event_id and last_event_id.isdigit() else 0
try:
while True:
payload = await queue.get()
event_id += 1
yield _to_sse_event(payload, str(event_id))
except asyncio.CancelledError:
pass
finally:
EventBus.unsubscribe(user_id)
logger.info(f"SSE 连接断开: user_id={user_id}")
def _to_sse_event(payload: str, event_id: str) -> ServerSentEvent:
"""将 EventBus JSON 消息转换为 ServerSentEvent"""
try:
data = json.loads(payload)
event_type = data.pop("type", "message")
return ServerSentEvent(data=data, event=event_type, id=event_id)
except json.JSONDecodeError:
return ServerSentEvent(raw_data=payload, id=event_id, event="raw")