前言: 最近在写一个聊天室的项目,前端写了挺多的JS(function),导致有点懵比,出了BUG,也迟迟找不到。所以昨天把写过的代码总结了一下,写成博客。
项目背景
参考博客: http://www.cnblogs.com/alex3714/articles/5337630.html
先直观来几张图感受下
最开始的界面布局:
加点bootstrap样式:
实时的聊天效果:
第一步:点击左侧界面的好友,触发事件,打开聊天界面
1.1、给点击好友添加active属性,使其高亮。
Alex Li是一个li标签,属性有联系类型,与Alex Li的用户id.
<li contact-type="single" id="1" class="list-group-item active" onclick="OpenChatWindow(this)"> </li>
1.2、上面的single与id是怎么来的呢? 见如下html代码。
<ul class="list-group">
{% for friend in request.user.userprofile.friends.select_related %}
<li contact-type="single" id="{{friend.id}}" class="list-group-item"
onclick="OpenChatWindow(this)">
<span class="badge hide">14</span> <!-- 新消息提醒数量 -->
<span class="contact-name">{{friend.name}}</span>
</li>
{%endfor%}
</ul>
第二步:使界面标题框出现"正在和Alex Li聊天"字样。
并给其div 添加contact-id,与contact-type属性。
<div class="chat-box-title" contact-id="1" contact-type="single"><span>正在和 Alex Li 聊天</span></div>
第三步:通过事件委托(可看博客《聊一聊JQ中delegate事件委托的好处》)绑定事件,一按回车键就调用SendMsg(msg_text);发送消息
// 事件委托
$("body").delegate("textarea", "keydown", function (e) { // e==event
if(e.which == 13){ // 按下键的数字(e.which);13是enter键的ASCII码
var msg_text = $("textarea").val();
if ($.trim(msg_text).length > 0){
console.log(msg_text);
// 发送消息给对方
SendMsg(msg_text);
// 将发送的信息打印到自己的window界面
AddSendMsgIntoWindow(msg_text);
$("textarea").val(""); // 将输入框清空
}else {
alert("请输入要发送的消息")
}
}
});
第四步:通过ajax将消息发送到后台
4.1、消息的格式:
var msg_item={
"from":"{{request.user.userprofile.id}}",
"to":contact_id,
"type":contact_type,
"msg":msg_text //要发送的消息
};
type为发送的格式:
- 为single表示一对一发送;
- 为group表示群发,此时的"to",后面的3表示群组id
前面都没什么难度的,到这里通过ajax将消息字典(json格式)发到后台,后台怎么处理么?
简单阿,将消息发给用户阿,那要是用户没登陆呢??那只好先将数据存起来,存在哪呢?要满足先进先出,可以用队列queue,当然,生产环境最好用rabbitmq(《python之rabbitMQ》);
4.2、后台每个用户都有一条队列queue. 后台的全局队列如下: 以用户的id作为key, 队列作为value
GLOBAL_MSG_QUEUES={
"id": queue.Queue(),
}#队列:全局变量
4.3、将消息字典存到待接收用户的队列中去,如果用户此时队列不存在,则先给待接收消息的用户生成对应的一条队列,再将消息字典存到待接收用户的队列中去。
1 #如果用户队列不存在,注意,如果id(key)对应的队列不存在,则输出None,不会曝错
2 if not GLOBAL_MSG_QUEUES.get(queue_id):
3 GLOBAL_MSG_QUEUES[queue_id]=queue.Queue() #创建队列
4
5 GLOBAL_MSG_QUEUES[queue_id].put(msg_dic) #将消息字典(带时间戳)放进队列
4.4、将消息字典(含时间戳)成功存到待接收用户的队列后,ajax请求完毕,返回"---receive msg---",表示后台已成功接收到要发送的消息,表示消息字典成功存到待接收用户的队列中。
return HttpResponse("---receive msg---")
第五步:将发送的信息打印到自己的window界面,调用AddSendMsgIntoWindow(msg_text);
function AddSendMsgIntoWindow(msg_text){
varnew_msg_ele="<divclass='msg-item'>"+
"<span>" + "{{request.user.userprofile.name}}" + "</span>" +
"<span>" + newDate().toLocaleDateString() + "</span>" +
"<divclass='msg-text'>" + msg_text + "</div>" +
"</div>";
$(".chat-box-window").append(new_msg_ele);
$(".chat-box-window").animate({
scrollTop:$(".chat-box-window")[0].scrollHeight//每隔0.5s自动向下滚动
},500);
//console.log($(".chat-box-window")[0]);
}
将消息打印到自己的界面一开始会出现问题,比如你发了很多数据,界面整个div已经装不下了,就会“溢出”div。简单阿,给聊天界面加个"overflow"样式就行了
overflow: auto; /* 给div 内容多了自动加滚动条 */
太棒了,现在聊天内容一多,就自动出现滚动条,牛!! 但问题又来了,虽然有滚动条,但每次你一发消息,都得自己去拉滚动条到最底部才能看到刚刚发的消息。这……
于是我上网找到了这篇博客:scrollTop 和 scrollHeight的意思
今天要用到实时显示最近更新内容,也就是要让对话框随时都在最底部。
查了一下,
用div.scrollTop=div.scrollHeight;就可以了。
又查了查这两个参数什么意思。stackoverflow上面有人是这样解答的。
If I scroll down 5px in this window, the window's scrollTop value is 5. If I scroll right 10px in a scrollable div, the div's scrollLeft value is 10.
When I scroll to the top left corner of this window, both its scrollTop and scrollLeft values are 0.
还有一个人作了补充: scrollTop and scrollHeight. In summary, scrollTop is how much it's currently scrolled, and scrollHeight is the total height, including content scrolled out of view.
总的来说,scrollTop就是卷起来的部分,也就是我们随着下拉,看不见的部分。scrollHeight就是整个窗口可以滑动的高度。
so, 我用下面的方法就可以解决了,每隔0.5s 自动向下滚动至底部, animate() 方法
$(".chat-box-window").animate({
scrollTop:$(".chat-box-window")[0].scrollHeight
},5000);
第六步:界面一加载完毕就开始调用GetNewMsgs();开始取消息,向后台发起ajax起求
如何取消息?? 即是说:A向B发数据,后台收到A的数据后,如何返回给B
- 后台设置一个队列,将A发送的消息存到专属于B的队列中。
- 前端写一个定时器,每隔3秒就通过ajax去后台查询用户的队列是否为空,不为空的话,则取出数据。
{# 用定时器浏览器会崩(卡)#}
{# setInterval(function () {#}
{# GetNewMsgs();#}
{# }, 3000)#}
3、用定时器的话,会出现消息不实时的情况。比如,A用户啪啪啪很快发了很多数据,但B用户每隔3秒才去后台取数据,中间有最多3秒的时延。这就出现消息不实时的问题。
4、用户查询自己的队列中是否有无数据,无数据的话则后台挂起。
# 超时挂起
# 若队列为空,则60秒后会曝queue.Empty异常(相当在这60秒内卡住挂起)
try:
msg_list.append(q_obj.get(timeout=60))
except queue.Empty: # 若队列为空,则60秒后会曝queue.Empty异常(相当在这60秒内卡住挂起)
print("