Files
desungongpai/app/routers/ws.py

82 lines
2.8 KiB
Python
Raw Normal View History

"""
WebSocket Router - WebSocket 实时推送接口
Real-time data push via WebSocket with topic subscriptions.
"""
import logging
import secrets
from fastapi import APIRouter, Query, WebSocket, WebSocketDisconnect
from app.config import settings
from app.websocket_manager import ws_manager, VALID_TOPICS
logger = logging.getLogger(__name__)
router = APIRouter(tags=["WebSocket / 实时推送"])
@router.websocket("/ws")
async def websocket_endpoint(
websocket: WebSocket,
api_key: str | None = Query(default=None, alias="api_key"),
topics: str | None = Query(default=None, description="Comma-separated topics"),
):
"""
WebSocket endpoint for real-time data push.
Connect: ws://host/ws?api_key=xxx&topics=location,alarm
Topics: location, alarm, device_status, attendance, bluetooth
If no topics specified, subscribes to all.
"""
# Authenticate
if settings.API_KEY is not None:
if api_key is None or not secrets.compare_digest(api_key, settings.API_KEY):
# For DB keys, do a simple hash check
if api_key is not None:
from app.dependencies import _hash_key
from app.database import async_session
from sqlalchemy import select
from app.models import ApiKey
try:
async with async_session() as session:
key_hash = _hash_key(api_key)
result = await session.execute(
select(ApiKey.id).where(
ApiKey.key_hash == key_hash,
ApiKey.is_active == True, # noqa: E712
)
)
if result.scalar_one_or_none() is None:
await websocket.close(code=4001, reason="Invalid API key")
return
except Exception:
await websocket.close(code=4001, reason="Auth error")
return
else:
await websocket.close(code=4001, reason="Missing API key")
return
# Parse topics
requested_topics = set()
if topics:
requested_topics = {t.strip() for t in topics.split(",") if t.strip() in VALID_TOPICS}
if not await ws_manager.connect(websocket, requested_topics):
return
try:
# Keep connection alive, handle pings
while True:
data = await websocket.receive_text()
# Client can send "ping" to keep alive
if data.strip().lower() == "ping":
await websocket.send_text("pong")
except WebSocketDisconnect:
pass
except Exception:
logger.debug("WebSocket connection error", exc_info=True)
finally:
ws_manager.disconnect(websocket)