Lesktop開源IM移動端:接入LayIM移動端UI


在《開源企業即時通訊和在線客服》中已介紹了Lesktop的桌面模式和Web模式,但是沒有移動端。評論中 dotnetcms.org工作室 提到了LayIM,看了一下官網的演示和文檔,如果用這套LayIM的移動端Lestop也可以輕松開發出移動端web版本。本文將說明如何接入LayIM移動端UI,同時對一些Lesktop的接口進行說明,作為接入其他前端UI的指引。 

移動端功能展示:

 

 

源代碼下載:https://files.cnblogs.com/files/lucc/IM3.1.zip 

源代碼Git: https://github.com/luchuncheng/Lesktop.git

 

在線演示 

注冊用戶&內部人員

Web版:http://im.luchuncheng.com   (注冊登錄后左上角會生成二維碼,掃碼進入移動端版本,創建訪客賬戶與Web端用戶私聊並加為好友)

PC版下載:http://client.luchuncheng.com

 

客服平台(訪客端)

Web版:http://service.luchuncheng.com

PC版http://im.luchuncheng.com/client.ashx?chatwith=4&embedcode=1&createaccount=true 
       (embedcode=1表示顯示ID=1的客服嵌入代碼指定的客服人員,chatwith=4表示啟動和ID=4的客服人員對話窗口)

 

移動端:http://im.luchuncheng.com/mobile.aspx

1、登錄

接入的第一個步驟就是登錄,登錄界面非常簡單,就是兩個文本框和一個登錄按鈕,服務單只需要調用ServerImpl.Instance.Login即可:

int userid = AccountImpl.Instance.GetUserID(user);
// 僅驗證不啟動回話,重定向到default.aspx再啟動回話
ServerImpl.Instance.Login("", Context, userid, false, null, false, 2);

最后第二個參數startSession=false,表示只是設置cookie不啟動會話,移動端的login.aspx僅僅只是驗證,登錄后重定向到default.aspx再啟動會話

最后一個參數device=2表示登錄設備為移動端web版

2、初始化

LayIM初始化時需要好友,群組和分組等信息,因此登錄完成后需要提供這些數據。在此之前先了解一下Lesktop的常用聯系人功能:


如上圖所示,Lesktop允許用戶自己創建常用聯系人分組,支持無限層級,用戶可以將好友或內部人員添加到自建的任何層級的分組中。由於LayIM不支持多層次分組,所以在移動端中將所有常用聯系人不分層級顯示,如下圖所示

除了常用聯系人,還需要一個“我的好友”分組,用於顯示已加自己為好友的注冊用戶。接下來需要了解幾個和分組,好友和群組相關的接口:

(1) ServerImpl.Instance.CommonStorageImpl.GetCategories

GetCategories用於獲取用戶創建的所有分組,返回值是一個DataRowCollection,每一行包括5個列:

ID   類別ID
UserID  添加到該列表的聯系人ID
Name  分組名稱
ParentID  父級分組ID(移動端用不上)
Type  分組類別(1:聯系人,2:群組,3:部門,移動端只用到聯系人的)

  

(2) ServerImpl.Instance.CommonStorageImpl.GetCategoryItems

GetCategoryItems獲取和分組相關的所有聯系人,群組和部門ID(移動端只用到聯系人),返回值為DataRowCollection,每一行包括4個列:

UserID 創建該分類的用戶ID
CategoryID 類別ID
ItemID 聯系人,群組或部門ID(移動端只用到聯系人)
CategoryType 分組類別(1:聯系人,2:群組,3:部門,移動端只用到聯系人的)

 

(3) Category_CH.GetAccountInfos

GetCategoryItems只能獲取和分組相關的所有聯系人的ID,還需要調用GetAccountInfos才能獲取到聯系人的全部詳細信息,返回值為AccountInfo數組,AccountInfo屬性如下:

ID 用戶ID
Name 登錄名
Nickname 昵稱
Type 類型,0-聯系人,1-群組
SubType 子類型:0-注冊用戶,1-管理員創建的內部人員
IsTemp 是否為臨時用戶,即訪客
IsDeleted 是否已被刪除
HeadIMG 頭像

 

 (4) AccountImpl.Instance.GetVisibleUsersDetails

GetVisibleUsersDetails用於獲取所有和指定用戶相關的聯系人和群組,包括所有由管理員創建的內部人員,已加自己為好友的注冊用戶,自己創建和加入的所有群組和自己創建或被拉進去的多人會話,這部分數據主要是為LayIM提供“我的好友”分組和群聊,返回值為一個Hashtable,每個項的值為AccountInfo。

 

(5)ServerImpl.Instance.GetCurrentUser

GetCurrentUser用戶獲取當前用戶詳細信息(AccountInfo)

 

以上5個接口已經獲取到了所有LayIM初始化需要的數據,打包成json,“賦值”給頁面的MobileInitParams全局變量即可:

namespace Core.Web
{
    public class Mobile_Default : System.Web.UI.Page
    {
        string init_params_ = "{}";

        protected void Page_Load(object sender, EventArgs e)
        {
            AccountInfo current_user = ServerImpl.Instance.GetCurrentUser(Context);
            if(current_user != null)
            {
                String sessionId = Guid.NewGuid().ToString().ToUpper();
                ServerImpl.Instance.Login(sessionId, Context, current_user.ID, false, DateTime.Now.AddDays(7), true, 2);

                DataRowCollection categories = ServerImpl.Instance.CommonStorageImpl.GetCategories(current_user.ID);

                DataRowCollection items = ServerImpl.Instance.CommonStorageImpl.GetCategoryItems(current_user.ID);
                Hashtable users = Category_CH.GetAccountInfos(items);

                AccountInfo[] visible_users = AccountImpl.Instance.GetVisibleUsersDetails(current_user.Name);

                init_params_ = Utility.RenderHashJson(
                    "Result", true,
                    "IsLogin", true,
                    "UserInfo", current_user.DetailsJson,
                    "SessionID", sessionId,
                    "CompanyInfo", ServerImpl.Instance.CommonStorageImpl.GetCompanyInfo(),
                    "Categories", categories,
                    "CategorieItems", items,
                    "CategorieUsers", users,
                    "VisibleUsers", visible_users
                );
            }
            else
            {
                Response.Redirect("login.aspx");
            }
        }

        public string InitParams
        {
            get { return init_params_; }
        }
    }
}

 

  

頁面加載后,LayIM_Init里面就可以通過MobileInitParams獲取到這些數據,LayIM初始化參數請看官網文檔,以下函數用於將Lesktop的數據轉換成LayIM需要的格式:

// 獲取分組和聯系人
function GetFriends()
{
    var friends = [];
    for (var i = 0; i < window.MobileInitParams.Categories.length; i++)
    {
        var category = window.MobileInitParams.Categories[i];
        if (category.Type == 1)
        {
            // Type=1為常用聯系人類別,將所有常用聯系人類別(不分層次)顯示為LayIM的分組
            var groupid = category.ID + 10000;
            var group = {
                "groupname": category.Name,
                "id": groupid.toString(),
                "online": 0,
                "list": []
            };
            var user_count = 0;
            var online_count = 0;
            for (var j = 0; j < window.MobileInitParams.CategorieItems.length; j++)
            {
                // 從CategorieItems中獲取該分組所有聯系人ID
                var item = window.MobileInitParams.CategorieItems[j];
                if (item.CategoryID == category.ID)
                {
                    // 通過聯系人ID從CategorieUsers中獲取聯系人詳細信息
                    var friend_info = window.MobileInitParams.CategorieUsers[item.ItemID.toString()];
                    if(friend_info != undefined)
                    {
                        group.list.push({
                            "username": friend_info.Nickname,
                            "id": friend_info.ID.toString(),
                            "avatar": Core.CreateHeadImgUrl(friend_info.ID, 150, false, friend_info.HeadIMG),
                            "sign": ""
                        });
                        user_count++;
                        if (friend_info.State == "Online")
                        {
                            online_count++;
                        }
                    }
                }
            }
            if (user_count > 0)
            {
                friends.push(group);
            }
        }
    }
    
    var grou_myfriend = {
        "groupname": "我的好友",
        "id": LayIMGroup_MyFriend,
        "online": 0,
        "list": []
    }
    
    var current_user = window.MobileInitParams.UserInfo;


    // 獲取所有好友並顯示到好友分組
    for (var i = 0; i < window.MobileInitParams.VisibleUsers.length; i++)
    {
        var user = window.MobileInitParams.VisibleUsers[i];
        if (user.Type == 0 && ((current_user.SubType == 1 && user.SubType == 0) || current_user.SubType == 0))
        {
            // 內部人員(SubType=1)顯示注冊用戶並添加自己為好友的,不包括其他內部人員
            // 注冊用戶(SubType=0)顯示添加自己為好友的其他注冊用戶和內部用戶
            grou_myfriend.list.push({
                "username": user.Nickname,
                "id": user.ID.toString(),
                "avatar": Core.CreateHeadImgUrl(user.ID, 150, false, user.HeadIMG),
                "sign": ""
            });
        }
    }

    friends.push(grou_myfriend);

    friends.push({
        "groupname": "其他聯系人",
        "id": LayIMGroup_Other,
        "online": 0,
        "list": []
    });

    return friends;
}

 

// 獲取群聊
function GetGroups()
{
    // 獲取所有群組和多人會話
    var groups = [];
    for (var i = 0; i < window.MobileInitParams.VisibleUsers.length; i++)
    {
        var user = window.MobileInitParams.VisibleUsers[i];
        if(user.Type == 1)
        {
            groups.push({
                "groupname": user.Nickname,
                "id": user.ID.toString(),
                "avatar": Core.CreateGroupImgUrl(user.HeadIMG, user.IsTemp)
            });
        }
    }
    return groups;
}  

3、接收消息

此次為了接入LayIM,加了一個全局委托Core.OnNewMessage,每當收到新消息是會調用該委托,如果需要監聽新消息,只需要附加一個處理函數即可

function LayIM_OnNewMessage(msg)
{
}
// 監聽新消息
Core.OnNewMessage.Attach(LayIM_OnNewMessage);

 

由於收到的消息可能是web或pc端發送的,包含LayIM消息面板不支持的富文本,因此需要先處理掉所有HTML tag,此外還需要處理文件標志([FILE:...])生成下載鏈接,完整代碼如下:

function LayIM_ParseMsg(text)
{
    var newText = text;
    try
    {
        // 處理掉HTML開始TAG
        newText = text.toString().replace(
            /<([a-zA-Z0-9]+)([\s]+)[^<>]*>/ig,
            function (html, tag)
            {
                if (tag.toLowerCase() == "img")
                {
                    var filename = Core.GetFileNameFromImgTag(html);
                    if (filename != "")
                    {
                        // Lesktop服務器上的文件,重新加上分辨率限制參數,改為下載縮略圖,鏈接到原圖
                        var url = Core.CreateDownloadUrl(filename);
                        return String.format("a({0})[img[{0}&MaxWidth=450&MaxHeight=800]]", url);
                    }
                    else
                    {
                        // 外源圖片,改成超鏈接,防止下載圖片浪費流量
                        var src = Core.GetSrcFromImgTag(html);
                        return String.format("a({0})[{1}]", src, "&nbsp;圖片&nbsp;");
                    }
                }
                return "";
            }
        )
        .replace(
            /\x5BFILE:([^\x5B\x5D]+)\x5D/ig,
            function (filetag, filepath)
            {
                // 提取文件消息,改為視頻,音頻或文件
                var path = unescape(filepath)
                var ext = Core.Path.GetFileExtension(path).toLowerCase();
                if (ext == ".mp4" || ext == ".mov")
                {
                    return String.format("video[{0}]", Core.CreateDownloadUrl(path), Core.Path.GetFileName(path));
                }
                else if (ext == "mp3")
                {
                    return String.format("audio[{0}]", Core.CreateDownloadUrl(path), Core.Path.GetFileName(path));
                }
                else
                {
                    return String.format("file({0})[{1}]", Core.CreateDownloadUrl(path), Core.Path.GetFileName(path));
                }
            }
        )
        .replace(
            /<([a-zA-Z0-9]+)[\x2F]{0,1}>/ig,
            function (html, tag)
            {
                // 清理<br/>等
                return "";
            }
        )
        .replace(
            /<\/([a-zA-Z0-9]+)>/ig,
            function (html, tag)
            {
                // 清理HTML結束TAG
                return "";
            }
        );
    }
    catch(ex)
    {
        newText += " ERROR:";
        newText += ex.message;
    }
    return newText;
}

function LayIM_OnNewMessage(msg)
{
    // msg.Sender, msg.Receiver只包括最基本的ID,Name,需重新獲取詳細信息
    var sender_info = Core.AccountData.GetAccountInfo(msg.Sender.ID);
    if (sender_info == null) sender_info = msg.Sender;
    var receiver_info = Core.AccountData.GetAccountInfo(msg.Receiver.ID);
    if (receiver_info == null) receiver_info = msg.Receiver;

    if (msg.Receiver.Type == 0)
    {
        // 私聊消息
        if (!LayIM_UserExists(sender_info.ID.toString()))
        {
            // 分組列表中不包括消息發送者,將發送者加入到其他聯系人分組
            layim.addList({
                type: 'friend',
                "username": sender_info.Nickname,
                "id": sender_info.ID.toString(),
                "groupid": LayIMGroup_Other,
                "avatar": Core.CreateHeadImgUrl(sender_info.ID, 150, false, sender_info.HeadIMG),
                "sign": ""
            });
        }
        // 顯示到LayIM消息面板
        layim.getMessage({
            username: sender_info.Nickname,
            avatar: Core.CreateHeadImgUrl(msg.Sender.ID, 150, false, sender_info.HeadIMG),
            id: msg.Sender.ID.toString(),
            type: "friend",
            cid: msg.ID.toString(),
            content: LayIM_ParseMsg(msg.Content)
        });
    }
    else if (msg.Receiver.Type == 1)
    {
        // 群消息
        if (!LayIM_GroupExists(receiver_info.ID.toString()))
        {
            // 群聊列表中不包括該群,加入到群聊中
            layim.addList({
                "type": "group",
                "groupname": receiver_info.Nickname,
                "id": receiver_info.ID.toString(),
                "avatar": Core.CreateGroupImgUrl(receiver_info.HeadIMG, receiver_info.IsTemp)
            });
        }
        // 顯示到LayIM消息面板
        layim.getMessage({
            username: sender_info.Nickname,
            avatar: Core.CreateHeadImgUrl(msg.Sender.ID, 150, false, sender_info.HeadIMG),
            id: msg.Receiver.ID.toString(),
            type: "group",
            cid: msg.ID.toString(),
            content: LayIM_ParseMsg(msg.Content)
        });
    }
}

// 監聽新消息
Core.OnNewMessage.Attach(LayIM_OnNewMessage);

4、發送消息

發送消息只需要調用服務端的WebIM.NewMessage方法即可,發送前,需要對消息進行預處理,把LayIM的標志(圖片,文件和表情)轉換成HTML,還需要調用Core.TranslateMessage,該函數用於將消息中的圖片(<img ...>),文件([FILE:...])轉換成服務端可以處理的附件,具體代碼如下:

function LayIM_SendMsg_GetFileName(fileurl)
{
    var filename_regex = /FileName\=([^\s\x28\x29\x26]+)/ig;
    filename_regex.lastIndex = 0
    var ret = filename_regex.exec(fileurl);
    if (ret == null || ret.length <= 1)
    {
        return "";
    }
    return ret[1];
}

function LayIM_SendMsg(data)
{
    var msgdata = {
        Action: "NewMessage",
        Sender: parseInt(data.mine.id, 10),
        Receiver: parseInt(data.to.id, 10),
        DelTmpFile: 0,
        Content: ""
    };

    var content = data.mine.content;
    // 轉換圖片消息
    content = content.replace(
        /img\x5B([^\x5B\x5D]+)\x5D/ig,
        function(imgtext, src)
        {
            var filename = LayIM_SendMsg_GetFileName(src);
            return String.format('<img src="{0}">', Core.CreateDownloadUrl(filename));
        }
    );
    // 轉換文件消息
    content = content.replace(
        /file\x28([^\x28\x29]+)\x29\x5B([^\x5B\x5D]+)\x5D/ig,
        function (filetext, fileurl, ope)
        {
            var path = unescape(LayIM_SendMsg_GetFileName(fileurl));
            return Core.CreateFileHtml([path]);
        }
    );
    // 將消息中的圖片(<img ...>),文件([FILE:...])轉換成服務端可以處理的附件
    content = Core.TranslateMessage(content, msgdata);
    // 轉換表情
    content = content.replace(
        /face\[([^\s\[\]]+?)\]/g,
        function (face, face_type)
        {
            var face_file = LayIM_FaceToFile[face_type];
            if(face_file != undefined)
            {
                return String.format('<img src="{0}/{1}">', Core.GetUrl("layim/images/face"), face_file);
            }
        }
    );

    msgdata.Content = content;

    Core.SendCommand(
        function (ret)
        {
            var message = ret;
        },
        function (ex)
        {
            var errmsg = String.format("由於網絡原因,消息\"{0}\"發送失敗,錯誤信息:{1}", text, ex.Message);
        },
        Core.Utility.RenderJson(msgdata), "Core.Web WebIM", false
    );
}

5、異常狀態處理

Lesktop有以下幾種異常狀態:

(1) 在其他瀏覽器或客戶端登錄,此時會收到強制下線通知(GLOBAL:OFFLINE)

(2) 服務端已升級,為簡化服務端開發,Lesktop服務端要求客戶端和前端都用對應的最新版本,不兼容舊版本。服務端網站升級后,升級前未退出重新連接上的客戶端和web端都會收到不兼容異常通知(IncompatibleException)。PC需要重啟升級,WEB端需要重登陸(發布版所有靜態資源都放在名稱為版本號的文件夾中,重登陸不會讀取到緩存的資源)

(3) 驗證身份異常,服務端網站可能會因為某種原因重新啟動,此時會重新生成cookie加密密鑰,會導致已在線的客戶端無法從cookie獲取身份信息,此時客戶端會收到驗證異常通知(UnauthorizedException)

移動端處理異常方法很簡單,收到異常通知后,立刻重定向到offline.aspx頁面,顯示異常消息和重新登錄按鈕,如下圖所示:
   

至此,接入LayIM的工作就基本完成,前端代碼基本都在mobile.js中。

因為LayIM不是開源的,因此git上不包括LayIM的源代碼,需要自行購買,然后將src下的所有文件放到CurrentVersion/layim下:


免責聲明!

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



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