web聊天室總結


前言: 最近在寫一個聊天室的項目,前端寫了挺多的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

  1. 后台設置一個隊列,將A發送的消息存到專屬於B的隊列中。
  2. 前端寫一個定時器,每隔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("\033[41;1mno msg for [%s][%s],timeout\033[0m" % (request.user.userprofile.id,request.user))

雖然隊列無數據時,后台可以掛起,但是前端用定時器每3秒發起一次ajax起求,問 "我的隊列中有沒有來新數據阿??", 每次起求相當於瀏覽器起一個線程,時間一長,瀏覽器會卡,撐不住。這怎么辦呢?? 難道不能用定時器?? 那還能用什么牛逼屌炸天的方法?

按我最好情況的理解是,前端每發起一次請問,若隊列有數據,則立馬取數據,若無數據,則掛起60秒(時間可在后台設置),這60秒內若隊列中有數據,同樣從隊列取數據,若60秒內隊列都無數據,則出queue.Empty異常,此時前端再發起ajax請求。

在解決上面的瀏覽器線程過多,太卡前,我們先來看看后台是如何處理隊列為空時掛起 這一功能的。看下面這張圖加上上面第4點的代碼,你應該懂的。

好,回到瀏覽器太卡這個BUG上來,我用了遞歸這個方法,代碼如下:

 1         // 用戶獲取消息
 2         function GetNewMsgs() {  3             console.log("----getting new msg----");  4             $.getJSON("{% url 'get_new_msgs' %}",  5                 function (callback) {  6                     // callback是列表對象,object, 列表每個元素都是一個消息字典
 7                     console.log(callback, typeof callback);  // Array [Object] object
 8                     // 解析消息,用戶可能收到與當前正在聊天用戶的消息,也可能是其他用戶發的消息
 9  ParseNewMsgs(callback); 10 
11                     return GetNewMsgs();  // 遞歸
12  }) 13         }

不用定時器。前端發起一個ajax請求,后端隊列無數據則掛起。當后台向前端返回數據時,有兩種情況,一種是超時,如60秒內,隊列都無新消息,出queue.Empty異常后返回數據;第二種是用戶接收到別人發給他的數據,隊列一有數據,則返加給前端。前端收到數據后,回調函數再發起一個ajax請求。

若前端沒收到數據(在后台掛起的時間內),則不會發起ajax請求,此時相當於實現前端掛起。不會起太多的線程。

(注: python專門設置的一種機制用來防止無限遞歸造成Python溢出崩潰。在python的遞歸是有層數據限制的,999層。超過就拋出 “RuntimeError: maximum recursion depth exceeded” )

 

第七步

后台查詢用戶隊列中是否有消息(數據字典),無的話就掛起,一分鍾無消息,則前端再發起ajax請求。若隊列中有消息的話,則返回給用戶,返回形式是列表形式,列表每個元素都是數據字典形式

eg:{"from":"3","to":"2","type":"single","msg":"1111"} <class 'str'>

return HttpResponse(json.dumps(msg_list))#序列化,轉化為json格式

 

第八步:

問題: A用戶與B女神聊的正嗨,點擊左側好友切換到C女神, 與C聊天,但聊天窗口,依舊是與B女神聊天的內容。

接下來要完成視圖切換功能,在切換之前將原本的視圖的html元素存到全局字典

   // 全局字典,用於存切換視圖前的html元素
    GLOBAL_CHAT_RECORD_DIC = { "single":{}, "group":{} };

全局字典的作用可將C女神發來的消息存入,格式應在"single"的value(字典),再設置一個用戶id與用戶聊天數據的字典(C用戶的id為3,xxx為將經過處理的html元素)。

如:"single":{"3": "xxx"}, 

切換視圖前將與B女神聊天窗口的html元素(數據)添加到用戶的全局字典中去:

// 視圖切換,在切換之前將原本的視圖的html元素存到全局字典 // 原本的框題框contact-id屬性不為空,即切換視圖前左側已有點擊對象
if ($(".chat-box-title").attr("contact-id")){ // 從標題框取出切換前用戶的id,與聯系方式contact-type
    var session_id = $(".chat-box-title").attr("contact-id"); var session_type = $(".chat-box-title").attr("contact-type"); // 將切換產前的視圖存入全局字典
    GLOBAL_CHAT_RECORD_DIC[session_type][session_id] = $(".chat-box-window").html(); }

框題框顯示"正在與C女神聊天"后,從全局字典取出與C的聊天數據,並顯示在聊天窗口。

// 把chat-window的html元素從全局字典中存出來 // 第一次點擊該聯系人時,chat_record為undefined,因為字典的single下還沒有生成id(key)對應的value
var chat_record = GLOBAL_CHAT_RECORD_DIC[contact_type][contact_id]; console.log(chat_record,typeof chat_record); if (typeof chat_record == "undefined"){ $(".chat-box-window").html(""); }else { // 如果chat_record為undefined,則下面代碼無法將對話界面清空(重要)
    $(".chat-box-window").html(chat_record); console.log("haha>>", chat_record) }

視圖切換功能基本完成。

 

第九步:前端通過ajax接收到后台返回的數據后,將數據渲染成html樣式后顯示在聊天窗口。

這里就要分情況了。

如果用戶收到的是與當前正在聊天用戶的消息,直接將html元素添加到聊天窗口$(".chat-box-window").append(new_msg_ele);  否則,如:用戶A正在與B聊天,此時收到來自C發來的消息。C發來的消息要發在哪里呢? 當然是前面設置的全局變量啊!!

    // 用戶收到的是與當前正在聊天用戶的消息
    if (current_session_id==callback[msg_item]["from"] && current_session_type==callback[msg_item]["type"]){ // 將消息的html元素添加到聊天窗口
        $(".chat-box-window").append(new_msg_ele); }else { // 用戶沒打開消息發送者的對話框,消息暫存到內存中(全局變量中)
        if (typeof GLOBAL_CHAT_RECORD_DIC[callback[msg_item]["type"]][callback[msg_item]["from"]]=="undefined"){ GLOBAL_CHAT_RECORD_DIC[callback[msg_item].type][callback[msg_item].from]=new_msg_ele; }else {  // 如果GLOBAL_CHAT_RECORD_DIC[current_session_type][current_session_id]不為undefined
            GLOBAL_CHAT_RECORD_DIC[callback[msg_item].type][callback[msg_item].from]+=new_msg_ele; } }

 

先寫這么多吧。轉發注明出版: http://www.cnblogs.com/0zcl/p/6903017.html 

前幾天學了git,有想一起做這個小項目的么??一個人寫代碼總感覺太慢了,有想一起完成的可以看看我的git項目https://github.com/0zcl/bbs_project,可以pull給我。歡迎交流。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM