zoukankan      html  css  js  c++  java
  • 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:([^x5Bx5D]+)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=([^sx28x29x26]+)/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(
            /imgx5B([^x5Bx5D]+)x5D/ig,
            function(imgtext, src)
            {
                var filename = LayIM_SendMsg_GetFileName(src);
                return String.format('<img src="{0}">', Core.CreateDownloadUrl(filename));
            }
        );
        // 转换文件消息
        content = content.replace(
            /filex28([^x28x29]+)x29x5B([^x5Bx5D]+)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下:

  • 相关阅读:
    Codeforces Round #257 (Div. 2) E题:Jzzhu and Apples 模拟
    【浅墨Unity3D Shader编程】之二 雪山飞狐篇:Unity的基本Shader框架写法&amp;颜色、光照与材质
    poj 1741 楼教主男人八题之中的一个:树分治
    Localhost 回环IP 127.0.0.1
    网络营销着陆页:怎么让游客成顾客?
    窗体的消息处理
    运行Java -jar somefile.jar时发生了什么(二)
    Why is processing a sorted array faster than an unsorted array(Stackoverflow)
    NYOJ 330 一个简单的数学题【数学题】
    Java 实现的断点下载
  • 原文地址:https://www.cnblogs.com/lucc/p/13779485.html
Copyright © 2011-2022 走看看