前几次主要完成了用户模块、问诊会话、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、确认诊断时,患者端需要看到状态变化。
  • 检查单生成后,患者端需要收到检查安排提醒。
  • 检查结果提交后,医生端和患者端都需要感知状态更新。
  • 管理员端需要看到设备时隙和调度队列变化。

整体设计可以理解为:

用户登录

前端保存 session_token

建立 WebSocket 长连接

RealtimeService 注册连接

业务服务产生事件

NotificationService 保存通知

RealtimeService 推送在线用户

前端更新消息角标/任务列表

3. WebSocket 连接管理

WebSocket 连接建立时需要先完成身份校验。因为浏览器原生 WebSocket 不方便像普通 HTTP 一样直接传 X-Session-Token 请求头,所以这里采用 query 参数传递会话令牌:

ws://127.0.0.1:8000/api/v1/ws/notifications?session_token=xxx

后端处理步骤如下:

  1. 读取 session_token
  2. 根据 session 查询当前用户。
  3. 校验 session 是否存在、是否过期。
  4. 建立连接并放入在线连接表。
  5. 向前端发送 connected 事件。
  6. 定时或被动发送 heartbeat,保持连接可观测。
  7. 用户断开后清理连接。

连接管理服务大致维护一个结构:

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_userbroadcast_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": "请补充一下头晕发作时是否伴随恶心、呕吐或视物旋转。"
}

后端处理医生消息时会做几件事:

  1. 校验医生是否已经接单。
  2. 校验任务是否属于当前医生。
  3. 写入 consult_messages
  4. 将会话状态更新为等待患者补充。
  5. 创建一条患者侧通知。
  6. 如果患者在线,通过 WebSocket 立即推送。

整体流程如下:

患者端 WebSocket NotificationService SQLite FastAPI 医生端 患者端 WebSocket NotificationService SQLite FastAPI 医生端 POST /review-tasks/{id}/messages 校验任务和医生身份 保存 DOCTOR_MESSAGE 更新会话状态 创建患者通知 写入 notifications 推送 chat_message 实时展示医生追问

这样患者即使没有主动刷新页面,也可以直接看到医生新消息和角标变化。

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

通知创建后,会同时执行两条路径:

在线

不在线

业务事件产生

创建 notification

保存到数据库

用户是否在线

WebSocket 实时推送

仅保留未读通知

前端更新角标

下次进入页面后拉取

前端进入页面后,可以调用:

GET /api/v1/notifications
GET /api/v1/notifications/unread-count

用户查看通知后调用:

PATCH /api/v1/notifications/{notification_id}/read

或者一次性清空:

PATCH /api/v1/notifications/read-all

这样可以保证“在线实时推送”和“离线消息补偿”同时存在。

6. 总结

本阶段完成后,系统从“请求响应式交互”进一步扩展到了“实时在线交互”:

  1. 患者端可以实时收到医生追问、检查安排和 AI 处理结果提醒。
  2. 医生端可以实时收到新的复核任务和患者补充消息。
  3. 管理员端可以在后续继续接入设备调度和维护预警事件。
  4. 通知持久化后,离线用户也不会丢失关键消息。
  5. SSE 和 WebSocket 形成了明确分工:SSE 负责单轮 AI 推理流,WebSocket 负责全局实时通知。

相比前几次主要关注后端业务链路,本阶段更偏向前后端联动体验。消息提醒补齐之后,整个问诊、医生复核、检查调度和结果回流流程不再依赖手动刷新,系统也更接近一个可以完整演示的实时辅助诊疗平台。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐