构建WebIM聊天程序

最近研究了一下WebIM,现将学习笔记记录于此。

一、WebIM采用技术

本篇实现的WebIM是对现有技术的整合,它包含了如下技术:
seajs:用于JavaScript模块化编程,seajs简介及用途可以看这儿:http://blog.csdn.net/fengshuiyue/article/details/51177458
layim:阿里大牛贤心制作的一款webim聊天界面,很美观,源码下载地为http://sentsin.com/layui/layim/
JsJac:基于jabber/xmpp的javascript实现,在此完成与后台servlet(JabberHttpBind)的数据交互。下载地址为https://github.com/sstrigler/JSJaC/
2)后台:
JabberHTTPBind:用于和Openfire进行通信,下载地址为http://stefan-strigler.de/jhb/
3)服务器:
openfire:开源的聊天服务器

二、通信技术简介

1.xmpp

1)webIM采用的是标准通信协议XMPP(Extensible Messageing and Presence Protocol:可扩展消息与存在协议),它目前是主流的四种IM(IM:instant messaging,即时消息)协议之一,其他三种分别为:即时信息和空间协议(IMPP)、空间和即时信息协议(PRIM)、针对即时通讯和空间平衡扩充的进程开始协议SIP(SIMPLE)。
2)xmpp是采用TCP进行传输的xml流,这不同于QQ的方式(QQ是用二进制流进行传输的)、不同于MSN的方式(MSN采用的是纯文本指令加空格加参数加换行苻的方式),下图是传输XMPP经过的协议栈:
构建WebIM聊天程序_第1张图片
3)xmpp地址格式:
一个实体在xmpp网络结构中被称为一个接点,它有唯一的标示符jabber identifier(JID),即实体地址,用来表示一个Jabber用户,但是也可以表示其他内容,例如一个聊天室。一个有效的JID包括下列元素:

域名(domain identifier);
节点(node identifier);
源(resource identifier);

JID完整格式: node@domain/resource
4)xmpp格式
xmpp中定义了3个顶层XML元素:

Message:用于在两个jabber用户之间发送信息
Presence:用来表明用户的状态
IQ:一种请求/响应机制,从一个实体从发送请求,另外一个实体接受请求,并进行响应.例如,client在stream的上下文中插入一个元素,向Server请求得到自己的好友列表,Server返回一个里面是请求的结果.

① Message实例

发送端发送的信息:



<body xmlns="http://jabber.org/protocol/httpbind" rid="424681" sid="Okig9EHLCTA15a-BZ3NRDqOW" key="08ca4f4beeaa588cd170d700fa78a01c8d469df1">
  <message xmlns="jabber:client"
           to="webchattest@openfire402/webim" 
           type="chat">
    <body>您好,webchattestbody>
  message>
body>

接收端接收到的信息:




<body xmlns="http://jabber.org/protocol/httpbind">
  <message xmlns="jabber:client"
           from="admin@openfire402/webim" 
           to="webchattest@openfire402/webim" 
           type="chat">
    <body>您好,webchattestbody>
    <delay xmlns="urn:xmpp:delay" from="openfire402" stamp="2016-06-11T04:52:23.153Z">delay>
  message>
body>



<body xmlns="http://jabber.org/protocol/httpbind">
  <message xmlns="jabber:client" 
           from="admin@openfire402/webim" 
           to="webchattest@openfire402/webim" 
           type="chat">
    <body>webchattest上线后admin发的信息body>
  message>
body>

message属性介绍:

from属性:设置消息发送方自身的FullJID(node@domain/resource)
to属性:设置消息接收方的Bare JID(node@domain),通常第一次发送方无法确知接收方的Full JID,通过服务器中转路由时由服务器根据Base JID映射接收方的Full JID;但如果这个消息是在回复之前接收到的消息,则to属性应该包含对方完整的Full JID;如此设计的好处在于:当to属性设定为Full JID时可以帮助服务器省却了接收者资源定位(接入定位),在一个IM服务集群环境中这种定位通常意味着一次分布式缓存读取操作。
type属性:XMPP约定了type的枚举值,包括:

  • chat:表明在一个点对点会话环境中的聊天消息。
  • groupchat:表明在一个多人会话环境中的聊天消息。
  • headline: 通常一些系统通知、警告、实时数据更新采用此类型,这类消息不期待客户端回复或响应,具有很高的实时性,不需要离线存储。
  • normal: 默认的消息类型(缺乏type属性时),通常表达一种要求接收方必须确认的消息,一般用于系统提示强制用户确认或取消等。
  • error: 表示一个错误消息,可能由服务端发送给客户端,也可能是另一个客户接收端回应给客户发送端,此类消息也不需要离线存储。

message子元素:

<subject>表明一个消息主题,通常客户端实现显示在聊天窗口标题栏处
<body>消息内容部分
<subject><subject>都允许包含多个元素标签,不同的标签根据xml:lang表达了不同的语言(XMPP可是一个国际化协议)

下面这是完整的message消息格式:

<message
    from='node@domain/resource'
    to='node@domain'
    type='chat'
    xml:lang='en'>
  <subject>hello!subject>
  <subject xml:lang='zh'>
    你好
  subject>
  <body>welcome to meet youbody>
  <body xml:lang='zh'>
     很高兴认识你
  body>
message>

② Presence
用来表明用户的状态,如:online、away、dnd(请勿打扰)等。当用户离线或改变自己的状态时,就会在stream的上下文中插入一个Presence元素,来表明自身的状态.结构如下所示:

  <presence>
    from='node@domain/resource'
    to='node@domain'
    <status>onlinestatus>
presence>

presence 元素可以取下面几种值:

  • probe :用于向接受消息方发送特殊的请求
  • subscribe:当接受方状态改变时,自动向发送方发送presence信息。

③ IQ

一种请求/响应机制,从一个实体从发送请求,另外一个实体接受请求,并进行响应.例如,client在stream的上下文中插入一个元素,向Server请求得到自己的好友列表,Server返回一个里面是请求的结果。结构如下所示:

<iq xmlns="jabber:client" 
    from="openfire402" 
    id="537-29" 
    to="webchattest@openfire402/webim" 
    type="get">
  <ping xmlns="urn:xmpp:ping">ping>
iq>

IQ 主要的属性是type。包括:

  • get:获取当前域值;
  • set:设置或替换get查询的值;
  • result:说明成功的响应了先前的查询;
  • error:查询和响应中出现的错误;

2.openfire

本文采用的是openfire4.0.2版本的,openfire的安装配置简单记录如下:
1)在官网http://www.igniterealtime.org/downloads/index.jsp#openfire 下载openfire,安装即可
2)在“openfire安装目录/bin”目录下运行openfired.exe程序,运行效果如下图:
openfired
3)第一次登陆http://localhost:9090/ 会进入openfire配置页面,基本上使用默认值即可,下面介绍需要注意的几个地方
a.服务器设置
需要注意的是 的设置,这块儿会关系到 JID(node@domain/resource) domain的取值
构建WebIM聊天程序_第2张图片
b.数据库设置
如果选择 标准数据库连接 标准数据库连接 ,则需要导入openfire的sql文件,该文件存储在:openfire安装目录\resources\database 目录下,如下图:
构建WebIM聊天程序_第3张图片

三、webIM实现

由于使用的都是现成的技术,故本篇的webIM重在前端的编程

1.封装JsJac通信类

该类采用了该博主(http://www.cnblogs.com/hoojo)js端 通信类,该类定义如下:

define(function(require, exports,module){
    require('../base_module/jsjac/jsjac.js');
    require('../base_module/jquery-1.11.1.min.js');
    var util = require('../util/util.js');

    var 
        chatConnConfig = {
            httpbase: '',//window.contextPath + "/JHB/", //请求后台http-bind服务器url
            domain: '', //window["serverDomin"], //"192.168.5.231", // 192.168.5.231 当前有效域名
            username: "",
            pass: "",
            timerval: 2000, // 设置请求超时
            resource: "webim", // 链接资源标识
            register: false // 是否注册
        },
        chatObj = null, //聊天实体
        //writeReceiveMessage = null;
        chatConnection = {},
        onlineStatus = {
            available: '在线',
            chat: '欢迎聊天',
            away: '离开',
            xa: '不可用',
            dnd: '请勿打扰',
            invisible: '隐身',
            unavailable: '离线'
        };
        chatConnection.connected = function(){

        }
    /**
     * chatConnCfg = {
     *      httpbase: '',//window.contextPath + "/JHB/", //请求后台http-bind服务器url
            domain: '', //window["serverDomin"], //"192.168.5.231", // 192.168.5.231 当前有效域名
            timerval: 2000, // 设置请求超时
            resource: ''
       }
     */
    init = function(chatConnCfg,chatobjparam){
        chatObj = chatobjparam;
        //chatConnConfig.oDbg = new JSJaCConsoleLogger(4);
        if(chatConnCfg){
            $.extend(chatConnConfig, chatConnConfig, chatConnCfg);
            //chatConnConfig.httpbase = chatConnCfg.httpbase;
            //chatConnConfig.domain = chatConnCfg.domain;
            //chatConnConfig.resource = chatConnCfg.resource;
            //chatConnConfig.timerval = chatConnCfg.timerval;
            //chatConnConfig.writeReceiveMessage = chatConnCfg.writeReceiveMessage;
        }

        // Debugger plugin
        if (typeof (Debugger) == "function") {
            chatConnConfig.oDbg = new Debugger(2, chatConnConfig.resource);
            chatConnConfig.oDbg.start();
        } else {
            chatConnConfig.oDbg = function () {
            };
            chatConnConfig.oDbg.log = function () {
            };
        }
        try {
            if (JSJaCCookie.read("btype").getValue() == "binding") {
                chatConnection = new JSJaCHttpBindingConnection({ "oDbg": chatConnConfig.oDbg});
                //rdbgerjac.chat.setupEvent(remote.connection);
                setupEvent(chatConnection)
                if (chatConnection.resume()) {
                }
            } 
        } catch (e) {

        } // reading cookie failed - never mind
    }

    /**
     * loginPerson为对象
     * loginPerson = {
            username:'',
            password:'',
            register:false  //是否注册改用户
       }
     */
    login = function (loginPerson) {
        try {
            // 链接参数
            var connectionConfig = chatConnConfig;
            // Debugger console
            if (typeof (oDbg) != "undefined") {
                connectionConfig.oDbg = oDbg;
            }
            chatConnection = new JSJaCHttpBindingConnection(connectionConfig);
            // 安装(注册)Connection事件模型
            setupEvent(chatConnection);
            // setup args for connect method
            if (loginPerson) {
                connectionConfig.username = loginPerson.username;
                connectionConfig.pass = loginPerson.password;
                connectionConfig.register = loginPerson.register;
            }
            // 连接服务器
            chatConnection.connect(connectionConfig);

            changeStatus("away", "away", 1, "chat");
        } catch (e) {
            //登录错误信息显示
            alert(e.toString());
        } finally {
            return false;
        }
    },

    setupEvent = function (con) {
        con.registerHandler('message', handleMessage);
        con.registerHandler('presence', handlePresence);
        con.registerHandler('iq', handleIQ);
        con.registerHandler('onconnect', handleConnected);
        con.registerHandler('onerror', handleError);
        con.registerHandler('status_changed', handleStatusChanged);
        con.registerHandler('ondisconnect', handleDisconnected);

        con.registerIQGet('query', NS_VERSION, handleIqVersion);
        con.registerIQGet('query', NS_TIME, handleIqTime);
    },

    // 改变用户状态
    changeStatus = function (type, status, priority, show) {
        type = type || "unavailable";
        status = status || "online";
        priority = priority || "1";
        show = show || "chat";
        var presence = new JSJaCPresence();
        presence.setType(type); // unavailable invisible
        presence.setStatus(status); // online
        presence.setPriority(priority); // 1
        presence.setShow(show); // chat
        if (chatConnection) {
            chatConnection.send(presence);
        }
    },

    // 发送远程消息
    sendMessage = function (msg, to) {
        try {
            if (msg == "") {
                return false;
            }

            var tosendstr = "";
            if (to) {
                tosendstr= to + '@' + chatConnConfig.domain 
                              + '/' +chatConnConfig.resource;
            } else {
                // 向chat接收信息区域写消息
                if (chatObj.writeReceiveMessage) {
//                    var html = "你没有指定发送者的名称";
//                    var msgobj = {
//                          receiveType:'noSendUser',
//                          objtime:'',
//                          objname:'',
//                          objface:'',
//                          content:html
//                    };
//                    chatObj.writeReceiveMessage(msgobj);
                }
                return false;
            }
            // 构建jsjac的message对象
            var message = new JSJaCMessage();
            message.setTo(new JSJaCJID(tosendstr));
            message.setType("chat"); // 单独聊天,默认为广播模式
            message.setBody(msg);
            // 发送消息
            chatConnection.send(message);
            return false;
        } catch (e) {
            //var html = "
Error: " + e.message + "
";
alert(e.message); return false; } }, // 退出、断开链接 logout = function () { var presence = new JSJaCPresence(); presence.setType("unavailable"); if (chatConnection) { chatConnection.send(presence); chatConnection.disconnect(); } }, errorHandler = function (event) { var e = event || window.event; //alert(e);显示错误信息 if (chatConnection && chatConnection.connected()) { chatConnection.disconnect(); } return false; }, unloadHandler = function () { var con = chatConnection; if (typeof con != "undefined" && con && con.connected()) { // save backend type if (con._hold) { // must be binding (new JSJaCCookie("btype", "binding")).write(); } if (con.suspend) { con.suspend(); } } }, // 重新连接服务器 reconnection = function () { chatConnConfig.register = false; if (chatConnection.connected()) { chatConnection.disconnect(); } login(); } /* ########################### Handler Event ############################# */ handleIQ = function (aIQ) { chatConnection.send(aIQ.errorReply(ERR_FEATURE_NOT_IMPLEMENTED)); var content = aIQ.xml().htmlEnc(); var nowstr = util.getDatetime(); var msgobj = { receiveType:'handleIQ', objtime:nowstr, objname:'', objface:'', content:content }; chatObj.writeReceiveMessage(msgobj); }, handleMessage = function (aJSJaCPacket) { var user = aJSJaCPacket.getFromJID().toString(); var userName = user.split("@")[0]; var content = aJSJaCPacket.getBody(); var nowstr = util.getDatetime(); var msgobj = { receiveType:'handleMessage', objtime:nowstr, objname:userName, objface:'', content:content }; if(content == undefined || content == null || content == ''){ }else{ chatObj.writeReceiveMessage(msgobj); } }, handlePresence = function (aJSJaCPacket) { var user = aJSJaCPacket.getFromJID(); var userName = user.toString().split("@")[0]; var status = ''; var html = "
"; if (!aJSJaCPacket.getType() && !aJSJaCPacket.getShow()) { html += "" + userName + " 上线了."; } else { html += "" + userName + " 设置 presence 为: "; if (aJSJaCPacket.getType()) { html += aJSJaCPacket.getType() + "."; } else { html += aJSJaCPacket.getShow() + "."; } if (aJSJaCPacket.getStatus()) { html += " (" + aJSJaCPacket.getStatus().htmlEnc() + ")"; } } html += "
"
; // 向chat接收信息区域写消息 var nowstr = util.getDatetime(); var msgobj = { receiveType:'handlePresence', objtime:nowstr, objname:userName, objface:'', content:html }; chatObj.writeReceiveMessage(msgobj); }, handleError = function (event) { var e = event || window.event; var html = "An error occured:
"
+ ("Code: " + e.getAttribute("code") + "\nType: " + e.getAttribute("type") + "\nCondition: " + e.firstChild.nodeName).htmlEnc(); var content = ""; switch (e.getAttribute("code")) { case "401": content = "登陆验证失败!"; break; // 当注册发现重复,表明该用户已经注册,那么直接进行登陆操作 case "409": //content = "注册失败!\n\n请换一个用户名!"; chatConnection.reconnection(); break; case "503": content = "无法连接到IM服务器,请检查相关配置!"; break; case "500": var content = "服务器内部错误!\n\n连接断开!
重新连接"
; // 向chat接收信息区域写消息 break; default: break; } if (content) { var nowstr = util.getDatetime(); var msgobj = { receiveType:'handleError', objtime:nowstr, objname:chatConnConfig.username, objface:'', content:content }; chatObj.writeReceiveMessage(msgobj); } if (chatConnection.connected()) { chatConnection.disconnect(); } }, // 状态变化触发事件 handleStatusChanged = function (status) { //remote.console.info("
当前用户状态: " + status + "
");
//remote.dbger.log("当前用户状态: " + status); if (status == "disconnecting") { var html = "你离线了!"; // 向chat接收信息区域写消息 var nowstr = util.getDatetime(); var msgobj = { receiveType:'handleStatusChanged', objtime:nowstr, objname:chatConnConfig.username, objface:'', content:html }; chatObj.writeReceiveMessage(msgobj); } }, // 建立链接触发事件方法 handleConnected = function () { chatConnection.send(new JSJaCPresence()); }, // 断开链接触发事件方法 handleDisconnected = function () { }, handleIqVersion = function (iq) { chatConnection.send(iq.reply([ iq.buildNode("name", chatConnConfig.resource), iq.buildNode("version", JSJaC.Version), iq.buildNode("os", navigator.userAgent) ])); return true; }, handleIqTime = function (iq) { var now = new Date(); chatConnection.send(iq.reply([ iq.buildNode("display", now.toLocaleString()), iq.buildNode("utc", now.jabberDate()), iq.buildNode("tz", now.toLocaleString().substring(now.toLocaleString().lastIndexOf(" ") + 1)) ])); return true; } //exports.writeReceiveMessage = writeReceiveMessage; exports.init = init; exports.login = login; exports.sendMessage = sendMessage; exports.changeStatus = changeStatus; exports.logout = logout; exports.chatObj = chatObj; $(window).bind({ unload: unloadHandler, error: errorHandler, beforeunload: logout }); });

2.调用layim.js实现聊天界面类

define(function(require, exports,module){

    //require('../base_module/lay/lib.js');
    require('../base_module/jquery-1.11.1.min.js');
    require('../base_module/lay/layer/layer.min.js');
    serverChat = require('./serverchat.js');
    var util = require('../util/util.js');

    var config = {
        msgurl: '私信地址',
        chatlogurl: '聊天记录url前缀',
        aniTime: 200,
        right: -232,
        api: {
            friend: 'js/layim/friend.json', //好友列表接口
            group: 'js/layim/group.json', //群组列表接口 
            chatlog: 'js/layim/chatlog.json', //聊天记录接口
            groups: 'js/layim/groups.json', //群组成员接口
            sendurl: '' //发送消息接口
        },
        user: { //当前用户信息
            name: '测试用户',
            face: 'http://tp3.sinaimg.cn/3850354634/180/40048989275/0'
        },

        //自动回复内置文案,也可动态读取数据库配置
        autoReplay: [
            '您好,我现在有事不在,一会再和您联系。', 
            '你没发错吧?',
            '洗澡中,请勿打扰,偷窥请购票,个体四十,团体八折,订票电话:一般人我不告诉他!',
            '你好,我是主人的美女秘书,有什么事就跟我说吧,等他回来我会转告他的。',
            '我正在拉磨,没法招呼您,因为我们家毛驴去动物保护协会把我告了,说我剥夺它休产假的权利。',
            '<(@ ̄︶ ̄@)>',
            '你要和我说话?你真的要和我说话?你确定自己想说吗?你一定非说不可吗?那你说吧,这是自动回复。',
            '主人正在开机自检,键盘鼠标看好机会出去凉快去了,我是他的电冰箱,我打字比较慢,你慢慢说,别急……',
            '(*^__^*) 嘻嘻,是贤心吗?'
        ],


        chating: {},
        hosts: (function(){
            var dk = location.href.match(/\:\d+/);
            dk = dk ? dk[0] : '';
            return 'http://' + document.domain + dk + '/';
        })(),
        json: function(url, data, callback, error){
            return $.ajax({
                type: 'POST',
                url: url,
                data: data,
                dataType: 'json',
                success: callback,
                error: error
            });
        },
        stopMP: function(e){
            e ? e.stopPropagation() : e.cancelBubble = true;
        }
    }, dom = [$(window), $(document), $('html'), $('body')], xxim = {};

    /**
     * obj = {
        receiveType:  //handleIQ,handleMessage,handlePresence,handleError,handleStatusChanged
        content:
       }
     */

    //主界面tab
    xxim.tabs = function(index){
        var node = xxim.node;
        node.tabs.eq(index).addClass('xxim_tabnow').siblings().removeClass('xxim_tabnow');
        node.list.eq(index).show().siblings('.xxim_list').hide();
        if(node.list.eq(index).find('li').length === 0){
            xxim.getDates(index);
        }
    };

    //节点
    xxim.renode = function(){
        var node = xxim.node = {
            tabs: $('#xxim_tabs>span'),
            list: $('.xxim_list'),
            online: $('.xxim_online'),
            setonline: $('.xxim_setonline'),
            onlinetex: $('#xxim_onlinetex'),
            xximon: $('#xxim_on'),
            layimFooter: $('#xxim_bottom'),
            xximHide: $('#xxim_hide'),
            xximSearch: $('#xxim_searchkey'),
            searchMian: $('#xxim_searchmain'),
            closeSearch: $('#xxim_closesearch'),
            layimMin: $('#layim_min')
        }; 
    };

    //主界面缩放
    xxim.expend = function(){
        var node = xxim.node;
        if(xxim.layimNode.attr('state') !== '1'){
            xxim.layimNode.stop().animate({right: config.right}, config.aniTime, function(){
                node.xximon.addClass('xxim_off');
                try{
                    localStorage.layimState = 1;
                }catch(e){}
                xxim.layimNode.attr({state: 1});
                node.layimFooter.addClass('xxim_expend').stop().animate({marginLeft: config.right}, config.aniTime/2);
                node.xximHide.addClass('xxim_show');
            });
        } else {
            xxim.layimNode.stop().animate({right: 1}, config.aniTime, function(){
                node.xximon.removeClass('xxim_off');
                try{
                    localStorage.layimState = 2;
                }catch(e){}
                xxim.layimNode.removeAttr('state');
                node.layimFooter.removeClass('xxim_expend');
                node.xximHide.removeClass('xxim_show');
            });
            node.layimFooter.stop().animate({marginLeft: 0}, config.aniTime);
        }
    };

    //初始化窗口格局
    xxim.layinit = function(){
        var node = xxim.node;

        //主界面
        try{
            if(!localStorage.layimState){       
                config.aniTime = 0;
                localStorage.layimState = 1;
            }
            if(localStorage.layimState === '1'){
                xxim.layimNode.attr({state: 1}).css({right: config.right});
                node.xximon.addClass('xxim_off');
                node.layimFooter.addClass('xxim_expend').css({marginLeft: config.right});
                node.xximHide.addClass('xxim_show');
            }
        }catch(e){
            layer.msg(e.message, 5, -1);
        }
    };

    //聊天窗口
    xxim.popchat = function(param){
        var node = xxim.node, log = {};

        log.success = function(layero){
            layer.setMove();

            xxim.chatbox = layero.find('#layim_chatbox');
            log.chatlist = xxim.chatbox.find('.layim_chatmore>ul');

            log.chatlist.html('
  • '" type="'+ param.type +'" id="layim_user'+ param.type + param.id +'">'+ param.name +'×
  • '
    ) xxim.tabchat(param, xxim.chatbox); //最小化聊天窗 xxim.chatbox.find('.layer_setmin').on('click', function(){ var indexs = layero.attr('times'); layero.hide(); node.layimMin.text(xxim.nowchat.name).show(); }); //关闭窗口 xxim.chatbox.find('.layim_close').on('click', function(){ var indexs = layero.attr('times'); layer.close(indexs); xxim.chatbox = null; config.chating = {}; config.chatings = 0; }); //关闭某个聊天 log.chatlist.on('mouseenter', 'li', function(){ $(this).find('em').show(); }).on('mouseleave', 'li', function(){ $(this).find('em').hide(); }); log.chatlist.on('click', 'li em', function(e){ var parents = $(this).parent(), dataType = parents.attr('type'); var dataId = parents.attr('data-id'), index = parents.index(); var chatlist = log.chatlist.find('li'), indexs; config.stopMP(e); delete config.chating[dataType + dataId]; config.chatings--; parents.remove(); $('#layim_area'+ dataType + dataId).remove(); if(dataType === 'group'){ $('#layim_group'+ dataType + dataId).remove(); } if(parents.hasClass('layim_chatnow')){ if(index === config.chatings){ indexs = index - 1; } else { indexs = index + 1; } xxim.tabchat(config.chating[chatlist.eq(indexs).attr('type') + chatlist.eq(indexs).attr('data-id')]); } if(log.chatlist.find('li').length === 1){ log.chatlist.parent().hide(); } }); //聊天选项卡 log.chatlist.on('click', 'li', function(){ var othis = $(this), dataType = othis.attr('type'), dataId = othis.attr('data-id'); xxim.tabchat(config.chating[dataType + dataId]); }); //发送热键切换 log.sendType = $('#layim_sendtype'), log.sendTypes = log.sendType.find('span'); $('#layim_enter').on('click', function(e){ config.stopMP(e); log.sendType.show(); }); log.sendTypes.on('click', function(){ log.sendTypes.find('i').text('') $(this).find('i').text('√'); }); xxim.transmit(); }; log.html = '
    ' +'
    ' +'' +' '" class="layim_face" target="_blank">'" >' +' '" class="layim_names" target="_blank">'+ param.name +'' +' ' +' ' +' ' +' ' +'
    '
    +'
    ' +'
      '
      +'
      '
      +'
      '
      +'
      ' +'
      ' +'
        '">
      '
      +'
      '
      +'
      ' +' ' +' ' //+' ' +' 聊天记录' +'
      '
      +' ' +'
      ' +'
      发送
      '
      +'
      ' +' 按Enter键发送' +' 按Ctrl+Enter键发送' +'
      '
      +'
      '
      +'
      '
      +'
      '
      ; if(config.chatings < 1){ $.layer({ type: 1, border: [0], title: false, shade: [0], area: ['620px', '493px'], move: ['.layim_chatbox .layim_move', true], moveType: 1, closeBtn: false, offset: [(($(window).height() - 493)/2)+'px', ''], page: { html: log.html }, success: function(layero){ log.success(layero); } }) } else { log.chatmore = xxim.chatbox.find('#layim_chatmore'); log.chatarea = xxim.chatbox.find('#layim_chatarea'); log.chatmore.show(); log.chatmore.find('ul>li').removeClass('layim_chatnow'); log.chatmore.find('ul').append('
    • '" type="'+ param.type +'" id="layim_user'+ param.type + param.id +'" class="layim_chatnow">'+ param.name +'×
    • '
      ); log.chatarea.find('.layim_chatview').removeClass('layim_chatthis'); log.chatarea.append('
        '">
      '
      ); xxim.tabchat(param); } //群组 log.chatgroup = xxim.chatbox.find('#layim_groups'); if(param.type === 'group'){ log.chatgroup.find('ul').removeClass('layim_groupthis'); log.chatgroup.append('
        '">
      '
      ); xxim.getGroups(param); } //点击群员切换聊天窗 log.chatgroup.on('click', 'ul>li', function(){ xxim.popchatbox($(this)); }); }; //定位到某个聊天队列 xxim.tabchat = function(param){ var node = xxim.node, log = {}, keys = param.type + param.id; xxim.nowchat = param; xxim.chatbox.find('#layim_user'+ keys).addClass('layim_chatnow').siblings().removeClass('layim_chatnow'); xxim.chatbox.find('#layim_area'+ keys).addClass('layim_chatthis').siblings().removeClass('layim_chatthis'); xxim.chatbox.find('#layim_group'+ keys).addClass('layim_groupthis').siblings().removeClass('layim_groupthis'); xxim.chatbox.find('.layim_face>img').attr('src', param.face); xxim.chatbox.find('.layim_face, .layim_names').attr('href', param.href); xxim.chatbox.find('.layim_names').text(param.name); xxim.chatbox.find('.layim_seechatlog').attr('href', config.chatlogurl + param.id); log.groups = xxim.chatbox.find('.layim_groups'); if(param.type === 'group'){ log.groups.show(); } else { log.groups.hide(); } $('#layim_write').focus(); }; //弹出聊天窗 xxim.popchatbox = function(othis){ var node = xxim.node, dataId = othis.attr('data-id'), param = { id: dataId, //用户ID type: othis.attr('type'), name: othis.find('.xxim_onename').text(), //用户名 face: othis.find('.xxim_oneface').attr('src'), //用户头像 href: config.hosts + 'user/' + dataId //用户主页 }, key = param.type + dataId; if(!config.chating[key]){ xxim.popchat(param); config.chatings++; } else { xxim.tabchat(param); } config.chating[key] = param; var chatbox = $('#layim_chatbox'); if(chatbox[0]){ node.layimMin.hide(); chatbox.parents('.xubox_layer').show(); } }; //请求群员 xxim.getGroups = function(param){ var keys = param.type + param.id, str = '', groupss = xxim.chatbox.find('#layim_group'+ keys); groupss.addClass('loading'); config.json(config.api.groups, {}, function(datas){ if(datas.status === 1){ var ii = 0, lens = datas.data.length; if(lens > 0){ for(; ii < lens; ii++){ str += '
    • '" type="one">'">'+ datas.data[ii].name +'
    • '
      ; } } else { str = '
    • 没有群员
    • '
      ; } } else { str = '
    • '+ datas.msg +'
    • '
      ; } groupss.removeClass('loading'); groupss.html(str); }, function(){ groupss.removeClass('loading'); groupss.html('
    • 请求异常
    • '
      ); }); }; //聊天模版 xxim.msgTpl = function(param, type){ return '
    • 'me' ? 'layim_chateme' : '') +'">' +'
      ' + function(){ if(type === 'me'){ return ''+ param.time +'' +''+ param.name +'' +''" >'; } else { return ''" >' +''+ param.name +'' +''+ param.time +''; } }() +'
      '
      +'
      '+ param.content +'
      '
      +'
    • '
      ; }; //消息接收 xxim.writeReceiveMessage = function(obj){ var node = xxim.node, log = {}; node.sendbtn = $('#layim_sendbtn'); node.imwrite = $('#layim_write'); var keys = xxim.nowchat.type + xxim.nowchat.id; var imarea = xxim.chatbox.find('#layim_area'+ keys); if(xxim.nowchat.id === obj.objname){ obj.objface = xxim.nowchat.face; }else{ obj.objface = 'images/3.png'; } if(obj.receiveType === 'handleIQ'){ }else if(obj.receiveType === 'handleMessage'){ xxim.messageTip(1); imarea.append(xxim.msgTpl({ time: obj.objtime, name: obj.objname, face: obj.objface, content: obj.content })); }else if(obj.receiveType === 'handlePresence'){ //对聊天对象to的头像状态的修改 }else if(obj.receiveType === 'handleError'){ imarea.append(webxxim.xxim.msgTpl({ time: obj.objtime, name: obj.objname, face: obj.objface, content: obj.content }),'me'); }else if(obj.receiveType === 'handleStatusChanged'){ //对自己头像状态的修改 }else{ } node.imwrite.val('').focus(); imarea.scrollTop(imarea[0].scrollHeight); } //消息传输 xxim.transmit = function(){ var node = xxim.node, log = {}; node.sendbtn = $('#layim_sendbtn'); node.imwrite = $('#layim_write'); //发送 log.send = function(){ var data = { content: node.imwrite.val(), id: xxim.nowchat.id, sign_key: '', //密匙 _: +new Date }; if(data.content.replace(/\s/g, '') === ''){ layer.tips('说点啥呗!', '#layim_write', 2); node.imwrite.focus(); } else { //此处皆为模拟 var keys = xxim.nowchat.type + xxim.nowchat.id; var imarea = xxim.chatbox.find('#layim_area'+ keys); var nowstr = util.getDatetime(); imarea.append(xxim.msgTpl({ time: nowstr, name: config.user.name, face: config.user.face, content: data.content }, 'me')); node.imwrite.val('').focus(); imarea.scrollTop(imarea[0].scrollHeight); /////////////////////////////////////发送信息到服务器/////////////////////////////////////////////////////////// serverChat.sendMessage(data.content,xxim.nowchat.id); ////////////////////////////////////////////////////////////////////////////////////////////////////////// // setTimeout(function(){ // imarea.append(xxim.msgTpl({ // time: '2014-04-26 0:38', // name: xxim.nowchat.name, // face: xxim.nowchat.face, // content: config.autoReplay[(Math.random()*config.autoReplay.length) | 0] // })); // imarea.scrollTop(imarea[0].scrollHeight); // }, 500); /* that.json(config.api.sendurl, data, function(datas){ }); */ } }; node.sendbtn.on('click', log.send); node.imwrite.keyup(function(e){ if(e.keyCode === 13){ log.send(); } }); }; //事件 xxim.event = function(){ var node = xxim.node; //主界面tab node.tabs.eq(0).addClass('xxim_tabnow'); node.tabs.on('click', function(){ var othis = $(this), index = othis.index(); xxim.tabs(index); }); //列表展收 node.list.on('click', 'h5', function(){ var othis = $(this), chat = othis.siblings('.xxim_chatlist'), parentss = othis.parent(); if(parentss.hasClass('xxim_liston')){ chat.hide(); parentss.removeClass('xxim_liston'); } else { chat.show(); parentss.addClass('xxim_liston'); } }); //设置在线隐身 node.online.on('click', function(e){ config.stopMP(e); node.setonline.show(); }); node.setonline.find('span').on('click', function(e){ var index = $(this).index(); config.stopMP(e); if(index === 0){ node.onlinetex.html('在线'); node.online.removeClass('xxim_offline'); } else if(index === 1) { node.onlinetex.html('隐身'); node.online.addClass('xxim_offline'); } node.setonline.hide(); }); node.xximon.on('click', xxim.expend); node.xximHide.on('click', xxim.expend); //搜索 node.xximSearch.keyup(function(){ var val = $(this).val().replace(/\s/g, ''); if(val !== ''){ node.searchMian.show(); node.closeSearch.show(); //此处的搜索ajax参考xxim.getDates node.list.eq(3).html('
    • 没有符合条件的结果
    • '
      ); } else { node.searchMian.hide(); node.closeSearch.hide(); } }); node.closeSearch.on('click', function(){ $(this).hide(); node.searchMian.hide(); node.xximSearch.val('').focus(); }); //弹出聊天窗 config.chatings = 0; node.list.on('click', '.xxim_childnode', function(){ var othis = $(this); xxim.popchatbox(othis); }); //点击最小化栏 node.layimMin.on('click', function(){ $(this).hide(); $('#layim_chatbox').parents('.xubox_layer').show(); }); //document事件 dom[1].on('click', function(){ node.setonline.hide(); $('#layim_sendtype').hide(); }); }; //请求列表数据 xxim.getDates = function(index){ var api = [config.api.friend, config.api.group, config.api.chatlog], node = xxim.node, myf = node.list.eq(index); myf.addClass('loading'); config.json(api[index], {}, function(datas){ if(datas.status === 1){ var i = 0, myflen = datas.data.length, str = '', item; if(myflen > 1){ if(index !== 2){ for(; i < myflen; i++){ str += '
    • '" class="xxim_parentnode">' +'
      '+ datas.data[i].name +'('+ datas.data[i].nums +')
      '
      +'
        '; item = datas.data[i].item; for(var j = 0; j < item.length; j++){ if(item[j].name == config.user.name) continue; str += '
      • '" class="xxim_childnode" type="'+ (index === 0 ? 'one' : 'group') +'">'" class="xxim_oneface">'+ item[j].name +'
      • '
        ; } str += '
    • '
      ; } } else { str += '
    • ' +'
        '; for(; i < myflen; i++){ str += '
      • '" class="xxim_childnode" type="one">'" class="xxim_oneface">'+ datas.data[i].name +''+ datas.data[i].time +'
      • '
        ; } str += '
    • '
      ; } myf.html(str); } else { myf.html('
    • 没有任何数据
    • '
      ); } myf.removeClass('loading'); } else { myf.html('
    • '+ datas.msg +'
    • '
      ); } }, function(){ myf.html('
    • 请求失败
    • '
      ); myf.removeClass('loading'); }); }; //渲染骨架 xxim.view = function(){ var xximNode = xxim.layimNode = $('
      ' +'
      ' +' ' +'
      '
      +'
        '
        +'
          '
          +'
            '
            +'
              '
              +'
              '
              +'
                ' +'
              • ' +'在线' +'
                ' +'在线' +'隐身' +'
                '
                +'
              • '
                +'
              • '" target="_blank">
              • '
                +'
              • ' +'' +'
                ' +'
                '
                +'
              • '
                +'
              • '
                +'
              • '
                +'
                '
                +'
              '
              +'
              '
              ); dom[3].append(xximNode); xxim.renode(); xxim.getDates(0); xxim.event(); xxim.layinit(); }; // 消息提示 xxim.messageTip = function (countparam) { if (countparam % 4 != 0) { window.focus(); document.title = "你来了新消息,请查收!"; countparam ++; window.setTimeout(function(){ xxim.messageTip(countparam) }, 1000); } else { document.title = ""; } } //exports.webim = xxim; /** * obj = { username: password: * } */ exports.initWebim = function(obj){ xxim.view(); var chatConnConfig = { httpbase: "http://localhost:8080/webim/JHB/", //请求后台http-bind服务器url domain: 'openfire402', // //"192.168.5.231", // 192.168.5.231 当前有效域名 username: "", pass: "", timerval: 2000, // 设置请求超时 resource: "webim", // 链接资源标识 register: true } //serverChat.writeReceiveMessage = xxim.writeReceiveMessage; serverChat.init(chatConnConfig,xxim); var loginPerson = { username:obj.username, password:obj.password, register:false //是否注册改用户 } serverChat.login(loginPerson); if(obj.username == 'webchattest') { config.user.face = 'http://tp1.sinaimg.cn/1571889140/180/40030060651/1'; } config.user.name = loginPerson.username; } });

              实现效果如下图:
              构建WebIM聊天程序_第4张图片

              源码可在此下载。

              你可能感兴趣的:(java,js)