2016/01/29

Node.js, MySQL ашиглан вэб push notification хийх

Friday, January 29, 2016 Posted by Anonymous , , , No comments

Push notification гэж юу вэ?

Push notification бол орчин үеийн вэб, аппликэйшнүүдийн салшгүй нэгэн ойлголт юм. Магадгүй хөгжүүлэгч биш хүмүүс push notification гэж яг юуг хэлээд байгааг ойлгохгүй байж болох юм. Тэгвэл дараах жишээгээр тайлбарлая.

Хэрвээ push notification байхгүй байсан бол шинэ и-мэйл ирсэн эсэх, фэйсбүүкт оруулсан зураг дээрээ хэн лайк дарсан, коммент бичсэн эсэхийг тэр дор нь мэдэх аргагүй болно. Тийм болохоор и-мэйл, фэйсбүүк рүүгээ байн байн орж шалгахаас өөр аргагүйд хүрнэ.
Push notification-ийг хэрэглэгчийн үйлдлээс үл хамааран сервэрээс хэрэглэгчид шууд дамжуулж буй мессеж гэж ойлгож болно.

Аппликэйшн дангаараа хэрэглэгч рүү push notification илгээх боломжгүй. iOS үйлдлийн системтэй бол APNS (Apple Push Notification Service), Android бол GCM (Google Cloud Messaging) гэсэн Push Service-үүд энэ үүргийг гүйцэтгэнэ. Эдгээр сервисийн онцлог нь тухайн аппликэйшнийг ажиллуулаагүй байсан ч notification хэрэглэгчийн төхөөрөмж дээр ирнэ.

Харин энгийн вэб хуудсанд push notification илгээхийн тулд заавал APNS, GCM зэрэг төлбөртэй сервисүүдийг ашиглах шаардлагагүй. Гэхдээ браузер нээлттэй, хэрэглэгч логин хийсэн байх хэрэгтэй. Үүнийг HTML5-ын WebSocket технологийг ашиглан хийж болно. Доорх жишээгээр хийж үзье.

Вэб push notification хийх жишээ

Жишээг Windows үйлдлийн систем дээр бэлдлээ. Бусад үйлдлийн систем дээр ч гэсэн доорх заавраар бэлдэхэд ялгаагүй ажиллана. 

Орчин бэлдэх

  1. Node.js суулгана
  2. MySQL Server суулгана
Node.js-ийг суулгахдаа https://nodejs.org руу ороод хамгийн сүүлийн үеийн LTS хувилбарыг татаж суулгана. 
MySQL cерверийг http://www.mysql.com руу ороод MySQL Community Server -ийг татаж суулгаарай.

Жишээнд ашиглах бааз үүсгэх

MySQL cерверээ суулгасан бол доорх query-г ажиллуулж nodejs гэсэн бааз үүсгэнэ..

CREATE DATABASE  IF NOT EXISTS `nodejs`;
USE `nodejs`;

DROP TABLE IF EXISTS `messages`;
CREATE TABLE `messages` (
  `id` int(10) NOT NULL DEFAULT '0',
  `title` text NOT NULL,
  `text` text NOT NULL,
  `photo` text NOT NULL,
  `read_flg` tinyint(1) unsigned DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Фолдер үүсгэх

Command Prompt нээгээд push_notification гэсэн фолдер үүсгэе.  

mkdir push_notification
cd push_notification

Mysql, Socket.io package-уудыг суулгана.

npm install mysql@2.0.0-alpha9
npm install socket.io

Command Prompt доорх байдалтай харагдана.

Үүсгэсэн фолдер дотроо client.html, server.js гэсэн 2 файлыг доорх байдлаар үүсгэнэ.

client.html файл

<html>
<head>
    <title>Push notification</title>
    <style>
        body {
            font-family: sans-serif;
            font-size: 14px;
            font-weight: 400;
        }
        header {
            background-color: #2F6FAD;
            padding: 8px 10px;
            color: #fff;
        }
        span#notification_number {
            padding: 2px 6px;
            background-color: red;
            color: #fff;
            font-weight: bold;
        }
        dd, dt {
            float: left;
            margin: 0;
            padding: 5px;
            clear: both;
            display: block;
            width: 100%;
        }
        dt {
            background: #ddd;
            font-weight: 700;
        }
        figure {
            float: left;
            width: 120px;
        }
        dd p {
            position: absolute;
            float: right;
            margin-left: 150px;;
        }

    </style>
</head>
<body>
<header>Notification <span id="notification_number"></span></header>
<div id="container">Loading ...</div>
<script src="socket.io/socket.io.js"></script>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script>

    // вэбсокет шинээр үүсгэх
    var socket = io.connect('http://localhost:8000');
    // тессэж ирсэн үед харуулах
    socket.on('notification', function (data) {
        $('#notification_number').html(data.messages.length);
        var messageList = "<dl>";
        $.each(data.messages, function (index, msg) {
            if (msg.read_flg==0) {
                messageList += "<dt>" + msg.title + "</dt>\n" +
                        "<dd>" +
                        "<figure> <img width='100px' src='" + msg.photo + "' /></figure>" +
                        "<p>" + msg.text + "</p>" +
                        "</dd>";
            }
        });
        messageList += "</dl>";
        $('#container').html(messageList);
    });
</script>
</body>
</html>

server.js файл

var app = require('http').createServer(handler);
var fs = require('fs');
var io = require('socket.io').listen(app);
var mysql = require('mysql');
var connection = mysql.createConnection({
        host: 'localhost',
        user: 'root',
        password: '',
        database: 'nodejs',
        port: 3306
    });
var connectedClients = [];
var pollingTimer;
const POLLING_INTERVAL = 1000;

// датабэйстэй холбогдох
connection.connect(function (err) {
    if (err) {
        console.log(err);
    }
});

// сервер үүсгэх ( localhost:8000 )
app.listen(8000);

// сервер ажиллаж эхэлсний дараа client.html хуудсыг дуудах
function handler(req, res) {
    fs.readFile('client.html', function (err, data) {
        if (err) {
            console.log(err);
            res.writeHead(500);
            return res.end('client.html хуудыг нээхэд алдаа гарлаа.');
        }
        res.writeHead(200);
        res.end(data);
    });
}

// Хэрэглэгч холбогдсон үед тогтмол интервалын хооронд
// баазаас өгөгдлийг хуудас руу дамжуулна.
var pollingLoop = function () {

    // баазаас өгөгдөл авах
    var query = connection.query('SELECT * FROM messages WHERE read_flg=0');
    var messages = [];

    query
        .on('error', function (err) {
            console.log(err);
            updateSockets(err);
        })
        .on('result', function (msg) {
            messages.push(msg);
        })
        .on('end', function () {
            // хэрвээ холбогдсон хэрэглэгч байвал функцыг өөрийг нь дахин дуудна
            if (connectedClients.length) {
                pollingTimer = setTimeout(pollingLoop, POLLING_INTERVAL);
                updateSockets({
                    messages: messages
                });
            } else {
                console.log('Холбоотой хэрэглэгч байхгүй тул сервер зогслоо.')
            }
        });
};

// шинээр сокет үүсгэх
io.sockets.on('connection', function (socket) {

    console.log('Холбогдсон хэрэглэгчдийн тоо:' + connectedClients.length);

    // дор хаяж нэг хэрэглэгч холбогдсон үед loop-ийг эхлүүлнэ.
    if (!connectedClients.length) {
        pollingLoop();
    }

    socket.on('disconnect', function () {
        var socketIndex = connectedClients.indexOf(socket);
        console.log('socketID = %s холболт тасарлаа.', socketIndex);

        if (~socketIndex) {
            connectedClients.splice(socketIndex, 1);
        }
    });

    console.log('Шинэ хэрэглэгч холбогдлоо!');
    connectedClients.push(socket);

});

var updateSockets = function (data) {
    data.time = new Date();
    console.log('Холбогдсон хэрэглэгчдэд өгөгдөл дамжуулж байна ( холбогдсон хэрэглэгчид = %s ) - %s', connectedClients.length, data.time);
    // сокетоор холбогдсон бүх хэрэглэгчид өгөгдөл дамжуулах
    connectedClients.forEach(function (tmpSocket) {
        tmpSocket.volatile.emit('notification', data);
    });
};

console.log('Браузерийг нээнэ үү. http://localhost:8000');

Энд байгаа MySQL-тэй холбогдох хэсгийг өөрийн локал орчныхоор солино.

var connection = mysql.createConnection({
        host: 'localhost',
        user: 'root',
        password: '',
        database: 'nodejs',
        port: 3306
    });


Дээрх бүгдийг хийсэн бол одоо жишээгээ ажиллуулъя. Command Prompt нээгээд
cd push_notification
node server.js

Жишээ зөв ажилласан бол доорх байдалтай байна.


Браузэр дээр http://localhost:8000 хаягийг нээхэд ингэж харагдана.


Одоо бааз дээрээ өгөгдөл оруулж үзье.
USE `nodejs`;
INSERT INTO `messages` VALUES (1,'Хэрэглэгчдэд ээлтэй хүнсний бүтээгдэхүүн үйлдвэрлэгч шалгарлаа','Нийслэлийн Мэргэжлийн Хяналтын Газраас “Хэрэглэгчдэд ээлтэй хүнсний бүтээгдэхүүн үйлдвэрлэгч” шалгаруулах болзолт уралдааныг хүнс үйлдвэрлэгчдийн дунд зарлаж, АПУ компанийн СҮҮНИЙ ҮЙЛДВЭР шалгарлаа.','http://cdn02.content.ikon.mn/news/2015/11/27/9ceddd_ce314aaa-bb5a-427a-9bc4-aa5005d73fab_x100.jpg',0);
INSERT INTO `messages` VALUES (2,'Японы мэргэжлийн сумод 10 жилийн дараа япон бөх түрүүллээ','Японы мэргэжлийн сумогийн нэгдүгээр сарын башёд озэки Котошогикү түрүүлж сүүлийн 10 жил буюу 58 башёд япон бөх түрүүлээгүй байсан муу үзүүлэлт зогслоо. Япончууд үндэснийхээ спортын оргилд гарч чадахгүй байгаагийн гол шалтгаан нь гарцаагүй монгол бөхчүүдийн амжилттай холбоотой. Японы мэргэжлийн сумо бөхийн дээд зиндаанд сүүлийн 16 жилийн хугацаанд аль орны бөхчүүд ноёлож буйг дүрсжүүлэн харвал 2000-2016 оны хооронд нийт 96 башё буюу барилдаан болоход Монголчууд 71, Япончууд 16, Самао 5, Хавай 2, Болгар, Эстони улсын бөхчүүд тус бүр нэг удаа түрүүлжээ.','http://cdn01.content.ikon.mn/news/2016/1/24/149a0c_Kotoomura_07_x100.jpg',0);
INSERT INTO `messages` VALUES (3,'Улс тунхагласны баярын барилдаанд Ч.Санжаадамба 3 дахиа түрүүллээ','Улс тунхагласны ойн барилдаанд заан Ч.Санжаадамба гурав дахь удаагаа түрүүллээ. Ойр мөд барилдаагүй зааныгаа ирж барилдсанд, тэр тусмаа түрүүлсэнд олон олон дэмжигчид нь их л баяртай байв. Түрүү булаалдах барилдаанд арслан П.Бүрэнтөгс заан Ч.Санжаадамба нар ёстой л хүчээ шавхан, хөлсөө урсган барилдаж, хоёр хоёр удаа барьц сонгон алдсаны дараа П.Бүрэнтөгс барьц сонгон шахаж татаад хойш гишгэдэл алдах хоромд Ч.Санжаадамба түрж хөөн давсан юм. П.Бүрэнтөгс арслан ийнхүү энэ намар, өвлийн барилдаануудаас дөрөв дэх үзүүрээ авлаа. Үүнээс гадна гурав түрүүлснийг бүгд санаж буй биз ээ.','http://cdn03.content.ikon.mn/news/2015/2/18/f280ab_MPA_PHOTO-6134_x100.jpg',0);

Дээрх өгөгдлийг баазад оруулсны дараа браузераа refresh хийгээгүй байхад өгөгдөл маань шууд харагдана.


Үүнийг хэд, хэдэн таб дээр зэрэг нээж туршиж болно. Шинэ таб дээр нээх, хаах, өгөгдөл өөрчлөх тухай бүртээ сервер ажиллуулсан Command Prompt цонх дээр гарч байгаа лог-ийг ажиглаарай.

Одоо бааз дээр байгаа өгөгдлийн read_flg баганы утгыг 1 болгож өөрчилж үзье.

UPDATE `nodejs`.`messages` SET `read_flg`='1' WHERE `id`='1';

Ингэхэд эхний мессеж уншигдсан гэсэн статустай болж браузер дээрээс алга болно.

Сүүлд нь браузер дээр нээсэн бүх  цонхоо хаагаад log-оо харахад cервер Idle горимд шилжинэ.


Төгсгөл

Дээрх жишээнд хэрэглэгчээс браузер дээрээ refresh хийх, ямар нэг AJAX request явуулахгүйгээр зөвхөн бааз дээрх өгөгдлийн өөрчлөлтийг серверээс хэрэглэгчид шууд real time дамжуулж байгааг та анзаарсан байх. Энэ бол Websocket-оор дамжуулж байгаа хэрэг юм.