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

基于Django Channels实现跨私聊会话实时更新的技术咨询

实现私聊会话列表实时更新与置顶

核心问题分析

你当前的Consumer只让用户加入单个聊天房间的组,所以只有在当前聊天窗口的用户能收到消息。要实现其他会话的更新,每个用户必须加入一个专属自己的全局组,这样任何涉及该用户的新消息都能推送到这个组,触发他的会话列表更新。

后端代码修改(ChatConsumer)

import json
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer
from django.shortcuts import get_object_or_404
from .models import PrivateRoom, PrivateRoomChatMessage

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.user = self.scope["user"]
        self.private_chat_room_id = self.scope['url_route']['kwargs']['private_chat_room_id']
        self.room_group_name = f'private_{self.private_chat_room_id}'
        # 新增:用户专属的全局通知组
        self.user_group_name = f'user_{self.user.id}'

        if self.user.is_anonymous:
            await self.close()
        else:
            # 同时加入聊天房间组和个人全局组
            await self.channel_layer.group_add(
                self.room_group_name,
                self.channel_name
            )
            await self.channel_layer.group_add(
                self.user_group_name,
                self.channel_name
            )
            await self.accept()

    async def disconnect(self, close_code):
        # 断开时同时退出两个组
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )
        await self.channel_layer.group_discard(
            self.user_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        data = json.loads(text_data)
        message_content = data["message"].strip()

        if message_content:
            # 保存消息并获取消息字典
            message_dict = await self.save_message(self.private_chat_room_id, self.user.id, message_content)
            message_dict["is_author"] = message_dict["author_id"] == self.user.pk

            # 1. 给当前聊天房间的所有成员发消息(原逻辑)
            await self.channel_layer.group_send(
                self.room_group_name,
                {"type": "chat_message", "message_dict": message_dict}
            )

            # 2. 获取聊天对象,给对方的个人组发送会话更新通知
            other_user = await self.get_other_user_in_room(self.private_chat_room_id, self.user.id)
            if other_user:
                # 准备会话更新数据:房间ID、最新消息内容、发送者ID等
                session_update_data = {
                    "room_id": self.private_chat_room_id,
                    "last_message": message_content,
                    "sender_id": self.user.id,
                    "sender_username": self.user.username
                }
                await self.channel_layer.group_send(
                    f'user_{other_user.id}',
                    {"type": "session_update", "data": session_update_data}
                )

    async def chat_message(self, event):
        # 原逻辑:推送聊天消息到当前窗口
        message_dict = event["message_dict"]
        message_html = render_to_string("chat/message_ajax.html", {"conversation_messages": [message_dict]})
        await self.send(text_data=json.dumps({"type": "chat", "html": message_html, "message_id": message_dict["pk"]}))

    async def session_update(self, event):
        # 新增:处理会话更新,推送给用户的个人组
        await self.send(text_data=json.dumps({
            "type": "session_update",
            "data": event["data"]
        }))

    @database_sync_to_async
    def save_message(self, room_id, author_id, message):
        return PrivateRoomChatMessage.objects.create(
            content=message,
            room_id=room_id,
            author_id=author_id,
        ).to_dict()

    @database_sync_to_async
    def get_other_user_in_room(self, room_id, current_user_id):
        # 获取私聊房间里的另一个用户
        room = get_object_or_404(PrivateRoom, id=room_id)
        if room.user1.id == current_user_id:
            return room.user2
        else:
            return room.user1

前端代码修改

const chat_socket = new WebSocket(`ws://${window.location.host}/ws/chat/{{ conversation_id }}/`);
const messages = $(".messages");
const message_input = $("#message-input");
const message_form = $("#message-form");
const form_submit_btn = $("#submit-btn");

chat_socket.onmessage = (e) => {
    let data = JSON.parse(e.data);

    if(data.type === 'chat'){
        // 原逻辑:更新当前聊天窗口的消息
        message_input.val("");
        message_form.removeClass("invalid");
        form_submit_btn.removeClass("valid").attr("disabled", false);
        messages.prepend(data.html);
        $(`li#${data.message_id}`)[0].scrollIntoView();
    } else if(data.type === 'session_update'){
        // 新增:处理会话列表更新与置顶
        const sessionData = data.data;
        const sessionItemSelector = `.session-item[data-room-id="${sessionData.room_id}"]`;
        let sessionItem = $(sessionItemSelector);

        // 如果会话已存在,更新最后一条消息并置顶
        if(sessionItem.length > 0){
            // 更新最后消息内容
            sessionItem.find(".last-message").text(sessionData.last_message);
            // 更新时间(如果有时间显示的话)
            sessionItem.find(".session-time").text(new Date().toLocaleTimeString());
            // 移除后插入到列表顶部
            sessionItem.detach().prependTo(".session-list");
        } else {
            // 如果是新会话,创建新的会话项并插入顶部
            const newSessionItem = `
                <div class="session-item" data-room-id="${sessionData.room_id}">
                    <div class="session-avatar">
                        <!-- 这里可以根据sender_id加载头像 -->
                        <img src="/avatar/${sessionData.sender_id}/" alt="${sessionData.sender_username}">
                    </div>
                    <div class="session-info">
                        <div class="session-name">${sessionData.sender_username}</div>
                        <div class="last-message">${sessionData.last_message}</div>
                    </div>
                    <div class="session-time">${new Date().toLocaleTimeString()}</div>
                </div>
            `;
            $(".session-list").prepend(newSessionItem);
        }

        // 可选:给新消息的会话项添加未读标记
        $(sessionItemSelector).addClass("unread");
    }
}

关键说明

  1. 用户专属组:每个用户连接时加入user_{user_id}组,所有涉及该用户的会话更新都会推送到这个组,不管他当前在哪个聊天窗口。
  2. 双推送机制:发送消息时,同时推送到当前聊天房间组(更新聊天窗口)和对方的个人组(更新会话列表)。
  3. 前端DOM操作:根据推送的会话数据,判断是已有会话还是新会话,分别执行更新内容、置顶或创建新项的操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 20:09:26