基于Node的Web聊天室

1 项目名称

Web聊天室(《这是NodeJs实战》第二章的一个案例,把整个开发过程记录下来)

2 项目描述

该项目是一个简单的在线聊天程序。打开聊天页面,程序自动给用户分配一个昵称,进入默认的Lobby聊天室。用户可以发送消息,也可以使用聊天命令(聊天命令以/开头)修改自己的昵称或者加入已有的聊天室(聊天室不存在时,创建新的聊天室)。在加入或创建聊天室时,新聊天室的名称会出现在聊天程序顶端的水平条上,也会出现在聊天消息区域右侧的可用房间列表中。在用户换到新房间后,系统会显示信息以确认这一变化。

3 系统设计

该项目使用Node实现,因为Node用一个端口就可以轻松地提供HTTP和WebSocket两种服务。使用HTTP处理静态文件的同时使用WebSocket实现实时数据(聊天消息)。程序的实现可以划分以下几个功能模块:

  1. 提供静态文件(比如HTML、CSS和客户端JavaScript)
  2. 在服务器上处理与聊天相关的消息
  3. 在用户的浏览器中处理与聊天相关的消息

为了提供静态文件,需要使用Node内置的http模块。但通过HTTP提供文件时,通常不能只是发送文件中的内容,还应该有所发送文件的类型。也就是说要用正确的MIME类型设置HTTP 头的Content-Type。为了查找这些MIME类型,会用到第三方的模块mime。

为了处理与聊天相关的消息,需要用Ajax轮询服务器。为了让这个程序能尽可能快的作出响应,我们不会用传统的Ajax发送消息。采用WebSocket,这是一个为支持实时通讯而设计的轻量的双向通信协议。因为在大多数情况下,只有兼容HTML5的浏览器才支持WebSocket,所以这个程序会使用流行的Socket.IO库,他给不能使用WebSocket的浏览器提供了一些后备措施。

4 系统实现

使用WebStorm开发该项目。WebStorm被称为“最强大的HTML5编辑器”、“最智能的JavaScript IDE”。

4.1 创建程序的文件结构

使用WebStorm,选择一个目录,创建一个新的空项目。设计项目结构如下所示:

4.2 指明依赖项

程序的依赖项是在package.json文件中指明的。这个文件总是被放在程序的根目录下。 package.json文件用于描述你的应用程序,它包含一些JSON表达式。在package.json文件中可以定义很多事情,但最重要的是程序的名称、版本号、对程序的描述,以及程序的依赖项。 代码清单1中是一个包描述文件,描述了项目的功能和依赖项。将这个文件保存到项目的根目录中,命名为package.json。


{
  "name": "chatrooms",
  "version": "0.0.1",
  "description":"Minimalist
multiroom chat server"
,
  "dependencies":{
    "socket.io":"~0.9.6",
    "mime":"~1.2.11"
 
}
}

4.3 安装依赖项

切换到DOS窗口,在项目的根目录下输入以下这条命令

npm install

如果按照失败,切换到国内的npm镜像,然后再安装。镜像使用方法(三种办法任意一种都能解决问题,建议使用第三种,将配置写死,下次用的时候配置还在):

1.通过config命令

npm config set registry
https://registry.npm.taobao.org

npm info underscore (如果上面配置正确,这个命令会有字符串response)

2.命令行指定

npm --registry
https://registry.npm.taobao.org info underscore

3.编辑 ~/.npmrc 加入下面内容

registry = https://registry.npm.taobao.org

安装成功后,在根目录下创建的node_modules目录,这个目录中放的就是程序的依赖项。

4.4提供HTML、CSS和客户端 JavaScript的服务

程序的逻辑是由一些文件实现的,有些运行在服务器上,有些运行在客户端。
在客户端运行的JavaScript需要作为静态资源发给浏览器,而不是在Node上执行。

服务器端的文件:

server.js

lib/chat_server.js

发送给客户端的文件:

public/index.html

public/stylesheets/style.css

public/javascripts/chat.js

public/javascripts/chat_ui.js

4.4.1 在server.js中提供静态文件服务器

/**
 * Created by Administrator on 2016-05-05.
 */
var http = require(‘http‘);
var fs = require(‘fs‘);
var path = require(‘path‘);
var mime = require(‘mime‘);
var cache={};//缓存文件内容的对象

  /* 请求的文件不存在时,发送404错误*, /
function send404(response){
    response.writeHead(404,{‘Content-Type‘:‘text/plain‘});
    response.write(‘Error 404:resource not found.‘);
    response.end();
}
  /*  发送数据文件*/
function sendFile(response,filePath,fileContents){
 response.writeHead(200,{"Content-type":mime.lookup(path.basename(filePath))});
    response.end(fileContents);
}
  /*提供静态文件服务*/
function serverStatic(response,cache,absPath){
    if(cache[absPath]){
        sendFile(response, absPath,cache[absPath]);//从内存中返回数据
    }else{
        fs.exists(absPath,function(exists){//检查文件是否存在
              if(exists){
                fs.readFile(absPath,function(err,data){
                    if(err){
                        send404(response);
                    }else{
                        cache[absPath] = data;
                        sendFile(response,absPath,data);
                    }
                });
            }else{
                send404(response);
            }
        });
    }
}
  /* 1 创建HTTP服务器*,从该句代码开始阅读*/
var server= http.createServer(function(request,response){
    var filePath = false;
    if(request.url == ‘/‘){
        filePath = ‘public/index.html‘;
    }else{
        filePath = ‘public‘ + request.url;
    }
    var absPath=‘./‘ + filePath;
    serverStatic(response,cache,absPath);
});
server.listen(3000,function(){
    console.log("server listening on port 3000.");
});
/* 2 加载chat_server,创建聊天服务器,chat_server 模块随后实现*/
var chatServer = require(‘./lib/chat_server‘);
chatServer.listen(server);

4.4.2添加HTML和CSS文件

Index.html文件内容:

<!doctype html>
<html lang=‘en‘>

<head>
    <title>Chat</title>
    <link rel=‘stylesheet‘ href=‘/stylesheets/style.css‘></link>
</head>

<body>
<div id=‘content‘>
    <div id=‘room‘></div>
    <div id=‘room-list‘></div>
    <div id=‘messages‘></div>

    <form id=‘send-form‘>
        <input id=‘send-message‘ />
        <input id=‘send-button‘ type=‘submit‘ value=‘Send‘/>

        <div id=‘help‘>
            Chat commands:
            <ul>
                <li>Change nickname: <code>/nick [username]</code></li>
                <li>Join/create room: <code>/join [room name]</code></li>
            </ul>
        </div>
    </form>
</div>

<script src=‘/socket.io/socket.io.js‘ type=‘text/javascript‘></script>
<script src=‘http://code.jquery.com/jquery-1.8.0.min.js‘ type=‘text/javascript‘></script>
<script src=‘/javascripts/chat.js‘ type=‘text/javascript‘></script>
<script src=‘/javascripts/chat_ui.js‘ type=‘text/javascript‘></script>
</body>
</html>

style.css文件内容

body {
    padding: 50px;
    font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
    color: #00B7FF;
}

#content {
    width: 800px;
    margin-left: auto;
    margin-right: auto;
}
#room {
    background-color: #ddd;
    margin-bottom: 1em;
}
#messages {
    width: 690px;
    height: 300px;
    overflow: auto;
    background-color: #eee;
    margin-bottom: 1em;
    margin-right: 10px;
}
#room-list {
    float: right;
    width: 100px;
    height: 300px;
    overflow: auto;
}

#room-list div {
    border-bottom: 1px solid #eee;
}
#room-list div:hover {
    background-color: #ddd;
}
#send-message {
    width: 700px;
    margin-bottom: 1em;
    margin-right: 1em;
}
#help {
    font: 10px "Lucida Grande", Helvetica, Arial, sans-serif;
}

4.5用Socket.IO处理与聊天相关的功能

4.5.1 chat_server.js实现服务器端功能

var socketio = require(‘socket.io‘);
var io;
var guestNumber = 1;
var nickNames = {};
var namesUsed = [];
var currentRoom = {};
exports.listen = function(server){
//启动socket.io服务器,允许它搭载在已有的http服务器上
      io = socketio.listen(server);
    io.set(‘log level‘,1);
//定义每个用户连接的处理逻辑
    io.sockets.on(‘connection‘,function(socket){

        // 1 客户端连接后,分配用户昵称
          guestNumber=assignGuestName(socket,guestNumber,nickNames,namesUsed);
        // 2 加入Lobby聊天室
          joinRoom(socket,‘Lobby‘);
        // 3 处理广播消息
          handleMessageBroadcasting(socket,nickNames);
        // 4处理修改昵称命令
          handleNameChangeAttempts(socket,nickNames,namesUsed);
        // 5 处理切换/创建聊天室命令
          handleRoomJoining(socket);
        // 6 当收到客户端请求后,发送给客户端房间列表
          socket.on(‘rooms‘,function(){
            socket.emit(‘rooms‘,io.sockets.manager.rooms);
        });
        // 7 处理客户端断开连接
          handleClientDisconnection(socket,nickNames,namesUsed);
    });
};
  /* 分配用户昵称*/
function assignGuestName(socket,guestNumber,nickNames,namesUsed){
    var name=‘Guest‘+guestNumber;
//将昵称保存在昵称集合nickNames中
      nickNames[socket.id] = name;
//发送给客户端知悉其昵称
      socket.emit(‘nameResult‘,{
        success:true,
        name:name
    });
//将昵称保存在另一个已使用的昵称数组中
      namesUsed.push(name);
    return guestNumber + 1;
}

  /*加入房间*/
function joinRoom(socket, room) {
//用户进入房间
      socket.join(room);
//记录用户的当前房间
      currentRoom[socket.id] = room;
//让用户知悉他进入了新的房间
    socket.emit(‘joinResult‘, {room: room});
    //让该房间里的其他用户知悉有新用户进入了房间
socket.broadcast.to(room).emit(‘message‘, {
        text: nickNames[socket.id] + ‘ has joined ‘ + room + ‘.‘
    });
    //获取该房间里所有的用户
      var usersInRoom = io.sockets.clients(room);
//如果用户数量大于1
      if (usersInRoom.length > 1) {
        var usersInRoomSummary = ‘Users currently in ‘ + room + ‘: ‘;
        for (var index in usersInRoom) {
            var userSocketId = usersInRoom[index].id;
//判断非当前用户
            if (userSocketId != socket.id) {
                  if (index > 0) {
                    usersInRoomSummary += ‘, ‘;
                }
                usersInRoomSummary += nickNames[userSocketId];
            }
        }
        usersInRoomSummary += ‘.‘;
        //汇总该房间里的其它成员名称发送给给该用户
          socket.emit(‘message‘, {text: usersInRoomSummary});
    }
}
  /* 处理更名请求*/
function handleNameChangeAttempts(socket, nickNames, namesUsed) {
    socket.on(‘nameAttempt‘, function(name) {
        if (name.indexOf(‘Guest‘) == 0) {//不能以Guest开头
              socket.emit(‘nameResult‘, {
                success: false,
                message: ‘Names cannot begin with "Guest".‘
            });
        } else {//注册用户名称
            if (namesUsed.indexOf(name) == -1) {
                var previousName = nickNames[socket.id];
                var previousNameIndex = namesUsed.indexOf(previousName);
                namesUsed.push(name);
                nickNames[socket.id] = name;
                delete namesUsed[previousNameIndex];
//当前用户会收到更名信息
                socket.emit(‘nameResult‘, {
                    success: true,
                    name: name
                });
//房间中其他用户知悉当前用户已更名
                  socket.broadcast.to(currentRoom[socket.id]).emit(‘message‘, {
                    text: previousName + ‘ is now known as ‘ + name + ‘.‘
                });
            } else {//该昵称已经存在
                  socket.emit(‘nameResult‘, {
                    success: false,
                    message: ‘That name is already in use.‘
                });
            }
        }
    });
}
  /*发送聊天消息,即Node服务器收到客户端消息,转发给该房间的其它用户*/
function handleMessageBroadcasting(socket) {
    socket.on(‘message‘, function (message) {
        socket.broadcast.to(message.room).emit(‘message‘, {
            text: nickNames[socket.id] + ‘: ‘ + message.text
        });
    });
}
  /* 切换聊天室,即离开当前房间,加入其他房间,房间不存在则创建新的房间*/
function handleRoomJoining(socket) {
    socket.on(‘join‘, function(room) {
        socket.leave(currentRoom[socket.id]);
        joinRoom(socket, room.newRoom);
    });
}
  /*处理客户端断开连接*/
function handleClientDisconnection(socket) {
    socket.on(‘disconnect‘, function() {
        var nameIndex = namesUsed.indexOf(nickNames[socket.id]);
        delete namesUsed[nameIndex];
        delete nickNames[socket.id];
    });
}

4.5.2 chat.js以及chat_ui.js实现客户端功能

客户端JavaScript需要实现以下功能:向服务器发送用户的消息和昵称/房间变更请求; 显示其他用户的消息,以及可用房间的列表。Chat.js中定义一个原型对象,用于处理聊天消息和命令,该原型对象中的函数在chat_ui.js中调用。

chat.js文件内容:

/**
 * Created by Administrator on 2016-05-05.
 */
/*JavaScript原型对象,处理发送聊天消息、变更房间、处理聊天命令*/
var Chat = function(socket) {
    this.socket = socket;
};

Chat.prototype.sendMessage = function(room, text) {
    var message = {
        room: room,
        text: text
    };
    this.socket.emit(‘message‘, message);
};

Chat.prototype.changeRoom = function(room) {
    this.socket.emit(‘join‘, {
        newRoom: room
    });
};

Chat.prototype.processCommand = function(command) {
    var words = command.split(‘ ‘);
    var command = words[0]
        .substring(1, words[0].length)
        .toLowerCase();
    var message = false;

    switch(command) {
        case ‘join‘:
            words.shift();
            var room = words.join(‘ ‘);
            this.changeRoom(room);//变更房间
              break;
        case ‘nick‘:
            words.shift();
            var name = words.join(‘ ‘);
            this.socket.emit(‘nameAttempt‘, name);//修改昵称
            break;
        default:
            message = ‘Unrecognized command.‘;
            break;
    };

    return message;
};

chat_ui.js文件内容:

/**
 * Created by Administrator on 2016-05-05.
 */
/*用来显示可疑的文本。它会净化文本,将特殊字符转换 成HTML实体*/
function divEscapedContentElement(message) {
    return $(‘<div></div>‘).text(message);
}
  /*显示系统创建的受信内容*/
function divSystemContentElement(message) {
    return $(‘<div></div>‘).html(‘<i>‘ + message + ‘</i>‘);
}
  /*显示用户输入的信息*/
function processUserInput(chatApp, socket) {
    var message = $(‘#send-message‘).val();
    var systemMessage;

    if (message.charAt(0) == ‘/‘) {//显示系统受信内容
        systemMessage = chatApp.processCommand(message);
        if (systemMessage) {
            $(‘#messages‘).append(divSystemContentElement(systemMessage));
        }
    } else {//显示用户输入内容
          chatApp.sendMessage($(‘#room‘).text(), message);
        $(‘#messages‘).append(divEscapedContentElement(message));
        $(‘#messages‘).scrollTop($(‘#messages‘).prop(‘scrollHeight‘));
    }
    //清空输入框
    $(‘#send-message‘).val(‘‘);
}
  /*客户端程序初始化逻辑*/
var socket = io.connect();

$(document).ready(function() {
    var chatApp = new Chat(socket);
    //显示用户更名结果
    socket.on(‘nameResult‘, function(result) {
        var message;
        if (result.success) {
            message = ‘You are now known as ‘ + result.name + ‘.‘;
        } else {
            message = result.message;
        }
        $(‘#messages‘).append(divSystemContentElement(message));
    });
    //显示用户切换聊天室结果
    socket.on(‘joinResult‘, function(result) {
        $(‘#room‘).text(result.room);
        $(‘#messages‘).append(divSystemContentElement(‘Room changed.‘));
    });
    //显示聊天消息
      socket.on(‘message‘, function (message) {
        var newElement = $(‘<div></div>‘).text(message.text);
        $(‘#messages‘).append(newElement);
    });
    //显示房间列表
    socket.on(‘rooms‘, function(rooms) {
        $(‘#room-list‘).empty();

        for(var room in rooms) {
            room = room.substring(1, room.length);
            if (room != ‘‘) {
                $(‘#room-list‘).append(divEscapedContentElement(room));
            }
        }

        $(‘#room-list div‘).click(function() {
            chatApp.processCommand(‘/join ‘ + $(this).text());
            $(‘#send-message‘).focus();
        });
    });
    //每间隔1秒,向服务器重新请求房间列表
      setInterval(function() {
        socket.emit(‘rooms‘);
    }, 1000);

    $(‘#send-message‘).focus();
    //提交表单,发送聊天消息
      $(‘#send-form‘).submit(function() {
        processUserInput(chatApp, socket);
        return false;
    });
});

4.6 服务器与客户端的事件分析

服务器与客户端的交互主要是通过相互发送事件-处理事件完成的,以下是在整个流程中发生的事件:

4.6.1服务器事件流程

‘connection‘事件//接收客户端连接

{

// 1 assignGuestName()函数中的事件

socket.emit(‘nameResult‘,{ //分配默认昵称

success:true,

name:name

});

// 2 joinRoom()函数中的事件

socket.emit(‘joinResult‘, {room: room});//加入房间

socket.broadcast.to(room).emit(‘message‘, {//广播消息

text: nickNames[socket.id] + ‘ has joined ‘ + room + ‘.‘

});

socket.emit(‘message‘, {text: usersInRoomSummary});//发送给每个用户包含该房间的其他用户的列表

// 3 handleMessageBroadcasting()函数中的事件

socket.on(‘message‘, function (message) {//收到客户端消息后,发射给同房间的其他用户

socket.broadcast.to(message.room).emit(‘message‘, {

text: nickNames[socket.id] + ‘: ‘ + message.text

});

});

// 4 handleNameChangeAttempts()函数中的事件

socket.emit(‘nameResult‘, {//更名

success: true,

name: name

});

socket.broadcast.to(currentRoom[socket.id]).emit(‘message‘, {//房间中其他用户知悉当前用户已更名

text: previousName + ‘ is now known as ‘ + name + ‘.‘

});

// 5 handleRoomJoining()函数中的事件

socket.on(‘join‘, function(room) {//切换聊天室

socket.leave(currentRoom[socket.id]);

joinRoom(socket, room.newRoom);

});

// 6 当收到客户端请求后,发送给客户端房间列表

socket.on(‘rooms‘,function(){

socket.emit(‘rooms‘,io.sockets.manager.rooms);

});

// 7 handleClientDisconnection()函数中的事件

socket.on(‘disconnect‘, function() {

var nameIndex = namesUsed.indexOf(nickNames[socket.id]);

delete namesUsed[nameIndex];

delete nickNames[socket.id];

});

}

4.6.2客户端事件流程

// 1 连接服务器

var socket = io.connect();

// 2 连接服务器后,处理服务器发送过来的事件

socket.on(‘nameResult‘, function(result) {...});//显示昵称

socket.on(‘joinResult‘, function(result) {...});//显示加入房间

socket.on(‘message‘, function (message) {...});//显示该房间的其他用户

socket.on(‘rooms‘, function(rooms) {...});//显示房间列表

setInterval(function() {

socket.emit(‘rooms‘);//定期向服务器请求房间列表

}, 1000);

// 3 点击房间列表

$(‘#room-list div‘).click(function() {

this.socket.emit(‘join‘, { newRoom: room});//切换聊天室

});

// 4 点击提交按钮调用processUserInput()函数中触发的客户端事件

this.socket.emit(‘message‘, message);//发送消息

this.socket.emit(‘nameAttempt‘, name);//更名

this.socket.emit(‘join‘, { newRoom: room});//创建聊天室

时间: 2024-10-14 00:23:42

基于Node的Web聊天室的相关文章

Netty学习摘记 —— 简单WEB聊天室开发

本文参考 本篇文章是对<Netty In Action>一书第十二章"WebSocket"的学习摘记,主要内容为开发一个基于广播的WEB聊天室 聊天室工作过程 请求的 URL 以/ws 结尾时,通过升级握手的机制把该协议升级为 WebSocket,之后客户端发送一个消息,这个消息会被广播到所有其它连接的客户端 当有新的客户端连入时,其它客户端也能得到通知 处理HTTP请求 首先实现该处理 HTTP 请求的组件,当请求的url没有指定的WebSocket连接的后缀时(如后缀/

Node实战之聊天室

Node实战之聊天室 Node如何同时处理Http和WebSocket 1.只出现在用户访问聊天程序网站时:Web浏览器->Http请求->Node服务器->Http响应->Web浏览器 2.在用户聊天时持续发生:Web浏览器->WebSocket数据发送->Node服务器->WebSocket数据接收->Web浏览器 开始搭建 1.创建程序文档结构(如下图所示) 2.指明依赖项 程序的依赖项是在package.json文件中指明的.这个文件总是被放在程序的

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(七) 之 历史记录查询(时间,关键字,图片,文件),关键字高亮显示。

前言 上一篇讲解了如何自定义右键菜单,都是前端的内容,本篇内容就一个:查询.聊天历史纪录查询,在之前介绍查找好友的那篇博客里已经提到过 Elasticsearch,今天它又要上场了.对于Elasticsearch不感冒的同学呢,本篇可以不用看啦. from baidu: ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(四) 之 用户搜索(Elasticsearch),加好友流程(1)。

前面几篇基本已经实现了大部分即时通讯功能:聊天,群聊,发送文件,图片,消息.不过这些业务都是比较粗犷的.下面我们就把业务细化,之前用的是死数据,那我们就从加好友开始吧.加好友,首先你得知道你要加谁.Layim界面右下角有个+号,点击它之后就会弹出查找好友的界面,不过那个界面需要自定义.由于前端不是我的强项,勉强凑了个页面.不过不要在意这些细节.这些都不重要,今天主要介绍一下ElasticSearch搜索解决方案.它是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基

基于Socket的Android聊天室

1        基于Socket的Android聊天室 Socket通信是网络通信中最常用的技术之一,通过Socket建立的可靠连接,可以让多个终端与服务器保持通信,最典型的应用是建立一个多人聊天程序.本实例使用ServerSocket建立聊天服务器.将服务器端所有的通讯线程保存到一个集合当中,当有用户发来数据,则转发给所有用户,实现聊天室效果.Android端通过使用Socket建立客户端链接,并且在AsyncTask中执行网络读写的任务,将用户输入的内容发送到服务器,并接收服务器发来的数据

分享基于 websocket 网页端聊天室

博客地址:https://ainyi.com/#/67 有一个月没有写博客了,也是因为年前需求多.回家过春节的原因,现在返回北京的第二天,想想,应该也要分享技术专题的博客了!! 主题 基于 websocket 网页端聊天室 WebSocket 协议是基于 TCP 的一种新的网络协议.它实现了浏览器与服务器全双工 (full-duplex) 通信--允许服务器主动发送信息给客户端. 使用 java 开发后台 需要导入一个jar包:javax.websocket-api-1.0-rc4.jar 后台

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(一) 之 基层数据搭建,让数据活起来(数据获取)

大家好,本篇是接上一篇 ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(零) 前言  ASP.NET SignalR WebIM系列第二篇.本篇会带领大家将 LayIM界面中的数据动态化.当然还不涉及即时消息通讯,如果你已经搞定了数据界面,那么本文您可以简单的看一下,或者略过. 进入正题,layim帮我们定义好了数据规则,我们只要写一个接口实现那个json规范就可以了,剩下的事情就交给layim去做,看一下json格式.(对应文件夹:demo/json/getLi

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(十四)之漏掉的客服消息

前言 不知不觉已经十四篇了,其实已经没有什么可写了.但是突然发现layim中带的客服功能没有用到.于是乎,抽点时间完成吧.其实之前的工作已经把客服功能完成了一大半,剩下的我们稍微调整即可.今天的演示我们放在后边,直接进入讲解. 客服思路讲解 大家去一些公司网站都会发现,网页侧面或者自动弹出一些客服聊天框,人家很热情的和你交谈.我们也可以用layim来实现.首先,页面添加一个按钮,点击按钮触发客服模式. <a onclick="javascript:global.other.kefu(148

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室 实战系列(不断更新中)

项目简介 利用ASP.NET SignalR技术与Layim前端im框架实现的一个简单的web聊天室,包括单聊,群聊,加好友,加群,好友搜索,管理,群组管理,好友权限设置等功能.涉及技术: ElasticSearch 搜索,支持各种条件搜索,效率高,速度快,稳准狠. Redis缓存,统计在线好友,登录token等 RabbitMQ消息队列,发送消息通过队列降低数据库访问压力,或者延迟执行任务. ASP.NET MVC,UI架构. 以及普通的三层架构等.CRUD 当然其中的这些技术也是纯粹为了使用