ASP.NET SignalR 與LayIM配合,輕松實現網站客服聊天室(三) 激動人心的時刻到啦,實現1v1聊天


  看起來挺簡單,細節還是很多的,好,接上一篇,我們已經成功連接singalR服務器了,那么剩下的內容呢,就是一步一步實現聊天功能。

  我們先看看缺什么東西

  1. 點擊好友彈框之后,要給服務器發消息,進入組Group.Group原理在上一篇已經介紹了,這里不再贅述。
  2. 點擊發送消息到后台,后台在傳送回來
  3. 將htmlappend到相應元素上,demo已經實現了,我們把代碼拿過來用就可以了
  4. 模擬用戶登錄,點擊發送聊天

  在做上述工作之前,還是要做許多准備工作的。我們分析一下界面元素

  

  好的,可以看到,一個消息里面有消息發送時間(addtime),用戶名(username),用戶頭像(userphoto),用戶消息體(msgcontent),除此之外還需要用戶id,聊天id,以及組名(groupname).以此我先在后台建立模型。

  

namespace LayIM.Model
{
    public enum CSMessageType
    {
        System = 1,//系統消息,出錯,參數錯誤等消息
        Custom = 2 //普通消息,對話,或者群組消息
    }
}
namespace LayIM.Model
{
    public class CSChatMessage
    {
        public CSChatMessage() {
            addtime = DateTime.Now.ToString("HH:mm:ss");
        }
        /// <summary>
        /// 消息來源
        /// </summary>
        public CSUser fromuser { get; set; }
        public CSUser touser { get; set; }
        /// <summary>
        /// 消息內容
        /// </summary>
        public string msg { get; set; }
        /// <summary>
        /// 消息發送時間
        /// </summary>
        public string addtime { get; set; }
        /// <summary>
        /// 消息類型
        /// </summary>
        public CSMessageType msgtype { get; set; }

        public object other { get; set; }
    }
}
namespace LayIM.Model
{
    public class SingalRUser
    {
        protected string _groupName { get; set; }
        private string _connectionId { get; set; }
        /// <summary>
        /// 用戶當前所在組
        /// </summary>
        public string groupname
        {
            get
            {
                return this._groupName;
            }
        }
        /// <summary>
        /// 用戶當前所在connectionid
        /// </summary>
        public string connectionid
        {
            get
            {
                return this._connectionId;
            }
        }
        public SingalRUser(string groupName, string connectionId)
        {
            _groupName = groupName;
            _connectionId = connectionId;
        }
        public SingalRUser() { }
    }
    /// <summary>
    /// 用戶Model
    /// </summary>
    public class CSUser : SingalRUser
    {
        public CSUser(string groupName, string connectionId) :
            base(groupName, connectionId)
        {
        }
        /// <summary>
        /// 用戶id
        /// </summary>
        public int userid { get; set; }
        /// <summary>
        /// 用戶昵稱
        /// </summary>
        public string username { get; set; }
        /// <summary>
        /// 用戶頭像
        /// </summary>
        public string photo { get; set; }
    }
}

  ok,很簡單的幾個model,CSUser為用戶,CSChatMessage為消息體。那么,如果想讓兩個用戶聯通,我們需要得到他們所在的組,即經常說到的 userID1+userID2,生成組名代碼如下:(主要保證兩個用戶的組名唯一性就可,方法隨意)

    /// <summary>
        /// 根據兩個用戶ID得到對應的組織名稱
        /// </summary>
        /// <param name="sendid">發送人(主動聯系人)</param>
        /// <param name="receiveid">接收人(被動聯系人)</param>
        /// <returns></returns>
        public static string GetGroupName(string sendid, string receiveid)
        {
            /*
                排序的目的就是為了保證,無論誰連接服務器,都能得到正確的組織ID
            */
            int compareResult = string.Compare(sendid, receiveid);
            if (compareResult > 0) {
                //重新排序 如果sendid>receiveid
                return string.Format("G{0}{1}", receiveid, sendid);
            }
            return string.Format("G{0}{1}", sendid, receiveid);
        }

現在groupName也有了,我們回到 CustomServiceHub 類中來。添加用戶加入組的方法,這個方法什么時候調用呢,就是當你點擊某個用戶頭像彈出聊天框的時候調用。

/// <summary>
        /// 人對人聊天 連接服務器
        /// </summary>
        /// <param name="sendid">發送人</param>
        /// <param name="receiveid">接收人</param>
        /// <returns></returns>
        public Task ClientToClient(string sendid, string receiveid)
        {
            if (sendid == null || receiveid == null) { throw new ArgumentNullException("sendid or receiveid can't be null"); }
            //獲取組名
            string groupName = MessageUtils.GetGroupName(sendid, receiveid);
            //將當前用戶添加到此組織內
            Groups.Add(CurrentUserConnectionId, groupName);
            //構建系統連接成功消息
            var msg = MessageUtils.GetSystemMessage(groupName, MessageConfig.ClientToClientConnectedSucceed, new { currentid = sendid, receiveid = receiveid });
            //將消息推送到當前組 (A和B聊天的組) 同樣調用receiveMessage方法
            return Clients.Caller.receiveMessage(msg);
        }

  里面有些代碼是我封裝的,大體看清思路就可以了。下面去讀一下layim.js里的源代碼,找到彈出用戶窗口那一段。

 //彈出聊天窗
        config.chatings = 0;
        node.list.on('click', '.xxim_childnode', function () {
            var othis = $(this);
            //當前登錄用戶id
            var currentid = config.user.id;
            //取得被點擊的用戶id
            var receiveid = othis.data('id');
            //調用signalR封裝的方法,連接服務器,將發送人id,接收人id傳給后台,當前用戶加入組
            csClient.server.ctoc(currentid, receiveid);
            xxim.popchatbox(othis);
        });

  在看一下csClient到底做了什么

 ctoc: function (sid, rid) {
                //調用hub的clientToClient方法
                if (!chat.isConnected(rid)) {
                    //如果沒有連接過,進行連接
                    console.log("用戶 " + rid + "沒有連接過...");
                    _this.proxy.proxyCS.server.clientToClient(sid, rid);
                } else {
                    console.log("用戶 " + rid + "已經連接過了,不需要連接了...");
                }
            },

  這里呢,我另外加了個js對象緩存,防止每次點擊都要重復連接數據庫,當然,頁面刷新之后緩存消失,需要重新 連。到這里我們點擊一下,看看效果。

  好,從上圖可以看到,服務器返回了成功的消息,並且,groupname也是按照順序生成的。這個消息有什么用呢,其實對於客戶端是沒有什么效果的,如果想要提示用戶連接成功或者提示對方是否在線可以用到,這里我不在擴展,只是為了打印看是否連接成功,當連接成功之后呢,用戶就會存在組 G1000010003中了,這時候你發消息如果對面沒有連接的話,他是看不見的。連接成功之后,就要做發消息功能了。繼續回到 CustomServiceHub 類,添加發送消息方法:

/// <summary>
        /// 發送消息 ,服務器接收的是CSChatMessage實體,他包含發送人,接收人,消息內容等信息
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public Task ClientSendMsgToClient(CSChatMessage msg)
        {
            var groupName = MessageUtils.GetGroupName(msg.fromuser.userid.ToString(), msg.touser.userid.ToString());
            /*
            中間處理一下消息直接轉發給(A,B所在組織,即聊天窗口)
            */
            msg.msgtype = CSMessageType.Custom;//消息類型為普通消息
            return Clients.Group(groupName).receiveMessage(msg);
        }

  可以看到,同樣是用到了receiveMessage方法,不過這里呢,調用的Clients.Group(groupName)也就是說,發送的這條消息職能在這個組內的人才能看到,那么組里就兩個人,是不是就實現了1對1 聊天呢,離線留言也支持哦。消息發送成功之后,其實不管對方在不在線,我們都可以做一下本地處理,為了演示消息發送效果,我們不用本地js在發送的時候直接拼接到頁面上,而是client端接收到消息體之后再處理,這樣會看出消息延時效果。(擴展:假如發送的消息很慢的話,就可以在消息體旁邊加一個等待的小菊花,提示發送成功,失敗等。)好,我直接將layim里模擬消息處理的代碼拿出來了,我們看詳細代碼。

   handleCustomMsg: function (result) {
            var log = {};
            //接收人
            var keys = 'one' + result.touser.userid;
            //發送人
            var keys1 = 'one' + result.fromuser.userid;
            //這里一定要注意,這個keys是會變的,也就是說,如果只取一個的話,會造成 log.imarea[0]為undefined的情況,至於為什么會變,看看代碼好好思考一下吧
            log.imarea = $('#layim_area' + keys);//layim_areaone0
            if (!log.imarea.length) {
                log.imarea = $('#layim_area' + keys1);//layim_areaone0
            }
            //拼接html模板
            log.html = function (param, type) {
                return '<li class="' + (type === 'me' ? 'layim_chateme' : '') + '">'
                    + '<div class="layim_chatuser">'
                        + function () {
                            if (type === 'me') {
                                return '<span class="layim_chattime">' + param.time + '</span>'
                                       + '<span class="layim_chatname">' + param.name + '</span>'
                                       + '<img src="' + param.face + '" >';
                            } else {
                                return '<img src="' + param.face + '" >'
                                       + '<span class="layim_chatname">' + param.name + '</span>'
                                       + '<span class="layim_chattime">' + param.time + '</span>';
                            }
                        }()
                    + '</div>'
                    + '<div class="layim_chatsay">' + param.content + '<em class="layim_zero"></em></div>'
                + '</li>';
            };
            //上述代碼還是layim里的代碼,只不過拼接html的時候,參數采用signalR返回的參數
            var type = result.fromuser.userid == currentUser.id ? "me" : "";//如果發送人的id==當前用戶的id,那么這條消息類型為me
            //拼接html 直接調用layim里的代碼
            log.imarea.append(log.html({
                time: result.addtime,
                name: result.fromuser.username,
                face: result.fromuser.photo,
                content: result.msg
            }, type));
            //滾動條處理
            log.imarea.scrollTop(log.imarea[0].scrollHeight);
        },

  好了, 代碼也都處理完了,這里呢有個小插曲,我們怎么確定當前用戶是誰呢?由於我寫的是死數據,所以我就采用隨機生成的方法,然后將用戶保存到 localStorage里面了,這樣當用戶再次打開頁面,還是會取到第一次的用戶,這里呢不多做介紹了。

 /*
        獲取隨機一個用戶
        當用戶第一次登陸就獲取,然后存到本地localStorage中模擬用戶,之后再登錄就直接從緩存里面取
        */
        function getRandomUser() {
            var userKey = "SIGNALR_USER";
            var user = local.get(userKey);
            if (user) { return JSON.parse(user);}
            var userids = [];
            var usernames = ["痴玉", "書筠", "詩冬", "飛楓", "盼玉", "靖菡", "宛雁", "之卉", "凡晴", "書楓", "沛夢"];
            var userphotos = [];
            //添加id,用戶頭像數組
            for (var i = 0; i < 9; i++) {
                userids.push(10000 + i);
                userphotos.push("/photos/00" + i.toString() + ".jpg");
            }
            //取一個random值,自動生成當前用戶
            var random = Math.random().toString().substr(3, 1);
            if (random > 8) { random = 8; }
            var user = {
                name: usernames[random],
                photo: userphotos[random],
                id:userids[random]
            };
            local.set(userKey, JSON.stringify(user));
            return user;
        }
        /*本地存儲*/
        var local = {
            get: function (key) {
                return localStorage.getItem(key);
            },
            set: function (key, value) {
                localStorage.setItem(key, value);
            }
        }

  當然里面有好多需要注意的細節沒有給大家講,具體的可以看詳細代碼,思路基本已經出來了。我在重復一遍吧:第一,點擊用戶,連接服務器,當前用戶加入對應的組。第二,發送消息,調用server端的方法,將消息發送出去后,在推送到組里面去,第三,客戶端接收到消息之后,加到html頁面上,就這么簡單。還有一個細節,注意消息在左邊還是右邊。

  演示一下吧:模擬第一個用戶登錄。(谷歌瀏覽器)

  好,很好聽的名字:飛楓,id為10003,下面第二個用戶登錄,(QQ瀏覽器)

  id為10001,名字為 書筠。那么我們先用第一個用戶點擊  書筠 頭像 打開聊天窗口,然后在用第二個用戶點擊 飛楓頭像打開聊天窗口(由於沒有做歷史記錄,所以離線留言功能暫時不支持,只支持在線)

  打開之后,唉,單身的我只能模擬兩個人聊天玩了。。

 

到此為止呢,1v1聊天就到一段落,僅支持。。。文本,還不知道輸入script有沒有處理。。另外還有一個bug,就是窗口多開的話,應該 信息可能會亂,不是因為 發送亂了,而是,里面有可能有重復的ID導致信息賦html錯誤,我沒測,但是我猜測是這樣的。非常感謝“賢心”大神的web前端通訊框架。本篇到此結束,喜歡的同學點個贊吧。多多轉發哦。下篇預告:最終章-修改1v1聊天bug,添加圖片表情,附件傳送功能。群聊功能實現。

GitHub地址: 

https://github.com/fanpan26/LayIM/tree/master

 


免責聲明!

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



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