第二十一 webchat
创建一个web聊天
1.创建新项目
2.设计数据库表
2.1.在原有用户表上追加
bbs\models.py
class UserProfile(models.Model): #关联到django的的User user = models.OneToOneField(User) #用户名 name = models.CharField(max_length=32) #个人签名 signature = models.CharField(max_length=255,blank=True,null=True) #头像 head_img = models.ImageField(height_field=200,width_field=200,blank=True) #for web chat friends = models.ManyToManyField('self',related_name="my_friends",blank=True) def __str__(self): return self.name
2.2.新增加聊天群表
webchat\models.py
from django.db import models from bbs.models import UserProfile # Create your models here. class WebGroup(models.Model): name = models.CharField(max_length=64) brief = models.CharField(max_length=255,blank=True,null=True) owner = models.ForeignKey(UserProfile) admins = models.ManyToManyField(UserProfile,blank=True,related_name="group_admins") members = models.ManyToManyField(UserProfile,blank=True,related_name="group_members") max_members = models.IntegerField(default=200) def __str__(self): return self.name
2.3.setting增加新项目
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'bbs.apps.BbsConfig', 'webchat', ]
2.4.创建数据库表
3.配置views
from django.shortcuts import render # Create your views here. def dashboard(request): return render(request,'wechat/bashboard.html')
4.配置url
from django.conf.urls import url,include from django.contrib import admin from webchat import views urlpatterns = [ url(r'^$',views.dashboard,name='chat_dashboard'), ]
5.配置页面展示
5.1.配置页面框架
5.1.1.框架页面配置
{% extends 'base.html' %} {% block page-container %} <div class="chat-container"> <div class="left-contact-panel"> contact </div> <div class="right-chat-panel"> <div class="chat-box-title"> title </div> <div class="chat-box-window"> dialog </div> <div class="chat-box-emoj"> emoj </div> <div class="chat-box-msg-box"> <textarea class="msg-box"></textarea> <button class="bt btn-success">发送</button> </div> </div> <div class="kan"></div> </div> {% endblock %} {% block bottom-js %} <script> $(document).ready(function () { $("#navbar a[href='{{ request.path }}']").parent().addClass("active"); }); </script> {% endblock %}
5.1.2.css样式配置
/* for chat */ .chat-container{ width: 1200px; height: 800px; border: 1px dashed rebeccapurple; margin-left: 200px; } .left-contact-panel { width: 25%; border: 1px solid palevioletred; height: 100%; float: left; } .right-chat-panel { width: 75%; border: 1px solid hotpink; height: 100%; float: left; } .kan { clear: both; } .chat-box-title { width:100%; border: 1px solid blue; height: 10%; } .chat-box-window { width: 100%; border: 1px solid darkcyan; height: 60%; } .chat-box-emoj { width: 100%; border: 1px solid lightskyblue; height: 7%; } .chat-box-msg-box { width: 100%; border: 1px solid darkgoldenrod; height: 23%; }
5.1.3.页面展示
5.2.好友及用户组
5.2.1.好友及用户组展示
地址:https://v3.bootcss.com/javascript/#tabs
<div class="left-contact-panel"> contact <!-- Nav tabs --> <ul class="nav nav-tabs" role="tablist"> <li role="presentation" class="active"> <a href="#contact-tab" role="tab" data-toggle="tab">好友</a></li> <li role="presentation"> <a href="#group-tab" role="tab" data-toggle="tab">群组</a></li> </ul> <!-- Tab panes --> <div class="tab-content"> <div role="tabpanel" class="tab-pane active" id="contact-tab">contact</div> <div role="tabpanel" class="tab-pane" id="group-tab">group</div> </div> </div>
5.2.2.页面展示
5.2.3.添加用户认证后获取好友等
from django.shortcuts import render from webchat import models from django.contrib.auth.decorators import login_required # Create your views here. @login_required def dashboard(request): return render(request,'wechat/bashboard.html')
5.2.4.用户显示
如果用户登录后,则直接可以在前端显示用户的好友等
<!-- Tab panes --> <div class="tab-content"> <div role="tabpanel" class="tab-pane active" id="contact-tab"> {% for friend in request.user.userprofile.friends.select_related %} {{ friend.name }} {% endfor %} </div> <div role="tabpanel" class="tab-pane" id="group-tab">group</div> </div>
5.2.5.用户及组创建添加
5.2.6.查看展示
5.2.7.用户展示优化
https://v3.bootcss.com/components/#list-group
<!-- Tab panes --> <div class="tab-content"> <div role="tabpanel" class="tab-pane active" id="contact-tab"> <ul class="list-group"> {% for friend in request.user.userprofile.friends.select_related %} <li class="list-group-item"> <span class="badge">14</span> {{ friend.name }} </li> {% endfor %} </ul> </div>
展示结果:
5.3.添加聊天头
5.3.1.定义聊天属性等
联系类型:群组还是单个联系人,联系人id,联系人用户名等
<ul class="list-group"> {% for friend in request.user.userprofile.friends.select_related %} <li contact-type="single" contact-id="{{ friend.id }}" onclick="OpenChatWindow(this)" class="list-group-item"> <span class="badge">14</span> {{ friend.name }} </li> {% endfor %} </ul>
5.3.2.定义点击事件
{% block bottom-js %} <script> $(document).ready(function () { $("#navbar a[href='{{ request.path }}']").parent().addClass("active"); }); function OpenChatWindow(ele) { console.log($(ele)); $(ele).addClass("active"); var contact_id = $(ele).attr("contact-id"); //联系人id var contact_name = $(ele).text(); //获取用户名 var contact_type = $(ele).attr("contact-type"); //联系人类型,组还是个人 var chat_box_title_contact = "正在跟" + contact_name + "聊天"; $(".chat-box-title").html(chat_box_title_contact); } </script>
5.3.4.聊天测试
同时获取到消息数:
5.3.5.增加单独样式
<ul class="list-group"> {% for friend in request.user.userprofile.friends.select_related %} <li contact-type="single" contact-id="{{ friend.id }}" onclick="OpenChatWindow(this)" class="list-group-item"> <span class="badge">14</span> <span class="contact-name">{{ friend.name }}</span> </li> {% endfor %} </ul>
function OpenChatWindow(ele) { console.log($(ele)); $(ele).addClass("active"); var contact_id = $(ele).attr("contact-id"); //联系人id var contact_name = $(ele).find(".contact-name").text(); //获取用户名 var contact_type = $(ele).attr("contact-type"); //联系人类型,组还是个人 var chat_box_title_contact = "正在跟" + contact_name + "聊天"; $(".chat-box-title").html(chat_box_title_contact); }
5.3.6.查看聊天titile
5.3.7.去掉多个active同时显示
function OpenChatWindow(ele) { console.log($(ele)); $(ele).addClass("active"); $(ele).siblings().removeClass("active"); //去掉多重亮显 var contact_id = $(ele).attr("contact-id"); //联系人id var contact_name = $(ele).find(".contact-name").text(); //获取用户名 var contact_type = $(ele).attr("contact-type"); //联系人类型,组还是个人 var chat_box_title_contact = "正在跟" + contact_name + "聊天"; $(".chat-box-title").html(chat_box_title_contact); }
结果
5.3.8.美化输入聊天框
.chat-box-msg-box textarea{ width: 90%; height: 100%; } .chat-box-msg-box button { margin-bottom: 100px; }
5.3.9.监听回车发送消息
http://www.w3school.com.cn/jquery/event_delegate.asp
$(document).ready(function () { $("#navbar a[href='{{ request.path }}']").parent().addClass("active"); //send msg //当整个页面只有一个textarea $("body").delegate("textarea","keydown",function (e) { if(e.which == 13){ var msg_text = $("textarea").val(); if($.trim(msg_text).length > 0){ console.log(msg_text) } } }) });
5.3.10.测试页面
5.3.11.消息内容
$(document).ready(function () { $("#navbar a[href='{{ request.path }}']").parent().addClass("active"); //send msg //当整个页面只有一个textarea $("body").delegate("textarea","keydown",function (e) { if(e.which == 13){ var msg_text = $("textarea").val(); //获取到消息内容 if($.trim(msg_text).length > 0){ console.log(msg_text); } addSentMsgIntoBox(msg_text); //发送消息 $("textarea").val(''); //清空输入框 } }); });// end doc ready function addSentMsgIntoBox(msg_text) { var new_msg_ele = "<div class='msg-item'>" + "<span>" + "{{ request.user.userprofile.name }}" + "</span>" + "<span>" + new Date().toLocaleDateString() + "</span>" + "<div class='msg-text'>" + msg_text + "</div>" + "</div>"; //用户名+日期+消息内容 $(".chat-box-window").append(new_msg_ele); //找到chat-box-window添加消息内容 }
发送消息测试:
消息已经越过边框:
5.3.13.div增加自动滑动属性
增加overflow
.chat-box-window { width: 100%; border: 1px solid darkcyan; height: 60%; overflow: auto; }
结果:
5.3.14.滑轮自动下滑
每当发送新消息后,滑轮自动下滑
function addSentMsgIntoBox(msg_text) { var new_msg_ele = "<div class='msg-item'>" + "<span>" + "{{ request.user.userprofile.name }}" + "</span>" + "<span>" + new Date().toLocaleDateString() + "</span>" + "<div class='msg-text'>" + msg_text + "</div>" + "</div>"; //用户名+日期+消息内容 $(".chat-box-window").append(new_msg_ele); //找到chat-box-window添加消息内容 $(".chat-box-window").animate({ scrollTop:$('.chat-box-window')[0].scrollHeight},500); }
效果:
5.4.发送日志保存到队列
5.4.1.增加全局csrf
https://docs.djangoproject.com/en/2.0/ref/csrf/
{% block bottom-js %} <script> //for csrf //using jQuery function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } var csrftoken = getCookie('csrftoken'); console.log(csrftoken); function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } //end csrf $(document).ready(function () { //set csrf before $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); $("#navbar a[href='{{ request.path }}']").parent().addClass("active"); //send msg //当整个页面只有一个textarea $("body").delegate("textarea","keydown",function (e) { if(e.which == 13){ var msg_text = $("textarea").val(); //获取到消息内容 if($.trim(msg_text).length > 0){ console.log(msg_text); //send msg SendMsg(msg_text); } addSentMsgIntoBox(msg_text); //发送消息 $("textarea").val(''); //清空输入框 } }); });// end doc ready //发送消息到队列 function SendMsg(msg_text){ var contact_type = $(".chat-box-title").attr("contact-type"); var contact_id = $(".chat-box-title").attr("contact-id"); //如果联系类型和id存在,必须选择发送用户 if (contact_type && contact_id){ var msg_item ={ 'from': "{{ request.user.userprofile.id }}", 'to' :contact_id, 'type':contact_type, 'msg' : msg_text }; //提交数据到url,stringify将字典转为json $.post("{% url 'send_msg' %}", {data:JSON.stringify(msg_item)},function(callback){ console.log(callback); });//end post }//end if } function addSentMsgIntoBox(msg_text) { var new_msg_ele = "<div class='msg-item'>" + "<span>" + "{{ request.user.userprofile.name }}" + "</span>" + "<span>" + new Date().toLocaleDateString() + "</span>" + "<div class='msg-text'>" + msg_text + "</div>" + "</div>"; //用户名+日期+消息内容 $(".chat-box-window").append(new_msg_ele); //找到chat-box-window添加消息内容 $(".chat-box-window").animate({ scrollTop:$('.chat-box-window')[0].scrollHeight},500); } function OpenChatWindow(ele) { console.log($(ele)); $(ele).addClass("active"); $(ele).siblings().removeClass("active"); //去掉多重亮显 var contact_id = $(ele).attr("contact-id"); //联系人id var contact_name = $(ele).find(".contact-name").text(); //获取用户名 var contact_type = $(ele).attr("contact-type"); //联系人类型,组还是个人 var chat_box_title_contact = "正在跟" + contact_name + "聊天"; $(".chat-box-title").html(chat_box_title_contact); $(".chat-box-title").attr("contact-id",contact_id); $(".chat-box-title").attr("contact-type",contact_type); } </script> {% endblock %}
5.4.2.添加提交的url
from django.conf.urls import url,include from django.contrib import admin from webchat import views urlpatterns = [ url(r'^$',views.dashboard,name='chat_dashboard'), url(r'^msg_send/$',views.send_msg,name='send_msg'), ]
5.4.3.增加view方法
from django.shortcuts import render,HttpResponse from webchat import models from django.contrib.auth.decorators import login_required # Create your views here. import json,time,queue GLOBAL_MSG_QUEUES = { } @login_required def dashboard(request): return render(request,'wechat/bashboard.html') @login_required def send_msg(request): print(request.POST) print(request.POST.get("msg")) print(request.POST.get('data')) msg_data = request.POST.get('data') #如果消息存在 if msg_data: msg_data = json.loads(msg_data) #消息增加时间蹉 msg_data['timestamp'] = time.time() #如果消息类型为‘single’ if msg_data['type'] == 'single': if not GLOBAL_MSG_QUEUES.get(msg_data["to"]): GLOBAL_MSG_QUEUES[msg_data["to"]] = queue.Queue() GLOBAL_MSG_QUEUES[msg_data["to"]].put(msg_data) print(GLOBAL_MSG_QUEUES) return HttpResponse('--- msg recevied --')
5.4.4.发送消息
5.5.获取消息
5.5.1.增加url
from django.conf.urls import url,include from django.contrib import admin from webchat import views urlpatterns = [ url(r'^$',views.dashboard,name='chat_dashboard'), url(r'^msg_send/$',views.send_msg,name='send_msg'), url(r'^new_msgs/$', views.get_new_msgs, name='get_new_msgs'), ]
5.5.2.增加获取新消息方法
from django.shortcuts import render,HttpResponse from webchat import models from django.contrib.auth.decorators import login_required # Create your views here. import json,time,queue GLOBAL_MSG_QUEUES = { } @login_required def dashboard(request): return render(request,'wechat/bashboard.html') @login_required def send_msg(request): print(request.POST) print(request.POST.get("msg")) print(request.POST.get('data')) msg_data = request.POST.get('data') #如果消息存在 if msg_data: msg_data = json.loads(msg_data) #消息增加时间蹉 msg_data['timestamp'] = time.time() #如果消息类型为‘single’ if msg_data['type'] == 'single':
#注意,这里要格式为int类型,否则无法接收消息 if not GLOBAL_MSG_QUEUES.get(int(msg_data["to"])): GLOBAL_MSG_QUEUES[int(msg_data["to"])] = queue.Queue() GLOBAL_MSG_QUEUES[int(msg_data["to"])].put(msg_data) print(GLOBAL_MSG_QUEUES) return HttpResponse('--- msg recevied --') def get_new_msgs(request): if request.user.userprofile.id not in GLOBAL_MSG_QUEUES: print("no queue for user [%s]" %request.user.userprofile.id,request.user) GLOBAL_MSG_QUEUES[request.user.userprofile.id] = queue.Queue() msg_count = GLOBAL_MSG_QUEUES[request.user.userprofile.id].qsize() q_obj = GLOBAL_MSG_QUEUES[request.user.userprofile.id] msg_list = [] if msg_count > 0: for msg in range(msg_count): msg_list.append(q_obj.get()) print("new msgs:",msg_list) return HttpResponse(json.dumps(msg_list)) else: print("no new msg for %s" % request.user.userprofile.id) return HttpResponse(json.dumps(msg_list))
5.5.3.增加获取定时任务
$(document).ready(function () { //set csrf before $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); //定时取消息 var MsgRefresher = setInterval(function () { GetNewMsgs(); },3000); //定时结束
function GetNewMsgs() { $.getJSON("{% url 'get_new_msgs' %}",function(callback){ console.log(callback); });//end post }
5.5.4.发送方法
发送给贾岛
5.5.5.贾岛登录接收
6.实时接收消息(很重要)
6.1.实时接收消息
原有的定人任务等三秒会有问题,其实没大明白,解决就是回调使用递归。
第一次运行后,再使用递归方法
6.1.1.取消定时任务
//定时取消息 //var MsgRefresher = setInterval(function () { // GetNewMsgs(); //},3000); //定时结束 GetNewMsgs();
6.1.2.配置回调递归
function GetNewMsgs() { console.log("--- getting new message ---"); $.getJSON("{% url 'get_new_msgs' %}",function(callback){ console.log(callback); GetNewMsgs(); });//end post }
6.2.配置获取方法
6.2.1.配置监听超时
def get_new_msgs(request): if request.user.userprofile.id not in GLOBAL_MSG_QUEUES: print("no queue for user [%s]" %request.user.userprofile.id,request.user) GLOBAL_MSG_QUEUES[request.user.userprofile.id] = queue.Queue() msg_count = GLOBAL_MSG_QUEUES[request.user.userprofile.id].qsize() q_obj = GLOBAL_MSG_QUEUES[request.user.userprofile.id] msg_list = [] if msg_count > 0: for msg in range(msg_count): msg_list.append(q_obj.get()) print("new msgs:",msg_list) return HttpResponse(json.dumps(msg_list)) else: print("no new msg for %s" % request.user.userprofile.id) try:
#监听超时 msg_list.append(q_obj.get(timeout=60)) except queue.Empty: print("no msg for [%s] [%s]" %(request.user.userprofile.id,request.user)) return HttpResponse(json.dumps(msg_list))
6.3.测试发送消息给贾岛
6.4.贾岛实时接收到消息
6.5.贾岛发送消息
ckl 接收消息: