64 lines
1.9 KiB
Python
64 lines
1.9 KiB
Python
"""全局一炁事件 SSE 端点(非 run-scoped)。
|
|
|
|
供前端 useGlobalEventStream hook 订阅全局广播事件:
|
|
- ``proactive.session`` —— 叩心枢新建的主动会话
|
|
- ``proactive.message`` —— 叩心枢追加到已有会话的消息
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from collections.abc import AsyncGenerator
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Request
|
|
from sse_starlette.sse import EventSourceResponse
|
|
|
|
from app.core.config import settings
|
|
from app.services.event_store import DbEventStore
|
|
|
|
router = APIRouter(prefix="/events", tags=["events"])
|
|
|
|
|
|
def _get_event_store(request: Request) -> DbEventStore:
|
|
return request.app.state.event_store
|
|
|
|
|
|
@router.get("")
|
|
async def global_event_stream(
|
|
request: Request,
|
|
) -> EventSourceResponse:
|
|
"""全局事件 SSE 流:通过 EventStore.subscribe_by_pattern 监听所有
|
|
proactive.* 事件,推送给前端。"""
|
|
if not settings.enable_sse:
|
|
from app.core.errors import AppError
|
|
|
|
raise AppError("invalid_request", "SSE 当前未启用。")
|
|
|
|
event_store = _get_event_store(request)
|
|
return EventSourceResponse(_event_generator(event_store))
|
|
|
|
|
|
async def _event_generator(
|
|
event_store: DbEventStore,
|
|
) -> AsyncGenerator[dict[str, Any], None]:
|
|
queue = event_store.subscribe_by_pattern("proactive.*")
|
|
try:
|
|
while True:
|
|
try:
|
|
event = await asyncio.wait_for(queue.get(), timeout=settings.sse_heartbeat_seconds)
|
|
yield {
|
|
"id": event.id,
|
|
"event": event.type,
|
|
"data": event.model_dump_json(),
|
|
}
|
|
except TimeoutError:
|
|
yield {
|
|
"event": "heartbeat",
|
|
"data": '{"type":"heartbeat"}',
|
|
}
|
|
except asyncio.CancelledError:
|
|
pass
|
|
finally:
|
|
event_store.unsubscribe_by_pattern("proactive.*", queue)
|