django+channel+vue 前后端分离 实现推送消息给前端和我的疑惑
原因:最新在写数据仓,其中一项是后端实时返回订单状态给前端,前端就一条一条显示。所有使用了django 推荐的websocket 插件channels本人也是刚学的,做个记录,新手勿喷版本号:和配置django==2.2.2channels == 3.0.4channels-redis == 3.4.0用的redis 是linxu ,windows版本不够(会报错的 兄弟们),直接用linux要先
原因:最新在写数据仓,其中一项是后端实时返回订单状态给前端,前端就一条一条显示。所有使用了django 推荐的websocket 插件channels
本人也是刚学的,做个记录,新手勿喷
版本号:和配置
django==2.2.2
channels == 3.0.4
channels-redis == 3.4.0
用的redis 是linxu ,windows版本不够(会报错的 兄弟们),直接用linux
要先安装 channels 和 channels-redis
pip3 install channels
pip3 install channels-redis
然后再setting 里面配置
setting
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework.authtoken',
'rest_framework',
'jojoapp',
'corsheaders',
'channels'
]
PS: 请注意 xybbcmysite.asgi.application
其中xybbcmysite 是我的项目名,
asgi 是我项目下的文件
application 是我文件下的方法名
ASGI_APPLICATION = "xybbcmysite.asgi.application"
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
# "hosts": [('127.0.0.1', 6379)],
"hosts": ["redis://:host:6379/3"], PS: 3 是你选择的哪个数据库
},
},
}
代码目录
和网络上教程一样,现在项目下创建一个文件 asgi 文件
asgi.py 文件内容
import os
import django
from channels.auth import AuthMiddlewareStack
from channels.http import AsgiHandler
from channels.routing import ProtocolTypeRouter,URLRouter
import jojoapp.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'xybbcmysite.settings')
django.setup()
# ProtocolTypeRouter 检查数据类型,检查这个请求是HTTP 还是ws, 进行一个分配的,简单理解哈哈哈
application = ProtocolTypeRouter({
"http": AsgiHandler(),
# Just HTTP for now. (We can add other protocols later.)
"websocket": AuthMiddlewareStack(
URLRouter(
jojoapp.routing.websocket_urlpatterns
注意:jojoapp 是我的APP名(别告诉我你不知道)
routing 是我APP名下的文件名(目录结构里有)
websocket_urlpatterns 是 routing 文件下的 参数
)
),
})
routing.py
这里一定要用 re_path ,不要用path, 网上版本的都是2的,3.0的一定是re_path。我换成path 的时候,根本就匹配不到
# 路由分发
from django.urls import re_path
from . import consumers,consumers2
from . import outconsumers
websocket_urlpatterns = [
re_path(r'ws/xyjojo/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
# re_path(r'ws/xyjojo/(?P<room_name>\w+)/$', consumers2.ChatConsumer.as_asgi(), name='获取订单状态')
]
我这里有两个 文件 consumers 和consumers2
这两个文件都相当于 django 里面的试图view, 如果你有多个ws 的需求,就多建几个这样的文件
consumers 是异步的
consumers2 是同步的
consumers
import json
import time
import asyncio
from channels.generic.websocket import AsyncWebsocketConsumer
from .src import orderrequest
from .scmenum import *
functtime = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
# 建立连接
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
# 断开连接
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat.message', # 这里的type 实际上就是 下面的chat_message自定义函数
'message': message
}
)
# Receive message from room group
async def chat_message(self, event):
orderno = event['message']
if orderno[0:1] !="PO" or orderno[0:1] !="O1":
if await orderrequest.get_channel_id_and_channelorderid(channelno=orderno,room_group_name=self.room_group_name,type=1) ==2001:
await orderrequest.get_channel_id_and_channelorderid(channelno=orderno, room_group_name=self.room_group_name, type=2)
else:
await self.send(text_data=json.dumps({
'message': f"{functtime}{OrderStatus.orderstate.value}",
}))
else:
await self.send(text_data=json.dumps({
"message":f"{functtime}请使用渠道单号发货"
}))
async def send_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message,
}))
consumers2
import json
import time
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from .src import orderrequest
from .scmenum import *
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
# 从websocket 接受消息
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
# 将消息返回给对应得组
def chat_message(self, event):
orderno = event['message']
if orderno is None or orderno is "":
self.send(text_data=json.dumps({
'message': OrderStatus.statusnull.value
}))
self.close()
return None
else:
message = orderrequest.get_channel_id_and_channelorderid(channelno=orderno,room_group_name=self.room_group_name)
# self.send(text_data=json.dumps({
# 'message': message
# }))
def order_message(self, event):
message = event['message']
self.send(text_data=json.dumps({
'message':message
}))
注意: 不管在同步还是在异步中都存在一段代码如下
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
# 两种写法 可以匹配下面的方法
# 'type': 'chat.message',
'type': 'chat_message',
'message': message
}
)
def chant_message(self, event):
message = event['message']
self.send(text_data=json.dumps({
'message':message
}))
官方已经说过了,我就复习一下。 这里的type 指的的 发送信息给相应组的函数,也就是说文件里一定存在于一个 函数名叫 chat_message的函数
让我想想还有啥问题
有的情况会在消费之外发送消息给通道层,怎么办,官方已经给出了答案
请注意这段代码只能在同步代码中使用,不能在异步中使用
from asgiref.sync import async_to_sync
async_to_sync(channel_layer.group_send)("chat", {"type": "chat.force_disconnect"})
异步使用如下
await channel_layer.group_send(
chat_name,
{"type": "chat.system_message", "text": announcement_text},
)
消费之外的代码如下:文件目录
下面的方法是异步的,异步方法都是async 开头, 执行异步方法是await 开头
如果想用官方的,在 消费之外的函数不加上 async ,和await ,这个时候就能使用 同步的代码了
async def get_channel_id_and_channelorderid(channelno,BBMALL_URL=BBMALL_URL,room_group_name=None,type=None):
'''
根据渠道单查询channelId 和channelOrderId 在bbmall 数据库中查找相应的平台订单和采购单号
:param room_group_name: 组名,用于区分组,进行不同组信息发送
:param channelno: 渠道单号
:param BBMALL_URL: url
:return: channelOrderId channelId
'''
headers = {
'Authorization': '',
'route': '',
'Origin': 'http://xingyun.test.bbmall.xyb2b.com.cn',
'Content-Type': 'application/json'
}
channeldetail_response = requests.request("POST", url=BBMALL_URL+ await query_channelorderno_url(),
headers=headers,data=await query_channelorderno_params(channelno))
if len(channeldetail_response.json()['data']['records'])>0:
# channelId = channeldetail_response.json()['data']['records'][0]["channelId"]
channelOrderId = channeldetail_response.json()['data']['records'][0]["channelOrderId"]
PS : 下面的代码{"type": "send_message", send_message:你的文件里一定存在一个这样的方法
await channel_layer.group_send(room_group_name, {"type": "send_message",
"message": f"{getstatustime}-{channelOrderId}-{OrderStatus.stat_con_mysql.value}"})
purchase_order = await connet_mysql(channelOrderId=channelOrderId,room_group_name=room_group_name)
return await purchase_order_status(orderNo=purchase_order,type=type,room_group_name=room_group_name)
else:
await channel_layer.group_send(room_group_name, {"type": "send_message",
"message": f"{getstatustime}-{OrderStatus.stat_query_error.value}"})
前端代码,前端代码
created() {
let nowdate = new Date();
let hh = nowdate.getHours();
let mf =
new Date().getMinutes() < 10
? "0" + new Date().getMinutes()
: new Date().getMinutes();
let ss =
new Date().getSeconds() < 10
? "0" + new Date().getSeconds()
: new Date().getSeconds();
let gettime = hh.toString() + mf.toString() + ss.toString();
console.log(gettime);
createSocket(`ws://127.0.0.1:8000/ws/xyjojo/${gettime}/`);
},
运行原理
vue的请求—> asgi,判断是HTTP 还是ws. 是ws ----> 进入routing-> 开始匹配路由–>查看相应的consumers文件,开始执行ws
我的问题(已经解决的使用super 直接调用父类的send方法)
各位大佬,虽然这个已经写完了。
我期望的结果:订单在不同的域中,状态是不一样的,前端显示的应该一条一条的显示后端的结果。
实际结果: 等代码全部运行完成,”Send message to WebSocket“ 这个方法会一直在循环,循环之前我在 channel_layer.ground_send() 的返回,将全部信息一下返回,没有做到一条一条的显示,就算我在中间加了 时间等待,返回数据的时间都是一摸一样。怎么解决
这个问题算是解决了吧,应该只能算符合预期,以下是解决方案
await orderrequest.get_channel_id_and_channelorderid(self, channelno=orderno)
其中的self 代表着consumers,我在consumers中,将self传给了其他文件里的函数然后执行下面的代码
await self.send(text_data=json.dumps({
"message":"进入发货准备o(=•ェ•=)m"
}))
await asyncio.sleep(0.1)
就可以达到预期,遇到send 这个方法直接返回给前端,但是必须要加上 await asyncio.sleep(0.1) 这航。如果不加,就返回不了。哎,在慢慢整吧。
问题解决了
当初没有仔细看官方文档,官方文档已经给出了使用方法,就像下面一样操作可以
更多推荐
所有评论(0)