在上一篇Node.js 與 Socket.io – 即時聊天室文章,我就假設你看過了也做完了,我們有了一個「超級無敵簡單」的聊天室,但那實在是太簡單了、太陽春了。
身為一個聊天室,它沒有前人的紀錄,也沒有每人專屬的名稱,喔對,還不會紀錄名稱!這真是太令人失望的聊天室,實在是太失望,完全就只是個拿來作為教學用的超級無敵簡單版本。作者到底做了什麼鬼東西啊!
喔等等,作者好像就是我。
好吧,為了盡棄前嫌,我決定來改良改良,增加一些新功能。
前情提要
請轉到Node.js 與 Socket.io – 即時聊天室實作收看。
名稱
第一個打算加入的是名稱,為什麼呢?因為比較簡單。
程式
views/insdex.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// 加入 Cookies function setCookie(cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); var expires = "expires="+d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; } function getCookie(cname) { var name = cname + "="; var ca = document.cookie.split(';'); for(var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1); } if (c.indexOf(name) == 0) { return c.substring(name.length, c.length); } } return ""; } // ... var content = document.getElementById("content"); // 加入下面這些 var nameInputBox = document.getElementById("name"); var name = getCookie("name"); if (name) { nameInputBox.value = name; } // .... |
1 2 3 4 5 6 7 |
if (ok) socket.emit("send", formData); // 這行替換成下面的程式片段 if (ok) { socket.emit("send", formData); setCookie("name", nameInputBox.value); } |
這部分我們新增了 Cookies 的存取,用來實現我們的名稱記錄的功能。
這功能會在第一次送出成功時,將名稱存入 Cookies 之中,之後用同一個瀏覽器進入聊天室,你的名稱將會維持與先前所輸入的名字一樣的名稱。
聊天記錄
程式
records.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
const {EventEmitter} = require("events"); let instance; let data = []; let MAX = 50; class Records extends EventEmitter { constructor () { super(); } push (msg) { data.push(msg); if (data.length > MAX) { data.splice(0, 1); } this.emit("new_message", msg); } get () { return data; } setMax (max) { MAX = max; } getMax () { return MAX; } } module.exports = (function () { if (!instance) { instance = new Records(); } return instance; })(); |
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
const server = require('http').Server(app); const io = require('socket.io')(server); const records = require('./records.js'); // 新增這行 // ... io.on('connection', (socket) => { // 有連線發生時增加人數 onlineCount++; // 發送人數給網頁 io.emit("online", onlineCount); socket.emit("maxRecord", records.getMax()); // 新增記錄最大值,用來讓前端網頁知道要放多少筆 socket.emit("chatRecord", records.get()); // 新增發送紀錄 socket.on("send", (msg) => { // 如果 msg 內容鍵值小於 2 等於是訊息傳送不完全 // 因此我們直接 return ,終止函式執行。 if (Object.keys(msg).length < 2) return; records.push(msg); //io.emit("msg", msg); // 這行刪除改由 Records 事件接手 }); socket.on('disconnect', () => { // 有人離線了,扣人 onlineCount = (onlineCount < 0) ? 0 : onlineCount-=1; io.emit("online", onlineCount); }); }); // 新增 Records 的事件監聽器 records.on("new_message", (msg) => { // 廣播訊息到聊天室 io.emit("msg", msg); }); |
views/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
document.addEventListener("DOMContentLoaded", () => { var max_record; // 新增 // ... // 加入新的事件監聽器 socket.on("chatRecord", function (msgs) { for (var i=0; i < msgs.length; i++) { (function () { addMsgToBox(msgs[i]); })(); } }); socket.on("maxRecord", function (amount) { max_record = amount; }); // 修改 msg 事件監聽器 socket.on("msg", addMsgToBox); // 新增兩個 function // 新增訊息到方框中 function addMsgToBox (d) { var msgBox = document.createElement("div") msgBox.className = "msg"; var nameBox = document.createElement("span"); nameBox.className = "name"; var name = document.createTextNode(d.name); var msg = document.createTextNode(d.msg); nameBox.appendChild(name); msgBox.appendChild(nameBox); msgBox.appendChild(msg); content.appendChild(msgBox); if (content.children.length > max_record) { rmMsgFromBox(); } } // 移除多餘的訊息 function rmMsgFromBox () { var childs = content.children; childs[0].remove(); } |
在這邊我們新增了一個物件Records
,它會將聊天記錄暫存於記憶體中,而當他收到新的訊息時,會通知伺服器主程式,然後伺服器再透過Websocket將訊息傳出去。此外,這個物件是全域型的物件,不過只會存在一個實體,是一種俗稱Singleton
的一種程式設計方式。
而前端的調整主要是在第一次進入畫面,收到紀錄資料時的顯示方式,以及聊天筆數太多時要除掉舊的記錄這樣。
好了,啟動伺服器,連到http://localhost:3000
,試試吧!
下一階:資料庫
這本篇教學中,我們已經成功地讓聊天訊息存在記憶體中,但重啟伺服器的話,這些存在記憶體中的暫時資料便會消失。為了解決每次重開伺服器聊天資料就被打掉重練的問題,我們可以為服務加入資料庫系統,有興趣的話可以繼續閱讀這篇文章:
完整程式
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
const express = require('express'); const app = express(); const server = require('http').Server(app); const io = require('socket.io')(server); const records = require('./records.js'); const port = process.env.PORT || 3000; // 加入線上人數計數 let onlineCount = 0; app.get('/', (req, res) => { res.sendFile( __dirname + '/views/index.html'); }); io.on('connection', (socket) => { // 有連線發生時增加人數 onlineCount++; // 發送人數給網頁 io.emit("online", onlineCount); // 發送紀錄最大值 socket.emit("maxRecord", records.getMax()); // 發送紀錄 socket.emit("chatRecord", records.get()); socket.on("greet", () => { socket.emit("greet", onlineCount); }); socket.on("send", (msg) => { // 如果 msg 內容鍵值小於 2 等於是訊息傳送不完全 // 因此我們直接 return ,終止函式執行。 if (Object.keys(msg).length < 2) return; records.push(msg); }); socket.on('disconnect', () => { // 有人離線了,扣人 onlineCount = (onlineCount < 0) ? 0 : onlineCount-=1; io.emit("online", onlineCount); }); }); records.on("new_message", (msg) => { // 廣播訊息到聊天室 io.emit("msg", msg); }); server.listen(port, () => { console.log("Server Started. http://localhost:" + port); }); |
records.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
const {EventEmitter} = require("events"); let instance; let data = []; let MAX = 50; class Records extends EventEmitter { constructor () { super(); } push (msg) { data.push(msg); if (data.length > MAX) { data.splice(0, 1); } this.emit("new_message", msg); } get () { return data; } setMax (max) { MAX = max; } getMax () { return MAX; } } module.exports = (function () { if (!instance) { instance = new Records(); } return instance; })(); |
views/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
<!DOCTYPE html> <html lang="zh-tw"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Chatroom</title> <script src="/socket.io/socket.io.js"></script> <script> var socket = io(); </script> <style> html, body { padding: 0; margin: 0; } #container { top: 50px; width: 500px; margin: 0 auto; display: block; position: relative; } #status-box { text-align: right; font-size: .6em; } #content { width: 100%; height: 350px; border: 1px solid darkolivegreen; border-radius: 5px; overflow: auto; } #send-box { width: 100%; text-align: center; } #send-box input { display: inline-block; } #send-box input.error { border: 1px solid red; } input[name="name"] { width: 15%; } input[name="msg"] { width: 70%; } input[type="button"] { width: 10%; } .msg { width: 73%; display: inline-block; padding: 5px 0 5px 10px; } .msg > span { width: 25%; display: inline-block; } .msg > span::before { color: darkred; content: " { "; } .msg > span::after { color: darkred; content: " } "; } </style> </head> <body> <div id="container"> <div id="status-box">Server: <span id="status">-</span> / <span id="online">0</span> online.</div> <div id="content"> </div> <div id="send-box"> <form id="send-form"> <input type="text" name="name" id="name" placeholder="暱稱"> <input type="text" name="msg" id="msg" placeholder="說點什麼?"> <input type="submit" value="送出"> </form> </div> </div> <script> document.addEventListener("DOMContentLoaded", () => { var max_record; var status = document.getElementById("status"); var online = document.getElementById("online"); var sendForm = document.getElementById("send-form"); var content = document.getElementById("content"); var nameInputBox = document.getElementById("name"); var name = getCookie("name"); if (name) { nameInputBox.value = name; } socket.on("connect", function () { status.innerText = "Connected."; }); socket.on("disconnect", function () { status.innerText = "Disconnected."; }); socket.on("online", function (amount) { online.innerText = amount; }); socket.on("maxRecord", function (amount) { max_record = amount; }); socket.on("chatRecord", function (msgs) { for (var i=0; i < msgs.length; i++) { (function () { addMsgToBox(msgs[i]); })(); } }); socket.on("msg", addMsgToBox); sendForm.addEventListener("submit", function (e) { e.preventDefault(); var ok = true; var formData = { time: new Date().toUTCString() }; var formChild = sendForm.children; for (var i=0; i< sendForm.childElementCount; i++) { var child = formChild[i]; if (child.name !== "") { var val = child.value; if (val === "" || !val) { ok = false; child.classList.add("error"); } else { child.classList.remove("error"); formData[child.name] = val; } } } if (ok) { socket.emit("send", formData); setCookie("name", nameInputBox.value); } }); function addMsgToBox (d) { var msgBox = document.createElement("div") msgBox.className = "msg"; var nameBox = document.createElement("span"); nameBox.className = "name"; var name = document.createTextNode(d.name); var msg = document.createTextNode(d.msg); nameBox.appendChild(name); msgBox.appendChild(nameBox); msgBox.appendChild(msg); content.appendChild(msgBox); if (content.children.length > max_record) { rmMsgFromBox(); } } function rmMsgFromBox () { var childs = content.children; childs[0].remove(); } function setCookie(cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); var expires = "expires="+d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; } function getCookie(cname) { var name = cname + "="; var ca = document.cookie.split(';'); for(var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1); } if (c.indexOf(name) == 0) { return c.substring(name.length, c.length); } } return ""; } }); </script> </body> </html> |
Have Fun