本文僅對一些關鍵性的知識點進行解釋,具體請Fork原始碼學習。
Demo頁面如果沒啥人的話可以自己新建幾個頁面複製地址進入,每個頁面都是一個獨立的訪客,相容PC和移動端訪問。
前端實現
前端技術棧
- Parcel:構建工具,零配置的打包構建工具
- socket.io:跨平臺的WebSocket通訊庫,具有前後端一致的API,可以觸發和響應自定義的事件。
前端介面
介面佈局這個沒有啥說的,直接按自己喜好寫就行了。也可以直接參照本專案DEMO和原始碼。
socket.io 簡介
socket.io
有前後端一致的API,所以在前端和Node端使用區別不大。
socket.io
最主要的API是觸發和響應自定義的事件,除了connect,message,disconnect這些事件的名字不能使用之外,你可以觸發任何自定義的事件名稱。下面列出本專案中所使用到的一些自定義事件。
# 客戶端觸發自定義事件:test
socket.emit('test', data)
# 服務端響應事件
socket.on('test', data => {})
複製程式碼
前端觸發事件
- 登入聊天室
- 傳送訊息
前端響應事件
- 登入狀態
- 系統通知
- 訊息傳送
原始碼解析
連線伺服器
import io from './assets/js/socket.io'
let socket = io('ws://47.91.235.153:3000')
// 連線伺服器
socket.on('connect', function () {
console.log('成功連線伺服器')
})
複製程式碼
登入聊天室
進入頁面彈窗要求輸入使用者名稱,後端驗證使用者名稱不重複後即可關閉登入彈窗進入聊天室。
# 傳送登入事件
function userLogin () {
let loginName = document.getElementById('js-loginName').value
if (loginName === '') {
alert('你必須輸入使用者名稱')
} else {
// 傳送登入事件
socket.emit('login', {
name: loginName
})
}
}
oLoginBtn.addEventListener('click', userLogin)
# 響應登入狀態
socket.on('login', function (data) {
if (data.status === 'ok') {
loginStatus = true
oLogin.style.visibility = 'hidden'
} else {
alert(data.text)
}
})
複製程式碼
系統通知
系統通知只能由服務端傳送,主要返回使用者進入、離開房間的通知,並返回當前線上使用者。
socket.on('sys', function (data) {
// 線上人數
oCount.innerHTML = data.count
// 加入訊息列表
oMessageBox.innerHTML += `<li class="sys">
<div class="name">系統通知</div>
<div class="message">${data.text}</div>
</li>`
// 遍歷顯示線上使用者
let sUser = ''
data.users.forEach(el => {
sUser += `<li>${el}</li>`
});
oUserBox.innerHTML = sUser
})
複製程式碼
訊息傳送
function sendMessage () {
// 獲取輸入框
let oText = document.getElementById('js-text')
// 當前輸入的內容
let sText = oText.value
// 為空不提交
if (sText === '') {
return false
}
// 觸發訊息傳送事件
socket.emit('message', {
name: nickName,
text: sText
})
// 訊息列表追加本人傳送的訊息
oMessageBox.innerHTML += `<li class="my">
<div class="name">${nickName}</div>
<div class="message">${sText}</div>
</li>`
// 重置內容為空
oText.value = ''
// 訊息列表滾動到最底部
oMessageBox.scrollTop = oMessageBox.scrollHeight
}
oEnter.addEventListener('click', sendMessage)
複製程式碼
接收群聊訊息
接收後臺傳送的廣播訊息,不包含本人傳送的訊息。
socket.on('message', function (data) {
// 訊息列表追加訊息
oMessageBox.innerHTML += `<li>
<div class="name">${data.name}</div>
<div class="message">${data.text}</div>
</li>`
// 訊息列表滾動到底部
oMessageBox.scrollTop = oMessageBox.scrollHeight
})
複製程式碼
使用 Parcel 開發和打包
Parcel
的使用非常簡單,不需要任何配置即可執行和打包應用程式
# 安裝
npm install -g parcel-bundler
# 開發:http://localhost:1234/ 訪問
parcel index.html
# 編譯
parcel build index.html
複製程式碼
Node 服務端實現
Node 技術棧
- http
- socket.io
Node端 socket.io
擁有和前端一樣的API,這裡不多做解釋
io.on('connection', function (socket) {
// 響應當前連線使用者的事件
socket.on('test', data => {})
// 給當前連線的使用者傳送事件
socket.emit('test', data)
// 廣播給所有人
io.emit('test', data)
// 廣播給除當前使用者外所有人
socket.broadcast.emit('test', data)
})
複製程式碼
原始碼解析
啟用WebSocket
var app = require('http').createServer()
var io = require('socket.io')(app)
// WebSocket 連線
io.on('connection', function (socket) {
// 所有的事件觸發響應都寫在這裡
})
// 啟用3000埠
app.listen(3000, function () {
console.log('WebSocket 啟用埠 on *: 3000')
})
複製程式碼
使用者登入聊天室
因為只是個練習小專案,也沒有真正的使用者中心啥的。只用了一個陣列users
來儲存當前線上使用者。
socket.on('login', function (data) {
// 檢查 users 中是否有重名使用者
if (users.indexOf(data.name) >= 0) {
console.log(data.name + ' 已有重名使用者,請重新輸入暱稱。')
// 傳送登入失敗事件
socket.emit('login', {
status: 'err',
text: '已有重名使用者,請重新輸入暱稱。'
})
} else {
// 新增一個使用者
users.push(data.name)
// 設定當前使用者的 nickName
socket.nickName = data.name
console.log(data.name + ' 進入了房間')
console.log('當前使用者', users)
// 傳送進入房間的系統通知
io.emit('sys', {
text: socket.nickName + ' 進入了房間',
count: users.length,
users: users
})
// 傳送登入成功的通知
socket.emit('login', {
status: 'ok'
})
}
})
複製程式碼
訊息推送
接收使用者傳送的資訊後廣播給除傳送使用者外的所有人
socket.on('message', function (data) {
socket.broadcast.emit('message', data)
})
複製程式碼
使用者斷開連線
socket.on('disconnect', function () {
let index = users.indexOf(socket.nickName)
if (index >= 0) users.splice(index, 1)
// 使用者離開房間傳送系統通知
io.emit('sys', {
text: socket.nickName + ' 離開了房間',
count: users.length,
users, users
})
console.log(socket.nickName + '離開了房間')
console.log('當前使用者', users)
})
複製程式碼
部署上線
專案雖小卻也是前後端分離的,所以作為練習將專案的程式碼分開部署到了不同的伺服器。
NodeJS 服務端部署
後端程式碼是部署到了阿里雲的香港伺服器的,系統 CentOS 7
。使用 Docker
執行了一個node環境的容器。
阿里雲的伺服器安全性很高,但也因此有超多的坑,主要注意以下幾點。
- 伺服器預設是關閉所有對外埠的,需要在控制檯中新增對應的安全組策略。
- 本地ssh和FTP操作頻繁可能被拉入黑名單,需要在雲盾·DDoS高防IP中設定本地IP到白名單中
- CentOS 7 預設防火牆從iptables換成了 FirewallD
1、在伺服器中安裝好 ftp
和 Docker
,安裝方法谷歌有很多
2、使用 Docker
安裝 Node
映象
$ Docker pull node
複製程式碼
3、執行容器並掛載本地目錄(容器中的所有資料都是快取,所以對一些需要經常變動修改的檔案直接掛載到本地目錄)
$ docker run -it -p 3000:3000 \
$ --mount type=bind,source=/home/www/chat,target=/home/www/chat \
$ node:latest \
$ /bin/bash
複製程式碼
4、上傳程式碼並進入容器執行
將程式碼檔案上傳到伺服器 /home/www/chat中
,然後進入容器。
- index.js
- package.json
$ npm install
$ node index.js
複製程式碼
如果從容器退出了需要重新進入容器
# 容器ID可以用 docker ps -a 檢視
$ docker exec -it f9dd88d7f
# 進入容器中的專案目錄
$ cd /home/www/chat
# 安裝依賴
$ npm install
# 執行專案
$ node index.js
複製程式碼
Web 前端部署
前端的程式碼部署相對後端來說簡單很多,因為前端程式碼只有一些HTML、CSS和JS等靜態的檔案,隨便找個靜態伺服器放就可以。
對於此類專案這裡強烈推薦使用阿里雲的物件儲存OSS
,超級便宜基礎版一年只需要9塊錢。
- 新建 Bucket 儲存塊
- 在域名管理中繫結域名
- 在基礎設定中設定靜態網站託管
- 打包上傳編譯後的前端檔案
打包前端程式碼
$ parcel build index.html
複製程式碼
打包成功後將/dist
目錄上傳到剛剛在阿里雲中建立的OSS儲存塊中,修改index.html目錄中的資源引用為根目錄,並將index.html移動到根目錄,然後通過繫結的域名訪問即可