基于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"); } }
关键说明
- 用户专属组:每个用户连接时加入
user_{user_id}组,所有涉及该用户的会话更新都会推送到这个组,不管他当前在哪个聊天窗口。 - 双推送机制:发送消息时,同时推送到当前聊天房间组(更新聊天窗口)和对方的个人组(更新会话列表)。
- 前端DOM操作:根据推送的会话数据,判断是已有会话还是新会话,分别执行更新内容、置顶或创建新项的操作。
内容的提问来源于stack exchange,提问作者rediz
相关产品推荐
相关产品推荐

