目錄
前言
大三的時候寫過一個小demo,socket.io實現在線匿名聊天室
博客地址:https://blog.csdn.net/qq_30604453/article/details/64159036
demo地址:http://www.chunling.online:2800/(此處請不要計較樣式,畢竟只是造着玩的demo)
示例圖:
1.需求場景
閒來無事,改造一下這個demo,做一個一對多的在線諮詢客服的系統(不涉及數據庫操作)。
很多企業官網都會有在線諮詢客服的功能。【很想要找個樣例截圖一下,可是懶得找啊,看粗糙版的樣例吧 = =】
希望達到的效果:(1)每個用戶進入網站,立即生成一個匿名身份,可與客服進行在線交流。(2)單一客服,客服可以接收所有用戶的信息,並進行回覆交流。所以這是一個一對多的關係,多個用戶,一個客服。
2.頁面效果圖
用戶頁面:如上的gif圖。點擊右下角“在線諮詢”按鈕,彈出對話框,點擊“發送”按鈕或者“回車”可以發送消息給客服。點擊“關閉”按鈕關閉對話框
客服頁面:
左側面板顯示用戶列表,點擊可切換當前聊天對象,如有未讀消息顯示橙色背景,當前聊天對象顯示藍色背景。
中間面板則是聊天視窗。
右側面板記錄用戶進入網站,離開網站的時間。
用戶頁面(潦草地做一下移動端的適配)
3.線上地址
代碼地址:https://gitee.com/wuchunling/contact-online
demo地址:
客服頁面:http://www.chunling.online:2666/serve
用戶頁面:http://www.chunling.online:2666
4.項目分析
在線客服系統主要用socket.io。簡單回顧一下socketio的用法》》socket.io的基本用法
由於是基於之前的小demo改造的,此處就不用什麼高大上的框架。後端採用:express + socket.io;前端採用:jQuery + socket.io
4.1 用戶客戶端
(1)一對多的關鍵在於如何標識一個用戶?在這裏,我採用最簡單的方法,用時間戳來標識一個用戶。當用戶進入網站瀏覽時,生成一個時間戳,這個時間戳就標誌這個用戶。
$(document).ready(function () {
if (!sessionStorage.username) {
sessionStorage.username = new Date().getTime()
}
var username = sessionStorage.username;
})
(2)當用戶進入網站瀏覽,告知服務端“用戶進入網站”;當用戶離開網站,告知服務端“用戶離開了網站”
在這裏觸發兩個事件 loginIn和loginOut(或者說兩個消息),loginIn和loginOut需要服務端去監聽,服務端再將這兩個消息下發給客服端。
$(document).ready(function () {
var iosocket = io.connect();
if (!sessionStorage.username) {
sessionStorage.username = new Date().getTime()
}
var username = sessionStorage.username;
iosocket.emit('loginIn', username); // 進入頁面
window.onunload = function () { // 關閉頁面
iosocket.emit('loginOut', username)
}
})
(3)用戶發送消息,主要是觸發msgFromClient事件,將聊天信息和用戶標識發送給服務端,服務端監聽此事件,再將此消息轉發給客服。
$(".btn-blue").click(function () { // 點擊“發送”按鈕進行發送消息
var text = $(".mesbox").val();
if (text != "") {
var data = {
name: username, // sessionStorage.username:標識用戶的時間戳
content: text
}
iosocket.emit('msgFromClient', data);
$('.mesbox').val('');
sendHtml(username, text); // dom操作,更新聊天窗的視圖
}
});
document.onkeydown = function (e) {
var ev = document.all ? window.event : e;
if (ev.keyCode == 13) { // 回車發送消息
$(".btn-blue").click();
stopDefaultKey(e)
}
}
(4)接收客服發送來的消息,主要是監聽發送給當前用戶的消息,此處用username接收,username即一開始進入頁面生成的時間戳。
iosocket.on('connect', function () {
iosocket.on(username, function (msg) { // 獲取客服消息
reciveHtml('客服', msg.content); // dom操作,更新聊天窗視圖
});
});
此時,用戶端的socket操作都已完成,剩下的都是dom操作,此處不贅述。
4.2 客服客戶端
(1)客服端有一個很重要的數據結構,需要存儲所有用戶的消息記錄。
var currentClient = { flag: -1, name: null } // 當前客戶
var clientList = {} // 聊天記錄
① currentClient表示當前聊天對象,name爲用戶標識(時間戳),flag爲服務端傳過來的客戶計數(表示第幾個用戶),例如:
{
"name": "1573104726051",
"flag": "11"
}
② clientList存儲所有用戶的所有聊天記錄,例如:
{
"1573104726051": {
"list": [
{
"from": "client",
"content": "helo world"
},
{
"from": "client",
"content": "this is Peter!"
},
{
"from": "client",
"content": "在嗎?"
},
{
"from": "serve",
"content": "(*´▽`)ノノ"
}
],
"flag": 11,
"noread": false
},
"1573105081440": {
"list": [
{
"from": "client",
"content": "this is Jessica!"
},
{
"from": "client",
"content": "Hello!"
}
],
"flag": 12,
"noread": true
},
"1573105218701": {
"list": [],
"flag": 13,
"noread": false
},
"1573105224254": {
"list": [],
"flag": 14,
"noread": false
},
"1573109712098": {
"list": [
{
"from": "client",
"content": "你好呀"
},
{
"from": "client",
"content": "諮詢一下"
}
],
"flag": 15,
"noread": true
}
}
(2)客服發送消息給用戶
$('.btn-blue').click(function () { // 點擊發送按鈕
if (currentClient) {
var text = $(".mesbox").val();
if (text != "") {
var data = {
from: 'serve',
to: currentClient,
content: text
}
iosocketServe.emit('msgFromServe', data); // data發送給服務端
$('.mesbox').val(''); // 清空輸入框
updateRoomView(0, data, currentClient.name); // dom操作
}
}
})
document.onkeydown = function (e) { // 回車發送
var ev = document.all ? window.event : e;
if (ev.keyCode == 13) {
$(".btn-blue").click();
}
}
(3)用戶進入網站,通知客服端
$(document).ready(function () {
const iosocketServe = io.connect();
iosocketServe.on('connect', function () {
iosocketServe.on('clientInto', function (client) { // 用戶進入網站
clientIn(client)
})
iosocketServe.on('clientLeave', function (client) { // 用戶離開網站
clientOut(client)
})
iosocketServe.on('reciveClientMsg', function (msg) { // 接收用戶消息
updateRoomView(1, msg, msg.name)
})
})
})
自此,客服端的socket操作已經完成,剩下的是dom操作,此處不贅述。
4.3 服務端
服務端主要起到橋樑的作用,連接用戶與客服,將用戶發送的消息轉發給客服,將客服的消息轉發給用戶。
const path = require('path');
const express = require('express');
const socketio = require("socket.io");
const app = new express();
const port = 2666;
app.use(express.static('public'));
app.get('/*', (req, res) => {
const pathname = req.params['0'];
if (!pathname) {
res.sendFile(path.join(__dirname, 'views', 'index.html'));
return;
}
res.sendFile(path.join(__dirname, 'views', pathname + '.html'))
});
var server = app.listen(port, (error) => {
if (error) {
console.error(error);
} else {
console.info('==> Listening on port %s. Open up http://localhost:%s/ in your browser.', port, port);
}
});
const clientList = []
let onlinePeople = 0
socketio.listen(server).on('connection', function (socket) {
socket.on('msgFromClient', function (msg) { // 接收客戶端發送的消息
socket.broadcast.emit('reciveClientMsg', {
from: 'client',
content: msg.content,
name: msg.name
}) // 發送給客服:客戶發送消息
});
socket.on('loginIn', function (msg) { // 客戶進入頁面
onlinePeople++
let obj = {
name: msg, // 用時間戳來標識用戶
flag: onlinePeople // 第幾個
}
clientList.push(obj)
console.log( obj.name + '進入頁面' )
socket.broadcast.emit('clientInto', obj) // 發送給客服:有新客戶進入頁面
});
socket.on('loginOut', function (msg) { // 客戶退出頁面
let obj = null
for(let i=0;i<clientList.length;i++) {
if(clientList[i].name == msg) {
obj = clientList[i]
clientList.splice(i, 1)
break
}
}
console.log(obj.name + '退出頁面')
socket.broadcast.emit('clientLeave', obj); // 發送給客服:用戶退出頁面
});
socket.on('msgFromServe', function (msg) {
socket.broadcast.emit(msg.to.name, msg)
})
});