WebSocket之仿QQWeb即时聊天系统(下)
1. 前言
上篇主要說(shuō)了準(zhǔn)備階段和要學(xué)的基本知識(shí),當(dāng)然學(xué)的知識(shí)還是死的,還是要敲代碼,下篇主要就是用上編學(xué)的知識(shí)實(shí)現(xiàn)本次課設(shè)任務(wù)
WebSocket之仿QQWeb即時(shí)聊天系統(tǒng)(上)
WebSocket之仿QQWeb即時(shí)聊天系統(tǒng)(下)
源碼:
鏈接:https://pan.baidu.com/s/1xmCzP0TTLkWqgHnp62K9sA
提取碼:Lin2
CSDN: https://download.csdn.net/download/RongLin02/19762352
本文原創(chuàng),創(chuàng)作不易,轉(zhuǎn)載請(qǐng)注明!!!
本文鏈接
個(gè)人博客:https://ronglin.fun/archives/238
PDF鏈接:見(jiàn)博客網(wǎng)站
CSDN: https://blog.csdn.net/RongLin02/article/details/117984160
白嫖容易,創(chuàng)作不易,希望能讓大家有所收獲。
所有copy到的代碼均是度娘、csdn等可以查到的,我的課設(shè)項(xiàng)目絕大部分都是原創(chuàng),只有一些特定的、不懂的用法是copy
基本上我查詢的資料(例如API等)都會(huì)在博客中列出網(wǎng)站,有需要的童鞋可以直接點(diǎn)擊查看
本博客原創(chuàng),轉(zhuǎn)載請(qǐng)注明!
仿QQWeb即時(shí)聊天系統(tǒng)
功能要求:
實(shí)現(xiàn)Web的點(diǎn)對(duì)點(diǎn)即時(shí)的文本消息聊天功能。
實(shí)現(xiàn)Web的表情的發(fā)送、接收和顯示功能。
實(shí)現(xiàn)Web的圖片的發(fā)送、接收和顯示功能。
實(shí)現(xiàn)本地消息的存儲(chǔ),在離線的時(shí)候也能加載和查看歷史消息;
要求使用WebSocket;
2. 服務(wù)器
服務(wù)器的代碼邏輯比較簡(jiǎn)單,而且比較固定,所以先說(shuō)服務(wù)器的代碼邏輯
基本功能和上篇的那個(gè)一樣,主要就是用socket.on和socket.emit,需要注意的是,所有用到這個(gè)兩個(gè)方法的地方都要寫(xiě)到
io.on的方法體中,應(yīng)該是表示服務(wù)器和客戶端建立好連接的部分。
2.1. 數(shù)據(jù)庫(kù)
2.1.1. 配置代碼
數(shù)據(jù)庫(kù)是個(gè)好東西,存儲(chǔ)數(shù)據(jù)可以幫我們省下很多的麻煩,接下來(lái)就說(shuō)用nodejs如何連接數(shù)據(jù)庫(kù)
要先安裝模塊
JavaScript代碼
var mysql = require('mysql'); var connection = mysql.createConnection({host : 'localhost',user : 'root',password : '123456',database : 'test' });這些就是配置數(shù)據(jù),先用require導(dǎo)包,然后創(chuàng)建連接,host是地址,端口如果不設(shè)置默認(rèn)是3306,user,password就是登錄數(shù)據(jù)庫(kù)的賬號(hào)密碼,database就是要用到的數(shù)據(jù)庫(kù),模板性很強(qiáng),就這么用就行了。
數(shù)據(jù)庫(kù)建表
數(shù)據(jù)庫(kù)名稱為test
字段名稱一共兩個(gè),一個(gè)是id另一個(gè)是password,類型都是varchar類型
2.1.2. 回調(diào)函數(shù)
用到數(shù)據(jù)庫(kù)不得不提一個(gè)概念就是回調(diào)函數(shù),因?yàn)镴avaScript訪問(wèn)數(shù)據(jù)庫(kù)是一個(gè)異步的過(guò)程,要用回調(diào)函數(shù)完成異步查詢
徹底理解 Node.js 中的回調(diào)(Callback)函數(shù)
這是菜鳥(niǎo)教程有關(guān)回調(diào)函數(shù)的解釋,如果想要熟練運(yùn)用js完成復(fù)雜的功能,一定要好好理解回調(diào)函數(shù)
簡(jiǎn)單地說(shuō),就是當(dāng)異步調(diào)用時(shí),數(shù)據(jù)還沒(méi)返回給調(diào)用者,但是調(diào)用者直接繼續(xù)執(zhí)行了。
舉個(gè)例子,假設(shè)有個(gè)getValue()方法,它因?yàn)樾屎艿?#xff0c;需要10s才能將結(jié)果返回給result,在js中,這樣寫(xiě)就錯(cuò)了
在①號(hào)的輸出是有值的,而在②的輸出就是一個(gè)空。原因是在執(zhí)行完getValue方法之后,它會(huì)立即執(zhí)行②的代碼,但是這時(shí)候value的值還沒(méi)得到,而在function內(nèi)部,value的值已經(jīng)得到了,輸出出來(lái)的就是有結(jié)果的。
這樣就需要一個(gè)回調(diào)函數(shù)callback,來(lái)告訴調(diào)用者:“我執(zhí)行完了,我把結(jié)果返回給你,你可以繼續(xù)執(zhí)行需要部分了”所以案例代碼要想用回調(diào)函數(shù)可以這樣寫(xiě)
由于時(shí)間緊迫,我并沒(méi)深入的去學(xué)習(xí)回調(diào)函數(shù)的機(jī)制,只是簡(jiǎn)單的理解了一下,如有問(wèn)題,請(qǐng)多多指點(diǎn)。
2.1.3. 數(shù)據(jù)庫(kù)代碼
說(shuō)了這么多,其實(shí)就是數(shù)據(jù)庫(kù)的查詢過(guò)程是一個(gè)異步過(guò)程,調(diào)用者想要獲取結(jié)果就得等數(shù)據(jù)返回。貼上代碼
//從數(shù)據(jù)庫(kù)中查詢 function select_user(data,callback){//連接數(shù)據(jù)庫(kù)開(kāi)始查詢let sql = 'SELECT * FROM user where id = \''+data.username+'\';';connection.query(sql,function (err, result) {if(err){console.log('[SELECT ERROR] - ',err.message);callback(null)}//用回調(diào)函數(shù)告訴調(diào)用者執(zhí)行完了callback(result);}); } //插入數(shù)據(jù)庫(kù) function insert_user(data){let sql ='INSERT INTO user VALUES (\''+data.username+'\',\''+data.password+'\');';connection.query(sql,(err,result)=>{if(err){console.log('[INSERT ERROR] - ',err.message);return;};}); } //調(diào)用者 //從數(shù)據(jù)庫(kù)中尋找 select_user(data,result=>{if(result.length){if(result[0].password != data.password){console.log('loginFail','密碼錯(cuò)誤!');return ;}}else{insert_user(data);}});2.2. 常用方法
這個(gè)部分主要是根據(jù)需求,設(shè)計(jì)常用方法。
2.2.1. 主要邏輯方法
然后就是設(shè)計(jì)常用的方法了。
根據(jù)需求,要有收發(fā)文本數(shù)據(jù)的的方法,還要有收發(fā)圖片的方法,驗(yàn)證登陸的方法,離線方法
基本的都是用socket.on和socket.emit實(shí)現(xiàn)
2.2.2. 服務(wù)器和特定的客戶端交互
因?yàn)楹孟裰粫?huì)群聊,但是如果和特定的客戶端交互應(yīng)該 怎么辦呢
先上我查詢的資料:
node如何使用socket.io向指定客戶端發(fā)送消息
socket.io發(fā)送給指定的客戶端
分享一個(gè)大佬的成品仿微信聊天室
當(dāng)客戶端成功連接服務(wù)器的時(shí)候,我會(huì)給它添加一個(gè)username的屬性。
服務(wù)器對(duì)于每一個(gè)已經(jīng)連接的客戶端,會(huì)用存儲(chǔ)在io.sockets.sockets數(shù)組里,然后根據(jù)我設(shè)置的username的屬性去找到這個(gè)客戶端
然后用socket.to(toSocket)方法單獨(dú)給特定的客戶端發(fā)消息。
2.2.3. nodejs讀取本地文件
在前端,一般來(lái)說(shuō),JavaScript讀取文件相當(dāng)麻煩而且還有限制。但是在服務(wù)器中,nodejs讀取文件已經(jīng)比較成熟了。
查詢的資料:
NodeJS 文件操作 —— fs 基本使用
appendFile函數(shù)的基本用法
安裝插件
貼代碼
const url = require('url'); const fs = require('fs'); app.use(express.json()) app.use(express.urlencoded({ extended: false }))let url = './history/'+data.name+'To'+data.toName+'.txt';fs.appendFile(url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}});if(data.toName != data.name){let t_url = './history/'+data.toName+'To'+data.name+'.txt';fs.appendFile(t_url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}});}主要是一個(gè)方法,就是appendFile()方法,它可以將新的內(nèi)容追加到已有的文件中,如果文件不存在,則會(huì)創(chuàng)建一個(gè)新的文件。
fs.appendFile(文件名,數(shù)據(jù),編碼,回調(diào)函數(shù)(err));
3. 客戶端
設(shè)計(jì)重點(diǎn)來(lái)了,接下來(lái)是對(duì)我的成果的各個(gè)部分的解釋。
這部分主要是針對(duì)各個(gè)功能部分對(duì)于HTML、CSS、JavaScript代碼的講解,以及一些好用但是不好搜的用法
主要界面展示
界面1:
界面2:
3.1. 界面設(shè)計(jì)
3.1.1. 界面1
對(duì)于第一個(gè)界面是從網(wǎng)上參考的樣式表,實(shí)在抱歉,我當(dāng)時(shí)沒(méi)保存下來(lái)網(wǎng)站,如有侵權(quán)聯(lián)系我刪除,本博客僅用于學(xué)習(xí),完成課程設(shè)計(jì)。
這個(gè)主要是有兩個(gè)CSS屬性要提一下
一個(gè)是漸變函數(shù),一個(gè)是移動(dòng)元素的,將元素塊向上下左右移動(dòng),還是很使用的。
3.1.2. 界面切換
然后切換界面有兩個(gè)方法
第一個(gè)方法就是設(shè)置兩個(gè)塊的display屬性
第一個(gè)界面的display屬性設(shè)置為none,然后把第二個(gè)界面的display屬性設(shè)置為inline
第二個(gè)方法就是用Jquery語(yǔ)句,我就是用的這個(gè)
括號(hào)里的是要操作的div的類名,這個(gè)元素選擇器的規(guī)則和CSS是一樣的,$('#chat')就是選中id為chat的元素
3.1.3. 界面2
第二個(gè)界面盒裝設(shè)計(jì)圖
雖然丑了點(diǎn),但是作為草圖足夠了,我的界面2也是這樣設(shè)計(jì)的。
左側(cè)是用戶列表,中間是聊天信息,然后是功能欄,下方是一個(gè)可編輯的div,最下方是一個(gè)發(fā)送按鈕。
可編輯的div的屬性是
contenteditable是控制可編輯的,tabindex是控制tab鍵次序。
3.2. 邏輯設(shè)計(jì)
在HTML和CSS中將款式設(shè)計(jì)好之后,就該在js文件中設(shè)計(jì)動(dòng)態(tài)邏輯了。
這部分是本次課設(shè)的重難點(diǎn) 也是查詢資料時(shí)間最長(zhǎng)的部分
3.2.1. 登錄界面
首先是注冊(cè)/登錄按鈕,這個(gè)btn就是將用戶填寫(xiě)的數(shù)據(jù)提交給服務(wù)器,服務(wù)器是這樣判斷的邏輯,如果用戶名存在就對(duì)比密碼,密碼正確,返回登錄成功;如果密碼錯(cuò)誤,返回密碼錯(cuò)誤提示;如果用戶名不存在就直接創(chuàng)建一個(gè)新用戶。
返回登錄成功之后,界面切換到界面2
界面2要說(shuō)的點(diǎn)太多了,綜合性很強(qiáng),就簡(jiǎn)單的闡述一下邏輯吧
3.2.2. 用戶列表
這個(gè)對(duì)應(yīng)的是最左側(cè)那一欄,要完成的邏輯是
文字居中,可點(diǎn)擊,鼠標(biāo)為點(diǎn)擊樣式
點(diǎn)擊之后
背景顏色改變同時(shí)其他用戶塊背景顏色復(fù)原,標(biāo)題框名稱改為用戶名
還有一個(gè)比較麻煩的部分就是聊天內(nèi)容的切換,點(diǎn)擊左側(cè)不同的用戶,要切換不同的聊天內(nèi)容
我的解決方案是,給每一個(gè)用戶一個(gè)聊天內(nèi)容塊,點(diǎn)擊的時(shí)候就顯示它的聊天內(nèi)容塊,隱藏其他內(nèi)容塊。
還有一個(gè)就是用戶列表的更新,當(dāng)有用戶登陸或離線的時(shí)候,要能自動(dòng)刷新用戶列表。
這個(gè)功能我是用服務(wù)器,服務(wù)器給客戶端一個(gè)消息,刷新用戶列表,同時(shí)把當(dāng)前在線的人名單傳給客戶端
當(dāng)前用戶列表和傳回來(lái)的名單經(jīng)行對(duì)比
有一個(gè)新用戶的時(shí)候,就增加一個(gè)塊,同時(shí)添加點(diǎn)擊事件
當(dāng)有用戶離線的話,就刪除它的塊
let t_cell = document.getElementById(cells[i].id); t_cell.parentNode.removeChild(t_cell);3.2.3. 發(fā)送按鈕
發(fā)送功能就算是比較重要的功能了,它的邏輯是將當(dāng)前的文本框(可編輯div)下的文本數(shù)據(jù)發(fā)送給服務(wù)器,然后清空文本框,再將數(shù)據(jù)添加到聊天內(nèi)容塊中,聊天內(nèi)容塊中要添加兩個(gè)小div,一個(gè)是標(biāo)題顯示發(fā)送人和時(shí)間,另一個(gè)就是要發(fā)送的數(shù)據(jù)了。
數(shù)據(jù)格式是這樣的
然后聊天內(nèi)容塊也要監(jiān)聽(tīng)服務(wù)器的消息,如果有消息了就顯示到聊天內(nèi)容塊中
然后有一個(gè)點(diǎn)要注意,就是讓滾動(dòng)條保持在最下方
讓DIV的滾動(dòng)條自動(dòng)滾動(dòng)到最底部的3種方法
這里博客給了三種方法,慚愧的是,三個(gè)方法我都沒(méi)成功,后來(lái)參考了一個(gè)大佬的代碼,才解決
3.2.4. emoji
核心功能之一,用的大佬的模板,但是用的過(guò)程中遠(yuǎn)沒(méi)有想的那么簡(jiǎn)單。
先上學(xué)習(xí)資料:jQuery-emoji
我用的就是這個(gè)大佬的模板,調(diào)用起來(lái)有點(diǎn)麻煩,雖然是一個(gè)中文官方API,但是,用起來(lái)還是有點(diǎn)麻煩,簡(jiǎn)單說(shuō)一下調(diào)用過(guò)程吧
首先是先下載大佬的安裝包,然后就是配置一下參數(shù),然后可能還需要再修改一下大佬代碼
導(dǎo)包一共需要導(dǎo)入四個(gè)js文件和兩個(gè)css文件
我把大佬的文件都放在了/js/emoji/文件夾下,然后直接導(dǎo)入
然后還要寫(xiě)一個(gè)js文件用來(lái)配置和調(diào)用emoji
簡(jiǎn)單的解釋一下,主要寫(xiě)配置信息。
我們主要寫(xiě)的有這幾個(gè)配置信息
button:用來(lái)顯示和隱藏表情面板的按鈕
path:emoji圖片存儲(chǔ)的路徑
position:emoji面板的顯示位置,相對(duì)于按鈕的位置
maxNum:同類emoji的數(shù)量
然后最后一行$(".text_view").emoji(options);用來(lái)設(shè)置哪些塊需要顯示emoji
然后如果顯示不出來(lái),可能還要去修改css文件中的display的屬性,改成inline或者block之類的,可能還有修改jquery.emoji.js文件中的部分屬性.
大佬的代碼寫(xiě)的真好,我沒(méi)怎么看明白,但是我大概看了一下,好像是做了一個(gè)映射,點(diǎn)擊之后,會(huì)把圖片添加到塊中.
所以,處理信息就方便了,每一條信息中的一個(gè)emoji顯示在代碼中其實(shí)就是img標(biāo)簽,我將這條信息用_text_view.innerHTML把其中的HTML代碼提取出來(lái),然后連同標(biāo)簽文字直接發(fā)給服務(wù)器,因?yàn)槭荋TML,然后emoji的顯示是用img標(biāo)簽然后用src鏈接顯示圖片的,因?yàn)槊總€(gè)客戶端的emoji的位置都是固定的,所以只傳輸HTML代碼,放在另一個(gè)客戶端中也能顯示.
大佬寫(xiě)的emoji模塊還是比較完善的,調(diào)用起來(lái)也比較簡(jiǎn)單.
3.2.5. 上傳圖片
圖片不像emoji,emoji可以事先存儲(chǔ)在客戶端的文件夾中,位置可以固定,直接用HTML鏈接文件就行了.
圖片在用戶的電腦上的位置是隨機(jī)的,樣子也是隨機(jī)的,有點(diǎn)犯難.
老規(guī)矩,先上查詢的資料:
FIleReader
JS讀取本地文件
FileReader - Web API 接口參考 | MDN
主要用到的就是一個(gè)叫FileReader的對(duì)象,對(duì)應(yīng)到HTML中是input標(biāo)簽type是file的.
輸出數(shù)據(jù)是一大大大大大段字符,這是用的base64編碼,瀏覽器自己會(huì)識(shí)別的,如果想要這個(gè)代碼顯示圖片
div.innerHTML ='<img src=\"'+ fr.result +'\">';就是用img的src屬性,直接將那段base64編碼賦給src屬性就可以顯示了.
圖片的顯示也搞定了.
file標(biāo)簽的樣式
有關(guān)于<input type="file">這個(gè)標(biāo)簽倒是要注意一下樣式,原版樣式很丑,而且看了很多資料,網(wǎng)絡(luò)上也沒(méi)有啥很好看的樣式,不過(guò)查到了一個(gè)思路就是用一個(gè)button用來(lái)顯示,然后點(diǎn)擊這個(gè)button,然后觸發(fā)file,不過(guò)用JavaScript設(shè)置click方法,不知道為啥沒(méi)顯示,也試過(guò)用了Jquery的().on注冊(cè)事件也不行.還是我太菜了,對(duì)js的語(yǔ)法不太熟,最終只能放棄.
然后我又剛好查到可以用一個(gè)label標(biāo)簽和一個(gè)元素綁定,然后點(diǎn)擊label標(biāo)簽就相當(dāng)于點(diǎn)擊那個(gè)綁定的元素,發(fā)現(xiàn)了新世界,用法如下
這樣input type="file"就不顯示了,只顯示label,這樣就只需要修改label的樣式就行了,太方便了.
3.2.6. 歷史記錄
這部分是最難的部分,因?yàn)樗獙?shí)現(xiàn)本地消息的存儲(chǔ),然后瀏覽器本身對(duì)于文件的操作有很有限.太頭禿了
先上資料:
在node.js下淺談前端下載文件的方法
js發(fā)送get 、post請(qǐng)求的方法簡(jiǎn)介
jQuery - AJAX get() 和 post() 方法
nodejs:下載文件到服務(wù)器或客戶端
HTML5 FileReader 讀取txt文件
js實(shí)現(xiàn)base64轉(zhuǎn)換
js正則表達(dá)式獲取字符串中多個(gè)大括號(hào){}中的內(nèi)容
我是這樣實(shí)現(xiàn)的,就是客戶端從服務(wù)器中下載聊天記錄.txt,然后用戶自己選擇這個(gè)文件,然后js把文件內(nèi)容讀出來(lái),把記錄顯示到聊天框中.
服務(wù)器提供下載服務(wù):
先導(dǎo)包,我的聊天記錄放在根目錄下的history文件夾下,數(shù)據(jù)保存的格式是xxxToxxx.txt,內(nèi)容是JSON格式數(shù)據(jù).
實(shí)現(xiàn)的功能就是讓nodejs處理客戶端的get請(qǐng)求,然后把文件傳回去,用req.query.屬性名來(lái)獲取屬性值,然后返回正確的文件給客戶端
然后就是客戶端怎么向服務(wù)器發(fā)出post/get請(qǐng)求
第一種方法就是比較簡(jiǎn)單的就是用js創(chuàng)建一個(gè)虛擬form,然后click,就會(huì)發(fā)出一個(gè)請(qǐng)求
第二個(gè)方法是用Jquery的get() 和 post()方法,用法很簡(jiǎn)單
第三個(gè)方法就是,上面第一個(gè)資料的用iframe提交,而且還沒(méi)有閃現(xiàn),大佬請(qǐng)收下我的膝蓋
//本方法copy自網(wǎng)絡(luò) function downloadByIframe(url){var iframe = document.getElementById("myIframe");if(iframe){iframe.src = url;}else{iframe = document.createElement("iframe");iframe.style.display = "none";iframe.src = url;iframe.id = "myIframe";document.body.appendChild(iframe);} }代碼復(fù)制自
在node.js下淺談前端下載文件的方法
如有侵權(quán)請(qǐng)聯(lián)系我刪除!
然后就是本地讀取文件
var history = document.getElementById('_history').files[0]; var fr = new window.FileReader() fr.readAsDataURL(history);fr.onload = function () {//編碼base64 Base64.encode//解碼base64var texts = Base64.decode(fr.result).match(/{(.*?)}/g);//console.log(texts);var t_meg = JSON.parse(texts[0]);console.log(t_meg);if(t_meg.name != _cilent.username && t_meg.toName != _cilent.username){alert('這不是你的聊天記錄!!!');return ;}}簡(jiǎn)單的說(shuō)一下,還是用<input type="file">這個(gè)標(biāo)簽,然后js中用FileReader讀取,但是有一個(gè)問(wèn)題就是讀取的文件數(shù)據(jù)是base64編碼之后的,我們需要先解碼,方法是參考js實(shí)現(xiàn)base64轉(zhuǎn)換的.
先安裝base64包
然后,就可以用了,兩個(gè)方法
Base64.decode() //解碼base64 Base64.encode() //編碼base64解碼之后數(shù)據(jù)是很多很多的JSON數(shù)據(jù),我們還要用正則表達(dá)式將JSON數(shù)據(jù)提取出來(lái)。
就是匹配{}中的數(shù)據(jù)。用的正則表達(dá)式是/{(.*?)}/g,然后將提取的JSON串轉(zhuǎn)化成js對(duì)象JSON.parse()
然后數(shù)據(jù)就讀出來(lái)。
總結(jié)
這次課設(shè)收獲了太多的東西,了解了nodejs、socket.io等技術(shù),熟悉了JavaScript的語(yǔ)法,查詢了太多了資料,收獲滿滿。
=w=
總結(jié)
以上是生活随笔為你收集整理的WebSocket之仿QQWeb即时聊天系统(下)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java zip 压缩乱码_java实现
- 下一篇: html css好看的渐变颜色