创新实训博客记录 | 7.WebSocket 实现在线聊天与消息提醒
前几次主要完成了用户模块、问诊会话、GraphDx 接入、SSE 流式返回、医生复核任务以及检查资源调度闭环。本阶段继续向前后端实时联动方向推进,重点补齐在线聊天和消息提醒能力。
1. 本阶段开发目标
本阶段主要完成:
- 设计 WebSocket 长连接入口。
- 维护用户在线连接和断开清理逻辑。
- 支持患者端、医生端接收实时事件。
- 在医生追问、AI 回复、检查单生成、检查结果回流等节点推送消息提醒。
- 补齐消息未读数、通知列表和已读状态更新。
- 与原有 SSE 问诊流式接口进行职责拆分。
目前实时通信相关代码主要放在:
server/src/api/routes/ws.py
server/src/services/realtime_service.py
server/src/services/notification_service.py
server/src/repositories/notification_repository.py
主要接口如下:
WebSocket /api/v1/ws/notifications?session_token=<session_token>
GET /api/v1/notifications
PATCH /api/v1/notifications/{notification_id}/read
PATCH /api/v1/notifications/read-all
GET /api/v1/notifications/unread-count
2. 为什么新增 WebSocket
之前问诊消息已经使用了 SSE:
POST /api/v1/consult-sessions/{session_id}/messages/stream
Content-Type: text/event-stream
SSE 更适合“患者发出一条消息后,后端持续返回 AI 推理阶段、候选疾病、最终回复”等单向流式内容。但是项目后续接入医生复核和检查调度后,出现了更多实时通知场景:
- AI 判断需要医生复核时,医生端需要立即收到任务。
- 医生接单、追问、退回 AI、确认诊断时,患者端需要看到状态变化。
- 检查单生成后,患者端需要收到检查安排提醒。
- 检查结果提交后,医生端和患者端都需要感知状态更新。
- 管理员端需要看到设备时隙和调度队列变化。
整体设计可以理解为:
3. WebSocket 连接管理
WebSocket 连接建立时需要先完成身份校验。因为浏览器原生 WebSocket 不方便像普通 HTTP 一样直接传 X-Session-Token 请求头,所以这里采用 query 参数传递会话令牌:
ws://127.0.0.1:8000/api/v1/ws/notifications?session_token=xxx
后端处理步骤如下:
- 读取
session_token。 - 根据 session 查询当前用户。
- 校验 session 是否存在、是否过期。
- 建立连接并放入在线连接表。
- 向前端发送 connected 事件。
- 定时或被动发送 heartbeat,保持连接可观测。
- 用户断开后清理连接。
连接管理服务大致维护一个结构:
class RealtimeConnectionManager:
def __init__(self):
self.active_connections: dict[int, set[WebSocket]] = {}
async def connect(self, user_id: int, websocket: WebSocket):
await websocket.accept()
self.active_connections.setdefault(user_id, set()).add(websocket)
def disconnect(self, user_id: int, websocket: WebSocket):
connections = self.active_connections.get(user_id)
if not connections:
return
connections.discard(websocket)
if not connections:
self.active_connections.pop(user_id, None)
async def send_to_user(self, user_id: int, payload: dict):
connections = self.active_connections.get(user_id, set())
closed = []
for websocket in connections:
try:
await websocket.send_json(payload)
except Exception:
closed.append(websocket)
for websocket in closed:
self.disconnect(user_id, websocket)
这样做的核心目的是让业务层不直接操作 WebSocket,只需要调用统一的 send_to_user 或 broadcast_to_role。
4. 在线聊天消息流转
在线聊天不是单独脱离问诊流程存在的,而是和原有 consult session、doctor review task 结合在一起。
目前消息主要分为几类:
PATIENT_MESSAGE 患者消息
AI_MESSAGE AI 回复
DOCTOR_MESSAGE 医生追问/说明
SYSTEM_NOTICE 系统状态提醒
EXAM_RESULT 检查结果消息
患者发送消息时,仍然走原来的流式问诊接口:
POST /api/v1/consult-sessions/{session_id}/messages/stream
医生在复核任务中追问患者时,走医生侧消息接口:
POST /api/v1/doctor/review-tasks/{task_id}/messages
Content-Type: application/json
{
"message_text": "请补充一下头晕发作时是否伴随恶心、呕吐或视物旋转。"
}
后端处理医生消息时会做几件事:
- 校验医生是否已经接单。
- 校验任务是否属于当前医生。
- 写入
consult_messages。 - 将会话状态更新为等待患者补充。
- 创建一条患者侧通知。
- 如果患者在线,通过 WebSocket 立即推送。
整体流程如下:
这样患者即使没有主动刷新页面,也可以直接看到医生新消息和角标变化。
5. 消息提醒与未读数
消息提醒没有只依赖前端临时状态,而是做了持久化。原因是用户可能不在线,也可能关闭页面。如果通知只存在 WebSocket 内存中,离线用户就会丢失消息。
通知表主要字段包括:
id
user_id
notification_type
title
content
biz_type
biz_id
is_read
created_at
read_at
其中:
notification_type表示通知类型,比如 chat_message、review_task、exam_order。biz_type表示关联业务对象,比如 consult_session、doctor_review_task、exam_order。biz_id用于前端点击通知后跳转到对应页面。is_read用于统计未读数量。
目前主要通知类型包括:
chat_message
review_task_created
review_task_updated
exam_order_created
exam_result_received
schedule_plan_created
candidate_confirmed
system_notice
通知创建后,会同时执行两条路径:
前端进入页面后,可以调用:
GET /api/v1/notifications
GET /api/v1/notifications/unread-count
用户查看通知后调用:
PATCH /api/v1/notifications/{notification_id}/read
或者一次性清空:
PATCH /api/v1/notifications/read-all
这样可以保证“在线实时推送”和“离线消息补偿”同时存在。
6. 总结
本阶段完成后,系统从“请求响应式交互”进一步扩展到了“实时在线交互”:
- 患者端可以实时收到医生追问、检查安排和 AI 处理结果提醒。
- 医生端可以实时收到新的复核任务和患者补充消息。
- 管理员端可以在后续继续接入设备调度和维护预警事件。
- 通知持久化后,离线用户也不会丢失关键消息。
- SSE 和 WebSocket 形成了明确分工:SSE 负责单轮 AI 推理流,WebSocket 负责全局实时通知。
相比前几次主要关注后端业务链路,本阶段更偏向前后端联动体验。消息提醒补齐之后,整个问诊、医生复核、检查调度和结果回流流程不再依赖手动刷新,系统也更接近一个可以完整演示的实时辅助诊疗平台。
更多推荐



所有评论(0)