第二十一 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 接收消息:

 

posted @ 2018-03-08 18:22  ckl893  阅读(379)  评论(0编辑  收藏  举报