使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序(二)

本文将分两部分来讲述使用Socket.IO结合Node.js、Express、MongoDB和AngularJS实现一个简单的聊天应用程序示例。本文是第一部分的内容《使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序(二)》主要讲述示例程序前端构建和用户注册登录功能接入与实现。

《使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序(一)》

《使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序(二)》


三、集成Socket.IO

现在正式进入本文的另一个主题,使用Socket.IO实现实时聊天功能。

前面已经讲到过,Socket.IO包括服务器和客户端两部分。所以集成Socket.IO时需要同时配置服务器端和客户端。

Socket.IO是事件驱动型的,利用事件去监听和传递状态和数据。最基本的聊天场景有两个事件监听,即用户登录在线和发送信息。

这一部分的内容包括Socket.IO的集成、聊天(群聊)的实际,用户状态的提醒以及聊天样式设置等。

1、Socket.IO集成

(1)服务器端

前面在搭建用户注册登录API接口的时候,我们在服务器端server文件夹中将服务器配置定义在server.js中,这个文件的配置仅是用户注册登录API接口的,不是作为整个程序的后端入口。这里在配置Socket.IO的时候,我们在项目根目录再定义一个server.js,这里将配置作为整个项目的后端入口,以及配置Socket.IO。

步骤一:安装Socket.IO

npm install --save socket.io

步骤二:引入express和Socket.IO,作初始化配置

var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);

步骤三:定义Socket.IO的连接事件

io.on('connection', function(socket){
	console.log('一个用户已连接');
});

这个基本的connection事件是所有关于Socket.IO操作的前提,只有处于“连接”状态才可能进行各种事件的传播和监控。

步骤四:定义输出入口页面

app.get('/', function(req, res){
    res.sendFile(__dirname + '/src/index.html'); 
});

这里是项目根目录,而入口文件是在客户端src目录下的,所以在这里设置入口页面的位置。这里还要注意,客户端src目录以及里面的图片文件、样式文件以及各种依赖库都是作为静态资源被调用的,所以这个目录需要设置为静态资源目录。使用以下代码:

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

步骤六:设置监听端口并监听

http.listen(8888, function(){
	console.log('伺服正在8888端口运行.')
});

至此,一个最基本的Socket.IO服务端就设置好了,当然这个服务器没有什么功能,只能在客户端访问指定入口文件时(需要客户端也做好配置的情况下),在服务端控制台中输出“一个用户已连接”字符串。

(2)客户端

客户端的配置分以下几步:

步骤一:下载Socket.IO客户端库(这里使用版本为1.4.5),并加到入口文件中。

<script src="lib/socket.io-1.4.5.js"></script>

步骤二:在客户端主脚本文件中加入

var socket = io();

至此,Socket.IO客户端配置就可以了。

在命令行中进入项目文件夹,执行nodemon server.js,然后打开浏览器,输入http://localhost:8888,运行效果如下 :

使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序

同时,控制台中会显示“一个用户已连接”字符串。

使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序

这表示Socket.IO的已成功配置,接下来,将完善程序功能。

 

2、发送消息

(1)服务端配置

socket.on('sendMsg', function(msg){
    socket.broadcast.emit('sendMsg', msg);
});

(2)客户端配置

初始化消息列表:

$scope.groupMsgs = [];

自定义指定,使聊天输入框(div)可以使用ng-model绑定:

.directive("contenteditable", function() {
  return {
    restrict: "A",
    require: "ngModel",
    link: function(scope, element, attrs, ngModel) {

      function read() {
        ngModel.$setViewValue(element.html());
      }

      ngModel.$render = function() {
        element.html(ngModel.$viewValue || "");
      };

      element.bind("blur keyup change", function() {
        scope.$apply(read);
      });
    }
  };
})
<div class="chatBox" contenteditable="true" contenteditable ng-model="msg"></div>

点击发送按钮响应事件:

$scope.send = function(){
	var myMsg = {username: sessionStorage.getItem('username'), nickname: sessionStorage.getItem('nickname'), msg: $scope.msg};
	socket.emit('sendMsg', myMsg);
	$scope.groupMsgs.push(myMsg);
	$scope.msg = '';
}

接收到新消息推送时:

socket.on('sendMsg', function(msg){
	$scope.groupMsgs.push(msg);
	$scope.$apply();
})

 

3、消息列表显示

因用户注册功能没有用户头像,所以这里拿两个静态图片,用于消息列表显示。

显示:

<div class="chatRightTop">
	<div class="messageItemContainer" ng-repeat="m in groupMsgs">
		<div ng-class="{true: 'message1', false: 'message2'}[user.username == m.username]">
			<img src="img/head1.png" ng-if="user.username == m.username" />
			<img src="img/head2.png" ng-if="user.username != m.username" />
			<div>
				<h4>{{m.nickname}}</h4>
				<p ng-bind-html="m.msg"></p>
			</div>
		</div>
	</div>
</div>

样式:

.chatRightTop .messageItemContainer{padding: 0.8rem;box-sizing: border-box;}
.message1{margin:0.6rem 0; float: left;width: 100%;text-align: left;}
.message1 img{width:40px; height: 40px;display: block;float: left;padding-right: 0.6rem;}
.message2{margin:0.6rem 0; float: right;width: 100%;text-align: right}
.message2 img{width:40px; height: 40px;display: block;float: right;padding-left: 0.6rem;}
.message1 h4,.message2 h4{margin: 0; padding: 0;}
.message1 p, .message2 p{margin: 0; padding: 0;}

 

4、显示用户列表以及用户上线、离线状态提示

(1)服务端配置

socket.on('online', function(user){
    var currentUser = user;
    var isUserLogined = false;
    for(var i=0; i<onlineUsers.length; i++){
        if(onlineUsers[i].username == user.username){
            isUserLogined = true;
            break;
        }else{
            isUserLogined = false;
        }
    }
    if(!isUserLogined){
        onlineUsers.push(user); 
    }
    socket.broadcast.emit('online', user);
    io.emit('showAllUsers', onlineUsers);

    socket.on('disconnect', function(){
        var key = null;
        socket.broadcast.emit('userDisconnect', currentUser);
        for(var i=0; i<onlineUsers.length; i++){
            if(onlineUsers[i].username == currentUser.username){
                key = i;
                break;
            }
        }
        onlineUsers.splice(key, 1);
        socket.broadcast.emit('showAllUsers', onlineUsers);
    });
});

这里分别监听了online、showAllUsers和disconnect事件。online事件用于处理用户登录后的上线提示;showAllUsers用于获取了新的用户列表。disconnect用于处理用户离开后的下线提示。

(2)客户端配置

$scope.friends = $scope.friends ? $scope.friends : [];

$scope.user = {
	username: sessionStorage.getItem('username') ? sessionStorage.getItem('username') : null,
	nickname: sessionStorage.getItem('nickname') ? sessionStorage.getItem('nickname') : null
};

if(sessionStorage.getItem('username') && sessionStorage.getItem('nickname')){
	socket.emit('online', $scope.user);
}


socket.on('online', function(user){
	ngToast.create({
	  	content: '<div class="info">' + user.nickname + '上线了</div>',
	  	timeout: 4000
	});
	$scope.$apply();
})

socket.on('showAllUsers', function(users){
	$scope.friends = users;
	$scope.$apply();
})

socket.on('userDisconnect', function(user){
	ngToast.create({
	  	content: '<div class="info">' + user.nickname + '下线了</div>',
	  	timeout: 4000
	});
	$scope.$apply();
})

另外,还要在响应用户登录的$scope.login方法中将用户登录成功后的信息发给Socket.IO后端。

$scope.user = {
username: sessionStorage.getItem('username') ? sessionStorage.getItem('username') : null,
nickname: sessionStorage.getItem('nickname') ? sessionStorage.getItem('nickname') : null
};
//登录成功后向Socket服务器发送上线通知
socket.emit('online', $scope.user);

 

至此,Socket.IO的集成已完成,主要文件代码如下:

var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);

var onlineUsers = [];

io.on('connection', function(socket){
	console.log('一个用户已连接');
    socket.on('online', function(user){
        var currentUser = user;
        var isUserLogined = false;
        for(var i=0; i<onlineUsers.length; i++){
            if(onlineUsers[i].username == user.username){
                isUserLogined = true;
                break;
            }else{
                isUserLogined = false;
            }
        }
        if(!isUserLogined){
            onlineUsers.push(user); 
        }
        socket.broadcast.emit('online', user);
        io.emit('showAllUsers', onlineUsers);

        socket.on('disconnect', function(){
            var key = null;
            socket.broadcast.emit('userDisconnect', currentUser);
            for(var i=0; i<onlineUsers.length; i++){
                if(onlineUsers[i].username == currentUser.username){
                    key = i;
                    break;
                }
            }
            onlineUsers.splice(key, 1);
            socket.broadcast.emit('showAllUsers', onlineUsers);
        });
    });

    socket.on('sendMsg', function(msg){
        console.log('客户端发来信息:' + msg);
        socket.broadcast.emit('sendMsg', msg);
    });
});

app.get('/', function(req, res){
    res.sendFile(__dirname + '/src/index.html'); 
});

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

http.listen(8888, function(){
	console.log('伺服正在8888端口运行.')
});
var socket = io();

angular.module('socketioApp', ['ngResource', 'ngSanitize', 'ngToast'])

.config(function ($httpProvider, $httpParamSerializerJQLikeProvider){
  $httpProvider.defaults.transformRequest.unshift($httpParamSerializerJQLikeProvider.$get());
  $httpProvider.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
  $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
  $httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
})

.config(['ngToastProvider', function(ngToast) {
	ngToast.configure({
		verticalPosition: 'bottom',
		horizontalPosition: 'center',
		maxNumber: 3
	});
}])

.constant('APPINFO', {
	apiurl: 'http://localhost:3000/user'
})


.factory('GetUserList', function($resource, APPINFO){
	return $resource(APPINFO.apiurl, {}, {});
})

.factory('UserReg', function($resource, APPINFO){
	return $resource(APPINFO.apiurl + '/reg', {}, {
	  'post': {method: 'POST'}
	});
})

.factory('UserLogin', function($resource, APPINFO){
	return $resource(APPINFO.apiurl + '/login', {}, {
	  'post': {method: 'POST'}
	});
})


.service('UserService', function($q, $http, $window, $resource, APPINFO, UserReg, UserLogin) {
    var isLogin = false;

    var reg = function(username, nickname, password){
    	return $q(function(resolve, reject){
    		if(username && nickname && password){
    			UserReg.post({username: username, nickname: nickname, password: password}).$promise.then(function(res){
    				resolve(res);
    			}, function(error){
    				reject(error);
    			})
	    	}else{
	    		reject({status: false, info: '用户名、昵称、密码不能为空!'});
	    	}
    	})
    };

    var login = function(username, password){
      return $q(function(resolve, reject) {
      	UserLogin.post({username: username, password: password}).$promise.then(function(res){
      		if(res.status){
	      		isLogin = true;
	      		sessionStorage.setItem('isLogin', isLogin);
	      		sessionStorage.setItem('username', res.data.username);
	      		sessionStorage.setItem('nickname', res.data.nickname);
	      		resolve(res);
      		}else{
      			reject(res);
      		}
      	}, function(error){
      		reject(res);
      	});
      });
    };

    var logout = function() {
      	sessionStorage.clear();
      	$window.location.reload(true);
    };

    return {
      login: login,
      logout: logout,
      reg: reg
    };
  })

.directive("contenteditable", function() {
  return {
    restrict: "A",
    require: "ngModel",
    link: function(scope, element, attrs, ngModel) {

      function read() {
        ngModel.$setViewValue(element.html());
      }

      ngModel.$render = function() {
        element.html(ngModel.$viewValue || "");
      };

      element.bind("blur keyup change", function() {
        scope.$apply(read);
      });
    }
  };
})

.controller('indexCtrl', function($scope, GetUserList, UserReg, UserService, ngToast){
	//获取用户列表
	// GetUserList.get({}).$promise.then(function(res){
	// 	console.log(res);
	// }, function(error){
	// 	console.log(error);
	// });

	//初始化
	$scope.showLoginForm = false;
	$scope.showRegForm = false;
	$scope.isLogined = sessionStorage.getItem('isLogin') === 'true' ? true : false;
	$scope.userinfo = {};
	$scope.errorInfo = '';
	$scope.friends = $scope.friends ? $scope.friends : [];
	$scope.groupMsgs = [];
	$scope.user = {
		username: sessionStorage.getItem('username') ? sessionStorage.getItem('username') : null,
		nickname: sessionStorage.getItem('nickname') ? sessionStorage.getItem('nickname') : null
	};

	if(sessionStorage.getItem('username') && sessionStorage.getItem('nickname')){
		socket.emit('online', $scope.user);
	}

	$scope.selectLogin = function(){
		$scope.userinfo = {};
		$scope.showLoginForm = true;
		$scope.showRegForm = false;
		$scope.isLogin = true;
		$scope.loginBtnStyle = {
			"background-color": '#cc33cc',
			color: '#fff'
		}
		$scope.regBtnStyle = {};
	}

	$scope.selectReg = function(){
		$scope.userinfo = {};
		$scope.showLoginForm = false;
		$scope.showRegForm = true;
		$scope.isLogin = false;
		$scope.regBtnStyle = {
			"background-color": '#cc33cc',
			color: '#fff'
		}
		$scope.loginBtnStyle = {};
	}

	

	$scope.checkPassword = function(password, password2){
		if(password === password2){
			$scope.isPasswordValid = true;
		}else{
			$scope.isPasswordValid = false;
		}
	}
	
	$scope.register = function(){
		UserService.reg($scope.userinfo.username, $scope.userinfo.nickname, $scope.userinfo.password).then(function(res){
			ngToast.create({
			  	content: '<div class="info">' + res.info + '</div>',
			  	timeout: 2000
			});
			if(res.status){
				$scope.selectLogin();
			}
		}, function(error){
			ngToast.create({
			  	content: '<div class="info">' + error.info + '</div>',
			  	timeout: 2000
			});
		});
	}

	$scope.login = function(){
		UserService.login($scope.userinfo.username, $scope.userinfo.password).then(function(res){
			if(res.status && res.data.username && res.data.nickname){
				$scope.isLogined = true;
				$scope.user = {
					username: sessionStorage.getItem('username') ? sessionStorage.getItem('username') : null,
					nickname: sessionStorage.getItem('nickname') ? sessionStorage.getItem('nickname') : null
				};

				//登录成功后向Socket服务器发送上线通知
				socket.emit('online', $scope.user);
			}else{
				ngToast.create({
				  	content: '<div class="info">' + res.info + '</div>',
				  	timeout: 2000
				});
			}
		}, function(error){
			ngToast.create({
			  	content: '<div class="info">' + error.info + '</div>',
			  	timeout: 2000
			});
		})
	}

	$scope.logout = function(){
		UserService.logout();
	}

	$scope.send = function(){
		var myMsg = {username: sessionStorage.getItem('username'), nickname: sessionStorage.getItem('nickname'), msg: $scope.msg};
		socket.emit('sendMsg', myMsg);
		$scope.groupMsgs.push(myMsg);
		$scope.msg = '';
	}

	//获取用户上线通知
	socket.on('online', function(user){
		ngToast.create({
		  	content: '<div class="info">' + user.nickname + '上线了</div>',
		  	timeout: 4000
		});
		$scope.$apply();
	})

	socket.on('showAllUsers', function(users){
		$scope.friends = users;
		$scope.$apply();
	})

	socket.on('sendMsg', function(msg){
		$scope.groupMsgs.push(msg);
		$scope.$apply();
	})

	socket.on('userDisconnect', function(user){
		ngToast.create({
		  	content: '<div class="info">' + user.nickname + '下线了</div>',
		  	timeout: 4000
		});
		$scope.$apply();
	})
})
<!DOCTYPE html>
<html ng-app="socketioApp">
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
 <title>Socket IO</title>
 <link rel="stylesheet" type="text/css" href="lib/pure-min.css">
 <link rel="stylesheet" type="text/css" href="lib/ngToast.min.css">
 <link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body ng-controller="indexCtrl">
 <toast></toast>
 <div class="container" ng-show="!isLogined">
 <img class="logo" src="img/logo.png" />
 <h1>使用Socket.io构建聊天应用程序</h1>
 <div class="buttonBox">
 <div ng-click="selectLogin()" ng-style="loginBtnStyle">登录</div>
 <div ng-click="selectReg()" ng-style="regBtnStyle">注册</div>
 </div>
 <div class="form" ng-show="showLoginForm">
 <form id="loginForm" name="loginForm" class="pure-form pure-form-aligned">
 <fieldset>
 <div class="pure-control-group">
 <label for="username">用户名</label>
 <input id="username" type="text" placeholder="用户名" ng-require="true" ng-pattern="/^[A-Za-z]+$/" ng-model="userinfo.username" required>
 </div>
 <div class="pure-control-group">
 <label for="password">密码</label>
 <input id="password" name="password" type="password" placeholder="密码" ng-require="true" ng-pattern="/^(\w){6,20}$/" ng-minlength="6" ng-model="userinfo.password" required>
 </div>
 <div class="pure-controls">
 <button type="submit" class="pure-button pure-button-primary" ng-disabled="loginForm.$invalid" ng-click="login()">登录</button>
 </div>
 </fieldset>
 </form>
 </div>
 <div class="form" ng-show="showRegForm">
 <form id="regForm" name="regForm" class="pure-form pure-form-aligned">
 <fieldset>
 <div class="pure-control-group">
 <label for="username">用户名</label>
 <input id="username" type="text" placeholder="用户名" ng-require="true" ng-pattern="/^[A-Za-z]+$/" ng-model="userinfo.username" required>
 </div>
 <div class="pure-control-group">
 <label for="nickname">昵称</label>
 <input id="nickname" type="text" placeholder="昵称" ng-require="true" ng-pattern="/^[\u4E00-\u9FA5A-Za-z0-9]+$/" ng-model="userinfo.nickname" required>
 </div>
 <div class="pure-control-group">
 <label for="password">密码</label>
 <input id="password" name="password" type="password" placeholder="密码" ng-require="true" ng-pattern="/^(\w){6,20}$/" ng-minlength="6" ng-model="userinfo.password" required>
 </div>
 <div class="pure-control-group">
 <label for="password2">重输密码</label>
 <input id="password2" name="password2" type="password" placeholder="重输密码" ng-require="true" ng-pattern="/^(\w){6,20}$/" ng-minlength="6" ng-model="userinfo.password2" ng-change="checkPassword(userinfo.password, userinfo.password2)" required>
 </div>
 <div class="pure-controls">
 <button type="submit" class="pure-button pure-button-primary" ng-disabled="regForm.$invalid || !isPasswordValid" ng-click="register()">注册</button>
 </div>
 </fieldset>
 </form>
 </div>
 </div>

 <div class="chatContainer" ng-show="isLogined">
 <div class="chatLeft">
 <div class="chatLeftTop">
 <img src="img/logo.png" />
 </div>
 <div>
 <!-- <h2>在线群聊</h2> -->
 <h2>在线好友</h2>
 <ul>
 <li ng-repeat="friend in friends">
 {{friend.nickname}}
 </li>
 </ul>
 </div>
 </div>
 <div class="chatRight">
 <div class="chatRightFixedTop">
 <span>您好, {{user.nickname}}</span>
 <span ng-click="logout()" class="logout">退出</span>
 </div>
 <div class="chatRightTop">
 <div class="messageItemContainer" ng-repeat="m in groupMsgs">
 <div ng-class="{true: 'message1', false: 'message2'}[user.username == m.username]">
 <img src="img/head1.png" ng-if="user.username == m.username" />
 <img src="img/head2.png" ng-if="user.username != m.username" />
 <div>
 <h4>{{m.nickname}}</h4>
 <p ng-bind-html="m.msg"></p>
 </div>
 </div>
 </div>
 </div>
 <div class="chatRightBottom">
 <div class="chatBox" contenteditable="true" contenteditable ng-model="msg"></div>
 </div>
 <button class="pure-button pure-button-primary" ng-click="send()">发送</button>
 </div>
 </div>
 
 <script src="lib/angular.min.js"></script>
 <script src="lib/angular-resource.min.js"></script>
 <script src="lib/angular-sanitize.min.js"></script>
 <script src="lib/ngToast.min.js"></script>
 <script src="lib/socket.io-1.4.5.js"></script>
 <script src="js/app.js"></script>
</body>
</html>
html{font-size: 100%;}
ul, li{list-style: none;margin:0; padding:0;}

.container{background-color: #99cccc;width:100%;height: 100%;min-width: 550px; min-height: 100vh;display: flex;align-items: center; justify-content: center;flex-direction: column;}
.logo{display: block;width: 320px;}
.container h1{font-family: '微软雅黑'; color:#cc33cc;}
.buttonBox{display:flex;}
.buttonBox div{width:140px; height: 70px; border:1px solid #fff; border-radius: 6px;display: flex; align-items: center; justify-content: center; margin:1rem 1rem;font-size: 1.6rem; font-family: '微软雅黑';color:#cc33cc;}
.buttonBox div:hover{background-color: #cc33cc; cursor: pointer;color:#fff;}
.form{padding:1rem;border:1px solid #f8f8f8;}
.pure-form-aligned .pure-controls{margin:0; text-align: center;margin-top: 1.2rem;}
.pure-button-primary{background-color: #cc33cc;}
.chatContainer{background-color: #99cccc;width:100%;height: 100%;min-width: 100vw; min-height: 100vh;display: flex; flex-direction: row;justify-content: max-width:268px; space-between;}
.chatLeft{width:20%;background-color: #99cccc; min-width: 200px; max-width:300px;box-sizing: border-box;}
.chatLeftTop{padding:1rem;}
.chatLeft img{width: 100%; border-bottom: 1px solid #fff;padding-bottom: 1rem;}
.chatLeft h2{font-size:1rem; cursor: pointer;padding:0 0.7rem;}
.chatLeft ul{}
.chatLeft li{line-height: 2rem;padding:0 0.7rem;}
.chatLeft li:hover{background-color: #cc33cc;cursor: pointer;color:#fff;}
.chatRight{width:80%;flex:1; background-color: #f8f8f8; min-width: 600px;box-sizing: border-box;display: flex;flex-direction: column;justify-content: space-between;box-sizing: border-box;}
.chatRightFixedTop{background-color: #cc33cc; color: #fff; height: 30px;line-height: 30px;padding:0 0.5rem; box-sizing: border-box;display: flex; justify-content: space-between;border-bottom: 1px solid #ccc;}
.chatRightFixedTop .logout{cursor: pointer;}
.chatRightFixedTop .logout:hover{color:#99cccc;}
.chatRightTop{flex:1;min-height: 400px;}
.chatRightBottom{height: 128px;border-top:2px solid #ccc;padding:0.5rem;margin-bottom: 2.5rem;box-sizing: border-box;}
.chatRight button{position: absolute; bottom:0.5rem;right:0.5rem;width:4rem;}
.chatBox{width: 100%;height: 100%;padding:0.5rem;box-sizing: border-box;}
.info{margin-bottom: 4rem;background-color: rgba(0,0,0,0.5);padding:0.6rem 1rem; color:#fff; border-radius: 4px;}

.chatRightTop .messageItemContainer{padding: 0.8rem;box-sizing: border-box;}
.message1{margin:0.6rem 0; float: left;width: 100%;text-align: left;}
.message1 img{width:40px; height: 40px;display: block;float: left;padding-right: 0.6rem;}
.message2{margin:0.6rem 0; float: right;width: 100%;text-align: right}
.message2 img{width:40px; height: 40px;display: block;float: right;padding-left: 0.6rem;}
.message1 h4,.message2 h4{margin: 0; padding: 0;}
.message1 p, .message2 p{margin: 0; padding: 0;}

运行效果:

使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序

使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序

使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序

使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序

 

四、将消息记录保存到MongoDB中

最后,我们再把消息记录保存到MongoDB中。

1、安装mongoose

npm install mongoose --save

2、在myMongodb数据库中新建msgs集合

db.createCollection('msgs')

3、引入mongoose并作配置

var mongoose = require('mongoose');
var db = mongoose.connect("mongodb://localhost/myMongodb");

4、定义并使用数据模型

var Schema = mongoose.Schema;
var msgModel = new Schema({
    username: String,
    nickname: String,
    msg: String
});
var MsgModel = mongoose.model('Msg', msgModel);

5、在“接收信息”事件中加入将消息保存到MongoDB数据库的操作

socket.on('sendMsg', function(msg){
console.log('客户端发来信息:' + msg);
var msgInfo = new MsgModel(msg);
msgInfo.save(function(err, msg){
if(err){
console.log(err);
    }else{
console.log(msg + '已保存到MongoDB');
}
})
socket.broadcast.emit('sendMsg', msg);
});

最后,socketIO/server.js文件代码如下:

var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);

var mongoose = require('mongoose');
var db = mongoose.connect("mongodb://localhost/myMongodb");
var Schema = mongoose.Schema;
var msgModel = new Schema({
    username: String,
    nickname: String,
    msg: String
});
var MsgModel = mongoose.model('Msg', msgModel);

var onlineUsers = [];

io.on('connection', function(socket){
	console.log('一个用户已连接');
    socket.on('online', function(user){
        var currentUser = user;
        var isUserLogined = false;
        for(var i=0; i<onlineUsers.length; i++){
            if(onlineUsers[i].username == user.username){
                isUserLogined = true;
                break;
            }else{
                isUserLogined = false;
            }
        }
        if(!isUserLogined){
            onlineUsers.push(user); 
        }
        socket.broadcast.emit('online', user);
        io.emit('showAllUsers', onlineUsers);

        socket.on('disconnect', function(){
            var key = null;
            socket.broadcast.emit('userDisconnect', currentUser);
            for(var i=0; i<onlineUsers.length; i++){
                if(onlineUsers[i].username == currentUser.username){
                    key = i;
                    break;
                }
            }
            onlineUsers.splice(key, 1);
            socket.broadcast.emit('showAllUsers', onlineUsers);
        });
    });

    socket.on('sendMsg', function(msg){
        console.log('客户端发来信息:' + msg);

        var msgInfo = new MsgModel(msg);
        msgInfo.save(function(err, msg){
            if(err){
                console.log(err);
            }else{
                console.log(msg + '已保存到MongoDB');
            }
        })

        socket.broadcast.emit('sendMsg', msg);
    });
});

app.get('/', function(req, res){
    res.sendFile(__dirname + '/src/index.html'); 
});

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

http.listen(8888, function(){
	console.log('伺服正在8888端口运行.')
});

运行效果:

使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序

使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序

最终项目文件目录如下:

使用Socket.IO+Node+Express+MongoDB+Angular构建聊天应用程序

 

五、总结

Socket.IO为前端实时通讯带来了极大的便利,大大降低了实时通讯功能实现技术的门槛,而Socket.IO+Node.js+Express+MongoDB+Angular的搭配更是前端通讯模块开发的首选(个人认为)。本文用一个简单的示例浅浅过了一下这几门技术,还有更多强大的功能有待发掘。就先写到这里了,Have Fun!

 

那时那我

随遇,随缘,随安,随喜!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.