差不多花了整整兩個(gè)星期,終于把這個(gè)聊天APP的后臺(tái)架構(gòu)搭建出來了跃惫。雖然花的時(shí)間比較多叮叹,但這也是我第一次寫后臺(tái),其實(shí)也并沒有想象中的那么難爆存,但也還是很折騰蛉顽,尤其是在數(shù)據(jù)庫這一塊,幾乎全部都是英文文檔(看得都只想**)先较。
項(xiàng)目概述
該聊天App高仿iOS端的微信携冤,當(dāng)然沒這么復(fù)雜,目前已實(shí)現(xiàn)功能有:
- 用戶注冊闲勺、登錄曾棕、注銷功能;
- 自動(dòng)緩存已登錄用戶菜循,關(guān)閉瀏覽器窗口失效翘地;
- 聊天室:所以在線用戶之間聊天;
- 與在線用戶之間聊天癌幕;
- 獲取所有在線用戶衙耕;
- 獲取好友列表;
- 添加好友:后臺(tái)接口已完成勺远,前端目前尚未實(shí)現(xiàn)橙喘。
現(xiàn)在幾乎每天都在更新,爭取把它做得更像一個(gè)正規(guī)的聊天應(yīng)用胶逢。不過由于該應(yīng)用是基于Web頁面的厅瞎,用戶體驗(yàn)和數(shù)據(jù)持久化等諸多方面肯定沒法跟客戶端應(yīng)用相比。
前端Web界面
前端界面在一個(gè)多月前就已經(jīng)差不多寫出來了初坠,苦于一直沒有后臺(tái)接口(API)的支持和簸,所以僅僅只是一個(gè)界面展示,并無實(shí)際聊天的功能碟刺。
- github地址:https://github.com/moohng/wchat-vue
- 在線測試地址:http://mohng.com/wchat-vue
對前端我就不做深入的介紹了比搭,主要是基于Vue
來實(shí)現(xiàn)的。而且對于一個(gè)前端開發(fā)者來說南誊,后臺(tái)實(shí)現(xiàn)可能更具有挑戰(zhàn)性身诺。
后臺(tái)實(shí)現(xiàn)
為了實(shí)現(xiàn)真實(shí)的聊天功能,我決定自己來搭建后臺(tái)抄囚,這也是我第一次寫后臺(tái)霉赡。整個(gè)后臺(tái)應(yīng)用基于Node.js
平臺(tái),采用express
模塊來搭建HTTP服務(wù)器幔托,聊天功能采用WebSocket
實(shí)現(xiàn)穴亏,數(shù)據(jù)庫使用的是MongoDB
。
- github地址:https://github.com/moohng/wchat-sv
主要使用的技術(shù)棧包括:Node.js
重挑、express
嗓化、express-session
、express-ws
谬哀、mongodb
刺覆、mongoose
。
后臺(tái)邏輯分析
初次寫后臺(tái)史煎,最難的可能就是架構(gòu)了谦屑,因?yàn)槟阋獙φ麄€(gè)應(yīng)用的需求、實(shí)現(xiàn)的功能篇梭、數(shù)據(jù)的模型等有一個(gè)清晰的思路邏輯氢橙。我可能也就是在這方面花的時(shí)間是最多的,總是不知道該如何下手恬偷。很多次都是寫著寫著就寫不下去了悍手,因?yàn)檫壿嬓胁煌恕?/p>
遇到的問題和難點(diǎn)
- 如何判斷用戶是否是登錄狀態(tài)?如何記住用戶的登錄狀態(tài)袍患?
- 如何斷定當(dāng)前登錄的用戶是否成功連接了
WebSocket
服務(wù)器坦康? - 當(dāng)一個(gè)來自客戶端的
websocket
請求時(shí),如何判斷該用戶是否已登錄协怒?需要一個(gè)身份識(shí)別功能涝焙,否則誰都可以任意接入websockt
服務(wù)器了。 - HTTP服務(wù)器與
WebSocket
服務(wù)器之間如何并存孕暇?又如何交互仑撞?因?yàn)橹挥辛奶旃δ芎拖⑼扑凸δ苁褂?code>ws,其他所有的請求都是與http服務(wù)器通信妖滔。 -
ws
服務(wù)器如何判斷消息的轉(zhuǎn)發(fā)目標(biāo)隧哮?如果目標(biāo)用戶不在線又如何處理? - 如何搭建數(shù)據(jù)庫座舍?對于初次接觸的人來說這也是個(gè)難題沮翔。
- 如何連接和操作數(shù)據(jù)庫?起碼要基本的增刪改查曲秉。
- 密碼加密問題采蚀,這同樣是一個(gè)很大的難題疲牵。
其實(shí)問題還有很多很多,這可能對于后臺(tái)開發(fā)人員來說都顯得小兒科榆鼠,但這些真是我開發(fā)過程中遇到的問題纲爸,當(dāng)然還不止如此。到目前為止妆够,有的問題已經(jīng)解決了识啦,有的問題仍未解決,或沒有找到更好的解決方案神妹。
其實(shí)颓哮,學(xué)習(xí)也就是一個(gè)發(fā)現(xiàn)問題,然后解決問題的過程鸵荠。當(dāng)你把一個(gè)一個(gè)的問題都解決之后冕茅,你也就在不知不覺中慢慢成長起來了。貴在堅(jiān)持腰鬼,也難在堅(jiān)持嵌赠。
模塊介紹
對于上面的問題,我也是自己網(wǎng)上找資料熄赡,目前主要引用到了這些模塊框架:
-
express
:基本上是整個(gè)后臺(tái)應(yīng)用的支撐姜挺,HTTP和ws
都是建立在此基礎(chǔ)之上。一個(gè)Node.js
上很強(qiáng)大的東西彼硫,可以讓你快速創(chuàng)建一個(gè)Web應(yīng)用炊豪。 -
express-session
:這個(gè)是express
的插件,主要用來解決上面說到的判斷用戶是否登錄的問題拧篮。 -
express-ws
:這也是一個(gè)express
的插件词渤,用來構(gòu)件一個(gè)ws
服務(wù)器。之前采用的是ws
框架串绩,但與express
交互性太差缺虐,不好在ws
和http
之間通信。 -
body-parser
:一個(gè)express
框架礁凡,主要用來解析POST
請求發(fā)過來的數(shù)據(jù)高氮。 -
mongoose
:一個(gè)用來操作mongodb
數(shù)據(jù)庫的框架。還有一個(gè)叫做mongolass
的框架顷牌,比這個(gè)量級(jí)要輕剪芍。
主目錄結(jié)構(gòu)
由于是第一次寫后臺(tái),后臺(tái)結(jié)構(gòu)分的并不是很清晰窟蓝。
-
index.js
:入口文件罪裹,創(chuàng)建一個(gè)http服務(wù)器和一個(gè)ws服務(wù)器,并連接到數(shù)據(jù)庫。 -
model
:該目錄主要寫一些與數(shù)據(jù)庫交互的代碼状共。 -
routes
:這個(gè)目錄主要處理路由套耕,大部分的操作都是在該目錄下進(jìn)行的。
主要的架構(gòu)就是這樣口芍,基本操作都在routes
目錄下箍铲,因?yàn)楹笈_(tái)也就是為前端寫接口。在routes
目錄下又分了不同的子路由鬓椭,比如:friend
、user
关划、ws
小染、message
等,分別處理不同的請求贮折。
看起來很簡單裤翩,但做起來真的不容易,最可怕的是代碼量大了调榄,你會(huì)陷入一個(gè)大量重復(fù)代碼和無限回調(diào)的噩夢踊赠,我想大部分人都經(jīng)歷過js
的回調(diào)噩夢。目前也只是有了個(gè)初步的邏輯架構(gòu)每庆,后面可能會(huì)根據(jù)需求的不同而變更筐带。代碼也需要優(yōu)化,有的自己一遍一遍寫起來就惡心缤灵。
兩個(gè)容易誤會(huì)的概念
本篇文章主要作個(gè)整體的介紹伦籍,因?yàn)樵揥eb應(yīng)用目前仍在開發(fā)中,很多功能還不確定腮出,等后面整個(gè)邏輯清晰了再作總結(jié)帖鸦。下面說兩個(gè)很經(jīng)典的問題,也是前端很容易誤會(huì)的問題胚嘲,至少我是誤會(huì)了很久作儿。
跨域
我的前端頁面是托管在GitHub上的,通過開啟靜態(tài)頁面的功能馋劈,可使用域名來訪問http://mohng.com/wchat-vue攻锰。而我的后臺(tái)是搭建在自己的服務(wù)器中的,所以自然就面臨了一個(gè)問題:跨域訪問侣滩。
在這之前口注,對跨域訪問是一知半解,不知道到底該如何解決這個(gè)問題君珠。這里要提出寝志,跨域訪問不是前端的問題,其實(shí)大部分都是后臺(tái)的問題。對于跨域材部,網(wǎng)上有兩種解決方案:JSONP和Ajax毫缆。對于JSONP沒什么研究,不作介紹乐导,好像也并不是很實(shí)用苦丁,這里主要介紹Ajax跨域的問題。
下面是我后臺(tái)解決跨域問題的方案:
app.use((req, res, next) => {
res.set({
// 跨域cookie 不能為通配符 *
'Access-Control-Allow-Origin': 'http://localhost:8808',
'Access-Control-Allow-Methods': 'GET,POST',
// 跨域cookie必須為true
'Access-Control-Allow-Credentials': true
});
next();
});
簡單的說一下物臂,跨域其實(shí)瀏覽器是可以正常的收到來自于服務(wù)的響應(yīng)旺拉,只是無法正確的解析。通過在服務(wù)器端對響應(yīng)頭寫入'Access-Control-Allow-Origin': '*'
和'Access-Control-Allow-Methods': 'GET,POST'
棵磷,瀏覽器才能正確的解析服務(wù)器的響應(yīng)蛾狗。記住是在服務(wù)器端對響應(yīng)頭的操作,我之前一直誤會(huì)是在前端的請求頭中寫入仪媒,現(xiàn)在想想有點(diǎn)傻逼了沉桌。
對于'Access-Control-Allow-Credentials': true
,是用來處理跨域中cookie的問題算吩。因?yàn)槟J(rèn)情況下留凭,cookie是不允許在跨域訪問中傳輸?shù)摹R鉀Q這個(gè)問題偎巢,Access-Control-Allow-Origin
的值就不能為通配符*
蔼夜,并且前端通過Ajax發(fā)起請求時(shí)也要做處理。
$.ajax(url, {
method: 'GET',
xhrFields: {
withCredentials: true
},
...
})
Cookie
之前對Cookie
的認(rèn)識(shí)一直就是一種類似于緩存的東西艘狭,但具體是做什么挎扰,怎么用,并不清楚巢音。這是要指出兩點(diǎn):
- Cookie基本上都是由后臺(tái)來管理的遵倦,前端不需要任何操作
- Cookie信息會(huì)在每次發(fā)起的請求中自動(dòng)攜帶
那么,這下就清晰多了官撼。如果你僅僅只是搞前端梧躺,基本上是用不到Cookie
的。雖然也可以通過js
代碼讀取到cookie
數(shù)據(jù)傲绣,但大部分服務(wù)器都是禁用掉此操作掠哥,也就是讓你在前端無法通過js
代碼讀取到cookie
的內(nèi)容,讀取到的是空字符串秃诵。
因?yàn)?code>cookie是每次發(fā)起請求都會(huì)自動(dòng)攜帶的续搀,所以服務(wù)器就可以通過cookie
來識(shí)別用戶的身份、是否處于登錄狀態(tài)等菠净,就像你進(jìn)入某個(gè)網(wǎng)站有時(shí)候會(huì)自動(dòng)識(shí)別你的身份并登錄禁舷。而cookie
也是可以設(shè)置過期時(shí)間的彪杉,所以服務(wù)器端就可以控制你的身份多久失效牵咙,失效之后你就要重新登錄了派近。
你可以自己嘗試在瀏覽器的控制臺(tái)通過document.cookie
來獲取一下網(wǎng)站的cookie
信息。也可以嘗試清除瀏覽器的cookie
洁桌,然后再刷新你登錄的網(wǎng)站渴丸,看是否需要重新登錄。
我這個(gè)項(xiàng)目中用到的express-session
就是通過cookie
來識(shí)別用戶身份的另凌。使用express-session
的好處就是你不需要自己要操作cookie
谱轨,使用起來簡單。
后記
我一般寫文章都是針對自己實(shí)際遇到的問題來的途茫,我目前也是在不斷的學(xué)習(xí)中碟嘴,過幾天就會(huì)寫一篇文章作個(gè)總結(jié)。