Django集成channels实现websocket微信群聊(前端+后台)
首先要有一个聊天界面,没有的话可以到bootstrapmb.com上扒一个
这里我使用的是类似微信的界面:
模板源码:
<html class="js cssanimations"><head> <meta charset="utf-8"> <title>聊天室</title> <link rel="stylesheet" href="css/amazeui.min.css"> <link rel="stylesheet" href="css/main.css"> </head> <body style=""> <div class="box"> <div class="wechat"> <div class="sidestrip"> <div class="am-dropdown" data-am-dropdown=""> <!--头像插件--> <div class="own_head am-dropdown-toggle"></div> <div class="am-dropdown-content"> <div class="own_head_top"> <div class="own_head_top_text"> <p class="own_name">彭于晏丶plus<img src="images/icon/head.png" alt=""></p> <p class="own_numb">微信号:123456</p> </div> <img src="images/own_head.jpg" alt=""> </div> <div class="own_head_bottom"> <p><span>地区</span>江西 九江</p> <div class="own_head_bottom_img"> <a href=""><img src="images/icon/head_1.png"></a> <a href=""><img src="images/icon/head_2.png"></a> </div> </div> </div> </div> <!--三图标--> <div class="sidestrip_icon"> <a id="si_1" style="background: url(images/icon/head_2_1.png) no-repeat;"></a> <a id="si_2"></a> <a id="si_3"></a> </div> <!--底部扩展键--> <div id="doc-dropdown-justify-js"> <div class="am-dropdown" id="doc-dropdown-js" style="position: initial;"> <div class="sidestrip_bc am-dropdown-toggle"></div> <ul class="am-dropdown-content" style=""> <li> <a href="#" data-am-modal="{target: '#doc-modal-1', closeViaDimmer: 0, width: 400, height: 225}">意见反馈</a> <div class="am-modal am-modal-no-btn" tabindex="-1" id="doc-modal-1"> <div class="am-modal-dialog"> <div class="am-modal-hd"> Modal 标题 <a href="javascript: void(0)" class="am-close am-close-spin" data-am-modal-close="">×</a> </div> <div class="am-modal-bd"> Modal 内容。本 Modal 无法通过遮罩层关闭。 </div> </div> </div> </li> <li><a href="#">备份与恢复</a></li> <li><a href="#">设置</a></li> </ul> </div> </div> </div> <!--聊天列表--> <div class="middle on"> <div class="wx_search"> <input type="text" placeholder="搜索"> <button><span class="glyphicon glyphicon-search form-control-feedback"></span></button> </div> <div class="office_text" style="overflow: hidden;"> <ul class="user_list" style="top: 0px; position: absolute;"> <!-- 群聊列表--> <li class="user_active"> <div class="user_head"><img src="images/head/15.jpg"></div> <div class="user_text"> <p class="user_name">早安无恙</p> <p class="user_message">我是(⊙﹏⊙)!</p> </div> <div class="user_time">下午 2:54</div> </li> <li> <div class="user_head"><img src="images/head/2.jpg"></div> <div class="user_text"> <p class="user_name">夏继涛</p> <p class="user_message">[小程序]</p> </div> <div class="user_time">上午 11:03</div> </li> <!-- 群聊列表/--> </ul> <div style="position: absolute; display: none; line-height: 0; height: 610px;" class="zUIpanelScrollBox"></div> <div style="position: absolute; display: none; line-height: 0; height: 491px; top: 4px;" class="zUIpanelScrollBar"></div> </div> </div> <!--好友列表--> <div class="middle"> <div class="wx_search"> <input type="text" placeholder="搜索"> <span class="glyphicon glyphicon-search form-control-feedback"></span> </div> <div class="office_text" style="overflow: hidden;"> <ul class="friends_list" style="top: 0px; position: absolute;"> <li> <p>新的朋友</p> <div class="friends_box"> <div class="user_head"><img src="images/head/1.jpg"></div> <div class="friends_text"> <p class="user_name">新的朋友</p> </div> </div> </li> <li> <p>公众号</p> <div class="friends_box"> <div class="user_head"><img src="images/head/2.jpg"></div> <div class="friends_text"> <p class="user_name">公众号</p> </div> </div> </li> <li> <p>A</p> <div class="friends_box"> <div class="user_head"><img src="images/head/3.jpg"></div> <div class="friends_text"> <p class="user_name">彭于晏丶plus</p> </div> </div> <div class="friends_box"> <div class="user_head"><img src="images/head/4.jpg"></div> <div class="friends_text"> <p class="user_name">陈依依</p> </div> </div> <div class="friends_box"> <div class="user_head"><img src="images/head/5.jpg"></div> <div class="friends_text"> <p class="user_name">毛毛</p> </div> </div> </li> <li> <p>B</p> <div class="friends_box"> <div class="user_head"><img src="images/head/6.jpg"></div> <div class="friends_text"> <p class="user_name">苏笑言</p> </div> </div> <div class="friends_box"> <div class="user_head"><img src="images/head/7.jpg"></div> <div class="friends_text"> <p class="user_name">往事不再提</p> </div> </div> </li> <li> <p>C</p> <div class="friends_box"> <div class="user_head"><img src="images/head/8.jpg"></div> <div class="friends_text"> <p class="user_name">夏继涛</p> </div> </div> <div class="friends_box"> <div class="user_head"><img src="images/head/9.jpg"></div> <div class="friends_text"> <p class="user_name">早安无恙</p> </div> </div> <div class="friends_box"> <div class="user_head"><img src="images/head/10.jpg"></div> <div class="friends_text"> <p class="user_name">王鹏</p> </div> </div> </li> <li> <p>D</p> <div class="friends_box"> <div class="user_head"><img src="images/head/11.jpg"></div> <div class="friends_text"> <p class="user_name">涨了潮了</p> </div> </div> <div class="friends_box"> <div class="user_head"><img src="images/head/12.jpg"></div> <div class="friends_text"> <p class="user_name">Ktz丶中融资</p> </div> </div> </li> </ul> <div style="position:absolute;display:none;line-height:0;" class="zUIpanelScrollBox"></div><div style="position: absolute; display: none; line-height: 0;" class="zUIpanelScrollBar"></div></div> </div> <!--程序列表--> <div class="middle"> <div class="wx_search"> <input type="text" placeholder="搜索收藏内容"> <span class="glyphicon glyphicon-search form-control-feedback"></span> </div> <div class="office_text" style="overflow: hidden;"> <ul class="icon_list" style="top: 0px; position: absolute;"> <li class="icon_active"> <div class="icon"><img src="images/icon/icon.png" alt=""></div> <span>全部收藏</span> </li> <li> <div class="icon"><img src="images/icon/icon1.png" alt=""></div> <span>链接</span> </li> <li> <div class="icon"><img src="images/icon/icon2.png" alt=""></div> <span>相册</span> </li> <li> <div class="icon"><img src="images/icon/icon3.png" alt=""></div> <span>笔记</span> </li> <li> <div class="icon"><img src="images/icon/icon4.png" alt=""></div> <span>文件</span> </li> <li> <div class="icon"><img src="images/icon/icon5.png" alt=""></div> <span>音乐</span> </li> <li> <div class="icon"><img src="images/icon/icon6.png" alt=""></div> <span>标签</span> </li> </ul> <div style="position:absolute;display:none;line-height:0;" class="zUIpanelScrollBox"></div><div style="position: absolute; display: none; line-height: 0;" class="zUIpanelScrollBar"></div></div> </div> <!--聊天窗口--> <div class="talk_window"> <div class="windows_top"> <div class="windows_top_box"> <span>早安无恙</span> <ul class="window_icon"> <li><a href=""><img src="images/icon/icon7.png"></a></li> <li><a href=""><img src="images/icon/icon8.png"></a></li> <li><a href=""><img src="images/icon/icon9.png"></a></li> <li><a href=""><img src="images/icon/icon10.png"></a></li> </ul> <div class="extend" data-am-offcanvas="{target: '#doc-oc-demo3'}"></div> </div> </div> <!--聊天内容--> <div class="windows_body"> <div class="office_text" style="height: 100%; overflow: hidden;"> <ul class="content" id="chatbox" style="top: 0px; position: absolute;"> <li class="me"><img src="images/own_head.jpg" title="金少凯"><span>疾风知劲草,板荡识诚臣</span></li> <li class="other"><img src="images/head/15.jpg" title="张文超"><span>勇夫安知义,智者必怀仁</span></li> <li class="me"><img src="images/own_head.jpg"><span>驱蚊器恶气</span></li></ul> <div style="position: absolute; display: none; line-height: 0; height: 0px;" class="zUIpanelScrollBox"></div><div style="position: absolute; display: none; line-height: 0; height: 0px;" class="zUIpanelScrollBar"></div></div> </div> <div class="windows_input" id="talkbox" style=""> <div class="input_icon"> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> </div> <div class="input_box"> <textarea name="" rows="" cols="" id="input_box" style=""></textarea> <button id="send">发送(S)</button> </div> </div> </div> </div> </div> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript" src="js/amazeui.min.js"></script> <script type="text/javascript" src="js/zUI.js"></script> <script type="text/javascript" src="js/wechat.js"></script> </body> </html>
我改造后需要的:
<!-- 内容区域 --> <div class="content-wrapper"> <!-- 正文区域 --> <section class="content"> <div class="wechat row"> <!--聊天列表--> <div class="middle on col-lg-2"> <div class="wx_search"> <input type="text" placeholder="搜索" id="search_name"> <!-- <span class="glyphicon glyphicon-search form-control-feedback"></span>--> </div> <div class="office_text" style="overflow: hidden;"> <ul class="user_list" style="top: 0px; position: absolute;" id="chat_list"></ul> </div> </div> <!--聊天窗口--> <div class="talk_window col-lg-10"> <div class="windows_top"> <div class="windows_top_box"> <span id="chat_title"></span> </div> </div> <!--聊天内容--> <div class="windows_body"> <div class="office_text" style="height: 100%; overflow: hidden;"> <ul class="content" id="chatbox" style="top: 0px; position: absolute;"></ul> <!-- <li class="me"><img src="/static/img/own_head.jpg" title="金少凯"><span>疾风知劲草,板荡识诚臣</span></li>--> <!-- <li class="other"><img src="/static/img/head/15.jpg" title="张文超"><span>勇夫安知义,智者必怀仁</span></li>--> <!-- <li class="me"><img src="/static/img/own_head.jpg"><span>嗨嗨嗨</span></li></ul>--> <div style="position: absolute; display: none; line-height: 0; height: 0px;" class="zUIpanelScrollBox"></div><div style="position: absolute; display: none; line-height: 0; height: 0px;" class="zUIpanelScrollBar"></div></div> </div> <div class="windows_input" id="talkbox" style=""> <div class="input_icon"> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> <a href="javascript:;"></a> </div> <div class="input_box"> <textarea id="input_box"></textarea> <button id="send" onclick="sendMessage()">发送(S)</button> </div> </div> </div> </div> </section> <!-- 正文区域 /--> </div>
后台代码配置:
引入channels包,直接在终端引入即可:pip install channels (可能不叫这个名)
进入settings.py修改配置:注册app、添加asgi和包
INSTALLED_APPS = [ 'channels', ] ASGI_APPLICATION = 'x.asgi.application' CHANNEL_LAYERS = { "default":{ "BACKEND":"channels.layers.InMemoryChannelLayer", } }
asgi.py文件修改配置:(http请求不变、websocket请求修改调用路径)
""" ASGI config for cloud_services project. It exposes the ASGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ """ import os from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter from . import routings os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'x.settings') application = ProtocolTypeRouter({ 'http': get_asgi_application(), 'websocket': URLRouter(routings.urlpatterns) })
同级目录下创建routings.py,配置请求路径的标识:(调用consumers的消息处理方法)
from django.urls import re_path from app import consumers urlpatterns = [ re_path(r'room/(?P<group>\w+)/$', consumers.ChatConsumer.as_asgi()), ]
创建consumers.py (里面定义了客户端收发消息的方法)
from channels.generic.websocket import WebsocketConsumer from channels.exceptions import StopConsumer from asgiref.sync import async_to_sync class ChatConsumer(WebsocketConsumer): def websocket_connect(self, message): # 接受客户端的连接 self.accept() # 获取群号 group = self.scope['url_route']['kwargs'].get('group') # 将这个客户端连接对象加入特定地方 (内存 or redis) async_to_sync(self.channel_layer.group_add)(group, self.channel_name) def websocket_receive(self, message): # 获取群号 group = self.scope['url_route']['kwargs'].get('group') # 通知组内所有客户端,执行函数 async_to_sync(self.channel_layer.group_send)(group, {"type": "send_msg", "message": message}) def websocket_disconnect(self, message): # 获取群号 group = self.scope['url_route']['kwargs'].get('group') async_to_sync(self.channel_layer.group_discard)(group, self.channel_name) def send_msg(self, event): text = event['message']['text'] self.send(text)
到此后台代码基本完成,等待前端的调用啦。
我这里实现的效果是点击列表内的群里进入,并关闭其他的群聊连接,主要代码:
function judgeTime(data){ var date = data.toString(); var year = date.substring(0, 4); var month = date.substring(4, 6); var day = date.substring(6, 8); var d1 = new Date(year + '/' + month + '/' + day); var dd = new Date(); var y = dd.getFullYear(); var m = dd.getMonth() + 1; var d = dd.getDate(); var d2 = new Date(y + '/' + m + '/' + d); var iday = parseInt(d2 - d1) / 1000 / 60 / 60 / 24; return iday; } function chat_select(e){ if (chat_id != $(e).attr('value')){ //清空聊天界面 $('#chatbox').html(''); $('#input_box').removeAttr('disabled'); closeConn(); //关闭其他群聊连接,如果不关闭,消息收发会重复 $('#chat_list li').removeClass('user_active'); $(e).addClass('user_active'); $('#chat_title').text($(e).find('p:first').html()); chat_id = $(e).attr('value') // 创建聊天室连接 web_socket = new WebSocket("ws://192.168.125.153:9081/room/"+chat_id+"/"); // 初始化聊天室内容 $.ajax({ async: true, type: 'post', url: '/drill/get_chat_message_by_chatID/', data: { "chat_id": chat_id, "isGroup":0, }, success: function (data) { var res = data.data; if (res.length > 0){ for (var i = res.length-1;i>=0;i--){ var img_url = res[i].photo == null||res[i].photo == ''?'/static/img/user2-160x160.jpg':res[i].photo; var user_name = res[i].name; var message = res[i].text; if (res[i].sender_id == login_id){ //判断是不是自己的消息,用于展示在群里里的效果 $('#chatbox').append($('<li class="me"><div style="height: 18px" align="right">' + user_name + '</div><img src="' + img_url + '" title="' + user_name + '"><span>' + message + '</span></li>')); }else { $('#chatbox').append($('<li class="other"><div style="height: 18px" align="left">' + user_name + '</div><img src="' + img_url + '" title="' + user_name + '"><span>' + message + '</span></li>')); } } $('.office_text').scrollTop($('#chatbox')[0].scrollHeight); } } });
//下面为进入聊天室和退出执行的方法,如果需要可以搞一些事情 // socket.onopen = function (e){ // $('#chatbox').append($('<li class="me"><img src="/static/img/own_head.jpg" title="金少凯"><span>连接成功</span></li>')); // } // socket.onclose = function (e){ // $('#chatbox').append($('<li class="me"><img src="/static/img/own_head.jpg" title="金少凯"><span>断开连接</span></li>')); // } web_socket.onmessage = function (e){ var data_str = e.data; var sender_id = data_str.slice(0,data_str.indexOf('|')); var message = data_str.slice(data_str.indexOf('|')+1); $.ajax({ async: true, type: 'post', url: '/drill/get_personal_by_id/', data:{ "login_id":sender_id, }, success: function (res) { var user_msg = res.data; var img_url = user_msg.photo == null||user_msg.photo == ''?'/static/img/user2-160x160.jpg':user_msg.photo; var user_name = user_msg.name; if (login_id == sender_id){ $('#chatbox').append($('<li class="me"><div style="height: 18px" align="right">' + user_name + '</div><img src="' + img_url + '" title="' + user_name + '"><span>' + message + '</span></li>')); }else { $('#chatbox').append($('<li class="other"><div style="height: 18px" align="left">' + user_name + '</div><img src="' + img_url + '" title="' + user_name + '"><span>' + message + '</span></li>')); } $('.office_text').scrollTop($('#chatbox')[0].scrollHeight); } }); } } } function sendMessage(){ if ($('#input_box').val() != ""&&$('#input_box').val() != null){ $.ajax({ async: true, type: 'post', url: '/drill/save_chat_message/', data: { 'text':$('#input_box').val(), 'sender_id':login_id, 'chat_id':chat_id, 'isGroup':0, }, success: function (res) { web_socket.send(login_id + "|" + $('#input_box').val()); $('#input_box').val(''); }, error:function(e){ $('#message_tips').modal('show'); $('#message').text(e.data); } }); } } $('#input_box').keydown(function (event) { if (event.keyCode == 13){ event.preventDefault(); sendMessage(); } }); function closeConn(){ web_socket.close(); }
效果图:

浙公网安备 33010602011771号