Tạo phòng chat sử dụng MEAN
Tiếp theo phần chat bằng nodejs, bài này tôi xin cùng các bạn đi thêm 1 ứng dụng khác sâu hơn chút đấy là tạo ra 1 phòng chat và có lưu database. Về các bước thiết lập cài đặt Nodejs, Express thì các bạn xem ở bài trước và sẽ không cân nhắc lại nữa. Server Phía server cơ bản cấu trúc gồm model, ...
Tiếp theo phần chat bằng nodejs, bài này tôi xin cùng các bạn đi thêm 1 ứng dụng khác sâu hơn chút đấy là tạo ra 1 phòng chat và có lưu database. Về các bước thiết lập cài đặt Nodejs, Express thì các bạn xem ở bài trước và sẽ không cân nhắc lại nữa.
Server
Phía server cơ bản cấu trúc gồm model, routes và socket events. Đây sẽ là cấu trúc xuyên suốt bài viết.
Cấu trúc folder
Chúng ta cần tổ chức cấu trúc folder theo dưới đây
|- public |- index.html |- server.js |- package.json
Bắt đầu với server
Việc đầu tiên ta cần thiết lập các biến toàn cục cho toàn app vào file server.js
// server.js // Import all our dependencies var express = require('express'); var mongoose = require('mongoose'); var app = express(); var server = require('http').Server(app); var io = require('socket.io')(server);
Để có thể khai báo cho Express nhận biết được folder chứa các file toàn cục ta cần lệnh sau:
// tell express where to serve static files from app.use(express.static(__dirname + '/public'));
Bước tiếp theo ta cần thiết lập kết nối đến cơ sở dữ liệu của chúng ta. Ở trong bài viết này tôi sẽ sử dụng MongoDb ở trên local và cũng là 1 lựa chọn tốt khi làm trên server. Mongoose là một ứng dụng của Node sẽ tạo ra các tiện ích cho chúng ta làm việc với MongoDb trở nên dễ dàng hơn. Và để kết nối đến database ta có lệnh sau:
mongoose.connect("mongodb://127.0.0.1:27017/scotch-chat");
Với Mongoose ta có thể tạo được các bảng cũng như Model và có thể kết nối được đến với domain khác. Dưới đây ta tạo 1 table là chat như sau:
// create a schema for chat var ChatSchema = mongoose.Schema({ created: Date, content: String, username: String, room: String }); // create a model from the chat schema var Chat = mongoose.model('Chat', ChatSchema); // allow CORS app.all('*', function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key'); if (req.method == 'OPTIONS') { res.status(200).end(); } else { next(); } });
Server chúng ta giờ sẽ có 3 đường dẫn, 1 là trang index, 2 và trang thiết lập thông tin và cuối cùng là trang vào các phòng chat.
// route for our index file app.get('/', function(req, res) { //send the index.html in our public directory res.sendfile('index.html'); }); //This route is simply run only on first launch just to generate some chat history app.post('/setup', function(req, res) { //Array of chat data. Each object properties must match the schema object properties var chatData = [{ created: new Date(), content: 'Hi', username: 'Chris', room: 'php' }, { created: new Date(), content: 'Hello', username: 'Obinna', room: 'laravel' }, { created: new Date(), content: 'Ait', username: 'Bill', room: 'angular' }, { created: new Date(), content: 'Amazing room', username: 'Patience', room: 'socet.io' }]; //Loop through each of the chat data and insert into the database for (var c = 0; c < chatData.length; c++) { //Create an instance of the chat model var newChat = new Chat(chatData[c]); //Call save to insert the chat newChat.save(function(err, savedChat) { console.log(savedChat); }); } //Send a resoponse so the serve would not get stuck res.send('created'); }); //This route produces a list of chat as filterd by 'room' query app.get('/msg', function(req, res) { //Find Chat.find({ 'room': req.query.room.toLowerCase() }).exec(function(err, msgs) { //Send res.json(msgs); }); }); /*||||||||||||||||||END ROUTES|||||||||||||||||||||*/
Như vạy ta có thể thấy ta sẽ có 3 đường dẫn ở đây đó là
Index.htmlĐây là đường dẫn khá dễ hiểu chạy trực tiếp vào file index.html
/setupĐây là đương dẫn thiết lập và khi chúng ta vào đường dẫn này sẽ tạo ra vài dữ liệu demo. Nếu chúng ta không cần dữ liệu để test thì có thể bỏ qua đường dẫn này. Việc thêm database sẽ có thể trùng lặp nếu ta liên tục vào link trên
/msgĐây là đường dẫn để giúp ta lọc các tin nhắn dựa vào tên của room chat. Và chúng ta sẽ tạo ra các socket cho các room như sau
/*||||||||||||||||SOCKET|||||||||||||||||||||||*/ //Listen for connection io.on('connection', function(socket) { //Globals var defaultRoom = 'general'; var rooms = ["General", "angular", "socket.io", "express", "node", "mongo", "PHP", "laravel"]; //Emit the rooms array socket.emit('setup', { rooms: rooms }); //Listens for new user socket.on('new user', function(data) { data.room = defaultRoom; //New user joins the default room socket.join(defaultRoom); //Tell all those in the room that a new user joined io.in(defaultRoom).emit('user joined', data); }); //Listens for switch room socket.on('switch room', function(data) { //Handles joining and leaving rooms //console.log(data); socket.leave(data.oldRoom); socket.join(data.newRoom); io.in(data.oldRoom).emit('user left', data); io.in(data.newRoom).emit('user joined', data); }); //Listens for a new chat message socket.on('new message', function(data) { //Create message var newMsg = new Chat({ username: data.username, content: data.message, room: data.room.toLowerCase(), created: new Date() }); //Save it to database newMsg.save(function(err, msg){ //Send message to those connected in the room io.in(msg.room).emit('message created', msg); }); }); }); /*||||||||||||||||||||END SOCKETS||||||||||||||||||*/
Node client
Đầu tiên ta tạo file lơp view là views/index.ejs
<html><head> <title>scotch-chat</title> <link rel="stylesheet" href="css/app.css"> <link rel="stylesheet" href="css/animate.css"> <link rel="stylesheet" href="libs/angular-material/angular-material.css"> <script src="libs/angular/angular.js"></script> <script src="http://localhost:2015/socket.io/socket.io.js"></script> <script type="text/javascript" src="libs/angular-animate/angular-animate.js"></script> <script type="text/javascript" src="libs/angular-aria/angular-aria.js"></script> <script type="text/javascript" src="libs/angular-material/angular-material.js"></script> <script type="text/javascript" src="libs/angular-socket-io/socket.js"></script> <script type="text/javascript" src="libs/angular-material-icons/angular-material-icons.js"></script> <script src="js/app.js"></script> </head> <body ng-controller="MainCtrl" ng-init="usernameModal()"> <md-content> <section> <md-list> <md-subheader class="md-primary header">Room: {{room}} <span align="right">Userame: {{username}} </span> </md-subheader> <md-whiteframe ng-repeat="m in messages" class="md-whiteframe-z2 message" layout layout-align="center center"> <md-list-item class="md-3-line"> <img ng-src="img/user.png" class="md-avatar" alt="User" /> <div class="md-list-item-text"> <h3>{{ m.username }}</h3> <p>{{m.content}}</p> </div> </md-list-item> </md-whiteframe> </md-list> </section> <div class="footer"> <md-input-container> <label>Message</label> <textarea ng-model="message" columns="1" md-maxlength="100" ng-enter="send(message)"></textarea> </md-input-container> </div> </md-content> </body> </html>
Trong đoạn HTML trên có thể thấy chúng ta sử dụng angular và sử dụng thuộc tính ng-repeat để lấy các tin nhắn từ server trả về. Và hàm khởi tạo init sẽ yêu cầu người dùng đăng nhập.
app.js
Đây là file phía client xử lý:
// app.js //Load angular var app = angular.module('scotch-chat', ['ngMaterial', 'ngAnimate', 'ngMdIcons', 'btford.socket-io']); //Set our server url var serverBaseUrl = 'http://localhost:2015'; //Services to interact with nodewebkit GUI and Window app.factory('GUI', function () { //Return nw.gui return require('nw.gui'); }); app.factory('Window', function (GUI) { return GUI.Window.get(); }); //Service to interact with the socket library app.factory('socket', function (socketFactory) { var myIoSocket = io.connect(serverBaseUrl); var socket = socketFactory({ ioSocket: myIoSocket }); return socket; });
Tiếp đến chúng ta tạo các Angular service, đầu tiên là service về GUI. Ở đây thiết lập việc nhầm nội dung và ấn Enter là gửi tin nhắn đi:
//ng-enter directive app.directive('ngEnter', function () { return function (scope, element, attrs) { element.bind("keydown keypress", function (event) { if (event.which === 13) { scope.$apply(function () { scope.$eval(attrs.ngEnter); }); event.preventDefault(); } }); }; });
Tạo danh sách các phòng chat
Đầu tiên ta tạo 1 Angular Controller
//Our Controller app.controller('MainCtrl', function ($scope, Window, GUI, $mdDialog, socket, $http){
Trong controller này ta sẽ tạo 4 menu tương ưng với các chức năng
// app.js //Global Scope $scope.messages = []; $scope.room = ""; //Build the window menu for our app using the GUI and Window service var windowMenu = new GUI.Menu({ type: 'menubar' }); var roomsMenu = new GUI.Menu(); windowMenu.append(new GUI.MenuItem({ label: 'Rooms', submenu: roomsMenu })); windowMenu.append(new GUI.MenuItem({ label: 'Exit', click: function () { Window.close() } }));
Chúng ta sẽ tạo 1 hiệu ứng đơn gian và 2 mục Room và Exit, trong đó Room click vào sẽ xổ xuống danh sách các phòng chat. Sự kiện khi click vào room ta xử lý như sau:
//Listen for the setup event and create rooms socket.on('setup', function (data) { var rooms = data.rooms; for (var r = 0; r < rooms.length; r++) { //Loop and append room to the window room menu handleRoomSubMenu(r); } //Handle creation of room function handleRoomSubMenu(r) { var clickedRoom = rooms[r]; //Append each room to the menu roomsMenu.append(new GUI.MenuItem({ label: clickedRoom.toUpperCase(), click: function () { //What happens on clicking the rooms? Swtich room. $scope.room = clickedRoom.toUpperCase(); //Notify the server that the user changed his room socket.emit('switch room', { newRoom: clickedRoom, username: $scope.username }); //Fetch the new rooms messagess $http.get(serverBaseUrl + '/msg?room=' + clickedRoom).success(function (msgs) { $scope.messages = msgs; }); } })); } //Attach menu GUI.Window.get().menu = windowMenu; });
Nhận tin nhắn
Bây giờ ta thực hiện việc xử lý tin nhắn khi 1 người dùng gửi đến server và các client nhận nó và thêm vào các message đã có.
//Dialog controller function UsernameDialogController($scope, $mdDialog) { $scope.answer = function (answer) { $mdDialog.hide(answer); }; }
Vậy ta đã có thể có 1 ưng dụng chat có các phòng chat khác nhau.
Kết luận
Qua bài này tôi chỉ giới thiệu qua về các tạo các phòng chat 1 cách đơn giản để hình dùng việc ta làm việc với cơ sở dữ liệu như thế nào, cũng như hình dung sơ qua về công nghệ MEAN. Tuy nhiên trong bài vẫn còn nhiều thiếu xót như xử lý đăng nhập hay số lượng tin nhắn gửi về vẫn chưa giải quyết được.