差不多花了整整兩個星期,終于把這個聊天APP的后臺架構搭建出來了。雖然花的時間比較多,但這也是我第一次寫后臺,其實也并沒有想象中的那么難,但也還是很折騰,尤其是在數(shù)據(jù)庫這一塊,幾乎全部都是英文文檔(看得都只想**)。
項目概述
該聊天App高仿iOS端的微信,當然沒這么復雜,目前已實現(xiàn)功能有:
- 用戶注冊、登錄、注銷功能;
- 自動緩存已登錄用戶,關閉瀏覽器窗口失效;
- 聊天室:所以在線用戶之間聊天;
- 與在線用戶之間聊天;
- 獲取所有在線用戶;
- 獲取好友列表;
- 添加好友:后臺接口已完成,前端目前尚未實現(xiàn)。
現(xiàn)在幾乎每天都在更新,爭取把它做得更像一個正規(guī)的聊天應用。不過由于該應用是基于Web頁面的,用戶體驗和數(shù)據(jù)持久化等諸多方面肯定沒法跟客戶端應用相比。
前端Web界面
前端界面在一個多月前就已經(jīng)差不多寫出來了,苦于一直沒有后臺接口(API)的支持,所以僅僅只是一個界面展示,并無實際聊天的功能。
- github地址:https://github.com/moohng/wchat-vue
- 在線測試地址:http://mohng.com/wchat-vue
對前端我就不做深入的介紹了,主要是基于Vue來實現(xiàn)的。而且對于一個前端開發(fā)者來說,后臺實現(xiàn)可能更具有挑戰(zhàn)性。
后臺實現(xiàn)
為了實現(xiàn)真實的聊天功能,我決定自己來搭建后臺,這也是我第一次寫后臺。整個后臺應用基于Node.js平臺,采用express模塊來搭建HTTP服務器,聊天功能采用WebSocket實現(xiàn),數(shù)據(jù)庫使用的是MongoDB。
- github地址:https://github.com/moohng/wchat-sv
主要使用的技術棧包括:Node.js、express、express-session、express-ws、mongodb、mongoose。
后臺邏輯分析
初次寫后臺,最難的可能就是架構了,因為你要對整個應用的需求、實現(xiàn)的功能、數(shù)據(jù)的模型等有一個清晰的思路邏輯。我可能也就是在這方面花的時間是最多的,總是不知道該如何下手。很多次都是寫著寫著就寫不下去了,因為邏輯行不通了。
遇到的問題和難點
- 如何判斷用戶是否是登錄狀態(tài)?如何記住用戶的登錄狀態(tài)?
- 如何斷定當前登錄的用戶是否成功連接了
WebSocket服務器? - 當一個來自客戶端的
websocket請求時,如何判斷該用戶是否已登錄?需要一個身份識別功能,否則誰都可以任意接入websockt服務器了。 - HTTP服務器與
WebSocket服務器之間如何并存?又如何交互?因為只有聊天功能和消息推送功能使用ws,其他所有的請求都是與http服務器通信。 -
ws服務器如何判斷消息的轉發(fā)目標?如果目標用戶不在線又如何處理? - 如何搭建數(shù)據(jù)庫?對于初次接觸的人來說這也是個難題。
- 如何連接和操作數(shù)據(jù)庫?起碼要基本的增刪改查。
- 密碼加密問題,這同樣是一個很大的難題。
其實問題還有很多很多,這可能對于后臺開發(fā)人員來說都顯得小兒科,但這些真是我開發(fā)過程中遇到的問題,當然還不止如此。到目前為止,有的問題已經(jīng)解決了,有的問題仍未解決,或沒有找到更好的解決方案。
其實,學習也就是一個發(fā)現(xiàn)問題,然后解決問題的過程。當你把一個一個的問題都解決之后,你也就在不知不覺中慢慢成長起來了。貴在堅持,也難在堅持。
模塊介紹
對于上面的問題,我也是自己網(wǎng)上找資料,目前主要引用到了這些模塊框架:
-
express:基本上是整個后臺應用的支撐,HTTP和ws都是建立在此基礎之上。一個Node.js上很強大的東西,可以讓你快速創(chuàng)建一個Web應用。 -
express-session:這個是express的插件,主要用來解決上面說到的判斷用戶是否登錄的問題。 -
express-ws:這也是一個express的插件,用來構件一個ws服務器。之前采用的是ws框架,但與express交互性太差,不好在ws和http之間通信。 -
body-parser:一個express框架,主要用來解析POST請求發(fā)過來的數(shù)據(jù)。 -
mongoose:一個用來操作mongodb數(shù)據(jù)庫的框架。還有一個叫做mongolass的框架,比這個量級要輕。
主目錄結構
由于是第一次寫后臺,后臺結構分的并不是很清晰。
-
index.js:入口文件,創(chuàng)建一個http服務器和一個ws服務器,并連接到數(shù)據(jù)庫。 -
model:該目錄主要寫一些與數(shù)據(jù)庫交互的代碼。 -
routes:這個目錄主要處理路由,大部分的操作都是在該目錄下進行的。
主要的架構就是這樣,基本操作都在routes目錄下,因為后臺也就是為前端寫接口。在routes目錄下又分了不同的子路由,比如:friend、user、ws、message等,分別處理不同的請求。
看起來很簡單,但做起來真的不容易,最可怕的是代碼量大了,你會陷入一個大量重復代碼和無限回調(diào)的噩夢,我想大部分人都經(jīng)歷過js的回調(diào)噩夢。目前也只是有了個初步的邏輯架構,后面可能會根據(jù)需求的不同而變更。代碼也需要優(yōu)化,有的自己一遍一遍寫起來就惡心。
兩個容易誤會的概念
本篇文章主要作個整體的介紹,因為該Web應用目前仍在開發(fā)中,很多功能還不確定,等后面整個邏輯清晰了再作總結。下面說兩個很經(jīng)典的問題,也是前端很容易誤會的問題,至少我是誤會了很久。
跨域
我的前端頁面是托管在GitHub上的,通過開啟靜態(tài)頁面的功能,可使用域名來訪問http://mohng.com/wchat-vue。而我的后臺是搭建在自己的服務器中的,所以自然就面臨了一個問題:跨域訪問。
在這之前,對跨域訪問是一知半解,不知道到底該如何解決這個問題。這里要提出,跨域訪問不是前端的問題,其實大部分都是后臺的問題。對于跨域,網(wǎng)上有兩種解決方案:JSONP和Ajax。對于JSONP沒什么研究,不作介紹,好像也并不是很實用,這里主要介紹Ajax跨域的問題。
下面是我后臺解決跨域問題的方案:
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();
});
簡單的說一下,跨域其實瀏覽器是可以正常的收到來自于服務的響應,只是無法正確的解析。通過在服務器端對響應頭寫入'Access-Control-Allow-Origin': '*'和'Access-Control-Allow-Methods': 'GET,POST',瀏覽器才能正確的解析服務器的響應。記住是在服務器端對響應頭的操作,我之前一直誤會是在前端的請求頭中寫入,現(xiàn)在想想有點傻逼了。
對于'Access-Control-Allow-Credentials': true,是用來處理跨域中cookie的問題。因為默認情況下,cookie是不允許在跨域訪問中傳輸?shù)摹R鉀Q這個問題,Access-Control-Allow-Origin的值就不能為通配符*,并且前端通過Ajax發(fā)起請求時也要做處理。
$.ajax(url, {
method: 'GET',
xhrFields: {
withCredentials: true
},
...
})
Cookie
之前對Cookie的認識一直就是一種類似于緩存的東西,但具體是做什么,怎么用,并不清楚。這是要指出兩點:
- Cookie基本上都是由后臺來管理的,前端不需要任何操作
- Cookie信息會在每次發(fā)起的請求中自動攜帶
那么,這下就清晰多了。如果你僅僅只是搞前端,基本上是用不到Cookie的。雖然也可以通過js代碼讀取到cookie數(shù)據(jù),但大部分服務器都是禁用掉此操作,也就是讓你在前端無法通過js代碼讀取到cookie的內(nèi)容,讀取到的是空字符串。
因為cookie是每次發(fā)起請求都會自動攜帶的,所以服務器就可以通過cookie來識別用戶的身份、是否處于登錄狀態(tài)等,就像你進入某個網(wǎng)站有時候會自動識別你的身份并登錄。而cookie也是可以設置過期時間的,所以服務器端就可以控制你的身份多久失效,失效之后你就要重新登錄了。
你可以自己嘗試在瀏覽器的控制臺通過document.cookie來獲取一下網(wǎng)站的cookie信息。也可以嘗試清除瀏覽器的cookie,然后再刷新你登錄的網(wǎng)站,看是否需要重新登錄。
我這個項目中用到的express-session就是通過cookie來識別用戶身份的。使用express-session的好處就是你不需要自己要操作cookie,使用起來簡單。
后記
我一般寫文章都是針對自己實際遇到的問題來的,我目前也是在不斷的學習中,過幾天就會寫一篇文章作個總結。