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 use
channel_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_sync
wrapper as specified inchannel layers.
要从同步环境发送事件,则必须使用通道层中asgiref.sync.async_to_sync
指定的包装器。因为我是在views.py中调用该函数,是同步的环境。刚开始的时候我没有加,运行也不报错,但前端就是一直收不到消息。
完成
接下来可以在前端创建websocket连接来测试啦。有说的不合理的地方欢迎大家指出。告诉你们个秘密,听说赞我的都找到对象了。
原文地址: https://zhuanlan.zhihu.com/p/43102770