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

Django Channels与信号联动问题:后台新增通知前端不更新

Django Channels实时通知推送问题排查与解决

问题背景

使用Vue从Django获取通知,需在徽章中显示并实时更新数量。当前技术栈:Django 4.2.8、Channels 4、uvicorn 0.26、websockets 12.0。通过Django的post_save信号触发consumers.py中的update_notification_count方法更新计数,浏览器端操作正常,但从Django后台新增通知时,post_save事件已触发,update_notification_count却未被调用,前端无法实时更新,仅刷新浏览器后同步数据。

相关代码配置

settings.py

# settings.py
CHANNEL_LAYERS = {
        'default': {
        'BACKEND': 'channels.layers.InMemoryChannelLayer',
        'CONFIG': {
            'capacity': 1000,
            'expiry': 60,
        },
    }
}

asgi.py

import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from notifapi.routing import websocket_urlpatterns

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproj.settings')

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": URLRouter(websocket_urlpatterns),
})

signals.py

from django.dispatch import receiver
from django.db.models.signals import post_save
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from .models import NotifyModel as Notification
from .consumers import NotificationConsumer


@receiver(post_save, sender=Notification)
def notification_created(sender, instance, created, **kwargs):
    try:
        if created:
            print(f"A new notification was created {instance.message}")
            channel_layer = get_channel_layer()
            async_to_sync(channel_layer.group_send)(
                "public_room",
                {
                    "type": "update_notification_count",
                    "message": instance.message
                }
            )
    except Exception as e:
        print(f"Error in group_send: {e}")

consumers.py

from channels.generic.websocket import AsyncWebsocketConsumer
from django.apps import apps
from django.core.serializers import serialize
from asgiref.sync import sync_to_async
import json
import logging

class NotificationConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        # Allow all connections
        await self.channel_layer.group_add("public_room", self.channel_name)
        await self.accept()
        await self.update_notification_count()

    
    async def update_notification_count(self, event=None):
        print("update_notification_count method called with event:", event)

        NotifyModel = apps.get_model('notifapi', 'NotifyModel')
        notifications = await sync_to_async(list)(NotifyModel.objects.all().values('is_read'))
        messages = await sync_to_async(list)(NotifyModel.objects.all().values('message'))
        # Get the notification count asynchronously using a custom utility method
        notification_count = len(notifications)
        
        
        #print(f"Notification count is {notification_count}")
        # Extracting is_read values from notifications
        is_read_values = [notification['is_read'] for notification in notifications]
        messages_values = [notification['message'] for notification in messages]
        #print("Am I here?")
        print(f"Messages values are: {messages_values}")
        await self.send(text_data=json.dumps({
            "type": "notification.update",
            "count": notification_count,
            "is_read_values": is_read_values,
            "messages_values": messages_values
        }))

    
    async def disconnect(self, close_code):
        print("Consumer disconnected")
        # Remove the channel from the public_room group when the WebSocket connection is closed
        await self.channel_layer.group_discard(
            "public_room",
            self.channel_name
        )


    async def receive(self, text_data):
        # Handle incoming messages (if any)
        data = json.loads(text_data)
        if data['type'] == 'update.notification.count':
            await self.update_notification_count()

Vue相关代码

App.vue

// App.vue
<template>
  <div id="app">
    <div id="wrapper">
      <NotificationBell />
    </div>
  </div>
</template>

<script>
import NotificationBell from './components/NotificationBell.vue';

export default {
  components: {
    NotificationBell,
  },
};
</script>

NotificationBell.vue

<template>
    <div class="fancy-container">
        <a href="#" class="position-relative">
            <i class="fa fa-bell _gray" style="font-size:24px"></i>
            <span class="my-text btnLnk">Visits</span>
            <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger _reduced">
                {{ notificationCount }}
            </span>
        </a>
    </div>
</template>
<script>
//import Axios from 'axios';

export default {
    data() {
        return {
            notifications: [],
            webSocket: null
        };
    },
    computed: {
        notificationCount() {
        // Calculate the notification count based on the current state of notifications
        return this.notifications.filter(notification => !notification.fields.is_read).length;
        }
    },
    mounted() {
        this.establishWebSocketConnection();
    },
    methods: {
        async establishWebSocketConnection() {
            this.webSocket = new WebSocket('ws://127.0.0.1:8001/websocket/ws/notifications/', 'echo-protocol');
            
            this.webSocket.onopen = () => {
                console.log('WebSocket connection established!');
                this.updateNotificationCount();
            };
            
            this.webSocket.onmessage = (event) => {
                console.log("Message received:", event.data);
                const message = JSON.parse(event.data);
                console.log("Received type:", message.type);  // Log the type field to identify the message type
                if(message.type === 'notification.update'){
                    this.notifications = message.is_read_values.map(is_read => ({ fields: { is_read } }));
                }else{
                    console.log("Notification count was not updated!")
                }

            };

            this.webSocket.onclose = () => {
                console.log('WebSocket connection closed.');
            // implement reconnect logic if desired
            };
        },
        updateNotificationCount() {
            console.log('updateNotificationCount called!');
            // Send a message to the WebSocket server to request updated notification count
            this.webSocket.send(JSON.stringify({
                "type": "update.notification.count"  // Define a custom type to trigger the count update
            }));

        },
    },
};
</script>

核心问题排查与解决步骤

1. 替换InMemoryChannelLayer为RedisChannelLayer

InMemoryChannelLayer是进程内通信实现,若用runserver启动Django,后台管理和Channels会运行在不同进程中,信号发送的消息无法跨进程传递到Consumer。切换到RedisChannelLayer解决跨进程通信问题:

  • 安装依赖:
    pip install channels-redis
    
  • 修改settings.py中的CHANNEL_LAYERS配置:
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": [("127.0.0.1", 6379)],
            },
        },
    }
    
  • 确保本地Redis服务处于运行状态。

2. 确认信号模块已正确注册

Django信号需要被应用加载才能生效,在notifapi/apps.py中添加信号导入:

from django.apps import AppConfig

class NotifapiConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'notifapi'

    def ready(self):
        import notifapi.signals  # 注册信号

同时在settings.py的INSTALLED_APPS中替换为:

INSTALLED_APPS = [
    # ...其他应用
    'notifapi.apps.NotifapiConfig',
]

3. 用uvicorn正确启动ASGI应用

不要用python manage.py runserver启动,改用uvicorn直接启动ASGI入口:

uvicorn myproj.asgi:application --host 127.0.0.1 --port 8001

确保所有请求(HTTP和WebSocket)都通过同一个ASGI进程处理,避免进程隔离导致的消息丢失。

4. 验证group_send的正确性

在信号中添加更多日志,确认channel_layer实例有效:

@receiver(post_save, sender=Notification)
def notification_created(sender, instance, created, **kwargs):
    try:
        if created:
            print(f"A new notification was created {instance.message}")
            channel_layer = get_channel_layer()
            print(f"Channel layer instance: {channel_layer}")  # 确认channel_layer不为None
            async_to_sync(channel_layer.group_send)(
                "public_room",
                {
                    "type": "update_notification_count",
                    "message": instance.message
                }
            )
            print("Group send executed successfully")
    except Exception as e:
        print(f"Error in group_send: {e}")

如果group_send执行无报错但Consumer没收到消息,检查Consumer是否正确加入了public_room组(代码中connect方法已添加,可保留日志确认)。

重构优化建议(可选)

  • 合并数据库查询:Consumer中两次查询NotifyModel可合并为一次,减少数据库交互:
    async def update_notification_count(self, event=None):
        NotifyModel = apps.get_model('notifapi', 'NotifyModel')
        # 一次查询获取所有需要的字段
        notifications = await sync_to_async(list)(NotifyModel.objects.all().values('is_read', 'message'))
        notification_count = len(notifications)
        is_read_values = [n['is_read'] for n in notifications]
        messages_values = [n['message'] for n in notifications]
        
        await self.send(text_data=json.dumps({
            "type": "notification.update",
            "count": notification_count,
            "is_read_values": is_read_values,
            "messages_values": messages_values
        }))
    
  • 直接传递未读计数:后端计算未读数量后传给前端,避免前端重复过滤:
    # 在Consumer中计算未读数量
    unread_count = await sync_to_async(lambda: NotifyModel.objects.filter(is_read=False).count())()
    # 发送时替换count为unread_count
    await self.send(text_data=json.dumps({
        "type": "notification.update",
        "count": unread_count,
        # ...其他字段
    }))
    
    前端直接使用message.count即可:
    computed: {
        notificationCount() {
            return this.notifications.length ? this.notifications[0].count : 0;
        }
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 23:10:54