+2

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, 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

<!-- 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.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí