You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过FastAPI WebSocket实现消息已读状态查询功能?

实现FastAPI WebSocket聊天应用的消息已读/未读状态

核心思路

要实现类似WhatsApp的已读/未读状态,需要完成以下关键环节:

  • 给每条消息分配唯一标识,追踪其状态(未读/已读)
  • 客户端收到消息后主动发送已读确认
  • 服务端更新消息状态并通知原发送者
  • 前端根据状态展示对应标识

代码实现步骤

1. 定义消息状态与数据结构

首先定义消息状态枚举和消息模型,规范消息的存储和状态追踪:

from enum import Enum
import uuid
from typing import Dict, List, Optional
from fastapi import WebSocket, HTMLResponse

class MessageStatus(str, Enum):
    UNREAD = "unread"
    READ = "read"

class Message:
    def __init__(self, message_id: str, sender_id: int, receiver_id: int, content: str):
        self.message_id = message_id
        self.sender_id = sender_id
        self.receiver_id = receiver_id
        self.content = content
        self.status = MessageStatus.UNREAD

2. 重构连接管理器

修改ConnectionManager,增加客户端ID与连接的映射,以及消息存储功能:

class ConnectionManager:
    def __init__(self):
        self.active_connections: Dict[int, WebSocket] = {}  # 存储客户端ID到WebSocket的映射
        self.messages: List[Message] = []  # 内存存储消息(实际项目建议用数据库)

    async def connect(self, websocket: WebSocket, client_id: int):
        await websocket.accept()
        self.active_connections[client_id] = websocket

    def disconnect(self, client_id: int):
        if client_id in self.active_connections:
            del self.active_connections[client_id]

    async def send_personal_message(self, data: dict, client_id: int):
        websocket = self.active_connections.get(client_id)
        if websocket:
            await websocket.send_json(data)

    def add_message(self, sender_id: int, receiver_id: int, content: str) -> Message:
        # 生成唯一消息ID
        message_id = str(uuid.uuid4())
        message = Message(message_id, sender_id, receiver_id, content)
        self.messages.append(message)
        return message

    def mark_message_as_read(self, message_id: str) -> Optional[Message]:
        # 标记消息为已读
        for message in self.messages:
            if message.message_id == message_id:
                message.status = MessageStatus.READ
                return message
        return None

manager = ConnectionManager()

3. 修改WebSocket端点逻辑

更新WebSocket处理函数,支持普通消息发送和已读确认的处理:

@app.get("/")
async def get():
    return HTMLResponse(html)  # 这里的html是你的前端页面代码

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await manager.connect(websocket, client_id)
    try:
        while True:
            data = await websocket.receive_json()
            # 处理普通消息发送
            if data.get("type") == "message":
                receiver_id = data["receiver_id"]
                content = data["content"]
                # 保存消息并获取实例
                message = manager.add_message(client_id, receiver_id, content)
                # 发送消息给接收者
                await manager.send_personal_message({
                    "type": "new_message",
                    "message_id": message.message_id,
                    "sender_id": message.sender_id,
                    "content": message.content
                }, receiver_id)
                # 回复发送者消息已发出,状态为未读
                await manager.send_personal_message({
                    "type": "message_sent",
                    "message_id": message.message_id,
                    "status": message.status
                }, client_id)
            # 处理已读确认
            elif data.get("type") == "read_confirmation":
                message_id = data["message_id"]
                message = manager.mark_message_as_read(message_id)
                if message:
                    # 通知原发送者消息已读
                    await manager.send_personal_message({
                        "type": "message_read",
                        "message_id": message.message_id,
                        "status": message.status
                    }, message.sender_id)
    except WebSocketDisconnect:
        manager.disconnect(client_id)
        await manager.broadcast(f"Client #{client_id} left the chat")

4. 前端交互逻辑

前端需要处理消息接收、发送已读确认,并根据状态更新界面:

// 替换为当前用户的ID
const currentClientId = 1;
const ws = new WebSocket(`ws://localhost:8000/ws/${currentClientId}`);

// 接收服务端消息
ws.onmessage = function(event) {
    const data = JSON.parse(event.data);
    switch(data.type) {
        case "new_message":
            // 在界面上渲染新消息
            renderMessage(data);
            // 发送已读确认给服务端
            ws.send(JSON.stringify({
                type: "read_confirmation",
                message_id: data.message_id
            }));
            break;
        case "message_sent":
            // 更新发送消息的初始状态(未读)
            updateMessageStatus(data.message_id, data.status);
            break;
        case "message_read":
            // 更新发送消息的状态为已读
            updateMessageStatus(data.message_id, data.status);
            break;
    }
};

// 发送消息到指定用户
function sendMessage(receiverId, content) {
    ws.send(JSON.stringify({
        type: "message",
        receiver_id: receiverId,
        content: content
    }));
}

// 更新消息状态展示(类似WhatsApp的对勾)
function updateMessageStatus(messageId, status) {
    const messageElement = document.getElementById(`msg-${messageId}`);
    if (!messageElement) return;
    
    const statusEl = messageElement.querySelector(".msg-status");
    if (status === "read") {
        statusEl.textContent = "✓✓";
        statusEl.style.color = "#25D366"; // WhatsApp蓝绿色
    } else {
        statusEl.textContent = "✓";
        statusEl.style.color = "#999";
    }
}

// 渲染消息到界面
function renderMessage(message) {
    const chatContainer = document.getElementById("chat-container");
    const msgDiv = document.createElement("div");
    msgDiv.id = `msg-${message.message_id}`;
    msgDiv.className = message.sender_id === currentClientId ? "sent-msg" : "received-msg";
    msgDiv.innerHTML = `
        <div class="msg-content">${message.content}</div>
        <span class="msg-status">✓</span>
    `;
    chatContainer.appendChild(msgDiv);
}

实际项目优化建议

  • 持久化存储:内存存储消息重启后会丢失,建议用SQLAlchemy连接MySQL/PostgreSQL,或者MongoDB存储消息记录
  • 离线消息处理:当接收者离线时,消息标记为未读,用户上线后主动同步未读消息
  • 批量已读确认:如果一次性接收多条消息,可以批量发送已读确认,减少WebSocket通信次数
  • 状态同步:用户重新连接时,拉取自己发送的消息的最新状态

内容的提问来源于stack exchange,提问作者RunCoderRun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 01:01:10