feat: KKS P240/P241 蓝牙工牌管理系统初始提交
FastAPI + SQLAlchemy + asyncio TCP 服务器,支持设备管理、实时定位、 告警、考勤打卡、蓝牙记录、指令下发、TTS语音播报等功能。
This commit is contained in:
81
app/routers/ws.py
Normal file
81
app/routers/ws.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user