django使用channels实现websocket消息通知

django使用channels实现websocket消息通知

需求:一个交易网站,当买家创建新订单的时候,使用websocket通知卖家。

前端效果

这个教程是我实现上面需求的填坑过程,希望帮助大家解决使用channels的部分问题,分享实现这个需求的思路。教程只列出后端代码,并省略所有和websocket无关的业务代码,方便大家高效阅读。

实现思路

每个用户访问网站时打开一个websocket连接,新订单创建时,找到卖家的连接发送字符串‘new’,前端收到后再做相应展示。

代码实现

本教程省略routing.py及channels的配置,不懂的可以先看Django Channels官方教程,实现一遍入门例子。

首先看创建订单的代码

#../proj/app/views.py

 #创建订单
def create_order(request): 
    ...
    #使用websocket通知卖家,seller_id 为卖家的User.id
    new_order_notify( seller_id )
    ...

视图层就已经写好了,接下来完成消费者consumer.py。先看一下官网的例子

class ChatConsumer(WebsocketConsumer):

    def connect(self):
        # Make a database row with our channel name
        Clients.objects.create(channel_name=self.channel_name)

    def disconnect(self, close_code):
        # Note that in some rare cases (power loss, etc) disconnect may fail
        # to run; this naive example would leave zombie channel names around.
        Clients.objects.filter(channel_name=self.channel_name).delete()

    def chat_message(self, event):
        # Handles the "chat.message" event when it's sent to us.
        self.send(text_data=event["text"])

To send to a single channel, just find its channel name (for the example above, we could crawl the database), and usechannel_layer.send:

from channels.layers import get_channel_layer

channel_layer = get_channel_layer()
await channel_layer.send("channel_name", {
    "type": "chat.message",
    "text": "Hello there!",
})

这个例子告诉我们,要发送到单个通道,只需找到其通道名称。所以我们要在websocket创建时将它的通道名self.channel_name保存,发送时调用channel_layer.send( 通道名,… )就可以了。所以我们先创建一个模型将通道名与用户id对应起来。

#../proj/app/models.py
class Clients(models.Model):
    user = models.OneToOneField(User, related_name='client',on_delete=models.CASCADE)
    channel_name = models.CharField(max_length=60,default="")

别忘记创建表哦

python3 manage.py makemigrations
python3 manage.py migrate

OK,现在我们可以改写官网例子实现我们自己的consumer.py,将通道名和用户id建立关系。

#../proj/app/consumer.py
from channels.generic.websocket import WebsocketConsumer
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from .models import Clients

class OrderConsumer(WebsocketConsumer):
    def connect(self):
        user_id = self.scope['user'].id
        client = Clients.objects.get_or_create(user_id=user_id)[0]
        client.channel_name = self.channel_name
        client.save()
        self.accept()

    def disconnect(self, close_code):
        Clients.objects.filter(channel_name=self.channel_name).delete()

    def order_message(self, event):
        # Handles the "order.message" event when it's sent to us.
        self.send(text_data=event["text"])

#顺便把new_order_notify在这里定义吧
def new_order_notify(user_id):
    channel_layer = get_channel_layer()
    channel_name = Clients.objects.get(user_id=user_id).channel_name
    #重要!!!此处必须使用async_to_sync,下面会说明
    async_to_sync(channel_layer.send)(channel_name,{
        "type": "order.message",
        "text": "new",
    })

这里的async_to_sync是个坑,官网中解释是


Also remember that if you are sending the event from a synchronous environment, you have to use theasgiref.sync.async_to_syncwrapper as specified inchannel layers.

要从同步环境发送事件,则必须使用通道层中asgiref.sync.async_to_sync指定的包装器。因为我是在views.py中调用该函数,是同步的环境。刚开始的时候我没有加,运行也不报错,但前端就是一直收不到消息。

完成

接下来可以在前端创建websocket连接来测试啦。有说的不合理的地方欢迎大家指出。告诉你们个秘密,听说赞我的都找到对象了。

原文地址: https://zhuanlan.zhihu.com/p/43102770

Comments are closed.