Django Channels Consumer推送WebSocket消息前端重复显示两次问题
问题根因
消息重复显示是消息推送/渲染链路存在冗余执行导致,按出现概率从高到低排序:
- 前端同时写了两套消息渲染逻辑:调用
send()发消息时本地直接插入一条消息,后续收到服务端广播的同一条消息时又插入一次 - Consumer端重复推送:
receive方法中既单独调用self.send()给当前发送方连接回推消息,又调用group_send()向群组广播消息——发送方本身在群组内,会收到两次同源消息 - 前端未做WebSocket实例单例,组件重渲染时创建了多个WS连接,单条广播会被多连接接收,触发多次渲染
修复方案
后端Consumer逻辑修正
核心规则:所有消息统一走群组广播,不要单独给发送方回推;消息体必须携带发送方唯一标识,供前端区分消息归属。
参考实现:
import json from channels.generic.websocket import AsyncWebsocketConsumer class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): self.room_id = self.scope["url_route"]["kwargs"]["room_id"] self.room_group = f"chat_room_{self.room_id}" # 加入聊天组 await self.channel_layer.group_add( self.room_group, self.channel_name ) await self.accept() async def disconnect(self, code): # 退出聊天组 await self.channel_layer.group_discard( self.room_group, self.channel_name ) async def receive(self, text_data=None, bytes_data=None): req_data = json.loads(text_data) content = req_data.get("content", "").strip() if not content: return # 取当前登录用户名作为发送者标识,未登录场景可用self.channel_name替代 sender_name = self.scope["user"].username if not self.scope["user"].is_anonymous else self.channel_name # 禁止在此处单独调用self.send()给当前连接回消息 # 所有消息统一广播到群组,发送方作为群组成员自然能收到 await self.channel_layer.group_send( self.room_group, { "type": "push_chat_msg", "content": content, "sender": sender_name } ) async def push_chat_msg(self, event): # 组广播触发的推送方法,直接把消息发给当前连接 await self.send(text_data=json.dumps({ "content": event["content"], "sender": event["sender"] }))
前端逻辑修正
核心规则:所有消息统一在WS的onmessage回调中渲染,发送时不要做本地回显;全局缓存WS实例,避免重复创建连接。
参考实现:
// 全局缓存WS实例,防止重复创建 let chatWs = null; const chatBox = document.getElementById("chat-box"); const inputEl = document.getElementById("msg-input"); const sendBtn = document.getElementById("send-btn"); // 替换为当前登录的用户名,和后端返回的sender标识对齐 const currentUser = window.currentUsername; function initChatWs() { // 存在旧实例先销毁 if (chatWs) chatWs.close(); const protocol = window.location.protocol === "https:" ? "wss" : "ws"; chatWs = new WebSocket(`${protocol}://${window.location.host}/ws/chat/替换为你的房间ID/`); chatWs.onmessage = (event) => { const msgData = JSON.parse(event.data); // 统一在这渲染所有消息,不管是自己发的还是其他人发的 const msgNode = document.createElement("div"); msgNode.className = msgData.sender === currentUser ? "msg-item self" : "msg-item other"; msgNode.innerText = `${msgData.sender}: ${msgData.content}`; chatBox.appendChild(msgNode); chatBox.scrollTop = chatBox.scrollHeight; }; chatWs.onclose = () => { // 断线3秒后重连 setTimeout(initChatWs, 3000); }; } // 页面加载完成初始化WS window.addEventListener("DOMContentLoaded", initChatWs); // 发送消息逻辑 sendBtn.addEventListener("click", () => { const msgContent = inputEl.value.trim(); if (!msgContent || chatWs?.readyState !== WebSocket.OPEN) return; chatWs.send(JSON.stringify({ content: msgContent })); // 禁止在这里写插入消息到聊天框的逻辑,等onmessage收到广播再渲染 inputEl.value = ""; });
快速排查步骤
- 打开浏览器F12控制台,进入网络面板筛选WS连接,查看消息帧:发送单条消息后如果服务端返回2条同源消息,就是后端重复推送问题;如果只返回1条,就是前端重复渲染问题
- 如果服务端返回2条以上消息,直接在
new WebSocket初始化位置打断点,统计执行次数,大概率是重复创建连接导致
内容的提问来源于stack exchange,提问作者Ronnil Oreo
相关产品推荐
相关产品推荐

