angular+nodejs+socket.io 聊天功能的实现

来源:互联网 发布:女装批发的软件 编辑:程序博客网 时间:2024/05/16 09:41

如题:本文部分是转载,部分是自己在实际联系中的收获

功能

着手开发之前,首先明确一下需要实现的功能:

新用户登入,广播通知其他用户
用户下线,广播通知其他用户
可显示在线人数及列表
可群聊,可私信
用户若发送群消息,广播通知其他所有用户
用户若发送私信,单独通知收方界面

因为自己是个审美渣,所以全靠bootstrap了,另外还模仿了下微信聊天记录里的气泡设计。

界面分左右两个板块,分别用于显示在线列表和聊天内容。

在左侧的在线列表中,点击不同项可以切换右侧板块的聊天对象。

右侧显示与当前聊天对象的对话记录,不过仅显示最近的30条。每一条聊天记录内容包括发送人的昵称及头像、发送时间、消息内容。关于头像,这里做简单处理,用填充了随机色的方块代替。另外,自己发出去的消息与收到的消息样式自然要做不同设计,

服务端:

首先本地D盘创建文件夹,取名为:chat

首先是实现服务端:

打开chat文件夹:

在根目录下执行npm  init

根据提示,生成一个package.json文件。打开并配置依赖项:

{
  "name": "c",
  "version": "1.0.0",
  "description":  {
  "express": "^4.14.0",
    "socket.io": "^1.5.1"
  },


}

之后执行 npm install 安装依赖模块。

接下来,我们在根目录下新建app.js,在其中写Server端代码。再新建public文件夹,存放client端代码

app.js(/**/里面的是注释):

关于express的学习,推荐  http://www.tuicool.com/articles/YRRRNbv

var express = require('express');
var app = require('express')();
var http = require('http').createServer(app);

/* 

Express将app初始化成一个函数处理器,

你可以将其提供给一个HTTP服务器(看第三行)
我们定义一个路由处理器,当我们访问网站主页时,这个处理器就会被调用。
我们让这个http服务器监听3012端口。
Socket.IO由两部分构成:
socket.io:一个服务器,集成了Node.JS的HTTP服务器
socket.io-client:一个客户端库,在浏览器端加载
在开发期间,socket.io会自动为我们提供客户端,所以现在我们只需安装一个模块:

npm install --save socket.io  


注意,接下来我传递了一个http(HTTP Server)对象来创建一个新的socket.io实例,

 然后,为即将来到的套接字监听connection事件,我会将其打印到控制台中。
*/

var io=require('socket.io')(http);

/*

其中,app.use是express“调用中间件的方法”。所谓“中间件(middleware),就是处理HTTP请求的函数,用来完成各种特定的任务,比如检查用户是否登录、分析数据、以及其他在需要最终将数据发送给用户之前完成的任务。”。这是阮一峰文章的原话。

简而言之,app.use() 里面使用的参数,主要是函数。但这个使用,并不是函数调用,而是使能的意思。当用户在浏览器发出请求的时候,这部分函数才会启用,进行过滤、处理。

express的路由,众所周知,是app.get(),app.post(),app.all(),。。。,但其实,它们都是app.use的别名,呵呵。怪不得,我说为什么看上去,app.use 跟它们那么像:app.use的调用方式,除了类似 app.use(匿名函数或函数名),也可以是类似 app.use("/",匿名函数或函数名)。



通过 Express 内置的 express.static 可以方便地托管静态文件,例如图片、CSS、JavaScript 文件等。

将静态资源文件所在的目录作为参数传递给 express.static 中间件就可以提供静态资源文件的访问了。例如,假设在 public 目录放置了图片、CSS 和 JavaScript 文件,你就可以:

app.use(express.static('public'));

现在,public 目录下面的文件就可以访问了。

http://localhost:3000/images/kitten.jpghttp://localhost:3000/css/style.csshttp://localhost:3000/js/app.jshttp://localhost:3000/images/bg.pnghttp://localhost:3000/hello.html

*/

app.use(express.static((__dirname + '/public')));
app.get('/',function(req,res){

res.sendfile('index.html');
});
var connectedSockets={};
//初始值即包含了群聊,用“”表示nickname

var allUsers=[{nickname:"",color:"#000"}];

/*用来监听连接*/

io.on('connection',function(socket){

socket.on('addUser',function(data){
//该事件由客户端输入昵称后触发,服务端收到后判断,昵称已被占用
if(connectedSockets[data.nickname]){
socket.emit('userAddingResult',{result:false});
}else{
//反之通知客户端昵称有效以及当前所有已经连接的用户信息,并广播
socket.emit('userAddingResult',{result:true});
socket.nickname=data.nickname;
/* 保存每个socket实例,发私信需要用
发给特定用户,如果消息是发给特定用户A,那么就需要获取A对应的socket实例,
然后调用起emit方法,所以没当一个客户端连接到server时,
我们得把socket实例保存起来,以备后续之需 */
connectedSockets[socket.nickname]=socket;
allUsers.push(data);
//广播欢迎新用户,除新用户外都可以看到
socket.broadcast.emit('userAdded',data);
//将所有在线用户发给新用户
socket.emit('allUser',allUsers);

}
});
socket.on('addMessage',function(data){
if(data.to){
console.log(data);
//需要发私信时,取出socket实例做操作即可:
connectedSockets[data.to].emit('messageAdded',data);

}else{
//群发,广播消息,除原发送外其他的都可以看到
socket.broadcast.emit('messageAdded',data);

}
});
socket.on('disconnect',function(){
//有用户退出聊天室
//广播有用户退出
socket.broadcast.emit('userRemoved',{
nickname: socket.nickname
});
for(var i=0;i<allUsers.length;i++){
if(allUsers[i].nickname==socket.nickname){
allUsers.splice(i,1);

}
}
//删除对应的socket实例
delete connectedSockets[socket.nickname];
});

});
http.listen(3012,function(){
console.log('hello  listening on * 3012');

});

前端app.js


var app=angular.module("chatRoom",[]);
app.factory('socket',function($rootScope){
var socket=io();
return {
on: function(eventName, callback) {
            socket.on(eventName, function() {
                var args = arguments;
                $rootScope.$apply(function() {   //手动执行脏检查
                    callback.apply(socket, args);
                });
            });
        },
emit:function(eventName,data,callback){
socket.emit(eventName,data,function(){
var args=arguments;
$rootScope.$apply(function(){
if(callback) 
{
callback.apply(socket,args);
}
});
});
}


};


});
app.factory('randomColor', function($rootScope) {
    return {
        newColor: function() {
            return '#'+('00000'+(Math.random()*0x1000000<<0).toString(16)).slice(-6);
        }
    };
});
app.factory('userService', function($rootScope) {
    return {
        get: function(users,nickname) {
            if(users instanceof Array){
                for(var i=0;i<users.length;i++){
                    if(users[i].nickname===nickname){
                        return users[i];
                    }
                }
            }else{
                return null;
            }
        }
    };
});
app.controller("chatCtrl",['$scope','socket','randomColor','userService',function($scope,socket,randomColor,userService){
 var messageWrapper= $('.message-wrapper');
    $scope.hasLogined=false;
    $scope.receiver="";//默认是群聊
    $scope.publicMessages=[];//群聊消息
    $scope.privateMessages={};//私信消息
    $scope.messages=$scope.publicMessages;//默认显示群聊
    $scope.users=[];//
    $scope.color=randomColor.newColor();//当前用户头像颜色
    $scope.login=function(){   //登录进入聊天室
        socket.emit("addUser",{nickname:$scope.nickname,color:$scope.color});
    }
$scope.scrollToBottom=function(){
        messageWrapper.scrollTop(messageWrapper[0].scrollHeight);
    }
$scope.postMessage=function(){
var msg={text:$scope.words,type:"normal",color:$scope.color,from:$scope.nickname,to:$scope.receiver};
var rec=$scope.receiver;
if(rec){
//私信
if(!$scope.privateMessages[rec]){
$scope.privateMessages[rec]=[];
}
$scope.privateMessages[rec].push(msg);

}else{
//群聊
$scope.publicMessages.push(msg);
}
$scope.words="";
if(rec!=$scope.nickname){
//排除给自己发的情况
socket.emit("addMessage",msg);
}
}
$scope.setReceiver=function(receiver){
$scope.receiver=receiver;
if(receiver){
//私信用户
if(!$scope.privateMessages[receiver]){
$scope.privateMessages[receiver]=[];
}
$scope.messages=$scope.privateMessages[receiver];

}else{
//广播
$scope.messages=$scope.publicMessages;
}
var user=userService.get($scope.users,receiver);
if(user){

user.hasNewMessage=false;
}
}
//收到登录结果
socket.on('userAddingResult',function(data){
if(data.result){

$scope.userExisted=false;
$scope.hasLogined=true;
}
else{
//昵称被占用
$scope.userExisted=true;
}

});
//接收到欢迎新用户的消息
socket.on('userAdded',function(data){
if(!$scope.hasLogined)return;
$scope.publicMessages.push({text:data.nickname,type:"welcome"});
$scope.users.push(data);

});
//接收到在线用户的信息
socket.on('allUser',function(data){
if(!$scope.hasLogined) return;
        $scope.users=data;

});
//接收到用户退出消息
socket.on('userRemoved',function(data){
if(!$scope.hasLogined) return;
alert(data.nickname);
        $scope.publicMessages.push({text:data.nickname,type:"bye"});
        for(var i=0;i<$scope.users.length;i++){
            if($scope.users[i].nickname==data.nickname){
                $scope.users.splice(i,1);
                return;
            }
        }

});
//接收到新消息
socket.on('messageAdded',function(data){
if(!$scope.hasLogined) return;
if(data.to){
//私信
if(!$scope.privateMessages[data.from]){
$scope.privateMessages[data.from]=[];
}
$scope.privateMessages[data.from].push(data);
}else{
//群发
$scope.publicMessages.push(data);
}
var fromUser=userService.get($scope.users,data.from);
var toUser=userService.get($scope.users,data.to);
if($scope.receiver!==data.from){
//与来信方不是正在聊天当中才提示新消息
if(fromUser&&toUser.nickname){
fromUser.hasNewMessage=true;//私信

}else{
toUser.hasNewMessage = true;//群发
}

}
});

}]);
app.directive('message',['$timeout',function($timeout){
return {
//E 表示该指令是一个element; A 表示该指令是attribute; C 表示该指令是class; M 表示该指令是注视
restrict:'E',
templateUrl:'message.html',
scope:{
info:"=",
self:"=",
            scrolltothis:"&"

},
link:function(scope,elem,attrs){
scope.time=new Date();
 $timeout(scope.scrolltothis);
                $timeout(function(){
                    elem.find('.avatar').css('background',scope.info.color);
                });
}

};

}])
       .directive('user',['$timeout',function($timeout){
 
return {
 
restrict:'E',
templateUrl: 'user.html',
            scope:{
                info:"=",
                iscurrentreceiver:"=",
                setreceiver:"&"
            },
link:function(scope, elem, attrs,chatCtrl){
                $timeout(function(){
                    elem.find('.avatar').css('background',scope.info.color);
                });
}
};
 }]);
前端index.html


<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="utf-8">
    <title>在线聊天室</title>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="./assets/style/app.css"/>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
    <script src="//cdn.bootcss.com/angular.js/1.4.3/angular.min.js"></script>
    <script src="./assets/js/app.js"></script>
</head>
<body ng-app="chatRoom" ng-controller="chatCtrl">
<!-- chat room -->
<div class="chat-room-wrapper" ng-show="hasLogined">
  <div class="online panel panel-success"> 
     <div class="panel-heading">
<h3 class="panel-title">在线<span class="user-number">
{{users.length-1}}
</span></h3>
</div>
<div class="user-wrapper panel-body">
<user iscurrentreceiver="receiver===user.nickname" info="user" 
ng-click="setReceiver(user.nickname)" ng-repeat="user in users">
</user>
</div>
  </div>
  <div class="chat-room panel panel-success">
       <div class="panel-heading">
    <h3 class="panel-title">
{{receiver?receiver:"群聊"}}
</h3>
  </div>
  <div class="message-wrapper panel-body">
    <message self="nickname" scrolltothis="scrollToBottom()"
info="message" ng-repeat="message in messages">
</message>
  </div>
  <div class="panel-footer">
    <form class="post-form form-inline" novalidate name="postform" ng-submit="postMessage()">
   <input type="text" class="form-control" ng-model="words" placeholder="说点什么呗" required>
    <button type="submit" class="btn btn-success" ng-disabled="postform.$invalid">发送</button>
</form>
  </div>
  </div>


</div>
<!-- end of chat room -->


<!-- login form -->
<div class="userform-wrapper"  ng-show="!hasLogined">
    <form class="form-inline login" novalidate name="userform" ng-submit="login()">
        <div class="form-group">
            <label for="nickname" class="sr-only"></label>
            <div class="input-group">
                <div class="input-group-addon"><span class="glyphicon glyphicon-user"></span></div>
                <input type="text" class="form-control" id="nickname" placeholder="Your Nickname" ng-model="nickname" required/>
            </div>
        </div>
        <button type="submit" class="btn btn-primary" ng-disabled="userform.$invalid">LOG IN</button>
        <p ng-show="userExisted" class="help-block">A user with this nickname already exists.</p>


    </form>
</div>
<!-- end of login form -->
</body>
</html>

前端message.html

<div ng-switch on="info.type">
    <!-- 欢迎消息 -->
    <div class="system-notification" ng-switch-when="welcome">
<strong>系统:&nbsp;&nbsp;</strong>
<span>{{info.text}}</span>来啦我们欢迎~
    </div>
    <!-- 退出消息 -->
    <div class="system-notification" ng-switch-when="bye"><strong>系统:&nbsp;&nbsp;</strong>byebye,
        <span>{{info.text}}</span></div>
    <!-- 普通消息 -->
    <div class="normal-message" ng-class="{others:self!==info.from,self:self===info.from}" ng-switch-when="normal">
        <div class="name-wrapper">
<span>{{info.from}} @ </span>
<span>{{time |  date: 'HH:mm:ss' }}</span>
</div>
        <div class="content-wrapper">
<span class="content">{{info.text}}</span>
<span class="avatar"></span>
</div>
    </div>
</div>

前端user.html


<div class="user">
<meta charset="utf-8">
<span class="avatar"></span><span class="nickname">{{info.nickname?info.nickname:"群聊"}}</span>
    <span class="unread" ng-show="info.hasNewMessage&&!iscurrentreceiver">[未读]</span>
</div>



























































































通过 Express 内置的 express.static 可以方便地托管静态文件,例如图片、CSS、JavaScript 文件等。

将静态资源文件所在的目录作为参数传递给 express.static 中间件就可以提供静态资源文件的访问了。例如,假设在 public 目录放置了图片、CSS 和 JavaScript 文件,你就可以:

app.use(express.static('public'));

现在,public 目录下面的文件就可以访问了。

http://localhost:3000/images/kitten.jpghttp://localhost:3000/css/style.csshttp://localhost:3000/js/app.jshttp://localhost:3000/images/bg.pnghttp://localhost:3000/hello.html
0 0
原创粉丝点击