vue+django實(shí)現(xiàn)一對(duì)一聊天和消息推送的功能。主要是通過websocket,由于Django不支持websocket吃挑,所以我使用了django-channels〗至ⅲ考慮到存儲(chǔ)量的問題舶衬,我并沒有把聊天信息存入數(shù)據(jù)庫,服務(wù)端的作用相當(dāng)于一個(gè)中轉(zhuǎn)站几晤。我只講述實(shí)現(xiàn)功能的結(jié)構(gòu)性代碼约炎,具體的實(shí)現(xiàn)還請(qǐng)大家看源代碼植阴。
效果展示
實(shí)現(xiàn)過程
后端
首先蟹瘾,我們需要先定義websocket的兩條連接路徑。ws/chat/xxx/
(xxx
指代聊天組)這條路徑是當(dāng)聊天雙方都進(jìn)入同一個(gè)聊天組以后掠手,開始聊天的路徑憾朴。push/xxx/
(xxx
指代用戶名)這條是指當(dāng)有一方不在聊天組,另一方的消息將通過這條路徑推送給對(duì)方喷鸽。ws/chat/xxx/
只有雙方都進(jìn)入聊天組以后才開啟众雷,而push/xxx/
是自用戶登錄以后,直至退出都開啟的。
websocket_urlpatterns = [
url(r'^ws/chat/(?P<group_name>[^/]+)/$', consumers.ChatConsumer),
url(r'^push/(?P<username>[0-9a-z]+)/$', consumers.PushConsumer),
]
我們采用user_a的id加上下劃線_加上user_b的id的方式來命名聊天組名砾省。其中id值從小到大放置鸡岗,例如:195752_748418。當(dāng)用戶通過ws/chat/group_name/
的方式向服務(wù)端請(qǐng)求連接時(shí)编兄,后端會(huì)把這個(gè)聊天組的信息放入一個(gè)字典里轩性。當(dāng)連接關(guān)閉時(shí),就把聊天組從字典里移除狠鸳。
class ChatConsumer(AsyncJsonWebsocketConsumer):
chats = dict()
async def connect(self):
self.group_name = self.scope['url_route']['kwargs']['group_name']
await self.channel_layer.group_add(self.group_name, self.channel_name)
# 將用戶添加至聊天組信息chats中
try:
ChatConsumer.chats[self.group_name].add(self)
except:
ChatConsumer.chats[self.group_name] = set([self])
#print(ChatConsumer.chats)
# 創(chuàng)建連接時(shí)調(diào)用
await self.accept()
async def disconnect(self, close_code):
# 連接關(guān)閉時(shí)調(diào)用
# 將關(guān)閉的連接從群組中移除
await self.channel_layer.group_discard(self.group_name, self.channel_name)
# 將該客戶端移除聊天組連接信息
ChatConsumer.chats[self.group_name].remove(self)
await self.close()
接著揣苏,我們需要判斷連接這個(gè)聊天組的用戶個(gè)數(shù)。當(dāng)有兩個(gè)用戶連接這個(gè)聊天組時(shí)件舵,我們就直接向這個(gè)聊天組發(fā)送信息卸察。當(dāng)只有一個(gè)用戶連接這個(gè)聊天組時(shí),我們就通過push/xxx/
把信息發(fā)給接收方铅祸。
async def receive_json(self, message, **kwargs):
# 收到信息時(shí)調(diào)用
to_user = message.get('to_user')
# 信息發(fā)送
length = len(ChatConsumer.chats[self.group_name])
if length == 2:
await self.channel_layer.group_send(
self.group_name,
{
"type": "chat.message",
"message": message.get('message'),
},
)
else:
await self.channel_layer.group_send(
to_user,
{
"type": "push.message",
"event": {'message': message.get('message'), 'group': self.group_name}
},
)
async def chat_message(self, event):
# Handles the "chat.message" event when it's sent to us.
await self.send_json({
"message": event["message"],
})
# 推送consumer
class PushConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.group_name = self.scope['url_route']['kwargs']['username']
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.group_name,
self.channel_name
)
# print(PushConsumer.chats)
async def push_message(self, event):
print(event)
await self.send(text_data=json.dumps({
"event": event['event']
}))
前端
前端實(shí)現(xiàn)websocket就比較簡(jiǎn)單了坑质。就是對(duì)websocket進(jìn)行初始化,定義當(dāng)websocket連接个少、關(guān)閉和接收消息時(shí)要執(zhí)行哪些動(dòng)作洪乍。
<script>
export default {
name : 'test',
data() {
return {
websock: null,
}
},
created() {
this.initWebSocket();
},
destroyed() {
this.websock.close() //離開路由之后斷開websocket連接
},
methods: {
initWebSocket(){ //初始化weosocket
const wsuri = "ws://127.0.0.1:8080";
this.websock = new WebSocket(wsuri);
this.websock.onmessage = this.websocketonmessage;
this.websock.onopen = this.websocketonopen;
this.websock.onerror = this.websocketonerror;
this.websock.onclose = this.websocketclose;
},
websocketonopen(){ //連接建立之后執(zhí)行send方法發(fā)送數(shù)據(jù)
let actions = {"test":"12345"};
this.websocketsend(JSON.stringify(actions));
},
websocketonerror(){//連接建立失敗重連
this.initWebSocket();
},
websocketonmessage(e){ //數(shù)據(jù)接收
const redata = JSON.parse(e.data);
},
websocketsend(Data){//數(shù)據(jù)發(fā)送
this.websock.send(Data);
},
websocketclose(e){ //關(guān)閉
console.log('斷開連接',e);
},
},
}
</script>